modules/adminapi/cluster/cluster_impl.h (292 lines of code) (raw):
/*
* Copyright (c) 2016, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef MODULES_ADMINAPI_CLUSTER_CLUSTER_IMPL_H_
#define MODULES_ADMINAPI_CLUSTER_CLUSTER_IMPL_H_
#include <list>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#include "mysqlshdk/libs/mysql/group_replication.h"
#include "scripting/types.h"
#include "scripting/types_cpp.h"
#include "shellcore/shell_options.h"
#include "modules/adminapi/cluster/api_options.h"
#include "modules/adminapi/cluster_set/api_options.h"
#include "modules/adminapi/common/base_cluster_impl.h"
#include "modules/adminapi/common/clone_options.h"
#include "modules/adminapi/common/cluster_types.h"
#include "modules/adminapi/common/common.h"
#include "modules/adminapi/common/group_replication_options.h"
#include "modules/adminapi/common/instance_validations.h"
#include "mysqlshdk/include/shellcore/shell_notifications.h"
#include "mysqlshdk/libs/db/connection_options.h"
#include "mysqlshdk/libs/db/mysql/session.h"
namespace mysqlsh {
namespace dba {
// User provided option to disabling clone
inline constexpr std::string_view k_cluster_attribute_disable_clone{
"opt_disableClone"};
// Flag to indicate the default cluster in the metadata
inline constexpr std::string_view k_cluster_attribute_default{"default"};
// Whether group_replication_start_on_boot should be enabled in each instance
// (false by default)
inline constexpr std::string_view k_cluster_attribute_manual_start_on_boot{
"opt_manualStartOnBoot"};
// Timestamp of when the instance was added to the group
inline constexpr std::string_view k_instance_attribute_join_time{"joinTime"};
inline constexpr std::string_view k_instance_attribute_server_id{"server_id"};
// replication account certificate subject
inline constexpr std::string_view k_instance_attribute_cert_subject{
"opt_certSubject"};
class MetadataStorage;
struct Cluster_metadata;
using Instance_md_and_gr_member =
std::pair<Instance_metadata, mysqlshdk::gr::Member>;
class Cluster_set_impl;
class ClusterSet;
namespace checks {
enum class Check_type;
}
class Cluster_impl final : public Base_cluster_impl,
public shcore::NotificationObserver {
public:
friend class Cluster;
Cluster_impl(const std::shared_ptr<Cluster_set_impl> &cluster_set,
const Cluster_metadata &metadata,
const std::shared_ptr<Instance> &group_server,
const std::shared_ptr<MetadataStorage> &metadata_storage,
Cluster_availability availability);
Cluster_impl(const Cluster_metadata &metadata,
const std::shared_ptr<Instance> &group_server,
const std::shared_ptr<MetadataStorage> &metadata_storage,
Cluster_availability availability);
Cluster_impl(const std::string &cluster_name, const std::string &group_name,
const std::shared_ptr<Instance> &group_server,
const std::shared_ptr<Instance> &primary_master,
const std::shared_ptr<MetadataStorage> &metadata_storage,
mysqlshdk::gr::Topology_mode topology_type);
virtual ~Cluster_impl();
// API functions
void add_instance(const Connection_options &instance_def,
const cluster::Add_instance_options &options);
void rejoin_instance(const Connection_options &instance_def,
const cluster::Rejoin_instance_options &options);
void remove_instance(const Connection_options &instance_def,
const cluster::Remove_instance_options &options);
shcore::Value describe();
shcore::Value options(const bool all);
shcore::Value status(int64_t extended);
shcore::Value list_routers(bool only_upgrade_required) override;
void remove_router_metadata(const std::string &router);
void force_quorum_using_partition_of(const Connection_options &instance_def,
const bool interactive);
void dissolve(const mysqlshdk::null_bool &force, const bool interactive);
void rescan(const cluster::Rescan_options &options);
void switch_to_single_primary_mode(const Connection_options &instance_def);
void switch_to_multi_primary_mode();
void set_primary_instance(
const Connection_options &instance_def,
const cluster::Set_primary_instance_options &options);
shcore::Value check_instance_state(const Connection_options &instance_def);
void reset_recovery_password(const mysqlshdk::null_bool &force,
const bool interactive);
void fence_all_traffic();
void fence_writes();
void unfence_writes();
bool is_fenced_from_writes() const;
shcore::Value create_cluster_set(
const std::string &domain_name,
const clusterset::Create_cluster_set_options &options);
std::shared_ptr<ClusterSet> get_cluster_set();
Cluster_availability cluster_availability() const { return m_availability; }
void check_cluster_online();
// Class functions
void sanity_check() const;
void add_metadata_for_instance(
const mysqlshdk::db::Connection_options &instance_definition,
const std::string &label = "") const;
void add_metadata_for_instance(const mysqlshdk::mysql::IInstance &instance,
const std::string &label = "") const;
void remove_instance_metadata(
const mysqlshdk::db::Connection_options &instance_def);
void update_metadata_for_instance(
const mysqlshdk::db::Connection_options &instance_definition,
Instance_id instance_id = 0, const std::string &label = "") const;
void update_metadata_for_instance(const mysqlshdk::mysql::IInstance &instance,
Instance_id instance_id = 0,
const std::string &label = "") const;
bool get_disable_clone_option() const;
void set_disable_clone_option(const bool disable_clone);
bool get_manual_start_on_boot_option() const;
const std::string &get_group_name() const { return m_group_name; }
const std::string get_view_change_uuid() const;
Cluster_type get_type() const override {
return Cluster_type::GROUP_REPLICATION;
}
std::string get_topology_type() const override {
return to_string(m_topology_type);
}
mysqlshdk::gr::Topology_mode get_cluster_topology_type() const {
return m_topology_type;
}
void set_topology_type(const mysqlshdk::gr::Topology_mode &topology_type) {
m_topology_type = topology_type;
}
std::pair<mysqlshdk::mysql::Auth_options, std::string>
create_replication_user(mysqlshdk::mysql::IInstance *target,
std::string_view auth_cert_subject,
bool only_on_target = false,
mysqlshdk::mysql::Auth_options creds = {},
bool print_recreate_node = true) const;
/**
* Reset the password of the recovery_account or the target instance's
* recovery account and update the credentials for the recovery channel
*
* @param target Target instance
* @param recovery_account Username of the recovery account that will have the
* password reset. If empty, the function will use the
* target_instance recovery account
* @param hosts List of hosts on which the password must be reset
*/
void reset_recovery_password(
const std::shared_ptr<Instance> &target,
const std::string &recovery_account = "",
const std::vector<std::string> &hosts = {}) const;
/**
* Recreate the recovery replication account for the instance instance
*
* Drops and creates a new recovery account for the target instance, ensuring
* the credentials for the recovery channel are updated too
*
* @param target Target instance
*/
std::pair<std::string, std::string> recreate_replication_user(
const std::shared_ptr<Instance> &target,
std::string_view auth_cert_subject) const;
bool drop_replication_user(mysqlshdk::mysql::IInstance *target,
const std::string &endpoint = "",
const std::string &server_uuid = "",
const uint32_t server_id = 0,
mysqlshdk::mysql::Sql_undo_list *undo = nullptr);
void drop_replication_users(mysqlshdk::mysql::Sql_undo_list *undo = nullptr);
/**
* Wipes out all replication users created/managed by the AdminAPI
*
* All accounts matching the format of users created by the AdminAPI are
* removed:
*
* - mysql_innodb_cluster_%
* - mysql_innodb_cs_%
*/
void wipe_all_replication_users();
bool contains_instance_with_address(const std::string &host_port) const;
mysqlsh::dba::Instance *acquire_primary(
bool primary_required = true, bool check_primary_status = false) override;
Cluster_metadata get_metadata() const;
void release_primary() override;
shcore::Value cluster_status(int64_t extended);
shcore::Value cluster_describe();
Cluster_status cluster_status(int *out_num_failures_tolerated = nullptr,
int *out_num_failures = nullptr) const;
void setup_admin_account(const std::string &username, const std::string &host,
const Setup_account_options &options) override;
void setup_router_account(const std::string &username,
const std::string &host,
const Setup_account_options &options) override;
/**
* Get the lowest server version of the cluster members.
*
* The version information is obtained from available (ONLINE and RECOVERING)
* members, ignoring not available instances.
*
* @param out_instances_addresses canonical addresses of the instances with
* the lowest version
*
* @return Version object of the lowest instance version in the cluster.
*/
mysqlshdk::utils::Version get_lowest_instance_version(
std::vector<std::string> *out_instances_addresses = nullptr) const;
/**
* Determine the replication(recovery) user being used by the target instance.
*
* A user can have several accounts (for different hostnames).
* This method will try to obtain the recovery user from the metadata and if
* the user is not found in the Metadata it will be obtained using the
* get_recovery_user method (in order to support older versions of the shell).
* It will throw an exception if the user was not created by the AdminAPI,
* i.e. does not start with the 'mysql_innodb_cluster_' prefix
* Note: The hostname values are not quoted
*
* @param target_instance Instance whose recovery user we want to know
* @return a tuple with the recovery user name, the list of hostnames of
* for recovery user and a boolean that is true if the information was
* retrieved from the metadata and false otherwise.
* @throws shcore::Exception::runtime_error if user not created by AdminAPI
*/
std::tuple<std::string, std::vector<std::string>, bool> get_replication_user(
const mysqlshdk::mysql::IInstance &target_instance) const;
/**
* Get mismatched recovery accounts.
*
* This function returns instances for which the recovery account name doesn't
* match with its server id.
*
* @return a list of {server_id, user} pairs, for each mismatch account
* found.
*/
std::unordered_map<uint32_t, std::string> get_mismatched_recovery_accounts()
const;
/**
* Get unused recovery accounts / users.
*
* This function returns recovery accounts that exist but aren't being used
* in either instances of a given cluster. The list excludes mismatch
* accounts.
*
* @param mismatched_recovery_accounts the list of mismatched account (@see
* get_mismatched_recovery_accounts)
*
* @return the list of unused recovery accounts: pairs of {users/hosts}
*/
std::vector<std::tuple<std::string, std::string>>
get_unused_recovery_accounts(const std::unordered_map<uint32_t, std::string>
&mismatched_recovery_accounts) const;
/**
* Get a session to a given instance of the cluster.
*
* This function verifies if it is possible to connect to the given instance,
* i.e., if it is reachable and if so returns an Instance object with an open
* session otherwise throws an exception.
*
* @param instance_address String with the address <host>:<port> of the
* instance to connect to.
*
* @returns shared_ptr to the Instance if it is reachable.
* @throws Exception if the instance is not reachable.
*/
std::shared_ptr<Instance> get_session_to_cluster_instance(
const std::string &instance_address) const;
/**
* Setup the clone plugin usage on the cluster (enable or disable it)
*
* This function will install or uninstall the clone plugin on all the cluster
* members If a member is unreachable or missing an error will be printed
* indicating the function failed to install/uninstall the clone plugin on
* that instance. The function will return the number of instances that failed
* to be updated.
*
* @param enable_clone Boolean to indicate if the clone plugin must be
* installed (true) or uninstalled (false.
*
* @return size_t with the number of instances on which the update process
* failed
*/
size_t setup_clone_plugin(bool enable_clone) const;
void execute_in_members(
const std::vector<mysqlshdk::gr::Member_state> &states,
const mysqlshdk::db::Connection_options &cnx_opt,
const std::vector<std::string> &ignore_instances_vector,
const std::function<bool(const std::shared_ptr<Instance> &instance,
const mysqlshdk::gr::Member &gr_member)>
&functor,
const std::function<bool(const Instance_md_and_gr_member &md)>
&condition = nullptr,
bool ignore_network_conn_errors = true) const;
void execute_in_members(
const std::function<bool(const std::shared_ptr<Instance> &instance,
const Instance_md_and_gr_member &info)>
&on_connect,
const std::function<bool(const shcore::Error &error,
const Instance_md_and_gr_member &info)>
&on_connect_error = {}) const;
/**
* Get the primary instance address.
*
* This function retrieves the address of a primary instance in the
* cluster. In single primary mode, only one instance is the primary and
* its address is returned, otherwise it is assumed that multi primary mode
* is used. In multi primary mode, the address of any available instance
* is returned.
*/
mysqlshdk::db::Connection_options pick_seed_instance() const;
/**
* Validate the value of var_name variable on the target instance to see if it
* is compatible with the value used on the cluster. An exception is thrown in
* case the values are different.
*
* @param target_instance Target instance rejoining the cluster
* @param var_name name of the variable to check
*/
void validate_variable_compatibility(
const mysqlshdk::mysql::IInstance &target_instance,
const std::string &var_name) const;
std::map<std::string, std::string> get_cluster_group_seeds() const;
/**
* Get ONLINE and RECOVERING instances from the cluster.
*
* @return vector with the Instance definition of the ONLINE
* and RECOVERING instances.
*/
std::vector<Instance_metadata> get_active_instances(
bool online_only = false) const;
/**
* Get the list of instances in the states described in the states vector.
* @param states Vector of strings with the states of members whose
* instance_metadata we will retrieve from the cluster. If the states vector
* is empty, return the list of all instances.
*
* @return vector with the Instance definitions
*/
std::vector<Instance_metadata> get_instances(
const std::vector<mysqlshdk::gr::Member_state> &states = {}) const;
/**
* Ensures the server_id of each instance is registered as an attribute on the
* instances table. If an instance does not have it then inserts it.
*
* @param target_instance: The instance to be used to get the cluster members
* to update the server_id attribute on the instances table.
*/
void ensure_metadata_has_server_id(
const mysqlshdk::mysql::IInstance &target_instance) const;
/**
* Checks for missing metadata recovery account entries, and fix them using
* info from mysql.slave_master_info.
*
*/
void ensure_metadata_has_recovery_accounts();
/**
* Get an online instance from the cluster.
*
* Return an online instance from the cluster that is reachable (able
* to connect to it), excluding the one with the given UUID if specified.
*
* @param exclude_uuid optional string with the UUID of an instance to
* exclude. By default, "" (empty) meaning that no instance will be
* excluded.
* @return shared pointer with an Instance object for the first online
* instance found, or a nullptr if no instance is available (not
* reachable).
*/
std::shared_ptr<mysqlsh::dba::Instance> get_online_instance(
const std::string &exclude_uuid = "") const;
void validate_server_uuid(const mysqlshdk::mysql::IInstance &instance) const;
void validate_server_id(
const mysqlshdk::mysql::IInstance &target_instance) const;
/**
* Return list of instances registered in metadata along with their current
* GR member state.
*/
std::vector<Instance_md_and_gr_member> get_instances_with_state(
bool allow_offline = false) const;
std::unique_ptr<mysqlshdk::config::Config> create_config_object(
const std::vector<std::string> &ignored_instances = {},
bool skip_invalid_state = false, bool persist_only = false,
bool best_effort = false, bool allow_cluster_offline = false) const;
void query_group_wide_option_values(
mysqlshdk::mysql::IInstance *target_instance,
std::optional<std::string> *out_gr_consistency,
std::optional<int64_t> *out_gr_member_expel_timeout) const;
/**
* Update the cluster members according to the removed instance.
*
* More specifically, remove the specified local address from the
* group_replication_group_seeds variable of all alive members of the
* cluster and then remove the recovery user used by the instance on the
* other members through a primary instance.
*
* @param local_gr_address string with the local GR address (XCom) to remove.
*/
void update_group_members_for_removed_member(const std::string &server_uuid);
void adopt_from_gr();
/**
* Validate the GTID consistency in regard to the cluster for an instance
* rejoining
*
* This function checks if the target instance has errant transactions, or if
* the binary logs have been purged from all cluster members to block the
* rejoin. It also checks if the GTID set is empty which, for an instance that
* is registered as a cluster member should not happen, to block its rejoin
* too.
*
* @param target_instance Target instance rejoining the cluster
*/
void validate_rejoin_gtid_consistency(
const mysqlshdk::mysql::IInstance &target_instance) const;
/**
* Enables super_read_only in the whole Cluster
*
* This functions enables super_read_only in the Cluster, starting by the
* primary instance and proceeding to the remaining secondary members
*/
void enable_super_read_only_globally() const;
void refresh_connections();
// Lock methods
[[nodiscard]] mysqlshdk::mysql::Lock_scoped get_lock_shared(
std::chrono::seconds timeout = {});
[[nodiscard]] mysqlshdk::mysql::Lock_scoped get_lock_exclusive(
std::chrono::seconds timeout = {});
public:
// clusterset related methods
const Cluster_set_member_metadata &get_clusterset_metadata() const;
bool is_cluster_set_member(const std::string &cs_id = "") const;
bool is_invalidated() const;
bool is_primary_cluster() const;
void invalidate_cluster_set_metadata_cache();
void set_cluster_set_remove_pending(bool flag) {
m_cs_md_remove_pending = flag;
}
std::shared_ptr<Cluster_set_impl> check_and_get_cluster_set_for_cluster();
bool is_cluster_set_remove_pending() const { return m_cs_md_remove_pending; }
std::shared_ptr<Cluster_set_impl> get_cluster_set_object(
bool print_warnings = false, bool check_status = false) const;
/**
* Reset the password for the Cluster's replication account in use for the
* ClusterSet replication channel
*
* @return A mysqlshdk::mysql::Auth_options with the username and the newly
* generated password
*/
// XXX removethis
mysqlshdk::mysql::Auth_options refresh_clusterset_replication_user() const;
void update_replication_allowed_host(const std::string &host);
/*
* Enable skip_slave_start and configures the managed replication channel if
* the target cluster is a replica.
*/
void configure_cluster_set_member(
const std::shared_ptr<mysqlsh::dba::Instance> &target_instance) const;
/**
* Recreate the recovery account for each Cluster member
*
* Required when using the 'MySQL' communication stack to ensure each active
* member of the Cluster will use its own recovery account instead of the one
* created whenever a new member joined since all possible seeds had to be
* updated to use that same account. This ensures distributed recovery is
* possible at any circumstance.
*/
void restore_recovery_account_all_members(bool reset_password = true) const;
/**
* Change the recovery user credentials of all Cluster members
*
* @param repl_account The authentication options of the recovery account
*/
void change_recovery_credentials_all_members(
const mysqlshdk::mysql::Auth_options &repl_account) const;
/**
* Create a local recovery account for the target instance
*
* Required when using the 'MySQL' communication stack. The account is
* created locally only to the target instance and with binary logging
* disabled to not introduce errant transactions
*/
void create_local_replication_user(
const std::shared_ptr<mysqlsh::dba::Instance> &target_instance,
std::string_view auth_cert_subject,
const Group_replication_options &gr_options,
bool propagate_credentials_donors);
/**
* Create all accounts present in the current members of the cluster to the
* target cluster
*
* Required when using the 'MySQL' communication stack and when the recovery
* accounts need certificates.
*/
void create_replication_users_at_instance(
const std::shared_ptr<mysqlsh::dba::Instance> &target_instance);
void update_group_peers(const mysqlshdk::mysql::IInstance &target_instance,
const Group_replication_options &gr_options,
int cluster_member_count,
const std::string &self_address,
bool group_seeds_only = false);
void check_instance_configuration(
const std::shared_ptr<mysqlsh::dba::Instance> &target_instance,
bool using_clone_recovery, checks::Check_type checks_type,
bool already_member) const;
protected:
void _set_option(const std::string &option,
const shcore::Value &value) override;
void _set_instance_option(const std::string &instance_def,
const std::string &option,
const shcore::Value &value) override;
// Used shell options
void init();
private:
std::string get_replication_user_host() const;
void verify_topology_type_change() const;
std::weak_ptr<Cluster_set_impl> m_cluster_set;
void handle_notification(const std::string &name,
const shcore::Object_bridge_ref &sender,
shcore::Value::Map_type_ref data) override;
void find_real_cluster_set_primary(Cluster_set_impl *cs) const;
[[nodiscard]] mysqlshdk::mysql::Lock_scoped get_lock(
mysqlshdk::mysql::Lock_mode mode, std::chrono::seconds timeout = {});
std::string m_group_name;
mysqlshdk::gr::Topology_mode m_topology_type =
mysqlshdk::gr::Topology_mode::NONE;
Cluster_availability m_availability = Cluster_availability::ONLINE;
mutable Cluster_set_member_metadata m_cs_md;
// cluster was removed from clusterset but local MD is not caught up
bool m_cs_md_remove_pending = false;
};
} // namespace dba
} // namespace mysqlsh
#endif // MODULES_ADMINAPI_CLUSTER_CLUSTER_IMPL_H_