Beispiel #1
0
def get_network_interface_state(network_interface):
    client = boto3.client('ec2')
    response = client.describe_network_interfaces(
        NetworkInterfaceIds=[network_interface])
    status = response['NetworkInterfaces'][0]['Attachment']['Status']
    LOG.debug('Interface %s, status = %s', network_interface, status)
    return status
Beispiel #2
0
def server_status(cfg):
    """Print list of MySQL servers registered in ProxySQL and their status."""
    kwargs = get_proxysql_options(cfg)
    LOG.debug('ProxySQL config %r', kwargs)
    proxysql = ProxySQL(**kwargs)

    writer_hostgroup_id = int(cfg.get('galera', 'writer_hostgroup_id'))
    reader_hostgroup_id = int(cfg.get('galera', 'reader_hostgroup_id'))

    for hostgroup_id, name in [(writer_hostgroup_id, 'Writers'),
                               (reader_hostgroup_id, 'Readers')]:
        servers = PrettyTable([
            'hostgroup_id', 'hostname', 'port', 'status', 'weight',
            'compression', 'max_connections', 'max_replication_lag', 'use_ssl',
            'max_latency_ms', 'comment'
        ])
        servers.align = 'r'
        servers.align['hostname'] = 'l'  # pylint: disable=unsupported-assignment-operation
        servers.align['comment'] = 'l'  # pylint: disable=unsupported-assignment-operation
        LOG.info('%s:', name)
        for backend in proxysql.find_backends(hostgroup_id):
            servers.add_row([
                backend.hostgroup_id, backend.hostname, backend.port,
                backend.status, backend.weight, backend.compression,
                backend.max_connections, backend.max_replication_lag,
                backend.use_ssl, backend.max_latency_ms, backend.comment
            ])

        print(servers)
Beispiel #3
0
def server_set_wsrep_desync(cfg, server_ip, port, wsrep_desync='ON'):
    """
    Set MySQL server in desync state.

    :param cfg: ProxySQL Tools configuration
    :type cfg: ConfigParser.ConfigParser
    :param server_ip: Server IP address
    :param port: Server port
    :param wsrep_desync: Value for wsrep_desync
    """
    kwargs = get_proxysql_options(cfg)
    LOG.debug('ProxySQL config %r', kwargs)
    proxysql = ProxySQL(**kwargs)

    writer_hostgroup_id = int(cfg.get('galera', 'writer_hostgroup_id'))
    reader_hostgroup_id = int(cfg.get('galera', 'reader_hostgroup_id'))

    backends = proxysql.find_backends(writer_hostgroup_id) + \
        proxysql.find_backends(reader_hostgroup_id)

    for backend in backends:
        if backend.hostname == server_ip and backend.port == port:

            backend.connect(cfg.get('galera', 'cluster_username'),
                            cfg.get('galera', 'cluster_password'))
            backend.execute("SET GLOBAL wsrep_desync=%s", wsrep_desync)
            return

    raise ProxySQLBackendNotFound('Could not find server %s:%d' %
                                  (server_ip, port))
Beispiel #4
0
def register_writer(galera_cluster,
                    proxysql,
                    writer_hostgroup_id,
                    reader_hostgroup_id,
                    ignore_writer=None):
    """
    Checks ProxySQL and Galera cluster and makes sure there is one
    healthy registered writer.

    :param galera_cluster: GaleraCluster instance.
    :type galera_cluster: GaleraCluster
    :param proxysql: ProxySQL instance
    :type proxysql: ProxySQL
    :param writer_hostgroup_id: Writer hostgroup_id
    :type writer_hostgroup_id: int
    :param reader_hostgroup_id: Reader hostgroup_id
    :type reader_hostgroup_id: int
    :param ignore_writer: Do not make this backend writer
    :type ignore_writer: ProxySQLMySQLBackend
    """
    # Get writer backend. If doesn't exist - add.
    # If exists - check state of respective Galera node.
    # if the Galera node is not healthy - delete the backend and add a healthy
    # one.
    LOG.debug('Registering writers')
    try:
        for backend in proxysql.find_backends(writer_hostgroup_id):
            check_backend(backend,
                          galera_cluster,
                          proxysql,
                          writer_hostgroup_id,
                          'Writer',
                          limit=1,
                          ignore_backend=ignore_writer,
                          recovered_hostgroup_id=reader_hostgroup_id,
                          recoverd_comment='Reader')

    except ProxySQLBackendNotFound:
        # add it
        register_synced_backends(galera_cluster,
                                 proxysql,
                                 writer_hostgroup_id,
                                 comment='Writer',
                                 limit=1,
                                 ignore_backend=ignore_writer)

    try:
        proxysql.find_backends(writer_hostgroup_id, BackendStatus.online)
    except ProxySQLBackendNotFound:
        LOG.warn('No writer backends were registered. '
                 'Will try to add previously ignored backends')
        register_synced_backends(galera_cluster,
                                 proxysql,
                                 writer_hostgroup_id,
                                 comment='Writer',
                                 limit=1)
