# Copyright 2021 Alibaba Group Holding Limited. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import click import re import sys from core import channel, convention from core.consensus import ConsensusRole, ConsensusExtRole, ConsensusNode from .common import global_mgr, print_rows @click.group(name='consensus') def consensus_group(): pass @click.command(name='role') @click.option('--report-leader', is_flag=True) def report_role(report_leader): with global_mgr.consensus_manager() as mgr: current_node = mgr.current_node() if current_node.role == ConsensusRole.FOLLOWER: if current_node.ext_role == ConsensusExtRole.LOGGER: print('logger') else: print('follower') else: print(current_node.role.value) if report_leader: if current_node.role == ConsensusRole.LEADER: print(global_mgr.context().pod_info().name()) else: leader_addr = current_node.local_info.current_leader if leader_addr != '': channel = global_mgr.shared_channel() print(channel.get_node_by_addr(leader_addr).pod) else: print('') consensus_group.add_command(report_role) @click.command(name='this') @click.option('--full', '-f', is_flag=True) def print_current_info(full): chan = global_mgr.shared_channel() def get_role(consensus_node: ConsensusNode, node_info: channel.Node): if consensus_node.role == ConsensusRole.FOLLOWER and \ node_info.role == convention.NODE_ROLE_VOTER: return 'logger' return consensus_node.role.value with global_mgr.consensus_manager() as mgr: current_node = mgr.current_node() current_node_info = chan.get_node_by_pod_name(global_mgr.context().pod_info().name()) leader_addr = current_node.local_info.current_leader leader_node_info = chan.get_node_by_addr(leader_addr) if leader_addr != '' else None if full: print_rows(sep=' | ', header=( 'pod', 'addr', 'server_id', 'role', 'leader_pod', 'leader_addr', 'current_term', 'last_log_index', 'applied_index', 'commit_index', ), rows=[( current_node_info.pod, current_node.addr, current_node.server_id, get_role(current_node, current_node_info), leader_node_info.pod if leader_node_info else '', current_node.local_info.current_leader, current_node.local_info.current_term, current_node.local_info.last_log_index, current_node.local_info.last_apply_index, current_node.local_info.commit_index, )]) else: print_rows(sep=' | ', header=( 'pod', 'addr', 'role', 'leader_pod', 'leader_addr', ), rows=[( current_node_info.pod, current_node.addr, get_role(current_node, current_node_info), leader_node_info.pod if leader_node_info else '', current_node.local_info.current_leader, )]) consensus_group.add_command(print_current_info) @click.command(name='list') @click.option('--full', '-f', is_flag=True) def list_nodes(full): chan = global_mgr.shared_channel() def get_role(consensus_node: ConsensusNode, node_info: channel.Node): if consensus_node.role == ConsensusRole.FOLLOWER and \ node_info.role == convention.NODE_ROLE_VOTER: return 'logger' return consensus_node.role.value with global_mgr.consensus_manager() as mgr: current_node = mgr.current_node() if current_node.role != ConsensusRole.LEADER: print('not allowed outside leader') sys.exit(1) all_nodes = mgr.list_consensus_nodes() def to_print_tuple(consensus_node: ConsensusNode): node_info = chan.get_node_by_addr(consensus_node.addr) if full: return node_info.pod, consensus_node.server_id, consensus_node.addr, \ get_role(consensus_node, node_info), consensus_node.global_info.match_index, \ consensus_node.global_info.applied_index, consensus_node.global_info.election_weight, \ consensus_node.global_info.force_sync, consensus_node.global_info.learner_source else: return node_info.pod, consensus_node.addr, get_role(consensus_node, node_info) if full: print_rows(sep=' | ', header=( 'pod', 'server_id', 'addr', 'role', 'match_index', 'applied_index', 'election_weight', 'force_sync', 'learner_source' ), rows=[to_print_tuple(n) for n in all_nodes]) else: print_rows(sep=' | ', header=( 'pod', 'addr', 'role' ), rows=[to_print_tuple(n) for n in all_nodes]) consensus_group.add_command(list_nodes) def _get_addr_from_argument(arg: str, shared_channel: channel.SharedFileChannel) -> str: # If in the format of "IP:port", return arg. if re.match('\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+', arg): return arg # Otherwise query the shared channel. node = shared_channel.get_node_by_pod_name(arg) if not node: raise ValueError('node not found: ' + arg) return node.addr() @click.command(name='configure') @click.option('--weight', required=True, type=int) @click.argument('nodes', required=True, nargs=-1) def configure_election_weight(weight, nodes): if not nodes or len(nodes) == 0: return shared_channel = global_mgr.shared_channel() weights = [] with global_mgr.consensus_manager() as mgr: consensus_nodes = dict([(n.addr, n) for n in mgr.list_consensus_nodes()]) for node in nodes: addr = _get_addr_from_argument(node, shared_channel) consensus_node = consensus_nodes[addr] mgr.configure_follower( target=consensus_node, election_weight=weight, force_sync=consensus_node.global_info.force_sync ) weights.append(str(consensus_node.global_info.election_weight)) print(','.join(weights)) consensus_group.add_command(configure_election_weight) @click.command(name='change-leader') @click.argument('node') def change_leader_to(node): shared_channel = global_mgr.shared_channel() addr = _get_addr_from_argument(node, shared_channel) with global_mgr.consensus_manager() as mgr: mgr.change_leader(addr) consensus_group.add_command(change_leader_to) @click.command(name='add-learner') @click.argument('node') def add_learner(node): shared_channel = global_mgr.shared_channel() addr = _get_addr_from_argument(node, shared_channel) with global_mgr.consensus_manager() as mgr: consensus_nodes = mgr.list_consensus_nodes() # check if the leaner node already existed for consensus_node in consensus_nodes: if consensus_node.role == ConsensusRole.LEARNER and consensus_node.addr == addr: return mgr.add_learner(addr) consensus_group.add_command(add_learner) @click.command(name='drop-learner') @click.argument('node') def drop_learner(node): shared_channel = global_mgr.shared_channel() addr = _get_addr_from_argument(node, shared_channel) with global_mgr.consensus_manager() as mgr: consensus_nodes = mgr.list_consensus_nodes() # check if the leaner node exists for consensus_node in consensus_nodes: if consensus_node.role == ConsensusRole.LEARNER and consensus_node.addr == addr: mgr.drop_learner(addr) return consensus_group.add_command(drop_learner) @click.command(name='learner-to-follower') @click.argument('node') def chanage_to_follower_from_learner(node): shared_channel = global_mgr.shared_channel() addr = _get_addr_from_argument(node, shared_channel) with global_mgr.consensus_manager() as mgr: mgr.upgrade_learner_to_follower(addr) consensus_group.add_command(chanage_to_follower_from_learner) @click.command(name='enable-election') def enable_election(): with global_mgr.consensus_manager() as mgr: mgr.enable_follower_election() consensus_group.add_command(enable_election) @click.command(name='disable-election') def enable_election(): with global_mgr.consensus_manager() as mgr: mgr.disable_follower_election() consensus_group.add_command(enable_election) @click.command(name='update-cluster-info') @click.argument('cluster-info') def update_cluster_info(cluster_info): with global_mgr.consensus_manager() as mgr: mgr.update_cluster_info(cluster_info) consensus_group.add_command(update_cluster_info) @click.command(name='prepare-handle-indicate') @click.argument('action') def prepare_handle_indicate(action): global_mgr.engine().prepare_handle_indicate(action) consensus_group.add_command(prepare_handle_indicate) @click.command(name='slave-status') def show_status(): with global_mgr.consensus_manager() as mgr: slave_status = mgr.show_slave_status() print_rows(sep=' | ', header=( 'relay_log_file', 'relay_log_pos', 'slave_io_running', 'slave_sql_running', 'slave_sql_running_state', 'seconds_behind_master', 'last_errno', 'last_error', 'last_io_errno', 'last_io_error', 'last_sql_errno', 'last_sql_error' ), rows=[(slave_status.relay_log_file, slave_status.relay_log_pos, slave_status.slave_io_running, slave_status.slave_sql_running, slave_status.slave_sql_running_state, slave_status.seconds_behind_master, slave_status.last_errno, slave_status.last_error, slave_status.last_io_errno, slave_status.last_io_error, slave_status.last_sql_errno, slave_status.last_sql_error)]) consensus_group.add_command(show_status) @click.command(name='set-readonly') def set_readonly(): with global_mgr.consensus_manager() as mgr: mgr.set_readonly() consensus_group.add_command(set_readonly) @click.group(name='log') def consensus_log_group(): pass consensus_group.add_command(consensus_log_group) @click.command(name='purge') @click.option('--local', is_flag=True) @click.option('--force', is_flag=True) @click.option('--left', default=5, type=int, show_default=True) def purge_consensus_log(local, force, left): shared_channel = global_mgr.shared_channel() before_index = shared_channel.last_backup_log_index with global_mgr.consensus_manager() as mgr: # at least left 5 files if not specified if not before_index or before_index < 0: logs = mgr.list_consensus_logs() if len(logs) <= left: print('not enough to purge') return before_index = list(logs)[-left].start_log_index mgr.purge_consensus_log_to(before_index, local=local, force=force) consensus_log_group.add_command(purge_consensus_log)