def _check_candidate_fail(group_id, slave_id): """Check if the candidate has all the prerequisites to become the new master. """ allowed_status = (_server.MySQLServer.SECONDARY, _server.MySQLServer.SPARE) group = _server.Group.fetch(group_id) slave = _retrieve_server(slave_id, group_id) slave.connect() if group.master == slave.uuid: raise _errors.ServerError("Candidate slave (%s) is already master." % (slave_id, )) master_issues, why_master_issues = _replication.check_master_issues(slave) if master_issues: raise _errors.ServerError("Server (%s) is not a valid candidate slave " "due to the following reason(s): (%s)." % (slave.uuid, why_master_issues)) if slave.status not in allowed_status: raise _errors.ServerError("Server (%s) is faulty." % (slave_id, )) _events.trigger_within_procedure(WAIT_SLAVE_FAIL, group_id, str(slave.uuid))
def execute(self, group_id, destn_address, server_id=None, timeout=None, synchronous=True): """Clone the objects of a given server into a destination server. :param group_id: The ID of the source group. :param destn_address: The address of the destination MySQL Server. :param source_id: The address or UUID of the source MySQL Server. :param timeout: Time in seconds after which an error is reported if the destination server is unreachable. :param synchronous: Whether one should wait until the execution finishes or not. """ # If the destination server is already part of a Fabric Group, raise # an error destn_server_uuid = _lookup_uuid(destn_address, timeout) _check_server_not_in_any_group(destn_server_uuid) host, port = split_host_port(destn_address, _backup.MySQLDump.MYSQL_DEFAULT_PORT) # Fetch information on backup and restore programs. config_file = self.config.config_file if self.config.config_file else "" mysqldump_binary = _utils.read_config_value(self.config, 'sharding', 'mysqldump_program') mysqlclient_binary = _utils.read_config_value(self.config, 'sharding', 'mysqlclient_program') if not _utils.is_valid_binary(mysqldump_binary): raise _errors.ServerError(MYSQLDUMP_NOT_FOUND % mysqldump_binary) if not _utils.is_valid_binary(mysqlclient_binary): raise _errors.ServerError(MYSQLCLIENT_NOT_FOUND % mysqlclient_binary) # Fetch a reference to source server. if server_id: server = _retrieve_server(server_id, group_id) else: group = _retrieve_group(group_id) server = _utils.fetch_backup_server(group) # Schedule the clone operation through the executor. procedures = _events.trigger(BACKUP_SERVER, self.get_lockable_objects(), str(server.uuid), host, port, mysqldump_binary, mysqlclient_binary, config_file) return self.wait_for_procedures(procedures, synchronous)
def _set_server_weight(server_id, weight): """Set server's weight. """ server = _retrieve_server(server_id) try: weight = float(weight) except ValueError: raise _errors.ServerError("Value (%s) must be a float." % (weight, )) if weight <= 0.0: raise _errors.ServerError( "Cannot set the server's weight (%s) to a value lower " "than or equal to 0.0" % (weight, )) server.weight = weight
def _restore_server(source_uuid, host, port, backup_image): """Restore the backup on the destination Server. :param source_uuid: The UUID of the source server. :param host: The hostname of the destination server. :param port: The port number of the destination server. :param backup_image: The backup image path. """ restore_user = _services_utils.read_config_value(_config.global_config, 'servers', 'restore_user') restore_passwd = _services_utils.read_config_value(_config.global_config, 'servers', 'restore_password') mysqlclient_binary = _services_utils.read_config_value( _config.global_config, 'sharding', 'mysqlclient_program') source_server = _server.MySQLServer.fetch(source_uuid) if not source_server: raise _errors.ServerError(SERVER_NOT_FOUND % source_uuid) #Build a backup image that will be used for restoring bk_img = _backup.BackupImage(backup_image) _backup.MySQLDump.restore_server(host, port, restore_user, restore_passwd, bk_img, mysqlclient_binary) _LOGGER.debug("Done with restore of server with host = %s, port = %s", host, port)
def _retrieve_server_mode(mode): """Check whether the server's mode is valid or not and if an integer was provided retrieve the correspondent string. """ valid = False try: idx = int(mode) try: mode = _server.MySQLServer.get_mode(idx) valid = True except IndexError: pass except ValueError: try: mode = str(mode).upper() _server.MySQLServer.get_mode_idx(mode) valid = True except ValueError: pass if not valid: values = [ str((_server.MySQLServer.get_mode_idx(value), value)) for value in _server.MySQLServer.SERVER_MODE ] raise _errors.ServerError("Trying to use an invalid mode (%s). " "Possible values are: %s." % (mode, ", ".join(values)) ) return mode
def _retrieve_server_status(status): """Check whether the server's status is valid or not and if an integer was provided retrieve the correspondent string. """ valid = False try: idx = int(status) try: status = _server.MySQLServer.get_status(idx) valid = True except IndexError: pass except ValueError: try: status = str(status).upper() _server.MySQLServer.get_status_idx(status) valid = True except ValueError: pass if not valid: values = [ str((_server.MySQLServer.get_status_idx(value), value)) for value in _server.MySQLServer.SERVER_STATUS ] raise _errors.ServerError("Trying to use an invalid status (%s). " "Possible values are %s." % (status, ", ".join(values)) ) return status
def _set_server_status_primary(server, update_only): """Set server's status to primary. """ raise _errors.ServerError( "If you want to make a server (%s) primary, please, use the " "group.promote function." % (server.uuid, ) )
def _check_requirements(server): """Check if the server fulfils some requirements. """ # Being able to connect to the server is the first requirment. server.connect() if not server.check_version_compat((5, 6, 8)): raise _errors.ServerError( "Server (%s) has an outdated version (%s). 5.6.8 or greater " "is required." % (server.uuid, server.version)) server.check_server_privileges() if not server.gtid_enabled or not server.binlog_enabled: raise _errors.ServerError( "Server (%s) does not have the binary log or gtid enabled." % (server.uuid, ))
def _do_set_server_mode(server, mode, allowed_mode): """Set server's mode. """ if mode not in allowed_mode: raise _errors.ServerError( "Cannot set mode to (%s) when the server's (%s) status is (%s)." % (mode, server.uuid, server.status)) server.mode = mode
def _check_server_exists(server_id): """Check whether a MySQLServer instance exists or not. :param server_id: The UUID or the host:port for the server. """ server = _server.MySQLServer.fetch(server_id) if server: raise _errors.ServerError("Server (%s) already exists." % (server_id, ))
def _lookup_uuid(address, timeout): """Return server's uuid. """ timeout = timeout or DEFAULT_UNREACHABLE_TIMEOUT try: return _server.MySQLServer.discover_uuid(address=address, connection_timeout=timeout) except _errors.DatabaseError as error: raise _errors.ServerError("Error accessing server (%s): %s." % (address, error))
def _configure_as_slave(group, server): """Configure the server as a slave. """ try: ### When master has been already elected, throw CREATE USER into master. if group.master: master = _server.MySQLServer.fetch(group.master) master.connect() host, port = split_host_port(server.address) master.exec_stmt(_server.MySQLServer.DROP_REPLICATION_USER, {"params": (server.repl_user, host,)}) master.exec_stmt(_server.MySQLServer.CREATE_REPLICATION_USER, {"params": (server.repl_user, host, server.repl_pass,)}) master.exec_stmt(_server.MySQLServer.GRANT_REPLICATION_USER, {"params": (server.repl_user, host,)}) _services_utils.switch_master(server, master) else: ### When master hasn't been elected yet and adding server is very first server in the group, ### throw CREATE USER into server itself. _LOGGER.critical(group.servers()) if len(group.servers()) == 1: server.connect() host, port = split_host_port(server.address) server.exec_stmt(_server.MySQLServer.DROP_REPLICATION_USER, {"params": (server.repl_user, host,)}) server.exec_stmt(_server.MySQLServer.CREATE_REPLICATION_USER, {"params": (server.repl_user, host, server.repl_pass,)}) server.exec_stmt(_server.MySQLServer.GRANT_REPLICATION_USER, {"params": (server.repl_user, host,)}) else: ### This means group has at least 1 server but master doesn't elect yet. raise _errors.ServerError("Master server doesn't elect yet") except _errors.DatabaseError as error: msg = "Error trying to configure server ({0}) as slave: {1}.".format( server.uuid, error) _LOGGER.debug(msg) raise _errors.ServerError(msg)
def _check_candidate_switch(group_id, slave_id): """Check if the candidate has all the features to become the new master. """ allowed_status = (_server.MySQLServer.SECONDARY, _server.MySQLServer.SPARE) group = _server.Group.fetch(group_id) if not group.master: raise _errors.GroupError("Group (%s) does not contain a valid " "master. Please, run a promote or failover." % (group_id, )) slave = _retrieve_server(slave_id, group_id) slave.connect() if group.master == slave.uuid: raise _errors.ServerError("Candidate slave (%s) is already master." % (slave_id, )) master_issues, why_master_issues = _replication.check_master_issues(slave) if master_issues: raise _errors.ServerError("Server (%s) is not a valid candidate slave " "due to the following reason(s): (%s)." % (slave.uuid, why_master_issues)) slave_issues, why_slave_issues = _replication.check_slave_issues(slave) if slave_issues: raise _errors.ServerError("Server (%s) is not a valid candidate slave " "due to the following reason: (%s)." % (slave.uuid, why_slave_issues)) master_uuid = _replication.slave_has_master(slave) if master_uuid is None or group.master != _uuid.UUID(master_uuid): raise _errors.GroupError( "The group's master (%s) is different from the candidate's " "master (%s)." % (group.master, master_uuid)) if slave.status not in allowed_status: raise _errors.ServerError("Server (%s) is faulty." % (slave_id, )) _events.trigger_within_procedure(BLOCK_WRITE_SWITCH, group_id, master_uuid, str(slave.uuid))
def _check_server_not_in_any_group(server_id): """Check for both the presence of the server object and its associated group. If the server belongs to a group an error is raised. :param server_id: The UUID or the host:port for the server. """ server = _server.MySQLServer.fetch(server_id) if server and server.group_id: raise _errors.ServerError( "Destination server (%s) is already part of group (%s)" % (server.address, server.group_id))
def _check_requirements(server): """Check if the server fulfils some requirements. """ # Being able to connect to the server is the first requirement. server.connect() if not server.check_version_compat((5, 6, 8)): raise _errors.ServerError( "Server (%s) has an outdated version (%s). 5.6.8 or greater " "is required." % (server.uuid, server.version)) if not server.has_required_privileges(): raise _errors.ServerError( "User (%s) does not have appropriate privileges (%s) on server " "(%s, %s)." % (server.user, ", ".join(_server.MySQLServer.ALL_PRIVILEGES), server.address, server.uuid)) if not server.gtid_enabled or not server.binlog_enabled: raise _errors.ServerError( "Server (%s) does not have the binary log or gtid enabled." % (server.uuid, ))
def _configure_as_slave(group, server): """Configure the server as a slave. """ try: if group.master: master = _server.MySQLServer.fetch(group.master) master.connect() _utils.switch_master(server, master) except _errors.DatabaseError as error: msg = "Error trying to configure server ({0}) as slave: {1}.".format( server.uuid, error) _LOGGER.debug(msg) raise _errors.ServerError(msg)
def _remove_server(group_id, server_id): """Remove a server from a group. """ group = _retrieve_group(group_id) server = _retrieve_server(server_id, group_id) if group.master == server.uuid: raise _errors.ServerError( "Cannot remove server (%s), which is master in group (%s). " "Please, demote it first." % (server.uuid, group_id)) _server.MySQLServer.remove(server) server.disconnect()
def _append_error_log(server_id, reporter, error): """Check whether the server exist and is not faulty and register error log. """ server = _retrieve_server(server_id) if server.status == _server.MySQLServer.FAULTY: raise _errors.ServerError("Server (%s) is already marked as faulty." % (server.uuid, )) _LOGGER.warning("Reported issue (%s) for server (%s).", error, server.uuid) now = get_time() _error_log.ErrorLog.add(server, now, reporter, error) return (now, server)
def _do_set_status(server, allowed_status, status, mode, update_only): """Set server's status. """ allowed_transition = server.status in allowed_status previous_status = server.status if allowed_transition: server.status = status server.mode = mode else: raise _errors.ServerError( "Cannot change server's (%s) status from (%s) to (%s)." % (str(server.uuid), server.status, status)) _LOGGER.debug( "Changed server's status (%s) from (%s) to (%s). Update-only " "state store parameter is (%s).", str(server.uuid), previous_status, server.status, update_only)
def _retrieve_server(server_id, group_id=None): """Return a MySQLServer object from a UUID or a HOST:PORT. """ server = _server.MySQLServer.fetch(server_id) if not server: raise _errors.ServerError("Server (%s) does not exist." % (server_id, )) if not server.group_id: raise _errors.GroupError("Server (%s) does not belong to a group." % (server_id, )) if group_id is not None and group_id != server.group_id: raise _errors.GroupError("Group (%s) does not contain server (%s)." % (group_id, server_id)) return server
def _define_ha_operation(group_id, slave_id, update_only): """Define which operation must be called based on the master's status and whether the candidate slave is provided or not. """ fail_over = True group = _server.Group.fetch(group_id) if not group: raise _errors.GroupError("Group (%s) does not exist." % (group_id, )) if update_only and not slave_id: raise _errors.ServerError( "The new master must be specified through --slave-uuid if " "--update-only is set.") if group.master: master = _server.MySQLServer.fetch(group.master) if master.status != _server.MySQLServer.FAULTY: if update_only: _do_block_write_master(group_id, str(group.master), update_only) fail_over = False if update_only: # Check whether the server is registered or not. _retrieve_server(slave_id, group_id) _change_to_candidate(group_id, slave_id, update_only) return if fail_over: if not slave_id: _events.trigger_within_procedure(FIND_CANDIDATE_FAIL, group_id) else: _events.trigger_within_procedure(CHECK_CANDIDATE_FAIL, group_id, slave_id) else: if not slave_id: _events.trigger_within_procedure(FIND_CANDIDATE_SWITCH, group_id) else: _events.trigger_within_procedure(CHECK_CANDIDATE_SWITCH, group_id, slave_id)
def _restore_server(source_uuid, host, port, backup_image, mysqlclient_binary, config_file): """Restore the backup on the destination Server. :param source_uuid: The UUID of the source server. :param host: The hostname of the destination server. :param port: The port number of the destination server. :param userid: The User Name to be used to connect to the destn server. :param passwd: The password to be used to connect to the destn server. :param mysqldump_binary: The MySQL Dump Binary path. :param mysqlclient_binary: The MySQL Client Binary path. :param config_file: The complete path to the fabric configuration file. """ source_server = _server.MySQLServer.fetch(source_uuid) if not source_server: raise _errors.ServerError(SERVER_NOT_FOUND % source_uuid) #Build a backup image that will be used for restoring bk_img = _backup.BackupImage(backup_image) _backup.MySQLDump.restore_server(host, port, source_server.USER, source_server.PASSWD, bk_img, config_file, mysqlclient_binary) _LOGGER.debug("Done with restore of server with host = %s, port = %s" %\ (host, port,))
def _set_server_status_faulty(server, update_only): raise _errors.ServerError( "If you want to set a server (%s) to faulty, please, use the " "threat.report_faulty interface." % (server.uuid, ) )