def test_re_bootstrap(self):
        """Tests start method over actively replicating server
        """
        # Fill the options
        options = {
            "group_name": None,
            "replication_user": None,
            "rep_user_passwd": "passwd",
            "gr_host": "",
        }

        # test start
        if is_member_of_group(self.server):
            leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        self.assertTrue(start(self.server, **options))

        # reconnect the server.
        self.server.connect()

        # test health
        health(self.server, **options)

        # reconnect the server.
        self.server.connect()

        # Trying to start a group with a server that is already a member
        # expect GadgetError: Query failed: is already a member of a GR group.
        with self.assertRaises(GadgetError) as test_raises:
            start(self.server, **options)
        exception = test_raises.exception
        self.assertTrue("is already a member" in exception.errmsg,
                        "The exception message was not the expected. {0}"
                        "".format(exception.errmsg))

        # reconnect the server.
        self.server.connect()

        # Bootstrap with dry-run
        options["dry_run"] = True
        # Trying to start a group with a server that is already a member
        # expect GadgetError: Query failed: is already a member of a GR group.
        with self.assertRaises(GadgetError) as test_raises:
            start(self.server, **options)
        exception = test_raises.exception
        self.assertTrue("is already a member" in exception.errmsg,
                        "The exception message was not the expected. {0}"
                        "".format(exception.errmsg))

        # reconnect the server.
        self.server.connect()

        self.assertTrue(leave(self.server, **options))

        # reconnect the server.
        self.server.connect()
