Skip to content

Commit 45bab58

Browse files
yanw-bqjusting-bq
authored andcommitted
Add failover mode (#154)
* add failover mode * remove unused header * update doc * fix integration tests * fix unit tests in linux * fix integration tests * create a helper function to check failover mode * addressing nits
1 parent 6547a55 commit 45bab58

16 files changed

+118
-96
lines changed

docs/using-the-aws-driver/UsingTheAwsDriver.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ In addition to the parameters that you can configure for the [MySQL Connector/OD
3232
| Option | Description | Type | Required | Default |
3333
| ---------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|------------------------------------------|
3434
| `ENABLE_CLUSTER_FAILOVER` | Set to `1` to enable the fast failover behaviour offered by the AWS ODBC Driver for MySQL. | bool | No | `1` |
35-
| `ALLOW_READER_CONNECTIONS` | Set to `1` to allow connections to reader instances during the failover process. | bool | No | `0` |
36-
| `ENABLE_STRICT_READER_FAILOVER` | Set to `1` to only allow failover to reader nodes during the reader failover process. If enabled, reader failover to a writer node will only be allowed for single-node clusters. This parameter is only applicable when `ALLOW_READER_CONNECTIONS` is set to `1`. | bool | No | `0` |
35+
| `FAILOVER_MODE` | Defines a mode for failover process. Failover process may prioritize nodes with different roles and connect to them. Possible values: <br><br>- `strict writer` - Failover process follows writer node and connects to a new writer when it changes.<br>- `reader or writer` - During failover, the driver tries to connect to any available/accessible reader node. If no reader is available, the driver will connect to a writer node. This logic mimics the logic of the Aurora read-only cluster endpoint.<br>- `strict reader` - During failover, the driver tries to connect to any available reader node. If no reader is available, the driver raises an error. Reader failover to a writer node will only be allowed for single-node clusters. This logic mimics the logic of the Aurora read-only cluster endpoint. | char* | No | Default value depends on connection url. For Aurora read-only cluster endpoint, it's set to `reader or writer`. Otherwise, it's `strict writer`. | |
3736
| `GATHER_PERF_METRICS` | Set to `1` to record failover-associated metrics. | bool | No | `0` |
3837
| `GATHER_PERF_METRICS_PER_INSTANCE` | Set to `1` to gather additional performance metrics per instance as well as cluster. | bool | No | `0` |
3938
| `HOST_PATTERN` | This parameter is not required unless connecting to an AWS RDS cluster via an IP address or custom domain URL. In those cases, this parameter specifies the cluster instance DNS pattern that will be used to build a complete instance endpoint. A "?" character in this pattern should be used as a placeholder for the DB instance identifiers of the instances in the cluster. <br/><br/>Example: `?.my-domain.com`, `any-subdomain.?.my-domain.com:9999`<br/><br/>Usecase Example: If your cluster instance endpoint follows this pattern:`instanceIdentifier1.customHost`, `instanceIdentifier2.customHost`, etc. and you want your initial connection to be to `customHost:1234`, then your connection string should look like this: `SERVER=customHost;PORT=1234;DATABASE=test;HOST_PATTERN=?.customHost` <br><br/> If the provided connection string is not an IP address or custom domain, the driver will automatically acquire the cluster instance host pattern from the customer-provided connection string. | char\* | If connecting using an IP address or custom domain URL: Yes <br><br> Otherwise: No <br><br> See [Host Pattern](#host-pattern) for more details. | `NONE` |

driver/failover.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,11 @@ class FAILOVER_HANDLER {
203203
static bool is_rds_cluster_dns(std::string host);
204204
static bool is_rds_proxy_dns(std::string host);
205205
static bool is_rds_writer_cluster_dns(std::string host);
206+
static bool is_rds_reader_cluster_dns(std::string host);
206207
static bool is_rds_custom_cluster_dns(std::string host);
207208
static std::string get_rds_cluster_host_url(std::string host);
208209
static std::string get_rds_instance_host_pattern(std::string host);
210+
static bool is_failover_mode(const char* expected_mode, DataSource* ds);
209211
bool is_ipv4(std::string host);
210212
bool is_ipv6(std::string host);
211213
bool failover_to_reader(const char*& new_error_code, const char*& error_msg);

driver/failover_handler.cc

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ const std::regex AURORA_CLUSTER_PATTERN(
5959
const std::regex AURORA_WRITER_CLUSTER_PATTERN(
6060
R"#((.+)\.(cluster-)+([a-zA-Z0-9]+\.[a-zA-Z0-9\-]+\.rds\.amazonaws\.com))#",
6161
std::regex_constants::icase);
62+
const std::regex AURORA_READER_CLUSTER_PATTERN(
63+
R"#((.+)\.(cluster-ro-)+([a-zA-Z0-9]+\.[a-zA-Z0-9\-]+\.rds\.amazonaws\.com))#",
64+
std::regex_constants::icase);
6265
const std::regex AURORA_CUSTOM_CLUSTER_PATTERN(
6366
R"#((.+)\.(cluster-custom-)+([a-zA-Z0-9]+\.[a-zA-Z0-9\-]+\.rds\.amazonaws\.com))#",
6467
std::regex_constants::icase);
@@ -74,6 +77,9 @@ const std::regex AURORA_CHINA_CLUSTER_PATTERN(
7477
const std::regex AURORA_CHINA_WRITER_CLUSTER_PATTERN(
7578
R"#((.+)\.(cluster-)+([a-zA-Z0-9]+\.rds\.[a-zA-Z0-9\-]+\.amazonaws\.com\.cn))#",
7679
std::regex_constants::icase);
80+
const std::regex AURORA_CHINA_READER_CLUSTER_PATTERN(
81+
R"#((.+)\.(cluster-ro-)+([a-zA-Z0-9]+\.rds\.[a-zA-Z0-9\-]+\.amazonaws\.com\.cn))#",
82+
std::regex_constants::icase);
7783
const std::regex AURORA_CHINA_CUSTOM_CLUSTER_PATTERN(
7884
R"#((.+)\.(cluster-custom-)+([a-zA-Z0-9]+\.rds\.[a-zA-Z0-9\-]+\.amazonaws\.com\.cn))#",
7985
std::regex_constants::icase);
@@ -113,7 +119,7 @@ FAILOVER_HANDLER::FAILOVER_HANDLER(DBC* dbc, DataSource* ds,
113119

114120
this->failover_reader_handler = std::make_shared<FAILOVER_READER_HANDLER>(
115121
this->topology_service, this->connection_handler, dbc->env->failover_thread_pool, ds->failover_timeout,
116-
ds->failover_reader_connect_timeout, ds->enable_strict_reader_failover,
122+
ds->failover_reader_connect_timeout, is_failover_mode(FAILOVER_MODE_STRICT_READER, ds),
117123
dbc->id, ds->save_queries);
118124
this->failover_writer_handler = std::make_shared<FAILOVER_WRITER_HANDLER>(
119125
this->topology_service, this->failover_reader_handler,
@@ -150,6 +156,14 @@ SQLRETURN FAILOVER_HANDLER::init_connection() {
150156
network_timeout != ds->read_timeout ||
151157
network_timeout != ds->write_timeout);
152158
}
159+
160+
if (!ds->failover_mode) {
161+
if (is_rds_reader_cluster_dns(this->current_host->get_host())) {
162+
ds_set_wstrnattr(&ds->failover_mode, (SQLWCHAR*)to_sqlwchar_string(FAILOVER_MODE_READER_OR_WRITER).c_str(), SQL_NTS);
163+
} else {
164+
ds_set_wstrnattr(&ds->failover_mode, (SQLWCHAR*)to_sqlwchar_string(FAILOVER_MODE_STRICT_WRITER).c_str(), SQL_NTS);
165+
}
166+
}
153167
}
154168

155169
if (should_connect_to_new_writer() || reconnect_with_updated_timeouts) {
@@ -416,6 +430,10 @@ bool FAILOVER_HANDLER::is_rds_writer_cluster_dns(std::string host) {
416430
return std::regex_match(host, AURORA_WRITER_CLUSTER_PATTERN) || std::regex_match(host, AURORA_CHINA_WRITER_CLUSTER_PATTERN);
417431
}
418432

433+
bool FAILOVER_HANDLER::is_rds_reader_cluster_dns(std::string host) {
434+
return std::regex_match(host, AURORA_READER_CLUSTER_PATTERN) || std::regex_match(host, AURORA_CHINA_READER_CLUSTER_PATTERN);
435+
}
436+
419437
bool FAILOVER_HANDLER::is_rds_custom_cluster_dns(std::string host) {
420438
return std::regex_match(host, AURORA_CUSTOM_CLUSTER_PATTERN) || std::regex_match(host, AURORA_CHINA_CUSTOM_CLUSTER_PATTERN);
421439
}
@@ -593,7 +611,9 @@ bool FAILOVER_HANDLER::trigger_failover_if_needed(const char* error_code,
593611
failover_start_time_ms = std::chrono::steady_clock::now();
594612

595613
if (current_topology && current_topology->total_hosts() > 1 &&
596-
ds->allow_reader_connections) { // there are readers in topology
614+
// Trigger reader failover if failover mode is not strict writer
615+
!is_failover_mode(FAILOVER_MODE_STRICT_WRITER, ds)) {
616+
597617
failover_success = failover_to_reader(new_error_code, error_msg);
598618
elasped_time_ms =
599619
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - failover_start_time_ms).count();
@@ -674,3 +694,7 @@ bool FAILOVER_HANDLER::failover_to_writer(const char*& new_error_code, const cha
674694
void FAILOVER_HANDLER::invoke_start_time() {
675695
invoke_start_time_ms = std::chrono::steady_clock::now();
676696
}
697+
698+
bool FAILOVER_HANDLER::is_failover_mode(const char* expected_mode, DataSource* ds) {
699+
return myodbc_strcasecmp(expected_mode, ds_get_utf8attr(ds->failover_mode, &ds->failover_mode8)) == 0;
700+
}

installer/myodbc-installer.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ int list_datasource_details(DataSource *ds)
525525
/* Failover */
526526
if (ds->host_pattern) printf("Failover Instance Host pattern: %s\n", ds_get_utf8attr(ds->host_pattern, &ds->host_pattern8));
527527
if (ds->cluster_id) printf("Failover Cluster ID: %s\n", ds_get_utf8attr(ds->cluster_id, &ds->cluster_id8));
528+
if (ds->failover_mode) printf("Failover Mode: %s\n", ds_get_utf8attr(ds->failover_mode, &ds->failover_mode8));
528529

529530
printf("Options:\n");
530531
if (ds->return_matching_rows) printf("\tFOUND_ROWS\n");
@@ -578,8 +579,7 @@ int list_datasource_details(DataSource *ds)
578579
if (ds->auth_secret_id) printf("\tSECRET_ID");
579580
/* Failover */
580581
if (ds->enable_cluster_failover) printf("\tENABLE_CLUSTER_FAILOVER\n");
581-
if (ds->allow_reader_connections) printf("\tALLOW_READER_CONNECTIONS\n");
582-
if (ds->enable_strict_reader_failover) printf("\tENABLE_STRICT_READER_FAILOVER\n");
582+
if (ds->failover_mode) printf("\tFAILOVER_MODE\n");
583583
if (ds->gather_perf_metrics) printf("\tGATHER_PERF_METRICS\n");
584584
if (ds->gather_metrics_per_instance) printf("\tGATHER_METRICS_PER_INSTANCE\n");
585585
if (ds->topology_refresh_rate) printf("\tTOPOLOGY_REFRESH_RATE=%d\n", ds->topology_refresh_rate);

integration/connection_string_builder.h

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ class ConnectionString {
4242
friend class ConnectionStringBuilder;
4343

4444
ConnectionString() : m_dsn(""), m_server(""), m_port(-1),
45-
m_uid(""), m_pwd(""), m_db(""), m_log_query(true), m_allow_reader_connections(false),
46-
m_enable_strict_reader_failover(false), m_multi_statements(false), m_enable_cluster_failover(true),
45+
m_uid(""), m_pwd(""), m_db(""), m_log_query(true),
46+
m_failover_mode(""), m_multi_statements(false), m_enable_cluster_failover(true),
4747
m_failover_timeout(-1), m_connect_timeout(-1), m_network_timeout(-1), m_host_pattern(""),
4848
m_enable_failure_detection(true), m_failure_detection_time(-1), m_failure_detection_timeout(-1),
4949
m_failure_detection_interval(-1), m_failure_detection_count(-1), m_monitor_disposal_time(-1),
5050
m_read_timeout(-1), m_write_timeout(-1), m_auth_mode(""), m_auth_region(""), m_auth_host(""),
5151
m_auth_port(-1), m_auth_expiration(-1), m_secret_id(""),
5252

5353
is_set_uid(false), is_set_pwd(false), is_set_db(false), is_set_log_query(false),
54-
is_set_allow_reader_connections(false), is_set_enable_strict_reader_failover(false),
54+
is_set_failover_mode(false),
5555
is_set_multi_statements(false), is_set_enable_cluster_failover(false),
5656
is_set_failover_timeout(false), is_set_connect_timeout(false), is_set_network_timeout(false), is_set_host_pattern(false),
5757
is_set_enable_failure_detection(false), is_set_failure_detection_time(false), is_set_failure_detection_timeout(false),
@@ -76,11 +76,8 @@ class ConnectionString {
7676
if (is_set_log_query) {
7777
length += sprintf(conn_in + length, "LOG_QUERY=%d;", m_log_query ? 1 : 0);
7878
}
79-
if (is_set_allow_reader_connections) {
80-
length += sprintf(conn_in + length, "ALLOW_READER_CONNECTIONS=%d;", m_allow_reader_connections ? 1 : 0);
81-
}
82-
if (is_set_enable_strict_reader_failover) {
83-
length += sprintf(conn_in + length, "ENABLE_STRICT_READER_FAILOVER=%d;", m_enable_strict_reader_failover ? 1 : 0);
79+
if (is_set_failover_mode) {
80+
length += sprintf(conn_in + length, "failover_mode=%s;", m_failover_mode.c_str());
8481
}
8582
if (is_set_multi_statements) {
8683
length += sprintf(conn_in + length, "MULTI_STATEMENTS=%d;", m_multi_statements ? 1 : 0);
@@ -155,16 +152,16 @@ class ConnectionString {
155152

156153
// Optional fields
157154
std::string m_uid, m_pwd, m_db;
158-
bool m_log_query, m_allow_reader_connections, m_enable_strict_reader_failover, m_multi_statements, m_enable_cluster_failover;
155+
bool m_log_query, m_multi_statements, m_enable_cluster_failover;
159156
int m_failover_timeout, m_connect_timeout, m_network_timeout;
160-
std::string m_host_pattern;
157+
std::string m_host_pattern, m_failover_mode;
161158
bool m_enable_failure_detection;
162159
int m_failure_detection_time, m_failure_detection_timeout, m_failure_detection_interval, m_failure_detection_count, m_monitor_disposal_time, m_read_timeout, m_write_timeout;
163160
std::string m_auth_mode, m_auth_region, m_auth_host, m_secret_id;
164161
int m_auth_port, m_auth_expiration;
165162

166163
bool is_set_uid, is_set_pwd, is_set_db;
167-
bool is_set_log_query, is_set_allow_reader_connections, is_set_enable_strict_reader_failover, is_set_multi_statements;
164+
bool is_set_log_query, is_set_failover_mode, is_set_multi_statements;
168165
bool is_set_enable_cluster_failover;
169166
bool is_set_failover_timeout, is_set_connect_timeout, is_set_network_timeout;
170167
bool is_set_host_pattern;
@@ -206,14 +203,9 @@ class ConnectionString {
206203
is_set_log_query = true;
207204
}
208205

209-
void set_allow_reader_connections(const bool& allow_reader_connections) {
210-
m_allow_reader_connections = allow_reader_connections;
211-
is_set_allow_reader_connections = true;
212-
}
213-
214-
void set_enable_strict_reader_failover(const bool& enable_strict_reader_failover) {
215-
m_enable_strict_reader_failover = enable_strict_reader_failover;
216-
is_set_enable_strict_reader_failover = true;
206+
void set_failover_mode(const std::string& failover_mode) {
207+
m_failover_mode = failover_mode;
208+
is_set_failover_mode = true;
217209
}
218210

219211
void set_multi_statements(const bool& multi_statements) {
@@ -358,13 +350,8 @@ class ConnectionStringBuilder {
358350
return *this;
359351
}
360352

361-
ConnectionStringBuilder& withAllowReaderConnections(const bool& allow_reader_connections) {
362-
connection_string->set_allow_reader_connections(allow_reader_connections);
363-
return *this;
364-
}
365-
366-
ConnectionStringBuilder& withEnableStrictReaderFailover(const bool& enable_strict_reader_failover) {
367-
connection_string->set_enable_strict_reader_failover(enable_strict_reader_failover);
353+
ConnectionStringBuilder& withFailoverMode(const std::string& failover_mode) {
354+
connection_string->set_failover_mode(failover_mode);
368355
return *this;
369356
}
370357

integration/connection_string_builder_test.cc

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ TEST_F(ConnectionStringBuilderTest, test_complete_string) {
7171
.withUID("testUser")
7272
.withPWD("testPwd")
7373
.withLogQuery(false)
74-
.withAllowReaderConnections(true)
75-
.withEnableStrictReaderFailover(true)
7674
.withMultiStatements(false)
7775
.withDSN("testDSN")
7876
.withFailoverTimeout(120000)
@@ -89,7 +87,7 @@ TEST_F(ConnectionStringBuilderTest, test_complete_string) {
8987
.withEnableClusterFailover(true)
9088
.build();
9189

92-
const std::string expected = "DSN=testDSN;SERVER=testServer;PORT=3306;UID=testUser;PWD=testPwd;DATABASE=testDb;LOG_QUERY=0;ALLOW_READER_CONNECTIONS=1;ENABLE_STRICT_READER_FAILOVER=1;MULTI_STATEMENTS=0;ENABLE_CLUSTER_FAILOVER=1;FAILOVER_TIMEOUT=120000;CONNECT_TIMEOUT=20;NETWORK_TIMEOUT=20;HOST_PATTERN=?.testDomain;ENABLE_FAILURE_DETECTION=1;FAILURE_DETECTION_TIME=10000;FAILURE_DETECTION_INTERVAL=100;FAILURE_DETECTION_COUNT=4;MONITOR_DISPOSAL_TIME=300;";
90+
const std::string expected = "DSN=testDSN;SERVER=testServer;PORT=3306;UID=testUser;PWD=testPwd;DATABASE=testDb;LOG_QUERY=0;MULTI_STATEMENTS=0;ENABLE_CLUSTER_FAILOVER=1;FAILOVER_TIMEOUT=120000;CONNECT_TIMEOUT=20;NETWORK_TIMEOUT=20;HOST_PATTERN=?.testDomain;ENABLE_FAILURE_DETECTION=1;FAILURE_DETECTION_TIME=10000;FAILURE_DETECTION_INTERVAL=100;FAILURE_DETECTION_COUNT=4;MONITOR_DISPOSAL_TIME=300;";
9391
EXPECT_EQ(0, expected.compare(connection_string));
9492
}
9593

@@ -129,13 +127,12 @@ TEST_F(ConnectionStringBuilderTest, test_setting_boolean_fields) {
129127
.withUID("testUser")
130128
.withPWD("testPwd")
131129
.withLogQuery(false)
132-
.withAllowReaderConnections(true)
133130
.withMultiStatements(true)
134131
.withEnableClusterFailover(false)
135132
.withEnableFailureDetection(true)
136133
.build();
137134

138-
const std::string expected("DSN=testDSN;SERVER=testServer;PORT=3306;UID=testUser;PWD=testPwd;LOG_QUERY=0;ALLOW_READER_CONNECTIONS=1;MULTI_STATEMENTS=1;ENABLE_CLUSTER_FAILOVER=0;ENABLE_FAILURE_DETECTION=1;");
135+
const std::string expected("DSN=testDSN;SERVER=testServer;PORT=3306;UID=testUser;PWD=testPwd;LOG_QUERY=0;MULTI_STATEMENTS=1;ENABLE_CLUSTER_FAILOVER=0;ENABLE_FAILURE_DETECTION=1;");
139136
EXPECT_EQ(0, expected.compare(connection_string));
140137
}
141138

0 commit comments

Comments
 (0)