MySQL 9.3.0
Source Code Documentation
cluster_aware_session.h
Go to the documentation of this file.
1/*
2 Copyright (c) 2024, 2025, Oracle and/or its affiliates.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2.0,
6 as published by the Free Software Foundation.
7
8 This program is designed to work with certain software (including
9 but not limited to OpenSSL) that is licensed under separate terms,
10 as designated in a particular file or component or in included license
11 documentation. The authors of MySQL hereby grant you an additional
12 permission to link the program and your derivative works with the
13 separately licensed software that they have either included with
14 the program or referenced in the documentation.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24*/
25
26#ifndef MYSQLROUTER_CLUSTER_AWARE_SESSION_INCLUDED
27#define MYSQLROUTER_CLUSTER_AWARE_SESSION_INCLUDED
28
29#include <algorithm>
30#include <array>
31#include <cstring>
32#include <fstream>
33#include <functional>
34#include <iomanip>
35#include <iostream>
36#include <random>
37#include <regex>
38#include <set>
39#include <sstream>
40#include <stdexcept>
41#include <system_error>
42
43#include "harness_assert.h"
45#include "mysqld_error.h"
48
49/**
50 * Error codes for MySQL Errors that we handle specifically
51 *
52 * @todo extend to other MySQL Error codes that need to be handled specifically
53 * and move into a place where other can access it too
54 */
55enum class MySQLErrorc {
56 kSuperReadOnly = ER_OPTION_PREVENTS_STATEMENT, // 1290
58};
59
60/**
61 * Cluster (GR or AR)-aware decorator for MySQL Sessions.
62 */
64 public:
67 const std::string &cluster_initial_username,
68 const std::string &cluster_initial_password,
69 const std::string &cluster_initial_hostname,
70 unsigned long cluster_initial_port,
71 const std::string &cluster_initial_socket,
72 unsigned long connection_timeout,
73 std::set<MySQLErrorc> failure_codes = {MySQLErrorc::kSuperReadOnly,
75 : metadata_(metadata),
76 cluster_initial_username_(cluster_initial_username),
77 cluster_initial_password_(cluster_initial_password),
78 cluster_initial_hostname_(cluster_initial_hostname),
79 cluster_initial_port_(cluster_initial_port),
80 cluster_initial_socket_(cluster_initial_socket),
81 connection_timeout_(connection_timeout),
82 failure_codes_(std::move(failure_codes)) {}
83
84 /**
85 * Cluster (GR or AR) aware failover.
86 *
87 * @param wrapped_func function will be called
88 *
89 * assumes:
90 *
91 * - actively connected mysql_ session
92 * - all nodes in the group have the same user/pass combination
93 * - wrapped_func throws MySQLSession::Error with .code in .failure_codes
94 */
95 template <class R>
96 R failover_on_failure(std::function<R()> wrapped_func) {
97 bool fetched_cluster_servers = false;
98 std::vector<std::tuple<std::string, unsigned long>> cluster_servers;
99
100 auto cluster_servers_it = cluster_servers.begin();
101 const auto cluster_specific_initial_id =
102 metadata_.get_cluster_type_specific_id();
103
104 bool initial_node = true;
105 do {
106 bool skip_node = false;
107 if (!initial_node) {
108 // let's check if the node we failed over to belongs to the same cluster
109 // the user is bootstaping against
110 const auto cluster_specific_id =
111 metadata_.get_cluster_type_specific_id();
112
113 if (cluster_specific_id != cluster_specific_initial_id) {
115 "Node on '%s' that the bootstrap failed over to, seems to belong "
116 "to different cluster(%s != %s), skipping...",
117 metadata_.get_session().get_address().c_str(),
118 cluster_specific_initial_id.c_str(), cluster_specific_id.c_str());
119 skip_node = true;
120 }
121 } else {
122 initial_node = false;
123 }
124
125 if (!skip_node) {
126 try {
127 return wrapped_func();
128 } catch (const mysqlrouter::MySQLSession::Error &e) {
129 MySQLErrorc ec = static_cast<MySQLErrorc>(e.code());
130
131 log_debug(
132 "Executing statements failed with: '%s' (%d), trying to connect "
133 "to "
134 "another node",
135 e.what(), e.code());
136
137 // code not in failure-set
138 if (failure_codes_.find(ec) == failure_codes_.end()) {
139 throw;
140 }
141 }
142 }
143
144 // bootstrap not successful, checking next node to fail over to
145 do {
146 if (!fetched_cluster_servers) {
147 // lazy fetch the GR members
148 //
149 fetched_cluster_servers = true;
150
151 log_info("Fetching Cluster Members");
152
153 for (auto &cluster_node : metadata_.fetch_cluster_hosts()) {
154 auto const &node_host = std::get<0>(cluster_node);
155 auto node_port = std::get<1>(cluster_node);
156
157 // if we connected through TCP/IP, ignore the initial host
158 if (cluster_initial_socket_.size() == 0 &&
159 (node_host == cluster_initial_hostname_ &&
160 node_port == cluster_initial_port_)) {
161 continue;
162 }
163
164 log_debug("added cluster node: %s:%ld", node_host.c_str(),
165 node_port);
166 cluster_servers.emplace_back(node_host, node_port);
167 }
168
169 // get a new iterator as the old one is now invalid
170 cluster_servers_it = cluster_servers.begin();
171 } else {
172 std::advance(cluster_servers_it, 1);
173 }
174
175 if (cluster_servers_it == cluster_servers.end()) {
176 throw std::runtime_error(
177 "no more nodes to fail-over too, giving up.");
178 }
179
180 if (metadata_.get_session().is_connected()) {
181 log_debug("%s", "disconnecting from mysql-server");
182 metadata_.get_session().disconnect();
183 }
184
185 auto const &tp = *cluster_servers_it;
186
187 auto const &host = std::get<0>(tp);
188 auto port = std::get<1>(tp);
189
190 log_info("trying to connect to mysql-server at %s:%ld", host.c_str(),
191 port);
192
193 try {
194 connect(metadata_.get_session(), host, port);
195 } catch (const std::exception &inner_e) {
196 log_info("Failed connecting to %s:%ld: %s, trying next", host.c_str(),
197 port, inner_e.what());
198 continue;
199 }
200
201 const auto result =
202 mysqlrouter::setup_metadata_session(metadata_.get_session());
203 if (!result) {
204 metadata_.get_session().disconnect();
205 log_info(
206 "Failed setting up a metadata session %s:%ld: %s, trying next",
207 host.c_str(), port, result.error().c_str());
208 }
209
210 // if this fails, we should just skip it and go to the next
211 } while (!metadata_.get_session().is_connected());
212 } while (true);
213 }
214
215 virtual ~ClusterAwareDecorator() = default;
216
217 protected:
218 void connect(mysqlrouter::MySQLSession &session, const std::string &host,
219 const unsigned port);
220
222 const std::string &cluster_initial_username_;
223 const std::string &cluster_initial_password_;
224 const std::string &cluster_initial_hostname_;
226 const std::string &cluster_initial_socket_;
227 unsigned long connection_timeout_;
228 std::set<MySQLErrorc> failure_codes_;
229};
230
231#endif // MYSQLROUTER_CLUSTER_AWARE_SESSION_INCLUDED
Cluster (GR or AR)-aware decorator for MySQL Sessions.
Definition: cluster_aware_session.h:63
unsigned long cluster_initial_port_
Definition: cluster_aware_session.h:225
const std::string & cluster_initial_password_
Definition: cluster_aware_session.h:223
mysqlrouter::ClusterMetadata & metadata_
Definition: cluster_aware_session.h:221
virtual ~ClusterAwareDecorator()=default
R failover_on_failure(std::function< R()> wrapped_func)
Cluster (GR or AR) aware failover.
Definition: cluster_aware_session.h:96
ClusterAwareDecorator(mysqlrouter::ClusterMetadata &metadata, const std::string &cluster_initial_username, const std::string &cluster_initial_password, const std::string &cluster_initial_hostname, unsigned long cluster_initial_port, const std::string &cluster_initial_socket, unsigned long connection_timeout, std::set< MySQLErrorc > failure_codes={MySQLErrorc::kSuperReadOnly, MySQLErrorc::kLostConnection})
Definition: cluster_aware_session.h:65
const std::string & cluster_initial_username_
Definition: cluster_aware_session.h:222
unsigned long connection_timeout_
Definition: cluster_aware_session.h:227
std::set< MySQLErrorc > failure_codes_
Definition: cluster_aware_session.h:228
const std::string & cluster_initial_hostname_
Definition: cluster_aware_session.h:224
const std::string & cluster_initial_socket_
Definition: cluster_aware_session.h:226
Definition: cluster_metadata.h:259
Definition: mysql_session.h:288
unsigned int code() const
Definition: mysql_session.h:302
Definition: mysql_session.h:157
Log log_debug(std::cout, "DEBUG")
MySQLErrorc
Error codes for MySQL Errors that we handle specifically.
Definition: cluster_aware_session.h:55
#define CR_SERVER_LOST
Definition: errmsg.h:72
#define log_warning(...)
Definition: log_client.h:154
#define log_info(...)
Definition: log_client.h:153
Logging interface for using and extending the logging subsystem.
#define IMPORT_LOG_FUNCTIONS()
convenience macro to avoid common boilerplate
Definition: logging.h:323
const char * host
Definition: mysqladmin.cc:66
stdx::expected< void, std::string > ROUTER_CLUSTER_EXPORT setup_metadata_session(MySQLSession &session)
Definition: cluster_metadata.cc:1348
stdx::expected< void, error_type > connect(native_handle_type native_handle, const struct sockaddr *addr, size_t addr_len)
wrap connect() in a portable way.
Definition: socket.h:353
required uint64 port
Definition: replication_asynchronous_connection_failover.proto:33
#define ROUTER_CLUSTER_EXPORT
Definition: router_cluster_export.h:15
Definition: result.h:30