Skip to content

Commit 450c8bc

Browse files
Andrzej Religadahlerlend
authored andcommitted
Bug #30617645 MYSQLROUTER DOES NOT ROUTE WHEN MYSQLX PORT VALUE IS INVALID
Fix for Bug #30354273 SUPPORT IPV6 ADDRESSES FOUND IN THE METADATA. changed the way the host and port in the metadata are parsed. As a result of that change now if the mysqlx port is invalid the node is discarded as a candidate also for a classic protocol connections. This is a change of behaviour and on top of the Shell bug#27677227 can cause the users stop being able to connect to cluster nodes via the classic protocol if they upgrade the Router without fixing the invalid metadata entries. This patch restores the old (pre .19) behavior and silently ignores the invalid mysqlx port in the metadata, letting the node still be used for the classic protocol connections. RB: 23492
1 parent c099062 commit 450c8bc

File tree

4 files changed

+133
-28
lines changed

4 files changed

+133
-28
lines changed

router/src/metadata_cache/src/cluster_metadata.cc

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,17 @@ bool set_instance_ports(metadata_cache::ManagedInstance &instance,
190190
const auto addr_port = mysqlrouter::split_addr_port(x_port);
191191
instance.xport = addr_port.second != 0 ? addr_port.second : 33060;
192192
} catch (const std::runtime_error &e) {
193-
log_warning(
194-
"Error parsing host:xport in metadata for instance %s: '%s': %s",
195-
instance.mysql_server_uuid.c_str(), row[x_port_column], e.what());
196-
return false;
193+
// There is a Shell bug (#27677227) that can cause the mysqlx port be
194+
// invalid in the metadata (>65535). For the backward compatibility we
195+
// need to tolerate this and still let the node be used for classic
196+
// connections (as the older Router versions did).
197+
198+
// log_warning(
199+
// "Error parsing host:xport in metadata for instance %s:"
200+
// "'%s': %s",
201+
// instance.mysql_server_uuid.c_str(), row[x_port_column],
202+
// e.what());
203+
instance.xport = 0;
197204
}
198205
} else {
199206
instance.xport = instance.port * 10;

router/tests/component/test_metadata_ttl.cc

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,28 +126,41 @@ class MetadataChacheTTLTest : public RouterComponentTest {
126126
return get_int_field_value(json_string, "update_last_check_in_count");
127127
}
128128

129-
bool wait_for_refresh_thread_started(const ProcessWrapper &router,
130-
std::chrono::milliseconds timeout) {
129+
bool wait_log_contains(const ProcessWrapper &router,
130+
const std::string &needle,
131+
std::chrono::milliseconds timeout) {
131132
if (getenv("WITH_VALGRIND")) {
132133
timeout *= 10;
133134
}
134135

135-
const auto MSEC_STEP = 10ms;
136-
bool thread_started = false;
136+
const auto MSEC_STEP = 50ms;
137+
bool found = false;
137138
const auto started = std::chrono::steady_clock::now();
138139
do {
139140
const std::string log_content = router.get_full_logfile();
140-
const std::string needle = "Starting metadata cache refresh thread";
141-
thread_started = (log_content.find(needle) != log_content.npos);
142-
if (!thread_started) {
141+
found = (log_content.find(needle) != log_content.npos);
142+
if (!found) {
143143
auto step = std::min(timeout, MSEC_STEP);
144144
std::this_thread::sleep_for(std::chrono::milliseconds(step));
145145
timeout -= step;
146146
}
147-
} while (!thread_started &&
148-
timeout > std::chrono::steady_clock::now() - started);
147+
} while (!found && timeout > std::chrono::steady_clock::now() - started);
149148

150-
return thread_started;
149+
return found;
150+
}
151+
152+
bool wait_for_refresh_thread_started(
153+
const ProcessWrapper &router, const std::chrono::milliseconds timeout) {
154+
const std::string needle = "Starting metadata cache refresh thread";
155+
156+
return wait_log_contains(router, needle, timeout);
157+
}
158+
159+
bool wait_metadata_read(const ProcessWrapper &router,
160+
const std::chrono::milliseconds timeout) {
161+
const std::string needle = "Potential changes detected in cluster";
162+
163+
return wait_log_contains(router, needle, timeout);
151164
}
152165

153166
auto &launch_router(const std::string &temp_test_dir,
@@ -481,6 +494,73 @@ INSTANTIATE_TEST_CASE_P(
481494
"unordered_ar_v2", ClusterType::RS_V2, "0.1")),
482495
get_test_description);
483496

497+
class MetadataChacheTTLTestInvalidMysqlXPort
498+
: public MetadataChacheTTLTest,
499+
public ::testing::WithParamInterface<MetadataTTLTestParams> {};
500+
501+
/**
502+
* @test Check that invalid mysqlx port in the metadata does not cause the node
503+
* to be discarded for the classic protocol connections (Bug#30617645)
504+
*/
505+
TEST_P(MetadataChacheTTLTestInvalidMysqlXPort, InvalidMysqlXPort) {
506+
TempDirectory temp_test_dir;
507+
TempDirectory conf_dir("conf");
508+
509+
const std::string json_metadata =
510+
get_data_dir().join(GetParam().tracefile).str();
511+
512+
SCOPED_TRACE("// single node cluster is fine for this test");
513+
const uint16_t node_classic_port{port_pool_.get_next_available()};
514+
const uint16_t node_http_port{port_pool_.get_next_available()};
515+
const uint32_t kInvalidPort{76000};
516+
517+
auto &cluster_node = launch_mysql_server_mock(
518+
json_metadata, node_classic_port, EXIT_SUCCESS, false, node_http_port);
519+
ASSERT_NO_FATAL_FAILURE(check_port_ready(cluster_node, node_classic_port));
520+
521+
ASSERT_TRUE(
522+
MockServerRestClient(node_http_port).wait_for_rest_endpoint_ready())
523+
<< cluster_node.get_full_output();
524+
525+
SCOPED_TRACE(
526+
"// let the metadata for our single node report invalid mysqlx port");
527+
set_mock_metadata(node_http_port, "", {node_classic_port}, 0, 0, false,
528+
"127.0.0.1", {kInvalidPort});
529+
530+
SCOPED_TRACE("// launch the router with metadata-cache configuration");
531+
const auto router_port = port_pool_.get_next_available();
532+
const std::string metadata_cache_section = get_metadata_cache_section(
533+
{node_classic_port}, GetParam().cluster_type, GetParam().ttl);
534+
const std::string routing_section = get_metadata_cache_routing_section(
535+
router_port, "PRIMARY", "first-available");
536+
auto &router = launch_router(temp_test_dir.name(), conf_dir.name(),
537+
metadata_cache_section, routing_section,
538+
EXIT_SUCCESS, true);
539+
540+
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
541+
ASSERT_TRUE(wait_metadata_read(router, 5000ms)) << router.get_full_output();
542+
543+
SCOPED_TRACE(
544+
"// Even though the metadata contains invalid mysqlx port we still "
545+
"should be able to connect on the classic port");
546+
MySQLSession client;
547+
try {
548+
client.connect("127.0.0.1", router_port, "username", "password", "", "");
549+
} catch (...) {
550+
FAIL() << router.get_full_logfile();
551+
}
552+
}
553+
554+
INSTANTIATE_TEST_CASE_P(
555+
InvalidMysqlXPort, MetadataChacheTTLTestInvalidMysqlXPort,
556+
::testing::Values(MetadataTTLTestParams("metadata_dynamic_nodes_v2_gr.js",
557+
"gr_v2", ClusterType::GR_V1, "5"),
558+
MetadataTTLTestParams("metadata_dynamic_nodes.js", "gr",
559+
ClusterType::GR_V2, "5"),
560+
MetadataTTLTestParams("metadata_dynamic_nodes_v2_ar.js",
561+
"ar_v2", ClusterType::RS_V2, "5")),
562+
get_test_description);
563+
484564
/**
485565
* @test Checks that the router operates smoothly when the metadata version has
486566
* changed between the metadata refreshes.

router/tests/helpers/mock_server_testutils.cc

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,31 @@ std::string json_to_string(const JsonValue &json_doc) {
5151
return out_buffer.GetString();
5252
}
5353

54-
JsonValue mock_GR_metadata_as_json(const std::string &gr_id,
55-
const std::vector<uint16_t> &gr_node_ports,
56-
unsigned primary_id, unsigned view_id,
57-
bool error_on_md_query,
58-
const std::string &gr_node_host) {
54+
JsonValue mock_GR_metadata_as_json(
55+
const std::string &gr_id, const std::vector<uint16_t> &gr_node_ports,
56+
unsigned primary_id, unsigned view_id, bool error_on_md_query,
57+
const std::string &gr_node_host,
58+
const std::vector<uint32_t> &gr_node_xports) {
5959
JsonValue json_doc(rapidjson::kObjectType);
6060
JsonAllocator allocator;
6161
json_doc.AddMember(
6262
"gr_id", JsonValue(gr_id.c_str(), gr_id.length(), allocator), allocator);
6363

6464
JsonValue gr_nodes_json(rapidjson::kArrayType);
65+
if (!gr_node_xports.empty() &&
66+
gr_node_xports.size() != gr_node_ports.size()) {
67+
throw std::runtime_error("gr_node_xports.size() != gr_node_ports.size(), " +
68+
std::to_string(gr_node_xports.size()) +
69+
" != " + std::to_string(gr_node_ports.size()));
70+
}
71+
size_t i = 0;
6572
for (auto &gr_node : gr_node_ports) {
6673
JsonValue node(rapidjson::kArrayType);
6774
node.PushBack(static_cast<int>(gr_node), allocator);
6875
node.PushBack(JsonValue("ONLINE", strlen("ONLINE"), allocator), allocator);
76+
if (!gr_node_xports.empty())
77+
node.PushBack(static_cast<int>(gr_node_xports[i]), allocator);
78+
++i;
6979
gr_nodes_json.PushBack(node, allocator);
7080
}
7181
json_doc.AddMember("gr_nodes", gr_nodes_json, allocator);
@@ -85,11 +95,11 @@ JsonValue mock_GR_metadata_as_json(const std::string &gr_id,
8595
void set_mock_metadata(uint16_t http_port, const std::string &gr_id,
8696
const std::vector<uint16_t> &gr_node_ports,
8797
unsigned primary_id, unsigned view_id,
88-
bool error_on_md_query,
89-
const std::string &gr_node_host) {
98+
bool error_on_md_query, const std::string &gr_node_host,
99+
const std::vector<uint32_t> &gr_node_xports) {
90100
const auto json_doc =
91101
mock_GR_metadata_as_json(gr_id, gr_node_ports, primary_id, view_id,
92-
error_on_md_query, gr_node_host);
102+
error_on_md_query, gr_node_host, gr_node_xports);
93103

94104
const auto json_str = json_to_string(json_doc);
95105

router/tests/helpers/mock_server_testutils.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,38 +53,46 @@ using JsonStringBuffer =
5353
* Converts the GR mock data to the JSON object.
5454
*
5555
* @param gr_id replication group id to set
56-
* @param gr_node_ports vector with the ports of the nodes that mock server
56+
* @param gr_node_ports vector with the classic protocol ports of the cluster
57+
* nodes
5758
* @param primary_id which node is the primary
5859
* @param view_id metadata view id (for AR cluster)
5960
* @param error_on_md_query if true the mock should return an error when
6061
* handling the metadata query
6162
* @param gr_node_host address of the host with the nodes
63+
* @param gr_node_xports vector with the X protocol ports of the cluster nodes
64+
* reported by the metadata
6265
*
6366
* @return JSON object with the GR mock data.
6467
*/
6568
JsonValue mock_GR_metadata_as_json(
6669
const std::string &gr_id, const std::vector<uint16_t> &gr_node_ports,
6770
unsigned primary_id = 0, unsigned view_id = 0,
6871
bool error_on_md_query = false,
69-
const std::string &gr_node_host = "127.0.0.1");
72+
const std::string &gr_node_host = "127.0.0.1",
73+
const std::vector<uint32_t> &gr_node_xports = {});
7074