def leave(server_info, **kwargs):
    """Removes a server from a Group Replication group.

    :param server_info: Connection information
    :type  server_info: dict | Server | str
    :param kwargs:      Keyword arguments:
                        dry_run:    Do not make changes to the server
                        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.
    :type kwargs:       dict

    :raise GadgetError: If the given server is not a member of a Group
                        Replication

    :return: True if the server stop_gr_plugin command was executed otherwise
             False.
    :rtype: boolean
    """

    # get the server instance
    server = get_server(server_info=server_info)

    dry_run = kwargs.get("dry_run", False)
    option_file = kwargs.get("option_file", None)
    skip_backup = kwargs.get("skip_backup", False)

    if server is None:
        raise GadgetError(_ERROR_NO_SERVER)

    msg = _RUNNING_COMMAND.format(LEAVE, server)
    _LOGGER.info("")
    _LOGGER.log(STEP_LOG_LEVEL_VALUE, msg)

    try:
        # verify server status (is in a group?)
        if is_member_of_group(server) and is_active_member(server):
            _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Attempting to leave from the "
                        "Group Replication group...")
            if not dry_run:
                stop_gr_plugin(server)
            _LOGGER.info("Server state: %s", get_member_state(server))
            _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Server %s has left the group.",
                        server)

            # Update the Group Replication options on defaults file.
            # Note: Set group_replication_start_on_boot=ON
            if option_file is not None and option_file != "":
                persist_gr_config(option_file, None, set_on=False,
                                  dry_run=dry_run, skip_backup=skip_backup)

            return True

        # server is not active
        elif is_member_of_group(server):
            _LOGGER.warning("The server %s is not actively replicating.",
                            server)
            _LOGGER.info("Server state: %s", get_member_state(server))
            _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Server %s is "
                        "not active in the group.", server)

            # Update the group_replication_start_on_boot option on defaults
            # file.
            # Note: Set group_replication_start_on_boot=OFF
            if option_file is not None and option_file != "":
                persist_gr_config(option_file, None, set_on=False,
                                  dry_run=dry_run, skip_backup=skip_backup)

        # the server has not been configured for GR
        else:
            raise GadgetError(_ERROR_NOT_A_MEMBER.format(server))

        if dry_run:
            _LOGGER.warning(_WARN_DRY_RUN_USED)

    finally:
        # disconnect servers.
        if server is not None:
            server.disconnect()

    return False
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 health(server_info, **kwargs):
    """Display Group Replication health/status information of a server.

    :param server_info: Connection information
    :type  server_info: dict | Server | str
    :param kwargs:      Keyword arguments:
                        verbose:    If the command should show more verbose
                                    information.
                        detailed:   Show extra health info for the server.
    :type kwargs:       dict

    :raise GadgetError:  If server_info is None or belongs to a server
                         that is not a member of Group Replication.
    :raise GadgetCnxInfoError:  If the connection information on
                                server_info could not be parsed.
    :raise GadgetServerError:   If a connection fails.
    """

    verbose = kwargs.get("verbose", False)
    detailed = kwargs.get("detailed", False)

    query_options = {
        "columns": True,
    }
    server = get_server(server_info=server_info)
    if server is None:
        raise GadgetError(_ERROR_NO_SERVER)

    _LOGGER.info("")
    msg = _RUNNING_COMMAND.format((STATUS if detailed else HEALTH), server)
    _LOGGER.log(STEP_LOG_LEVEL_VALUE, msg)

    try:
        # SELECT privilege is required for check for membership
        if not is_member_of_group(server):
            raise GadgetError("The server '{0}' is not a member of a GR "
                              "group.".format(server))

        # Retrieve members information
        columns = [MEMBER_ID, MEMBER_HOST, MEMBER_PORT, MEMBER_STATE]
        res = server.exec_query("SELECT {0} FROM {1}"
                                "".format(", ".join(columns),
                                          REP_GROUP_MEMBERS_TABLE),
                                query_options)

        # If no info could be retrieve raise and error
        if len(res[1]) == 0:
            _LOGGER.debug("Cannot retrieve any value from table %s",
                          REP_GROUP_MEMBERS_TABLE)
            if server is not None:
                server.disconnect()
            raise GadgetError(_ERROR_NOT_A_MEMBER.format(server))

        total_members = 0
        online_members = 0

        # Log members information
        _LOGGER.info("Group Replication members: ")
        for results_set in res[1]:
            member_dict = OrderedDict(zip(res[0], results_set))
            if detailed:
                member_dict["ID"] = member_dict.pop(MEMBER_ID)
            else:
                member_dict.pop(MEMBER_ID)
            member_dict["HOST"] = member_dict.pop(MEMBER_HOST)
            member_dict["PORT"] = member_dict.pop(MEMBER_PORT)
            member_dict["STATE"] = member_dict.pop(MEMBER_STATE)
            total_members += 1
            if member_dict["STATE"] == "ONLINE":
                online_members += 1
            _report_dict(member_dict, "{0:<4}{1}: {2}", "ID" if detailed
                         else "HOST", "{0:<2}- {1}: {2}",
                         show_empty_values=verbose)

        _LOGGER.info("")

        # The detailed information is logged as the full HEALTH command.
        if not verbose and not detailed:
            return

        # Retrieve the (replication channels) applier and retriever threads
        # status
        columns = ["m.{0}".format(MEMBER_ID), MEMBER_HOST, MEMBER_PORT,
                   MEMBER_STATE, VIEW_ID, COUNT_TRANSACTIONS_IN_QUEUE,
                   COUNT_TRANSACTIONS_CHECKED, COUNT_CONFLICTS_DETECTED,
                   COUNT_TRANSACTIONS_ROWS_VALIDATING,
                   TRANSACTIONS_COMMITTED_ALL_MEMBERS,
                   LAST_CONFLICT_FREE_TRANSACTION]
        res = server.exec_query("SELECT {0} FROM {1} as m JOIN {2} as s "
                                "on m.MEMBER_ID = s.MEMBER_ID"
                                "".format(", ".join(columns),
                                          REP_GROUP_MEMBERS_TABLE,
                                          REP_MEMBER_STATS_TABLE),
                                query_options)

        # Log the member information of the server that is owner of the
        # following stats, current status of applier and retriever threads
        output = "Server stats: "
        _LOGGER.info(output)
        res_pairs = OrderedDict(zip(res[0], res[1][0]))

        # Log member information
        output = "{0:<2}{1}".format("", "Member")
        _LOGGER.info(output)
        member_dict = OrderedDict()
        member_dict["HOST"] = res_pairs.pop(MEMBER_HOST)
        member_dict["PORT"] = res_pairs.pop(MEMBER_PORT)
        member_dict["STATE"] = res_pairs.pop(MEMBER_STATE)
        member_dict[VIEW_ID] = res_pairs.pop(VIEW_ID)
        member_dict["ID"] = res_pairs.pop(MEMBER_ID)

        _report_dict(member_dict, "{0:<4}{1}: {2}", show_empty_values=verbose)

        # Log Transactions stats
        output = "{0:<2}{1}".format("", "Transactions")
        _LOGGER.info(output)
        trans_dict = OrderedDict()
        trans_dict["IN_QUEUE"] = res_pairs.pop(COUNT_TRANSACTIONS_IN_QUEUE)
        trans_dict["CHECKED"] = res_pairs.pop(COUNT_TRANSACTIONS_CHECKED)
        trans_dict["CONFLICTS_DETECTED"] = \
            res_pairs.pop(COUNT_CONFLICTS_DETECTED)
        trans_dict["VALIDATING"] = res_pairs.pop(
            COUNT_TRANSACTIONS_ROWS_VALIDATING)
        trans_dict["LAST_CONFLICT_FREE"] = res_pairs.pop(
            LAST_CONFLICT_FREE_TRANSACTION)
        trans_dict["COMMITTED_ALL_MEMBERS"] = res_pairs.pop(
            TRANSACTIONS_COMMITTED_ALL_MEMBERS)
        _report_dict(trans_dict, "{0:<4}{1}: {2}", show_empty_values=verbose)

        _report_dict(res_pairs, "{0:<2}{1}: {2}", show_empty_values=verbose)

        res = server.exec_query(
            "SELECT * FROM {0} as c "
            "JOIN {1} as a ON c.channel_name = a.channel_name "
            "ORDER BY c.channel_name, a.worker_id"
            "".format(REP_CONN_STATUS_TABLE, REP_APPLIER_STATUS_BY_WORKER),
            query_options)

        # Log connection stats of the channels
        output = "{0:<2}{1}".format("", "Connection status")
        _LOGGER.info(output)
        for results_set in res[1]:
            res_pairs = OrderedDict(zip(res[0], results_set))
            errors_dict = OrderedDict()
            errors_dict["NUMBER"] = res_pairs.pop("LAST_ERROR_NUMBER")
            errors_dict["MESSAGE"] = res_pairs.pop("LAST_ERROR_MESSAGE")
            errors_dict["TIMESTAMP"] = res_pairs.pop("LAST_ERROR_TIMESTAMP")

            _report_dict(res_pairs, "{0:<4}{1}: {2}", "CHANNEL_NAME",
                         "{0:<2}- {1}: {2}", show_empty_values=verbose)

            if "0000-00-00" not in errors_dict["TIMESTAMP"] or verbose:
                output = "{0:<4}{1}".format("", "Last error")
                _LOGGER.info(output)
                _report_dict(errors_dict, "{0:<6}{1}: {2}",
                             show_empty_values=verbose)
            else:
                output = "{0:<4}{1}".format("", "Last error: None")
                _LOGGER.info(output)
            _LOGGER.info("")
        if online_members < 3:
            plural = "s" if online_members > 1 else ""
            output = ("The group is currently not HA because it has {0} "
                      "ONLINE member{1}.".format(online_members, plural))
            _LOGGER.warning(output)
        _LOGGER.info("")

    finally:
        # Disconnect the server prior to end method invocation.
        if server is not None:
            server.disconnect()

    return