Beispiel #5
0
def docker_pull_image(image):
    """Pull the specified image using docker-py. This function will parse the
        result from docker-py and raise an exception if there is an error.

    :param str image: Name of the image to pull
    """
    client = docker_client()
    api = client.api

    response = api.pull(image)
    LOG.debug('Response: %s', response)
    def ping(self):
        """Check health of ProxySQL.

        :return: True if ProxySQL healthy and False otherwise.
        :rtype: bool"""
        try:
            result = self.execute('SELECT 1 AS result')
            return result[0]['result'] == '1'
        except pymysql.err.OperationalError:
            LOG.debug('ProxySQL %s:%d is dead', self.host, self.port)
            return False
Beispiel #7
0
def bug1258464killer(default_file):
    """
    bug1258464killer checks status of a local Galera node
    and if a) There are stuck COMMIT queries and b) There is an ALTER TABLE
    it will kill the node. This command workarounds a known bug
    https://bugs.launchpad.net/percona-xtradb-cluster/+bug/1258464
    """
    if default_file:
        if os.path.isfile(default_file):
            bug1258464(default_file)
        else:
            LOG.error('File not found : %s', default_file)
    else:
        bug1258464('/root/.my.cnf')
Beispiel #8
0
def eventually(func, *args, **kwargs):
    retries = kwargs.pop('retries', 90)
    sleep_time = kwargs.pop('sleep_time', 0.5)

    for i in xrange(retries):
        try:
            if func(*args, **kwargs):
                return
            else:
                LOG.info('Waiting for %s to return True', func)
                time.sleep(sleep_time)
        except Exception:
            time.sleep(sleep_time)
            continue

    raise EnvironmentError('Function %s never returned True' % func)
Beispiel #9
0
def set_password(cfg, username, password):
    """Change password of exists MySQL user"""
    try:
        change_password(cfg, username, password)
        LOG.info('Password for user %s changed', username)
    except ProxySQLUserNotFound:
        LOG.error("User not found")
        exit(1)
    except MySQLError as err:
        LOG.error('Failed to talk to database: %s', err)
    except (NoOptionError, NoSectionError) as err:
        LOG.error('Failed to parse config: %s', err)
        exit(1)
    except ProxySQLBackendNotFound as err:
        LOG.error('ProxySQL backends not found: %s', err)
        exit(1)
Beispiel #10
0
def main(ctx, cfg, debug, config, version):
    """proxysql-tool entrypoint"""
    if ctx.invoked_subcommand is None:
        if version:
            print(__version__)
            exit(0)
        else:
            print(ctx.get_help())
            exit(-1)

    setup_logging(LOG, debug=debug)

    if os.path.exists(config):
        cfg.read(config)
    else:
        LOG.error("Config file %s doesn't exist", config)
        exit(1)
Beispiel #11
0
def debian_container():
    client = docker_client()
    api = client.api

    # Pull the image locally
    docker_pull_image(DEBIAN_IMAGE)

    container = api.create_container(image=DEBIAN_IMAGE,
                                     labels=[CONTAINERS_FOR_TESTING_LABEL],
                                     command='/bin/sleep 36000')
    LOG.debug('Starting container %s', container['Id'])
    api.start(container['Id'])

    container_info = client.containers.get(container['Id'])

    yield container_info

    api.remove_container(container=container['Id'], force=True)