7175
/**
7276
* Sets the metadata returned by the mock server.
7377
*
7478
* @param http_port mock server's http port where it services the http requests
7579
* @param gr_id replication group id to set
76-
* @param gr_node_ports vector with the ports of the nodes that mock server
80+
* @param gr_node_ports vector with the classic protocol ports of the cluster
81+
* nodes
7782
* @param primary_id which node is the primary
7883
* @param view_id metadata view id (for AR cluster)
7984
* @param error_on_md_query if true the mock should return an error when
80-
* @param gr_node_host address of the host with the nodes
81-
* handling the metadata query
85+
* @param gr_node_host address of the host with the nodes handling the metadata
86+
* query
87+
* @param gr_node_xports vector with the X protocol ports of the cluster nodes
88+
* reported by the metadata
8289
*/
8390
void set_mock_metadata(uint16_t http_port, const std::string &gr_id,
8491
const std::vector<uint16_t> &gr_node_ports,
8592
unsigned primary_id = 0, unsigned view_id = 0,
8693
bool error_on_md_query = false,
87-
const std::string &gr_node_host = "127.0.0.1");
94+
const std::string &gr_node_host = "127.0.0.1",
95+
const std::vector<uint32_t> &gr_node_xports = {});
8896

8997
void set_mock_bootstrap_data(
9098
uint16_t http_port, const std::string &cluster_name,

0 commit comments

Comments
 (0)