Merge branch '2.2' into develop

This commit is contained in:
Markus Mäkelä
2018-08-28 16:06:23 +03:00
9 changed files with 137 additions and 64 deletions

View File

@ -12,6 +12,8 @@ a routing hint to all following statements. This routing hint guides the routing
module to route the statement to the master server where data is guaranteed to module to route the statement to the master server where data is guaranteed to
be in an up-to-date state. be in an up-to-date state.
### Controlling the Filter with SQL Comments
The triggering of the filter can be limited further by adding MaxScale supported The triggering of the filter can be limited further by adding MaxScale supported
comments to queries and/or by using regular expressions. The query comments take comments to queries and/or by using regular expressions. The query comments take
precedence: if a comment is found it is obayed even if a regular expression precedence: if a comment is found it is obayed even if a regular expression

View File

@ -1534,8 +1534,20 @@ to `true` and provide the three files for `ssl_cert`, `ssl_key` and
After this, MaxScale connections between the server and/or the client will be After this, MaxScale connections between the server and/or the client will be
encrypted. Note that the database must be configured to use TLS/SSL connections encrypted. Note that the database must be configured to use TLS/SSL connections
if backend connection encryption is used. When client-side encryption is if backend connection encryption is used.
enabled, only encrypted connections to MaxScale can be created.
**Note:** MaxScale does not allow mixed use of TLS/SSL and normal connections on
the same port.
If TLS encryption is enabled for a listener, any unencrypted connections to it
will be rejected. MaxScale does this to improve security by preventing
accidental creation on unencrypted connections.
The separation of secure and insecure connections differs from the MariaDB
server which allows both secure and insecure connections on the same port. As
MaxScale is the gateway through which all connections go, in order to guarantee
a more secure system MaxScale enforces a stricter security policy than what the
server does.
#### `ssl` #### `ssl`

View File

@ -318,7 +318,6 @@ MAXAVRO_FILE* maxavro_file_open(const char* filename)
} }
else else
{ {
MXS_ERROR("Failed to initialize avrofile.");
maxavro_schema_free(avrofile->schema); maxavro_schema_free(avrofile->schema);
error = true; error = true;
} }

View File