Beispiel #12
0
def modify(ctx, cfg, username):
    """Modify MySQL backend user by username"""
    try:
        modify_user(cfg, username, ctx.args)
        LOG.info("User %s has modified", username)
    except ProxySQLUserNotFound:
        LOG.error("User not found")
        exit(1)
    except MySQLError as err:
        LOG.error('Failed to talk to database: %s', err)
    except ValueError:
        LOG.error("Invalid input")
        exit(1)
Beispiel #13
0
def get_users(cfg):
    """Print list of MySQL users from mysql_users"""
    args = get_proxysql_options(cfg)
    users = ProxySQL(**args).get_users()
    if not users:
        LOG.info('User list is empty')
        return
    table = PrettyTable([
        'username', 'password', 'active', 'use_ssl', 'default_hostgroup',
        'default_schema', 'schema_locked', 'transaction_persistent',
        'fast_forward', 'backend', 'frontend', 'max_connections'
    ])
    for user in users:
        table.add_row([
            user.username, user.password, user.active, user.use_ssl,
            user.default_hostgroup, user.default_schema, user.schema_locked,
            user.transaction_persistent, user.fast_forward, user.backend,
            user.frontend, user.max_connections
        ])
    print(table)
Beispiel #14
0
def proxysql_instance(proxysql_container):
    LOG.debug("Container %r", proxysql_container)
    connection = ProxySQL(host=proxysql_container['ip'],
                          port=PROXYSQL_ADMIN_PORT,
                          user=PROXYSQL_ADMIN_USER,
                          password=PROXYSQL_ADMIN_PASSWORD)

    def check_started():
        LOG.debug('Checking if proxysql is up')
        ret = connection.ping()
        LOG.debug(ret)
        return ret

    # Allow ProxySQL to startup completely. The problem is that ProxySQL starts
    # listening to the admin port before it has initialized completely which
    # causes the test to fail with the exception:
    # OperationalError: (2013, 'Lost connection to MySQL server during query')
    eventually(check_started, retries=15, sleep_time=4)

    return connection
Beispiel #15
0
def proxysql_container(proxysql_config_contents, tmpdir, container_network):
    client = docker_client()
    api = client.api

    # Pull the container image locally first
    docker_pull_image(PROXYSQL_IMAGE)

    # Setup the ProxySQL config
    config = tmpdir.join('proxysql.cnf')
    config.write(proxysql_config_contents)
    LOG.debug('ProxySQL Config:\n %s', proxysql_config_contents)

    # The ports that the ProxySQL container will be listening on inside the
    # container
    container_ports = [PROXYSQL_ADMIN_PORT, PROXYSQL_CLIENT_PORT]
    container_info = {
        'name': 'proxysql01',
        'ip': '172.25.3.100',
    }

    host_config = api.create_host_config(
        binds=["{}:/etc/proxysql.cnf".format(str(config))], port_bindings={})

    networking_config = api.create_networking_config({
        container_network:
        api.create_endpoint_config(ipv4_address=container_info['ip'])
    })

    container = api.create_container(image=PROXYSQL_IMAGE,
                                     name='proxysql01',
                                     labels=[CONTAINERS_FOR_TESTING_LABEL],
                                     ports=container_ports,
                                     host_config=host_config,
                                     networking_config=networking_config)
    LOG.debug('Starting container %s', container['Id'])
    api.start(container['Id'])
    # 1/0

    yield container_info

    api.remove_container(container=container['Id'], force=True)
Beispiel #16
0
def ping(cfg):
    """Checks the health of ProxySQL."""
    kwargs_maps = {
        'host': 'host',
        'port': 'admin_port',
        'user': '******',
        'password': '******'
    }
    kwargs = {}
    for key in kwargs_maps:
        try:
            kwargs[key] = cfg.get('proxysql', kwargs_maps[key])
        except NoOptionError:
            pass

    if ProxySQL(**kwargs).ping():
        LOG.info('ProxySQL is alive')
        exit(0)
    else:
        LOG.info('ProxySQL is dead')
        exit(1)