def check(**kwargs):
    """Check and update instance configurations relative to Group Replication.

    :param kwargs:    Keyword arguments:
                        update:     Make changes to the options file.
                        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.
                        server:     Connection information (dict | Server |
                                    str)
    :type kwargs:     dict

    :raise GadgetError:  If the file cannot be updated.

    :return: True if the options file was modified.
    :rtype: boolean
    """

    _LOGGER.info("")
    verbose = kwargs.get("verbose", False)
    update = kwargs.get("update", False)
    option_file = kwargs.get("option_file", None)
    skip_backup = kwargs.get("skip_backup", False)
    server_info = kwargs.get("server", None)

    # Get the server instance
    server = get_server(server_info=server_info)

    # This method requires at least one of the server or the option file.
    if (option_file is None or option_file == "") and server is None:
        raise GadgetError("Either the server or the defaults file was not "
                          "given.")

    if option_file:
        msg = "Running {0} command for file '{1}'.".format(CHECK, option_file)
    else:
        msg = "Running {0} command.".format(CHECK)
    _LOGGER.log(STEP_LOG_LEVEL_VALUE, msg)

    try:
        # if server already belongs to a group and the update option
        # was provided, dump its GR configurations to the option file
        if is_member_of_group(server) and is_active_member(server):
            gr_configs = get_gr_configs_from_instance(server)
            if update:
                # the variable comes from the server, no need to test for
                # loose prefix variant.
                if not gr_configs.get("group_replication_group_seeds", None):
                    _LOGGER.warning(
                        "The 'group_replication_group_seeds' is not defined "
                        "on %s. This option is mandatory to allow the server "
                        "to automatically rejoin the cluster after reboot. "
                        "Please manually update its value on option file "
                        "'%s'.", str(server), option_file)
                _LOGGER.info("Updating option file '%s' with Group "
                             "Replication settings from "
                             "%s", option_file, str(server))
                persist_gr_config(option_file, gr_configs)
                result = True
            else:
                result = False
        # If the server doesn't belong to any group, check if it meets GR
        # requirements, printing to stdout what needs to be changed and
        # update the configuration file if provided
        else:
            # The dictionary with the requirements to be verified.
            if server is not None:
                req_dict = get_req_dict(server, None, peer_server=None,
                                        option_file=option_file)
                skip_schema_checks = False
            else:
                req_dict = get_req_dict_for_opt_file(option_file)
                skip_schema_checks = True

            _LOGGER.log(STEP_LOG_LEVEL_VALUE, "Checking Group Replication "
                        "prerequisites.")

            # set dry_run to avoid changes on server as replication user
            # creation.
            result = check_server_requirements(
                server, req_dict, None, verbose=verbose, dry_run=True,
                skip_schema_checks=skip_schema_checks, update=update,
                skip_backup=skip_backup)

            # verify the group replication is installed and not disabled.
            if server is not None:
                check_gr_plugin_is_installed(server, option_file,
                                             dry_run=(not update))

    finally:
        # Disconnect the server prior to end method invocation.
        if server is not None:
            server.disconnect()

    return result