@ -28,6 +28,7 @@
#include <poll.h> #include <poll.h>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <iterator>
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
@ -121,7 +122,7 @@ public:
Closer(T t): Closer(T t):
m_t(t), m_t(t),
m_close(false) m_close(true)
{ {
} }
@ -185,7 +186,6 @@ Connection::Connection(const std::string& address,
m_timeout(timeout), m_timeout(timeout),
m_connected(false) m_connected(false)
{ {
m_buf_ptr = m_buffer.begin();
} }
Connection::~Connection() Connection::~Connection()
@ -276,7 +276,7 @@ bool Connection::connect(const std::string& table, const std::string& gtid)
m_error = "Failed to write request: "; m_error = "Failed to write request: ";
m_error += strerror_r(errno, err, sizeof(err)); m_error += strerror_r(errno, err, sizeof(err));
} }
else if ((m_first_row = read())) else if (read_schema())
{ {
rval = true; rval = true;
} }
@ -391,18 +391,13 @@ SRow Connection::process_row(json_t* js)
return rval; return rval;
} }
SRow Connection::read() bool Connection::read_schema()
{ {
m_error.clear(); m_error.clear();
SRow rval; bool rval = false;
std::string row; std::string row;
if (m_first_row) if (read_row(row))
{
rval.swap(m_first_row);
assert(!m_first_row);
}
else if (read_row(row))
{ {
json_error_t err; json_error_t err;
json_t* js = json_loads(row.c_str(), JSON_ALLOW_NUL, &err); json_t* js = json_loads(row.c_str(), JSON_ALLOW_NUL, &err);
@ -413,13 +408,43 @@ SRow Connection::read()
{ {
m_schema = row; m_schema = row;
process_schema(js); process_schema(js);
rval = Connection::read(); rval = true;
}
json_decref(js);
} }
else else
{ {
rval = process_row(js); m_error = "Failed to parse JSON: ";
m_error += err.text;
}
} }
if (m_error == CDC::TIMEOUT)
{
assert(rval == false);
m_error += ". Data received so far: '";
std::copy(m_buffer.begin(), m_buffer.end(), std::back_inserter(m_error));
m_error += "'";
}
return rval;
}
SRow Connection::read()
{
m_error.clear();
SRow rval;
std::string row;
if (read_row(row))
{
json_error_t err;
json_t* js = json_loads(row.c_str(), JSON_ALLOW_NUL, &err);
if (js)
{
rval = process_row(js);
json_decref(js); json_decref(js);
} }
else else
@ -517,14 +542,14 @@ bool Connection::do_registration()
return rval; return rval;
} }
bool Connection::is_error(const char* str) bool Connection::is_error()
{ {
bool rval = false; bool rval = false;
if (str[0] == 'E' && str[1] == 'R' && str[2] == 'R') if (m_buffer.size() >= 3 && m_buffer[0] == 'E' && m_buffer[1] == 'R' && m_buffer[2] == 'R')
{ {
m_error = "MaxScale responded with an error: "; m_error = "MaxScale responded with an error: ";
m_error += str; m_error.append(m_buffer.begin(), m_buffer.end());
rval = true; rval = true;
} }
@ -539,11 +564,19 @@ bool Connection::read_row(std::string& dest)
{ {
if (!m_buffer.empty()) if (!m_buffer.empty())
{ {
std::vector<char>::iterator it = std::find(m_buf_ptr, m_buffer.end(), '\n'); if (is_error())
{
rval = false;
break;
}
std::deque<char>::iterator it = std::find(m_buffer.begin(), m_buffer.end(), '\n');
if (it != m_buffer.end()) if (it != m_buffer.end())
{ {
dest.assign(m_buf_ptr, it); dest.assign(m_buffer.begin(), it);
m_buf_ptr = it + 1; m_buffer.erase(m_buffer.begin(), std::next(it));
assert(m_buffer.empty() || m_buffer[0] != '\n');
break; break;
} }
} }
@ -566,29 +599,16 @@ bool Connection::read_row(std::string& dest)
break; break;
} }
if (!m_connected) assert(std::find(m_buffer.begin(), m_buffer.end(), '\n') == m_buffer.end());
{ std::copy(buf, buf + rc, std::back_inserter(m_buffer));
// This is here to work around a missing newline in MaxScale error messages
buf[rc] = '\0';
if (is_error(buf)) if (is_error())
{ {
rval = false; rval = false;
break; break;
} }
} }
m_buffer.erase(m_buffer.begin(), m_buf_ptr);
assert(std::find(m_buffer.begin(), m_buffer.end(), '\n') == m_buffer.end());
m_buffer.insert(m_buffer.end(), buf, buf + rc);
m_buf_ptr = m_buffer.begin();
}
if (!m_connected && is_error(dest.c_str()))
{
rval = false;
}
return rval; return rval;
} }

View File

@ -21,6 +21,7 @@
#include <string> #include <string>
#include <tr1/memory> #include <tr1/memory>
#include <vector> #include <vector>
#include <deque>
#include <map> #include <map>
#include <algorithm> #include <algorithm>
#include <jansson.h> #include <jansson.h>
@ -137,17 +138,17 @@ private:
SValueVector m_keys; SValueVector m_keys;
SValueVector m_types; SValueVector m_types;
int m_timeout; int m_timeout;
std::vector<char> m_buffer; std::deque<char> m_buffer;
std::vector<char>::iterator m_buf_ptr;
SRow m_first_row; SRow m_first_row;
bool m_connected; bool m_connected;
bool do_auth(); bool do_auth();
bool do_registration(); bool do_registration();
bool read_row(std::string& dest); bool read_row(std::string& dest);
bool read_schema();
void process_schema(json_t* json); void process_schema(json_t* json);
SRow process_row(json_t*); SRow process_row(json_t*);
bool is_error(const char* str); bool is_error();
// Lower-level functions // Lower-level functions
int wait_for_event(short events); int wait_for_event(short events);