Beispiel #17
0
def bug1258464(default_file):
    """Check if node is affected by
    https://bugs.launchpad.net/percona-xtradb-cluster/+bug/1258464
    and if yes, kill the node.

    :param default_file: Path to my.cnf
    :return: True if it killed the node.
    :rtype: bool
    """
    try:
        connection = pymysql.connect(read_default_file=default_file)
        with connection.cursor() as cursor:
            cursor.execute("SELECT COUNT(*)"
                           "FROM information_schema.processlist "
                           "WHERE State = 'wsrep in pre-commit stage' "
                           "AND Info = 'COMMIT'")
            count_pre_commit = cursor.fetchone()[0]
            cursor.execute("SELECT COUNT(*)"
                           "FROM information_schema.processlist "
                           "WHERE State = "
                           "'Waiting for table metadata lock' "
                           "AND Info LIKE 'ALTER TABLE%'")
            count_waiting = cursor.fetchone()[0]
            if count_pre_commit > 100 and count_waiting > 0:
                my_cnf_path = get_my_cnf()
                pid = get_pid(my_cnf_path)
                kill_process(pid)
                return True
    except OperationalError as err:
        LOG.error(str(err))
    except OSError as err:
        LOG.error(str(err))
    except NotImplementedError as err:
        LOG.error(str(err))
    return False
Beispiel #18
0
def singlewriter(galera_cluster,
                 proxysql,
                 writer_hostgroup_id,
                 reader_hostgroup_id,
                 ignore_writer=None):
    """
    Implements singlewriter balancing mode.

    :param galera_cluster: GaleraCluster instance.
    :type galera_cluster: GaleraCluster
    :param proxysql: ProxySQL instance
    :type proxysql: ProxySQL
    :param writer_hostgroup_id: Writer hostgroup_id
    :type writer_hostgroup_id: int
    :param reader_hostgroup_id: Reader hostgroup_id
    :type reader_hostgroup_id: int
    :param ignore_writer: Do not make this backend writer
    :type ignore_writer: ProxySQLMySQLBackend
    """
    register_writer(galera_cluster,
                    proxysql,
                    writer_hostgroup_id,
                    reader_hostgroup_id,
                    ignore_writer=ignore_writer)
    register_readers(galera_cluster, proxysql, writer_hostgroup_id,
                     reader_hostgroup_id)

    LOG.debug('Register all missing backends')
    for galera_node in galera_cluster.find_synced_nodes():
        reader = ProxySQLMySQLBackend(galera_node.host,
                                      hostgroup_id=reader_hostgroup_id,
                                      port=galera_node.port,
                                      comment='Reader')
        writer = ProxySQLMySQLBackend(galera_node.host,
                                      hostgroup_id=writer_hostgroup_id,
                                      port=galera_node.port)
        if not (proxysql.backend_registered(reader)
                or proxysql.backend_registered(writer)):
            proxysql.register_backend(reader)
            LOG.info('Added backend %s to hostgroup %d', reader,
                     reader_hostgroup_id)

    LOG.debug('Make sure writer is not reader')
    writer = proxysql.find_backends(writer_hostgroup_id)[0]
    readers = proxysql.find_backends(reader_hostgroup_id)
    writer_as_reader = writer
    writer_as_reader.hostgroup_id = reader_hostgroup_id

    is_readers_offline = False
    if writer_as_reader in readers:
        readers_without_writer = readers[:]
        readers_without_writer.remove(writer_as_reader)

        is_readers_offline = all(x.status == BackendStatus.offline_soft
                                 for x in readers_without_writer)

    if len(readers) > 2 and proxysql.backend_registered(writer_as_reader) \
        and not is_readers_offline:
        proxysql.deregister_backend(writer_as_reader)