Exemple #6
0
    def test_join(self):
        """Tests join method
        """
        # Fill the options
        options = {
            "group_name": None,
            "gr_host": "{0}:{1}".format(self.server.host, self.server.port),
            "replication_user": "******",
            "rep_user_passwd": "passwd",
            "verbose": False,
            "dry_run": False,
        }

        # join needs start
        if is_member_of_group(self.server):
            leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        self.assertTrue(start(self.server, **options))

        # reconnect the server.
        self.server.connect()

        # health
        health(self.server, **options)

        # reconnect the server.
        self.server.connect()

        leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        member_state_qry = ("SELECT MEMBER_STATE FROM {0} as m JOIN {1} "
                            "as s on m.MEMBER_ID = s.MEMBER_ID"
                            "".format(REP_GROUP_MEMBERS_TABLE,
                                      REP_MEMBER_STATS_TABLE))
        frozen_queries = {
            member_state_qry: [
                [
                    'ONLINE',
                ],
            ],
        }
        mock_server = get_mock_server(self.server, queries=frozen_queries)

        # Join with defaults ("dry_run": True) and required password
        options = {
            "dry_run": True,
            "replication_user": "******",
            "rep_user_passwd": "passwd",
        }
        join(self.server, mock_server, **options)

        # reconnect the server.
        self.server.connect()

        # Join with no defaults ("dry_run": True)
        options = {
            "group_name": None,
            "gr_host": "{0}:{1}".format(self.server.host, self.server.port),
            "replication_user": "******",
            "rep_user_passwd": "passwd",
            "verbose": False,
            "dry_run": True,
        }
        # leave the group
        if is_member_of_group(self.server):
            leave(self.server, **options)

        self.assertFalse(join(self.server, mock_server, **options))

        # reconnect the server.
        self.server.connect()

        self.assertFalse(leave(self.server, **options))

        # reconnect the server.
        self.server.connect()
Exemple #7
0
    def test_bootstrap(self):
        """Tests start method
        """
        # Fill the options
        options = {
            "group_name": None,
            "replication_user": None,
            "rep_user_passwd": "passwd",
            "verbose": True
        }

        # test start
        if is_member_of_group(self.server):
            leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        self.assertTrue(start(self.server, **options))

        # reconnect the server.
        self.server.connect()

        # health
        health(self.server, **options)

        # reconnect the server.
        self.server.connect()

        # Bootstrap active server
        # Trying to start a group with a server that is already a member
        # expect GadgetError: Query failed: is already a member of a GR group.
        with self.assertRaises(GadgetError) as test_raises:
            start(self.server, **options)
        exception = test_raises.exception
        self.assertTrue(
            "is already a member" in exception.errmsg,
            "The exception message was not the expected. {0}"
            "".format(exception.errmsg))

        # reconnect the server.
        self.server.connect()

        leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        group_name = "b7286041-3016-11e6-ba52-507b9d87510a"
        # start using not defaults
        options = {
            "group_name": "b7286041-3016-11e6-ba52-507b9d87510a",
            "gr_host": "{0}:".format(self.server.host),
            "replication_user": "******",
            "rep_user_passwd": "passwd",
            "verbose": True,
            "dry_run": False,
        }

        self.assertTrue(start(self.server, **options))
        # reconnect the server.
        self.server.connect()
        self.assertTrue(is_member_of_group(self.server, group_name))

        self.assertFalse(is_member_of_group(self.server, "group_name"))

        if is_member_of_group(self.server):
            leave(self.server, **options)

        # reconnect the server.
        self.server.connect()
