def test_get_rpl_usr(self): """Tests get_rpl_usr method""" options = {"rep_user_passwd": "rpl_pass"} rpl_user_dict = { 'host': '%', 'recovery_user': '******', 'rep_user_passwd': 'rpl_pass', 'replication_user': "******", 'ssl_mode': GR_SSL_REQUIRED, } self.assertDictEqual(get_rpl_usr(options), rpl_user_dict) options = { "rep_user_passwd": "my_password", "replication_user": "******", 'ssl_mode': GR_SSL_DISABLED, } rpl_user_dict = { 'host': 'oracle.com', "recovery_user": "******", 'rep_user_passwd': 'my_password', 'replication_user': "******", 'ssl_mode': GR_SSL_DISABLED, } self.assertDictEqual(get_rpl_usr(options), rpl_user_dict)
def test_check_server_requirements(self): """Tests check_server_requirements method""" skip_if_not_GR_approved(self.server1) self.server.exec_query("drop user if exists 'new_rpl_user'") self.server.exec_query("drop user if exists 'replic_user'@'%'") self.server.exec_query("drop user if exists 'replic_user'@'localhost'") # Test with default values, server.user must have SUPER options = { "replication_user": "******", "rep_user_passwd": "rplr_pass", } rpl_settings = get_rpl_usr(options) req_dict = get_req_dict(self.server, rpl_settings["replication_user"]) self.assertTrue( check_server_requirements(self.server, req_dict, rpl_settings, verbose=True, dry_run=False, skip_backup=True)) self.assertTrue( User(self.server, "replic_user@%", None).exists(), "User was not created on check_server_requirements") # Test using an admin user without CREATE USER privilege. grant_list = ["SELECT"] self.server.exec_query("DROP USER IF EXISTS " "'replic_user'@'localhost'") change_user_privileges(self.server, "replic_user", 'localhost', "rplr_pass", grant_list=grant_list, create_user=True) server2 = Server({ "conn_info": "replic_user@localhost:{0}" "".format(self.server.port) }) server2.passwd = "rplr_pass" server2.connect() qry_key = ("select MEMBER_HOST, MEMBER_PORT from {0}" "".format(REP_GROUP_MEMBERS_TABLE)) frozen_queries = {qry_key: [[server2.host, server2.port]]} mock_server = get_mock_server(server2, variables=frozen_queries) options = { "replication_user": "******", "rep_user_passwd": "rpl_pass", } rpl_settings = get_rpl_usr(options) req_dict = get_req_dict(server2, rpl_settings["replication_user"]) # expect GadgetError: Query failed: No required privileges # to create the replication user with self.assertRaises(GadgetError) as test_raises: check_server_requirements(mock_server, req_dict, rpl_settings, verbose=True, dry_run=False, skip_backup=True) exception = test_raises.exception self.assertTrue( "required privileges to create" in exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) # Test existing user and admin user without REPLICATION SLAVE grant grant_list = ["SELECT"] revoke_list = ["REPLICATION SLAVE"] change_user_privileges(self.server, "new_rpl_user", "%", grant_list=grant_list, revoke_list=revoke_list, create_user=True, with_grant=True) revoke_list = ["REPLICATION SLAVE"] change_user_privileges(self.server, "replic_user", "%", revoke_list=revoke_list) # expect GadgetError: does not have the REPLICATION SLAVE privilege, # and can not be granted by. with self.assertRaises(GadgetError) as test_raises: check_server_requirements(mock_server, req_dict, rpl_settings, verbose=True, dry_run=False, skip_backup=True) exception = test_raises.exception self.assertTrue( "does not have the REPLICATION SLAVE privilege, " "and can not be granted by" in exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) self.assertTrue( "SUPER privilege required to disable the binlog." in exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) # self.server.exec_query("drop user if exists 'replic_user'@'%'") # Test existing user and admin user without REPLICATION SLAVE grant grant_list = ["SELECT"] revoke_list = ["REPLICATION SLAVE"] change_user_privileges(self.server, "new_rpl_user", "%", grant_list=grant_list, revoke_list=revoke_list) grant_list = ["REPLICATION SLAVE", "SUPER"] change_user_privileges(self.server, "replic_user", "localhost", grant_list=grant_list, with_grant=True) # reset session to get new privileges. server2.disconnect() server2.connect() mock_server = get_mock_server(server2, variables=frozen_queries) self.assertTrue( check_server_requirements(mock_server, req_dict, rpl_settings, verbose=True, dry_run=False, skip_backup=True)) # Test existing rpl user and admin user without grant # admin user: replic_user # rpl user: new_rpl_user grant_list = ["REPLICATION SLAVE"] change_user_privileges(self.server, "new_rpl_user", "%", grant_list=grant_list) grant_list = ["SELECT", "CREATE USER"] change_user_privileges(self.server, "create_rpl", "127.0.0.1", user_passwd="c_pass", grant_list=grant_list, create_user=True, with_grant=True) server3 = Server({ "conn_info": "[email protected]:{0}" "".format(self.server.port) }) server3.passwd = "c_pass" server3.connect() req_dict3 = get_req_dict(server3, rpl_settings["replication_user"]) # expect GadgetError: No required privileges # to grant Replication Slave privilege with self.assertRaises(GadgetError) as test_raises: check_server_requirements(server3, req_dict3, rpl_settings, verbose=True, dry_run=False, skip_backup=True) exception = test_raises.exception self.assertTrue( "SUPER privilege needed to run the CHANGE MASTER " "command" in exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) # Test invalid server_id mock_server = get_mock_server(self.server, variables={"server_id": "0"}) req_dict = get_req_dict(self.server, rpl_settings["replication_user"]) # expect GadgetError: server_id not valid with self.assertRaises(GadgetError) as test_raises: check_server_requirements(mock_server, req_dict, rpl_settings, verbose=True, dry_run=False, skip_backup=True) exception = test_raises.exception self.assertTrue( "is not valid, it must be a positive integer" in exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) # Test duplicate server_id mock_server = get_mock_server(self.server, variables={"server_id": "101"}) req_dict["SERVER_ID"] = {"peers": [mock_server]} # expect GadgetError: server_id is already used by peer with self.assertRaises(GadgetError) as test_raises: check_server_requirements(mock_server, req_dict, rpl_settings, verbose=True, dry_run=False, skip_backup=True) exception = test_raises.exception self.assertTrue( "is already used by peer" in exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) # Test existing user and admin with required grants req_dict = get_req_dict(self.server, rpl_settings["replication_user"]) grant_list = ["REPLICATION SLAVE"] change_user_privileges(self.server, "new_rpl_user", "%", grant_list=grant_list) self.assertTrue( check_server_requirements(self.server, req_dict, rpl_settings, verbose=True, dry_run=False, skip_backup=True)) # Tests server variables not meet required values req_dict = get_req_dict(self.server, rpl_settings["replication_user"]) req_dict[SERVER_VARIABLES] = { "log_bin": { ONE_OF: ("0", ) }, "binlog_format": { ONE_OF: ("", ) }, "binlog_checksum": { ONE_OF: ("", ) }, "gtid_mode": { ONE_OF: ("OFF", ) }, "log_slave_updates": { ONE_OF: ("0", ) }, "enforce_gtid_consistency": { ONE_OF: ("OFF", ) }, } # expect GadgetError: change the configuration values with self.assertRaises(GadgetError) as test_raises: check_server_requirements(self.server, req_dict, rpl_settings, verbose=True, dry_run=True, update=False, skip_backup=True) exception = test_raises.exception self.assertIn( "on server {0} are incompatible with Group " "Replication.".format(self.server), exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) # Test server version req_dict[SERVER_VERSION] = "99.9.9" # expect GadgetError: Query failed: server version with self.assertRaises(GadgetError) as test_raises: check_server_requirements(self.server, req_dict, rpl_settings, verbose=True, dry_run=True, update=False, skip_backup=True) exception = test_raises.exception self.assertTrue( "does not meet the required MySQL server version" in exception.errmsg, "The exception message was not the expected. {0}" "".format(exception.errmsg)) self.server.exec_query("drop user if exists 'replic_user'") self.server.exec_query("drop user if exists 'create_rpl'") self.server.exec_query("drop user if exists 'new_rpl_user'")
def join(server_info, peer_server_info, **kwargs): """Add a server to an existing Group Replication group. The contact point to add the new server to the group replication is the specified peer server, which must be already a member of the group. :param server_info: Connection information :type server_info: dict | Server | str :param peer_server_info: Connection information of a server member of a Group Replication group. :type peer_server_info: dict | Server | str :param kwargs: Keyword arguments: gr_address: The host:port that the gcs plugin uses to other servers communicate with this server. dry_run: Do not make changes to the server verbose: If the command should show more verbose information. option_file: The path to a options file to check configuration. skip_backup: if True, skip the creation of a backup file when modifying the options file. ssl_mode: SSL mode to be used with group replication. (Note server GR SSL modes need to be consistent with the SSL GR modes on the peer-server otherwise an error will be thrown). skip_rpl_user: If True, skip the creation of the replication user. :type kwargs: dict :raise GadgetError: If server_info or peer_server_info is None. If the given peer_server_info is from a server that is not a member of Group Replication. :raise GadgetCnxInfoError: If the connection information on server_info or peer_server_info could not be parsed. :raise GadgetServerError: If a connection fails. :return: True if the start_gr_plugin method was executed otherwise False. :rtype: boolean """ verbose = kwargs.get("verbose", False) dry_run = kwargs.get("dry_run", False) gr_host = kwargs.get("gr_host", None) option_file = kwargs.get("option_file", None) skip_backup = kwargs.get("skip_backup", False) # Default is value for ssl_mode is REQUIRED ssl_mode = kwargs.get("ssl_mode", GR_SSL_REQUIRED) skip_rpl_user = kwargs.get("skip_rpl_user", False) # Connect to the server server = get_server(server_info=server_info) if server is None: raise GadgetError(_ERROR_NO_SERVER) msg = _RUNNING_COMMAND.format(JOIN, server) _LOGGER.info("") _LOGGER.log(STEP_LOG_LEVEL_VALUE, msg) _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Checking Group Replication " "prerequisites.") peer_server = get_server(server_info=peer_server_info) # Connect to the peer server if peer_server is None: if server is not None: server.disconnect() raise GadgetError("No peer server provided. It is required to get " "information from the group.") try: # verify the peer server belong to a GR group. if not is_member_of_group(peer_server): raise GadgetError("Peer server '{0}' is not a member of a GR " "group.".format(peer_server)) # verify the server status is ONLINE peer_server_state = get_member_state(peer_server) if peer_server_state != 'ONLINE': raise GadgetError("Cannot join instance {0}. Peer instance {1} " "state is currently '{2}', but is expected to " "be 'ONLINE'.".format(server, peer_server, peer_server_state)) # Throw an error in case server doesn't support SSL and the ssl_mode # option was provided a value other than DISABLED if server.select_variable(HAVE_SSL) != 'YES' and \ ssl_mode != GR_SSL_DISABLED: raise GadgetError(_ERROR_NO_HAVE_SSL.format(server)) # Throw an error in case there is any SSL incompatibilities are found # on the peer server or if the peer-server is incompatible with the # value of the ssl_mode option. check_peer_ssl_compatibility(peer_server, ssl_mode) if not skip_rpl_user: rpl_user_dict = get_rpl_usr(kwargs) req_dict = get_req_dict(server, rpl_user_dict["replication_user"], peer_server, option_file=option_file) else: # if replication user is to be skipped, no need to add the # replication user to the requirements list req_dict = get_req_dict(server, None, peer_server, option_file=option_file) rpl_user_dict = None check_server_requirements(server, req_dict, rpl_user_dict, verbose, dry_run, skip_backup=skip_backup) # verify the group replication is installed and not disabled. check_gr_plugin_is_installed(server, option_file, dry_run) # Initialize log error access and get current position in it error_log_size = None if server.is_alias("127.0.0.1"): error_log = LocalErrorLog(server) try: error_log_size = error_log.get_size() except Exception as err: # pylint: disable=W0703 _LOGGER.warning( "Unable to access the server error log: %s", str(err)) else: _LOGGER.warning("Not running locally on the server and can not " "access its error log.") # verify the server does not belong already to a GR group. if is_active_member(server): health(server, **kwargs) raise GadgetError(_ERROR_ALREADY_A_MEMBER.format(server, JOIN)) gr_host, local_port = resolve_gr_local_address(gr_host, server.host, server.port) local_address = "{0}:{1}".format(gr_host, local_port) _LOGGER.debug("local_address to use: %s", local_address) # Get local_address from the peer server to add to the list of # group_seeds. peer_local_address = get_gr_local_address_from(peer_server) option_parser = req_dict.get(OPTION_PARSER, None) gr_config_vars = get_gr_config_vars(local_address, kwargs, option_parser, peer_local_address) # Do several replication user related tasks if the # skip-replication-user option was not provided if not skip_rpl_user: # The replication user for be check/create on the peer server. # NOTE: rpl_user_dict["host"] has the FQDN resolved from the host # provided by the user replication_user = "******".format( rpl_user_dict["recovery_user"], rpl_user_dict["host"]) rpl_user_dict["replication_user"] = replication_user # Check the given replication user exists on peer req_dict_user = get_req_dict_user_check(peer_server, replication_user) # Check and create the given replication user on peer server. # NOTE: No other checks will be performed, only the replication # user. check_server_requirements(peer_server, req_dict_user, rpl_user_dict, verbose, dry_run, skip_schema_checks=True) # IF the group name is not set, try to acquire it from a peer server. if gr_config_vars[GR_GROUP_NAME] is None: _LOGGER.debug("Trying to retrieve group replication name from " "peer server.") group_name = get_gr_name_from_peer(peer_server) _LOGGER.debug("Retrieved group replication name from peer" " server: %s.", group_name) gr_config_vars[GR_GROUP_NAME] = group_name # Set the single_primary mode according to the value set on peer # server if gr_config_vars[GR_SINGLE_PRIMARY_MODE] is None: gr_config_vars[GR_SINGLE_PRIMARY_MODE] = \ get_gr_variable_from_peer(peer_server, GR_SINGLE_PRIMARY_MODE) if gr_config_vars[GR_GROUP_NAME] is None: raise GadgetError( _ERROR_UNABLE_TO_GET.format("Group Replication group name", peer_server)) _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Joining Group Replication group: %s", gr_config_vars[GR_GROUP_NAME]) if gr_config_vars[GR_GROUP_SEEDS] is None: raise GadgetError( _ERROR_UNABLE_TO_GET.format("peer addresses", peer_server)) # Remove IP whitelist variable if not set (by user or from the option # file) to use the default server value and not set it with None. if gr_config_vars[GR_IP_WHITELIST] is None: gr_config_vars.pop(GR_IP_WHITELIST) setup_gr_config(server, gr_config_vars, dry_run=dry_run) if not skip_rpl_user: # if the skip replication user option was not specified, # run the change master to store MySQL replication user name or # password information in the master info repository do_change_master(server, rpl_user_dict, dry_run=dry_run) _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Attempting to join to Group Replication group...") try: start_gr_plugin(server, dry_run) _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Server %s joined " "Group Replication group %s.", server, gr_config_vars[GR_GROUP_NAME]) except: _LOGGER.error("\nGroup Replication join failed.") if error_log_size is not None: log_data = error_log.read(error_log_size) if log_data: _LOGGER.error("Group Replication plugin failed to start. " "Server error log contains the following " "errors: \n %s", log_data) raise # Update the Group Replication options on defaults file. # Note: Set group_replication_start_on_boot=ON if OPTION_PARSER in req_dict.keys(): persist_gr_config(req_dict[OPTION_PARSER], gr_config_vars, dry_run=dry_run, skip_backup=skip_backup) if dry_run: _LOGGER.warning(_WARN_DRY_RUN_USED) return False finally: # disconnect servers. if server is not None: server.disconnect() if peer_server is not None: peer_server.disconnect() return True
def start(server_info, **kwargs): """Start a group replication group with the given server information. :param server_info: Connection information :type server_info: dict | Server | str :param kwargs: Keyword arguments: gr_address: The host:port that the gcs plugin uses to other servers communicate with this server. dry_run: Do not make changes to the server verbose: If the command should show more verbose information. option_file: The path to a options file to check configuration. skip_backup: if True, skip the creation of a backup file when modifying the options file. skip_schema_checks: Skip schema validation. ssl_mode: SSL mode to be used with group replication. :type kwargs: dict :raise GadgetError: If server_info is None. :raise GadgetCnxInfoError: If the connection information on server_info could not be parsed. :raise GadgetServerError: If a connection fails. :return: True if the start_gr_plugin method was executed otherwise False. :rtype: boolean """ server = get_server(server_info=server_info) if server is None: raise GadgetError(_ERROR_NO_SERVER) msg = _RUNNING_COMMAND.format(START, server) _LOGGER.info("") _LOGGER.log(STEP_LOG_LEVEL_VALUE, msg) verbose = kwargs.get("verbose", False) dry_run = kwargs.get("dry_run", False) gr_host = kwargs.get("gr_address", None) skip_schema_checks = kwargs.get("skip_schema_checks", []) option_file = kwargs.get("option_file", None) skip_backup = kwargs.get("skip_backup", False) _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Checking Group Replication " "prerequisites.") try: # Throw an error in case server doesn't support SSL and the ssl_mode # option was provided a value other than DISABLED if server.select_variable(HAVE_SSL) != 'YES' and \ kwargs["ssl_mode"] != GR_SSL_DISABLED: raise GadgetError(_ERROR_NO_HAVE_SSL.format(server)) rpl_user_dict = get_rpl_usr(kwargs) req_dict = get_req_dict(server, rpl_user_dict["replication_user"], option_file=option_file) check_server_requirements(server, req_dict, rpl_user_dict, verbose, dry_run, skip_schema_checks, skip_backup=skip_backup) gr_host, local_port = resolve_gr_local_address(gr_host, server.host, server.port) # verify the group replication is not Disabled in the server. check_gr_plugin_is_installed(server, option_file, dry_run) # verify the server does not belong already to a GR group. if is_active_member(server): health(server, **kwargs) raise GadgetError(_ERROR_ALREADY_A_MEMBER.format(server, START)) local_address = "{0}:{1}".format(gr_host, local_port) option_parser = req_dict.get(OPTION_PARSER, None) gr_config_vars = get_gr_config_vars(local_address, kwargs, option_parser) if gr_config_vars[GR_GROUP_SEEDS] is None: gr_config_vars.pop(GR_GROUP_SEEDS) # Remove IP whitelist variable if not set (by user or from the option # file) to use the default server value and not set it with None. if gr_config_vars[GR_IP_WHITELIST] is None: gr_config_vars.pop(GR_IP_WHITELIST) if gr_config_vars[GR_GROUP_NAME] is None: new_uuid = get_group_uuid_name(server) _LOGGER.debug("A new UUID has been generated for the group " "replication name %s", new_uuid) gr_config_vars[GR_GROUP_NAME] = new_uuid # Set the single_primary mode, if no value given then set ON as # default. if gr_config_vars[GR_SINGLE_PRIMARY_MODE] is None: gr_config_vars[GR_SINGLE_PRIMARY_MODE] = '"ON"' _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Group Replication group name: %s", gr_config_vars[GR_GROUP_NAME]) setup_gr_config(server, gr_config_vars, dry_run=dry_run) # Run the change master to store MySQL replication user name or # password information in the master info repository do_change_master(server, rpl_user_dict, dry_run=dry_run) set_bootstrap(server, dry_run) _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Attempting to start the Group Replication group...") # Attempt to start the Group Replication plugin start_gr_plugin(server, dry_run) # Wait for the super_read_only to be unset. super_read_only = server.select_variable("super_read_only", 'global') _LOGGER.debug("super_read_only: %s", super_read_only) waiting_time = 0 informed = False while int(super_read_only) and waiting_time < TIME_OUT: time.sleep(WAIT_SECONDS) waiting_time += WAIT_SECONDS _LOGGER.debug("have been waiting %s seconds", waiting_time) # inform what are we waiting for if waiting_time >= 10 and not informed: _LOGGER.info("Waiting for super_read_only to be unset.") informed = True super_read_only = server.select_variable("super_read_only", 'global') _LOGGER.debug("super_read_only: %s", super_read_only) if int(super_read_only): raise GadgetError("Timeout waiting for super_read_only to be " "unset after call to start Group Replication " "plugin.") _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Group Replication started for group: %s.", gr_config_vars[GR_GROUP_NAME]) unset_bootstrap(server, dry_run) # Update the Group Replication options on defaults file. # Note: Set group_replication_start_on_boot=ON if OPTION_PARSER in req_dict.keys(): persist_gr_config(req_dict[OPTION_PARSER], gr_config_vars, dry_run=dry_run, skip_backup=skip_backup) if dry_run: _LOGGER.warning(_WARN_DRY_RUN_USED) return False finally: # Disconnect the server prior to end method invocation. if server is not None: server.disconnect() return True
def join(server_info, peer_server_info, **kwargs): """Add a server to an existing Group Replication group. The contact point to add the new server to the group replication is the specified peer server, which must be already a member of the group. :param server_info: Connection information :type server_info: dict | Server | str :param peer_server_info: Connection information of a server member of a Group Replication group. :type peer_server_info: dict | Server | str :param kwargs: Keyword arguments: gr_address: The host:port that the gcs plugin uses to other servers communicate with this server. dry_run: Do not make changes to the server verbose: If the command should show more verbose information. option_file: The path to a options file to check configuration. skip_backup: if True, skip the creation of a backup file when modifying the options file. ssl_mode: SSL mode to be used with group replication. (Note server GR SSL modes need to be consistent with the SSL GR modes on the peer-server otherwise an error will be thrown). failover_consistency: Group Replication failover Consistency, must be a string containing either "BEFORE_ON_PRIMARY_FAILOVER", "EVENTUAL", "0" or "1". expel_timeout: Group Replication member expel Timeout. Must must be an integer value containing the time in seconds to wait before ghe killer node expels members suspected of having failed from the group. skip_rpl_user: If True, skip the creation of the replication user. target_is_local: Target is running in the same host :type kwargs: dict :raise GadgetError: If server_info or peer_server_info is None. If the given peer_server_info is from a server that is not a member of Group Replication. :raise GadgetCnxInfoError: If the connection information on server_info or peer_server_info could not be parsed. :raise GadgetServerError: If a connection fails. :return: True if the start_gr_plugin method was executed otherwise False. :rtype: boolean """ verbose = kwargs.get("verbose", False) dry_run = kwargs.get("dry_run", False) gr_host = kwargs.get("gr_address", None) option_file = kwargs.get("option_file", None) skip_backup = kwargs.get("skip_backup", False) # Default is value for ssl_mode is REQUIRED ssl_mode = kwargs.get("ssl_mode", GR_SSL_REQUIRED) skip_rpl_user = kwargs.get("skip_rpl_user", False) target_is_local = kwargs.get("target_is_local", False) exit_state_action = kwargs.get("exit_state_action", None) member_weight = kwargs.get("member_weight", None) failover_consistency = kwargs.get("failover_consistency", None) expel_timeout = kwargs.get("expel_timeout", None) # Connect to the server server = get_server(server_info=server_info) if server is None: raise GadgetError(_ERROR_NO_SERVER) msg = _RUNNING_COMMAND.format(JOIN, server) _LOGGER.info("") _LOGGER.step(msg) _LOGGER.step("Checking Group Replication " "prerequisites.") peer_server = get_server(server_info=peer_server_info) # Connect to the peer server if peer_server is None: if server is not None: server.disconnect() raise GadgetError("No peer server provided. It is required to get " "information from the group.") try: # verify the peer server belong to a GR group. if not is_member_of_group(peer_server): raise GadgetError("Peer server '{0}' is not a member of a GR " "group.".format(peer_server)) # verify the server status is ONLINE peer_server_state = get_member_state(peer_server) if peer_server_state != 'ONLINE': raise GadgetError("Cannot join instance {0}. Peer instance {1} " "state is currently '{2}', but is expected to " "be 'ONLINE'.".format(server, peer_server, peer_server_state)) # Throw an error in case server doesn't support SSL and the ssl_mode # option was provided a value other than DISABLED if server.select_variable(HAVE_SSL) != 'YES' and \ ssl_mode != GR_SSL_DISABLED: raise GadgetError(_ERROR_NO_HAVE_SSL.format(server)) # Throw an error in case there is any SSL incompatibilities are found # on the peer server or if the peer-server is incompatible with the # value of the ssl_mode option. check_peer_ssl_compatibility(peer_server, ssl_mode) if not skip_rpl_user: rpl_user_dict = get_rpl_usr(kwargs) else: rpl_user_dict = None # Do not check/create the replication user in the instance to add, # in order to avoid errors if it is in read-only-mode. # (GR automatically enables super-read-only when stopping the # plugin, starting with version 8.0.2) req_dict = get_req_dict(server, None, peer_server, option_file=option_file) check_server_requirements(server, req_dict, rpl_user_dict, verbose, dry_run, skip_backup=skip_backup, var_change_warning=True) # verify the group replication is installed and not disabled. check_gr_plugin_is_installed(server, option_file, dry_run) # attempt to set the group_replication_exit_state_action in order to # let GR do the value validation and catch any error right away if exit_state_action is not None: validate_exit_state_action(server, exit_state_action, dry_run) # attempt to set the group_replication_member_weight in order to # let GR do the value validation and catch any error right away if member_weight is not None: validate_member_weight(server, member_weight, dry_run) # attempt to set the group_replication_consistency in order to # let GR do the value validation and catch any error right away if failover_consistency is not None: validate_failover_consistency(server, failover_consistency, dry_run) # attempt to set the group_replication_member_expel_timeout in order to # let GR do the value validation and catch any error right away if expel_timeout is not None: validate_expel_timeout(server, expel_timeout, dry_run) # Initialize log error access and get current position in it error_log_size = None # is_alias(127.0.0.1) != is_alias(gethostname()), but they should # match. also is_alias() can't be made to work nicely with recording if target_is_local: # server.is_alias("127.0.0.1"): try: error_log = LocalErrorLog(server) error_log_size = error_log.get_size() except Exception as err: # pylint: disable=W0703 _LOGGER.warning( "Unable to access the server error log: %s", str(err)) else: _LOGGER.warning("Not running locally on the server and can not " "access its error log.") # verify the server does not belong already to a GR group. if is_active_member(server): health(server, **kwargs) raise GadgetError(_ERROR_ALREADY_A_MEMBER.format(server, JOIN)) gr_host, local_port = resolve_gr_local_address(gr_host, server.host, server.port) local_address = "{0}:{1}".format(gr_host, local_port) _LOGGER.debug("local_address to use: %s", local_address) # Get local_address from the peer server to add to the list of # group_seeds. peer_local_address = get_gr_local_address_from(peer_server) if peer_server.select_variable( "group_replication_single_primary_mode") in ('1', 'ON'): kwargs["single_primary"] = "ON" else: kwargs["single_primary"] = "OFF" option_parser = req_dict.get(OPTION_PARSER, None) gr_config_vars = get_gr_config_vars(local_address, kwargs, option_parser, peer_local_address, server_id=server.select_variable("server_id")) # The following code has been commented because the logic to create # the replication-user has been moved to the Shell c++ code. # The code wasn't removed to serve as knowledge base for the MP # refactoring to C++ # Do several replication user related tasks if the # skip-replication-user option was not provided #if not skip_rpl_user: # The replication user for be check/create on the peer server. # NOTE: rpl_user_dict["host"] has the FQDN resolved from the host # provided by the user # replication_user = "******".format( # rpl_user_dict["recovery_user"], rpl_user_dict["host"]) # rpl_user_dict["replication_user"] = replication_user # Check the given replication user exists on peer # req_dict_user = get_req_dict_user_check(peer_server, # replication_user) # Check and create the given replication user on peer server. # NOTE: No other checks will be performed, only the replication # user. # check_server_requirements(peer_server, req_dict_user, # rpl_user_dict, verbose, dry_run, # skip_schema_checks=True) # IF the group name is not set, try to acquire it from a peer server. if gr_config_vars[GR_GROUP_NAME] is None: _LOGGER.debug("Trying to retrieve group replication name from " "peer server.") group_name = get_gr_name_from_peer(peer_server) _LOGGER.debug("Retrieved group replication name from peer" " server: %s.", group_name) gr_config_vars[GR_GROUP_NAME] = group_name # Set the single_primary mode according to the value set on peer # server if gr_config_vars[GR_SINGLE_PRIMARY_MODE] is None: gr_config_vars[GR_SINGLE_PRIMARY_MODE] = \ get_gr_variable_from_peer(peer_server, GR_SINGLE_PRIMARY_MODE) if gr_config_vars[GR_GROUP_NAME] is None: raise GadgetError( _ERROR_UNABLE_TO_GET.format("Group Replication group name", peer_server)) _LOGGER.step( "Joining Group Replication group: %s", gr_config_vars[GR_GROUP_NAME]) if gr_config_vars[GR_GROUP_SEEDS] is None: raise GadgetError( _ERROR_UNABLE_TO_GET.format("peer addresses", peer_server)) # Remove IP whitelist variable if not set (by user or from the option # file) to use the default server value and not set it with None. if gr_config_vars[GR_IP_WHITELIST] is None: gr_config_vars.pop(GR_IP_WHITELIST) if gr_config_vars[GR_EXIT_STATE_ACTION] is None: gr_config_vars.pop(GR_EXIT_STATE_ACTION) if gr_config_vars[GR_MEMBER_WEIGHT] is None: gr_config_vars.pop(GR_MEMBER_WEIGHT) if gr_config_vars[GR_FAILOVER_CONSISTENCY] is None: gr_config_vars.pop(GR_FAILOVER_CONSISTENCY) if gr_config_vars[GR_EXPEL_TIMEOUT] is None: gr_config_vars.pop(GR_EXPEL_TIMEOUT) gr_config_vars[GR_START_ON_BOOT] = "ON" setup_gr_config(server, gr_config_vars, dry_run=dry_run) if not skip_rpl_user: # if the skip replication user option was not specified, # run the change master to store MySQL replication user name or # password information in the master info repository do_change_master(server, rpl_user_dict, dry_run=dry_run) _LOGGER.step( "Attempting to join to Group Replication group...") try: start_gr_plugin(server, dry_run) _LOGGER.step("Server %s joined " "Group Replication group %s.", server, gr_config_vars[GR_GROUP_NAME]) except: _LOGGER.error("\nGroup Replication join failed.") if error_log_size is not None: log_data = error_log.read(error_log_size) if log_data: _LOGGER.error("Group Replication plugin failed to start. " "Server error log contains the following " "errors: \n %s", log_data) raise # Update the Group Replication options on defaults file. # Note: Set group_replication_start_on_boot=ON if OPTION_PARSER in req_dict.keys(): persist_gr_config(req_dict[OPTION_PARSER], gr_config_vars, dry_run=dry_run, skip_backup=skip_backup) if dry_run: _LOGGER.warning(_WARN_DRY_RUN_USED) return False finally: # disconnect servers. if server is not None: server.disconnect() if peer_server is not None: peer_server.disconnect() return True
def start(server_info, **kwargs): """Start a group replication group with the given server information. :param server_info: Connection information :type server_info: dict | Server | str :param kwargs: Keyword arguments: gr_address: The host:port that the gcs plugin uses to other servers communicate with this server. dry_run: Do not make changes to the server verbose: If the command should show more verbose information. option_file: The path to a options file to check configuration. skip_backup: if True, skip the creation of a backup file when modifying the options file. skip_schema_checks: Skip schema validation. ssl_mode: SSL mode to be used with group replication. exit_state_action: Group Replication Exit State Action, must be a string containing either "ABORT_SERVER", "READ_ONLY", "0" or "1". The string is case-insensitive. member_weight: Group Replication Member Weight, must be an integer value, with a percentage weight for automatic primary election on failover. failover_consistency: Group Replication failover Consistency, must be a string containing either "BEFORE_ON_PRIMARY_FAILOVER", "EVENTUAL", "0" or "1". The string is case-insensitive. expel_timeout: Group Replication member expel Timeout. Must must be an integer value containing the time in seconds to wait before ghe killer node expels members suspected of having failed from the group. :type kwargs: dict :raise GadgetError: If server_info is None. :raise GadgetCnxInfoError: If the connection information on server_info could not be parsed. :raise GadgetServerError: If a connection fails. :return: True if the start_gr_plugin method was executed otherwise False. :rtype: boolean """ server = get_server(server_info=server_info) if server is None: raise GadgetError(_ERROR_NO_SERVER) msg = _RUNNING_COMMAND.format(START, server) _LOGGER.info("") _LOGGER.step(msg) verbose = kwargs.get("verbose", False) dry_run = kwargs.get("dry_run", False) gr_host = kwargs.get("gr_address", None) skip_schema_checks = kwargs.get("skip_schema_checks", []) option_file = kwargs.get("option_file", None) skip_backup = kwargs.get("skip_backup", False) skip_rpl_user = kwargs.get("skip_rpl_user", False) exit_state_action = kwargs.get("exit_state_action", None) member_weight = kwargs.get("member_weight", None) failover_consistency = kwargs.get("failover_consistency", None) expel_timeout = kwargs.get("expel_timeout", None) _LOGGER.step("Checking Group Replication prerequisites.") try: # Throw an error in case server doesn't support SSL and the ssl_mode # option was provided a value other than DISABLED if server.select_variable(HAVE_SSL) != 'YES' and \ kwargs["ssl_mode"] != GR_SSL_DISABLED: raise GadgetError(_ERROR_NO_HAVE_SSL.format(server)) # Skip replication user checks if requested. rpl_user_dict = None if not skip_rpl_user: rpl_user_dict = get_rpl_usr(kwargs) # Do not check the replication user, already handled by the AdminAPI. req_dict = get_req_dict(server, None, option_file=option_file) check_server_requirements(server, req_dict, rpl_user_dict, verbose, dry_run, skip_schema_checks, skip_backup=skip_backup, var_change_warning=True) gr_host, local_port = resolve_gr_local_address(gr_host, server.host, server.port) # verify the group replication is not Disabled in the server. check_gr_plugin_is_installed(server, option_file, dry_run) # attempt to set the group_replication_exit_state_action in order to # let GR do the value validation and catch any error right away if exit_state_action is not None: validate_exit_state_action(server, exit_state_action, dry_run) # attempt to set the group_replication_member_weight in order to # let GR do the value validation and catch any error right away if member_weight is not None: validate_member_weight(server, member_weight, dry_run) # attempt to set the group_replication_consistency in order to # let GR do the value validation and catch any error right away if failover_consistency is not None: validate_failover_consistency(server, failover_consistency, dry_run) # attempt to set the group_replication_member_expel_timeout in order to # let GR do the value validation and catch any error right away if expel_timeout is not None: validate_expel_timeout(server, expel_timeout, dry_run) # verify the server does not belong already to a GR group. if is_active_member(server): health(server, **kwargs) raise GadgetError(_ERROR_ALREADY_A_MEMBER.format(server, START)) local_address = "{0}:{1}".format(gr_host, local_port) option_parser = req_dict.get(OPTION_PARSER, None) gr_config_vars = get_gr_config_vars(local_address, kwargs, option_parser, server_id=server.select_variable("server_id")) if gr_config_vars[GR_GROUP_SEEDS] is None: gr_config_vars.pop(GR_GROUP_SEEDS) # Remove IP whitelist variable if not set (by user or from the option # file) to use the default server value and not set it with None. if gr_config_vars[GR_IP_WHITELIST] is None: gr_config_vars.pop(GR_IP_WHITELIST) if gr_config_vars[GR_EXIT_STATE_ACTION] is None: gr_config_vars.pop(GR_EXIT_STATE_ACTION) if gr_config_vars[GR_MEMBER_WEIGHT] is None: gr_config_vars.pop(GR_MEMBER_WEIGHT) if gr_config_vars[GR_FAILOVER_CONSISTENCY] is None: gr_config_vars.pop(GR_FAILOVER_CONSISTENCY) if gr_config_vars[GR_EXPEL_TIMEOUT] is None: gr_config_vars.pop(GR_EXPEL_TIMEOUT) if gr_config_vars[GR_GROUP_NAME] is None: new_uuid = get_group_uuid_name(server) _LOGGER.debug("A new UUID has been generated for the group " "replication name %s", new_uuid) gr_config_vars[GR_GROUP_NAME] = new_uuid # Set the single_primary mode, if no value given then set ON as # default. if gr_config_vars[GR_SINGLE_PRIMARY_MODE] is None: gr_config_vars[GR_SINGLE_PRIMARY_MODE] = '"ON"' _LOGGER.step("Group Replication group name: %s", gr_config_vars[GR_GROUP_NAME]) gr_config_vars[GR_START_ON_BOOT] = "ON" setup_gr_config(server, gr_config_vars, dry_run=dry_run) # Run the change master to store MySQL replication user name or # password information in the master info repository, but only if # replication user is NOT skipped. if not skip_rpl_user: do_change_master(server, rpl_user_dict, dry_run=dry_run) set_bootstrap(server, dry_run) _LOGGER.step("Attempting to start the Group Replication group...") # Attempt to start the Group Replication plugin start_gr_plugin(server, dry_run) # Wait for the super_read_only to be unset. super_read_only = server.select_variable("super_read_only", 'global') _LOGGER.debug("super_read_only: %s", super_read_only) waiting_time = 0 informed = False while int(super_read_only) and waiting_time < TIME_OUT: time.sleep(WAIT_SECONDS) waiting_time += WAIT_SECONDS _LOGGER.debug("have been waiting %s seconds", waiting_time) # inform what are we waiting for if waiting_time >= 10 and not informed: _LOGGER.info("Waiting for super_read_only to be unset.") informed = True super_read_only = server.select_variable("super_read_only", 'global') _LOGGER.debug("super_read_only: %s", super_read_only) if int(super_read_only): raise GadgetError("Timeout waiting for super_read_only to be " "unset after call to start Group Replication " "plugin.") _LOGGER.step( "Group Replication started for group: %s.", gr_config_vars[GR_GROUP_NAME]) unset_bootstrap(server, dry_run) # Update the Group Replication options on defaults file. # Note: Set group_replication_start_on_boot=ON if OPTION_PARSER in req_dict.keys(): persist_gr_config(req_dict[OPTION_PARSER], gr_config_vars, dry_run=dry_run, skip_backup=skip_backup) if dry_run: _LOGGER.warning(_WARN_DRY_RUN_USED) return False finally: # Disconnect the server prior to end method invocation. if server is not None: server.disconnect() return True