Beispiel #19
0
def galera_register(cfg):
    """Registers Galera cluster nodes with ProxySQL."""

    kwargs = {}
    try:
        kwargs['user'] = cfg.get('galera', 'cluster_username')
    except NoOptionError:
        pass
    try:
        kwargs['password'] = cfg.get('galera', 'cluster_password')
    except NoOptionError:
        pass

    LOG.debug('Galera config %r', kwargs)
    galera_cluster = GaleraCluster(cfg.get('galera', 'cluster_host'), **kwargs)

    kwargs = get_proxysql_options(cfg)
    LOG.debug('ProxySQL config %r', kwargs)
    proxysql = ProxySQL(**kwargs)

    load_balancing_mode = cfg.get('galera', 'load_balancing_mode')
    writer_hostgroup_id = int(cfg.get('galera', 'writer_hostgroup_id'))
    reader_hostgroup_id = int(cfg.get('galera', 'reader_hostgroup_id'))

    if load_balancing_mode == 'singlewriter':
        kwargs = {}
        try:
            host, port = cfg.get('galera', 'writer_blacklist').split(':')
            bcknd = ProxySQLMySQLBackend(host,
                                         hostgroup_id=writer_hostgroup_id,
                                         port=port)
            kwargs['ignore_writer'] = bcknd
        except NoOptionError:
            pass
        singlewriter(galera_cluster, proxysql, writer_hostgroup_id,
                     reader_hostgroup_id, **kwargs)
    else:
        raise NotImplementedError('Balancing mode %s not implemented yet.' %
                                  load_balancing_mode)
Beispiel #20
0
def register_synced_backends(
        galera_cluster,
        proxysql,  # pylint: disable=too-many-arguments
        hostgroup_id,
        comment=None,
        limit=None,
        ignore_backend=None):
    """
    Find SYNCED node and register it as a backend.

    :param galera_cluster: GaleraCluster instance.
    :type galera_cluster: GaleraCluster
    :param proxysql: ProxySQL instance
    :type proxysql: ProxySQL
    :param hostgroup_id: hostgroup_id
    :type hostgroup_id: int
    :param comment: Optional comment to add to mysql_server
    :type comment: str
    :param limit: Register not more than limit number of backends
    :type limit: int
    :param ignore_backend: Do not register this backend
    :type ignore_backend: ProxySQLMySQLBackend
    """
    try:
        galera_nodes = galera_cluster.find_synced_nodes()

        if ignore_backend:
            node = GaleraNode(ignore_backend.hostname,
                              port=ignore_backend.port)
            LOG.debug('Ignoring backend %s', ignore_backend)
            if node in galera_nodes:
                LOG.debug('Remove %s from candidates', ignore_backend)
                galera_nodes.remove(node)

        if limit:
            candidate_nodes = galera_nodes[:limit]
        else:
            candidate_nodes = galera_nodes

        for galera_node in candidate_nodes:
            backend = ProxySQLMySQLBackend(galera_node.host,
                                           hostgroup_id=hostgroup_id,
                                           port=galera_node.port,
                                           comment=comment)
            proxysql.register_backend(backend)
            LOG.info('Added backend %s to hostgroup %d', backend, hostgroup_id)

    except GaleraClusterSyncedNodeNotFound as err:
        LOG.error(err)
Beispiel #21
0
def set_sync(cfg, ip_address, port):
    """Set server to sync state."""

    try:
        server_set_wsrep_desync(cfg, ip_address, port, wsrep_desync='OFF')
    except NotImplementedError as err:
        LOG.error(err)
        exit(1)
    except (NoOptionError, NoSectionError) as err:
        LOG.error('Failed to parse config: %s', err)
        exit(1)

    except MySQLError as err:
        LOG.error('Failed to talk to database: %s', err)
    except ProxySQLBackendNotFound as err:
        LOG.error(err)
Beispiel #22
0
def delete(cfg, username):
    """Delete MySQL backend user by username"""
    try:
        delete_user(cfg, username)
        LOG.info('User %s has deleted', username)
    except MySQLError as err:
        LOG.error('Failed to talk to database: %s', err)
    except (NoOptionError, NoSectionError) as err:
        LOG.error('Failed to parse config: %s', err)
        exit(1)