View File

@ -24,13 +24,13 @@ void reset_replication(TestConnections& test)
{ {
int ind = master_id - 1; int ind = master_id - 1;
replicate_from(test, 0, ind); replicate_from(test, 0, ind);
test.maxscales->wait_for_monitor(); test.maxscales->wait_for_monitor(2);
get_output(test); get_output(test);
int ec; int ec;
stringstream switchover; stringstream switchover;
switchover << "maxadmin call command mysqlmon switchover MySQL-Monitor server1 server" << master_id; switchover << "maxadmin call command mysqlmon switchover MySQL-Monitor server1 server" << master_id;
test.maxscales->ssh_node_output(0, switchover.str().c_str() , true, &ec); test.maxscales->ssh_node_output(0, switchover.str().c_str() , true, &ec);
test.maxscales->wait_for_monitor(); test.maxscales->wait_for_monitor(2);
master_id = get_master_server_id(test); master_id = get_master_server_id(test);
cout << "Master server id is now back to " << master_id << endl; cout << "Master server id is now back to " << master_id << endl;
test.assert(master_id == 1, "Switchover back to server1 failed"); test.assert(master_id == 1, "Switchover back to server1 failed");
@ -106,7 +106,7 @@ void check_test_2(TestConnections& test)
// Reset state // Reset state
replicate_from(test, 1, master_id - 1); replicate_from(test, 1, master_id - 1);
test.maxscales->wait_for_monitor(); test.maxscales->wait_for_monitor(2);
get_output(test); get_output(test);
StringSet node_states = test.get_server_status("server2"); StringSet node_states = test.get_server_status("server2");
test.assert(node_states.find("Slave") != node_states.end(), "Server 2 is not replicating."); test.assert(node_states.find("Slave") != node_states.end(), "Server 2 is not replicating.");
@ -143,7 +143,7 @@ void prepare_test_3(TestConnections& test)
test.repl->start_node(2, (char *) ""); test.repl->start_node(2, (char *) "");
test.repl->start_node(3, (char *) ""); test.repl->start_node(3, (char *) "");
test.maxscales->start_maxscale(0); test.maxscales->start_maxscale(0);
test.maxscales->wait_for_monitor(); test.maxscales->wait_for_monitor(2);
test.repl->connect(); test.repl->connect();
test.tprintf("Settings changed."); test.tprintf("Settings changed.");

View File

@ -20,6 +20,7 @@
#include <limits.h> #include <limits.h>
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <algorithm>
#include <string> #include <string>
#include <vector> #include <vector>
@ -1545,21 +1546,30 @@ static bool reauthenticate_client(MXS_SESSION* session, GWBUF* packetbuf)
if (session->client_dcb->authfunc.reauthenticate) if (session->client_dcb->authfunc.reauthenticate)
{ {
uint64_t payloadlen = gwbuf_length(packetbuf) - MYSQL_HEADER_LEN;
MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol; MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol;
uint8_t payload[gwbuf_length(packetbuf) - MYSQL_HEADER_LEN]; std::vector<uint8_t> payload;
gwbuf_copy_data(packetbuf, MYSQL_HEADER_LEN, sizeof(payload), payload); payload.resize(payloadlen);
gwbuf_copy_data(packetbuf, MYSQL_HEADER_LEN, payloadlen, &payload[0]);
// Will contains extra data but the username is null-terminated // Will contains extra data but the username is null-terminated
char user[gwbuf_length(proto->stored_query) - MYSQL_HEADER_LEN - 1]; char user[MYSQL_USER_MAXLEN + 1];
gwbuf_copy_data(proto->stored_query, MYSQL_HEADER_LEN + 1, gwbuf_copy_data(proto->stored_query, MYSQL_HEADER_LEN + 1, sizeof(user), (uint8_t*)user);
sizeof(user), (uint8_t*)user);
char* end = user + sizeof(user);
if (std::find(user, end, '\0') == end)
{
mysql_send_auth_error(session->client_dcb, 3, 0, "Malformed AuthSwitchRequest packet");
return false;
}
// Copy the new username to the session data // Copy the new username to the session data
MYSQL_session* data = (MYSQL_session*)session->client_dcb->data; MYSQL_session* data = (MYSQL_session*)session->client_dcb->data;
strcpy(data->user, user); strcpy(data->user, user);
int rc = session->client_dcb->authfunc.reauthenticate(session->client_dcb, data->user, int rc = session->client_dcb->authfunc.reauthenticate(session->client_dcb, data->user,
payload, sizeof(payload), &payload[0], payload.size(),
proto->scramble, sizeof(proto->scramble), proto->scramble, sizeof(proto->scramble),
data->client_sha1, sizeof(data->client_sha1)); data->client_sha1, sizeof(data->client_sha1));

View File

@ -343,17 +343,17 @@ void AvroSession::process_command(GWBUF *queue)
} }
else else
{ {
dcb_printf(dcb, "ERR NO-FILE File '%s' not found.", avro_binfile.c_str()); dcb_printf(dcb, "ERR NO-FILE File '%s' not found.\n", avro_binfile.c_str());
} }
} }
else else
{ {
dcb_printf(dcb, "ERR REQUEST-DATA with no data"); dcb_printf(dcb, "ERR REQUEST-DATA with no data\n");
} }
} }
else else
{ {
const char err[] = "ERR: Unknown command"; const char err[] = "ERR: Unknown command\n";
GWBUF *reply = gwbuf_alloc_and_load(sizeof(err), err); GWBUF *reply = gwbuf_alloc_and_load(sizeof(err), err);
dcb->func.write(dcb, reply); dcb->func.write(dcb, reply);
} }
@ -571,8 +571,7 @@ bool AvroSession::stream_data()
} }
else else
{ {
fprintf(stderr, "No file specified\n"); dcb_printf(dcb, "ERR avro file not specified\n");
dcb_printf(dcb, "ERR avro file not specified");
} }
return read_more; return read_more;

