370 lines
12 KiB
Python
370 lines
12 KiB
Python
# 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)
|