Beispiel #23
0
def register(cfg):
    """Registers Galera cluster nodes with ProxySQL."""

    try:
        galera_register(cfg)
    except NotImplementedError as err:
        LOG.error(err)
        exit(1)
    except (NoOptionError, NoSectionError) as err:
        LOG.error('Failed to parse config: %s', err)
        exit(1)

    except MySQLError as err:
        LOG.error('Failed to talk to database: %s', err)
Beispiel #24
0
def status(cfg):
    """Show status of MySQL backends."""

    try:
        server_status(cfg)
    except NotImplementedError as err:
        LOG.error(err)
        exit(1)
    except (NoOptionError, NoSectionError) as err:
        LOG.error('Failed to parse config: %s', err)
        exit(1)

    except MySQLError as err:
        LOG.error('Failed to talk to database: %s', err)
 def find_synced_nodes(self):
     """Find a node in the cluster in SYNCED state.
     :return: List of Galera node in SYNCED state.
     :rtype: list(GaleraNode)
     :raise: GaleraClusterSyncedNodeNotFound
     """
     LOG.debug('Looking for a SYNCED node')
     nodes = []
     for galera_node in self._nodes:
         try:
             state = galera_node.wsrep_local_state
             LOG.debug('%s state: %s', galera_node, state)
             if state == GaleraNodeState.SYNCED:
                 nodes.append(galera_node)
         except OperationalError as err:
             LOG.error(err)
             LOG.info('Skipping node %s', galera_node)
     if nodes:
         return nodes
     else:
         raise GaleraClusterSyncedNodeNotFound('Cluster has '
                                               'no SYNCED nodes')
Beispiel #26
0
def aws_notify_master(cfg):
    """The function moves network interface to local instance and brings it up.
    Steps:

    - Detach network interface if attached to anywhere.
    - Attach the network interface to the local instance.
    - Configure IP address on this instance

    :param cfg: config object
    """
    try:
        os.environ['AWS_ACCESS_KEY_ID'] = cfg.get('aws', 'aws_access_key_id')
        os.environ['AWS_SECRET_ACCESS_KEY'] = cfg.get('aws',
                                                      'aws_secret_access_key')
        os.environ['AWS_DEFAULT_REGION'] = cfg.get('aws', 'aws_default_region')
    except NoOptionError:
        LOG.error('aws_access_key_id, aws_secret_access_key and '
                  'aws_default_region must be defined in '
                  'aws section of the config file.')
        exit(-1)

    instance_id = get_my_instance_id()
    try:
        ip = cfg.get('proxysql', 'virtual_ip')
        netmask = cfg.get('proxysql', 'virtual_netmask')

        network_interface = get_network_interface(ip)

        if network_interface_attached(network_interface):
            detach_network_interface(network_interface)

        local_interface = "eth%d" % DEVICE_INDEX
        ensure_local_interface_is_gone(local_interface)

        ensure_network_interface_is_detached(network_interface)

        attach_network_interface(network_interface, instance_id)

        configure_local_interface(local_interface, ip, netmask)
    except NoOptionError as err:
        LOG.error('virtual_ip and virtual_netmask must be defined in '
                  'proxysql section of the config file.')
        LOG.error(err)
Beispiel #27
0
def create(
        cfg,
        username,
        password,
        active,
        use_ssl,  # pylint: disable=too-many-arguments
        default_hostgroup,
        default_schema,
        schema_locked,
        transaction_persistent,
        fast_forward,
        backend,
        frontend,
        max_connections):
    """Add user of MySQL backend to ProxySQL"""
    kwargs = {
        'username': username,
        'password': password,
        'use_ssl': use_ssl,
        'active': active,
        'default_hostgroup': default_hostgroup,
        'default_schema': default_schema,
        'schema_locked': schema_locked,
        'transaction_persistent': transaction_persistent,
        'backend': backend,
        'frontend': frontend,
        'fast_forward': fast_forward,
        'max_connections': max_connections
    }
    try:
        create_user(cfg, kwargs)
        LOG.info('User %s successfully created', username)
    except MySQLError as err:
        LOG.error('Failed to talk to database: %s', err)
    except (NoOptionError, NoSectionError) as err:
        LOG.error('Failed to parse config: %s', err)
        exit(1)