Exemple #8
0
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
Exemple #9
0
    def test_join(self):
        """Tests join method
        """
        # Fill the options
        options = {
            "group_name": None,
            "gr_host": "{0}:{1}".format(self.server.host, self.server.port),
            "replication_user": "******",
            "rep_user_passwd": "passwd",
            "verbose": False,
            "dry_run": False,
        }

        # join needs start
        if is_member_of_group(self.server):
            leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        self.assertTrue(start(self.server, **options))

        # reconnect the server.
        self.server.connect()

        # health
        health(self.server, **options)

        # reconnect the server.
        self.server.connect()

        leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        member_state_qry = ("SELECT MEMBER_STATE FROM {0} as m JOIN {1} "
                            "as s on m.MEMBER_ID = s.MEMBER_ID"
                            "".format(REP_GROUP_MEMBERS_TABLE,
                                      REP_MEMBER_STATS_TABLE))
        frozen_queries = {
            member_state_qry: [['ONLINE', ], ],
        }
        mock_server = get_mock_server(self.server, queries=frozen_queries)

        # Join with defaults ("dry_run": True) and required password
        options = {
            "dry_run": True,
            "replication_user": "******",
            "rep_user_passwd": "passwd",
        }
        join(self.server, mock_server, **options)

        # reconnect the server.
        self.server.connect()

        # Join with no defaults ("dry_run": True)
        options = {
            "group_name": None,
            "gr_host": "{0}:{1}".format(self.server.host, self.server.port),
            "replication_user": "******",
            "rep_user_passwd": "passwd",
            "verbose": False,
            "dry_run": True,
        }
        # leave the group
        if is_member_of_group(self.server):
            leave(self.server, **options)

        self.assertFalse(join(self.server, mock_server, **options))

        # reconnect the server.
        self.server.connect()

        self.assertFalse(leave(self.server, **options))

        # reconnect the server.
        self.server.connect()
Exemple #10
0
    def test_bootstrap(self):
        """Tests start method
        """
        # Fill the options
        options = {
            "group_name": None,
            "replication_user": None,
            "rep_user_passwd": "passwd",
            "verbose": True
        }

        # test start
        if is_member_of_group(self.server):
            leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        self.assertTrue(start(self.server, **options))

        # reconnect the server.
        self.server.connect()

        # health
        health(self.server, **options)

        # reconnect the server.
        self.server.connect()

        # Bootstrap active server
        # Trying to start a group with a server that is already a member
        # expect GadgetError: Query failed: is already a member of a GR group.
        with self.assertRaises(GadgetError) as test_raises:
            start(self.server, **options)
        exception = test_raises.exception
        self.assertTrue("is already a member" in exception.errmsg,
                        "The exception message was not the expected. {0}"
                        "".format(exception.errmsg))

        # reconnect the server.
        self.server.connect()

        leave(self.server, **options)

        # reconnect the server.
        self.server.connect()

        group_name = "b7286041-3016-11e6-ba52-507b9d87510a"
        # start using not defaults
        options = {
            "group_name": "b7286041-3016-11e6-ba52-507b9d87510a",
            "gr_host": "{0}:".format(self.server.host),
            "replication_user": "******",
            "rep_user_passwd": "passwd",
            "verbose": True,
            "dry_run": False,
        }

        self.assertTrue(start(self.server, **options))
        # reconnect the server.
        self.server.connect()
        self.assertTrue(is_member_of_group(self.server, group_name))

        self.assertFalse(is_member_of_group(self.server, "group_name"))

        if is_member_of_group(self.server):
            leave(self.server, **options)

        # reconnect the server.
        self.server.connect()