View File

@ -444,6 +444,29 @@ static bool pos_is_ok(Avro* router, const REP_HEADER& hdr, uint64_t pos)
return rval; return rval;
} }
bool read_fde(Avro* router)
{
bool rval = false;
avro_binlog_end_t rc;
REP_HEADER hdr;
if (read_header(router, 4, &hdr, &rc))
{
if (GWBUF *result = read_event_data(router, &hdr, 4))
{
router->handler.handle_event(hdr, GWBUF_DATA(result));
rval = true;
}
}
else if (rc == AVRO_OK)
{
// Empty file
rval = true;
}
return rval;
}
/** /**
* @brief Read all replication events from a binlog file. * @brief Read all replication events from a binlog file.
* *
@ -457,12 +480,19 @@ static bool pos_is_ok(Avro* router, const REP_HEADER& hdr, uint64_t pos)
*/ */
avro_binlog_end_t avro_read_all_events(Avro *router) avro_binlog_end_t avro_read_all_events(Avro *router)
{ {
mxb_assert(router->binlog_fd != -1);
if (!read_fde(router))
{
MXS_ERROR("Failed to read the FDE event from the binary log: %d, %s",
errno, mxs_strerror(errno));
return AVRO_BINLOG_ERROR;
}
uint64_t pos = router->current_pos; uint64_t pos = router->current_pos;
std::string next_binlog; std::string next_binlog;
bool rotate_seen = false; bool rotate_seen = false;
mxb_assert(router->binlog_fd != -1);
while (!service_should_stop) while (!service_should_stop)
{ {
avro_binlog_end_t rc; avro_binlog_end_t rc;