Beispiel #28
0
def notify_master(cfg):
    """The notify_master script for keepalived."""
    LOG.debug('Switching to master role and executing keepalived '
              'notify_master script.')
    aws_notify_master(cfg)
Beispiel #29
0
 def check_started():
     LOG.debug('Checking if proxysql is up')
     ret = connection.ping()
     LOG.debug(ret)
     return ret
Beispiel #30
0
def check_backend(
        backend,
        galera_cluster,
        proxysql,
        hostgroup_id,
        comment,  # pylint: disable=too-many-arguments
        limit=None,
        ignore_backend=None,
        recovered_hostgroup_id=None,
        recoverd_comment=None):
    """
    Check health of given backed and if necessary replace it.

    :param backend: MySQL backend.
    :type backend: ProxySQLMySQLBackend
    :param galera_cluster: GaleraCluster instance.
    :type galera_cluster: GaleraCluster
    :param proxysql: ProxySQL instance.
    :type proxysql: ProxySQL
    :param hostgroup_id: Hostgroup_id which the backend belongs to.
    :type hostgroup_id: int
    :param comment: Comment to add to mysql_servers in ProxySQL
    :param limit: Register not more than limit number of backends
    :type limit: int
    :param ignore_backend: Do not register this backend
    :type ignore_backend: ProxySQLMySQLBackend
    :param recovered_hostgroup_id: If backend recovers from OFFLINE_SOFT assign
        it to this hostgroup_id. Default hostgroup_id.
    :type recovered_hostgroup_id: int
    :param recoverd_comment: If backend recovers from OFFLINE_SOFT set
        this comment
    :type recoverd_comment: str
    :return: True if backend successfully registered.
    :rtype: bool
    """
    # check it
    LOG.debug('Backend %s is already registered', backend)
    LOG.debug('Checking its health')
    try:
        node = galera_cluster.find_node(backend.hostname, backend.port)

        state = node.wsrep_local_state
        LOG.debug('%s state: %d', node, state)

        if state == GaleraNodeState.SYNCED:
            LOG.debug('Node %s (%s) is healthy', node, backend.status)

            if backend.status != BackendStatus.online:

                LOG.debug('Deregistering %s (%s)', backend, backend.status)
                proxysql.deregister_backend(backend)

                backend.status = BackendStatus.online

                if not recovered_hostgroup_id:
                    recovered_hostgroup_id = hostgroup_id
                backend.hostgroup_id = recovered_hostgroup_id

                if recoverd_comment:
                    backend.comment = recoverd_comment

                LOG.debug('Registering %s (%s)', backend, backend.status)
                proxysql.register_backend(backend)
        else:
            LOG.warn(
                'Node %s is reachable but unhealty, '
                'setting it OFFLINE_SOFT', node)
            proxysql.set_status(backend, BackendStatus.offline_soft)
            register_synced_backends(galera_cluster,
                                     proxysql,
                                     hostgroup_id,
                                     comment=comment,
                                     limit=limit,
                                     ignore_backend=ignore_backend)

    except GaleraClusterNodeNotFound:
        LOG.warn('Backend %s is not a cluster member. Deregistering it.',
                 backend)
        proxysql.deregister_backend(backend)
        register_synced_backends(galera_cluster,
                                 proxysql,
                                 hostgroup_id,
                                 comment=comment,
                                 limit=limit,
                                 ignore_backend=ignore_backend)
    except OperationalError as err:
        LOG.error(err)
        LOG.error('Looks like backend %s is unhealthy. Deregistering it.',
                  backend)
        proxysql.deregister_backend(backend)
        register_synced_backends(galera_cluster,
                                 proxysql,
                                 hostgroup_id,
                                 comment=comment,
                                 limit=limit,
                                 ignore_backend=ignore_backend)
    return True