Skip to content

Commit

Permalink
Merge pull request #1270 from liangxin1300/20231101_node_standby_onli…
Browse files Browse the repository at this point in the history
…ne_completer_crmsh46

[crmsh-4.6] Dev: completers: Add online_nodes and standby_nodes
  • Loading branch information
liangxin1300 authored Dec 5, 2023
2 parents 779324d + c8137e5 commit 38304a7
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 153 deletions.
6 changes: 3 additions & 3 deletions crmsh/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ def wait_for_resource(message, resource, timeout_ms=WAIT_TIMEOUT_MS_DEFAULT):
with logger_utils.status_long(message) as progress_bar:
start_time = int(time.clock_gettime(time.CLOCK_MONOTONIC) * 1000)
while True:
if xmlutil.CrmMonXmlParser.is_resource_started(resource):
if xmlutil.CrmMonXmlParser().is_resource_started(resource):
break
status_progress(progress_bar)
if 0 < timeout_ms <= (int(time.clock_gettime(time.CLOCK_MONOTONIC) * 1000) - start_time):
Expand Down Expand Up @@ -471,7 +471,7 @@ def is_online():
Check whether local node is online
Besides that, in join process, check whether init node is online
"""
if not xmlutil.CrmMonXmlParser.is_node_online(utils.this_node()):
if not xmlutil.CrmMonXmlParser().is_node_online(utils.this_node()):
return False

# if peer_node is None, this is in the init process
Expand All @@ -482,7 +482,7 @@ def is_online():
# The communication IP maybe mis-configured
user, cluster_node = _parse_user_at_host(_context.cluster_node, None)
cluster_node = get_node_canonical_hostname(cluster_node)
if not xmlutil.CrmMonXmlParser.is_node_online(cluster_node):
if not xmlutil.CrmMonXmlParser().is_node_online(cluster_node):
shutil.copy(COROSYNC_CONF_ORIG, corosync.conf())
sync_file(corosync.conf())
ServiceManager(sh.ClusterShellAdaptorForLocalShell(sh.LocalShell())).stop_service("corosync")
Expand Down
2 changes: 2 additions & 0 deletions crmsh/completers.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ def primitives(args):


nodes = call(xmlutil.listnodes)
online_nodes = call(xmlutil.CrmMonXmlParser().get_node_list, "online")
standby_nodes = call(xmlutil.CrmMonXmlParser().get_node_list, "standby")

shadows = call(xmlutil.listshadows)

Expand Down
2 changes: 1 addition & 1 deletion crmsh/ocfs2.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ def join_ocfs2(self, peer):
if not target:
return
with logger_utils.status_long("Verify OCFS2 environment"):
use_cluster_lvm2 = xmlutil.CrmMonXmlParser.is_resource_configured(constants.LVMLOCKD_RA, peer)
use_cluster_lvm2 = xmlutil.CrmMonXmlParser(peer).is_resource_configured(constants.LVMLOCKD_RA)
self._verify_packages(use_cluster_lvm2)
if utils.is_dev_a_plain_raw_disk_or_partition(target, peer):
utils.compare_uuid_with_peer_dev([target], peer)
Expand Down
2 changes: 1 addition & 1 deletion crmsh/qdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def evaluate_qdevice_quorum_effect(mode, diskless_sbd=False, is_stage=False):
elif mode == QDEVICE_ADD and not is_stage:
# Add qdevice from init process, safe to restart
return QdevicePolicy.QDEVICE_RESTART
elif xmlutil.CrmMonXmlParser.is_any_resource_running():
elif xmlutil.CrmMonXmlParser().is_any_resource_running():
# will lose quorum, and with RA running
# no reload, no restart cluster service
# just leave a warning
Expand Down
10 changes: 5 additions & 5 deletions crmsh/sbd.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ def _restart_cluster_and_configure_sbd_ra(self):
"""
Try to configure sbd resource, restart cluster on needed
"""
if not xmlutil.CrmMonXmlParser.is_any_resource_running():
if not xmlutil.CrmMonXmlParser().is_any_resource_running():
logger.info("Restarting cluster service")
utils.cluster_run_cmd("crm cluster restart")
bootstrap.wait_for_cluster()
Expand Down Expand Up @@ -523,7 +523,7 @@ def configure_sbd_resource_and_properties(self):
"""
if not utils.package_is_installed("sbd") or \
not ServiceManager().service_is_enabled("sbd.service") or \
xmlutil.CrmMonXmlParser.is_resource_configured(self.SBD_RA):
xmlutil.CrmMonXmlParser().is_resource_configured(self.SBD_RA):
return
shell = sh.cluster_shell()

Expand Down Expand Up @@ -625,9 +625,9 @@ def has_sbd_device_already_initialized(dev):


def clean_up_existing_sbd_resource():
if xmlutil.CrmMonXmlParser.is_resource_configured(SBDManager.SBD_RA):
sbd_id_list = xmlutil.CrmMonXmlParser.get_resource_id_list_via_type(SBDManager.SBD_RA)
if xmlutil.CrmMonXmlParser.is_resource_started(SBDManager.SBD_RA):
if xmlutil.CrmMonXmlParser().is_resource_configured(SBDManager.SBD_RA):
sbd_id_list = xmlutil.CrmMonXmlParser().get_resource_id_list_via_type(SBDManager.SBD_RA)
if xmlutil.CrmMonXmlParser().is_resource_started(SBDManager.SBD_RA):
for sbd_id in sbd_id_list:
utils.ext_cmd("crm resource stop {}".format(sbd_id))
utils.ext_cmd("crm configure delete {}".format(' '.join(sbd_id_list)))
4 changes: 2 additions & 2 deletions crmsh/ui_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def do_print(uname):
return True

@command.wait
@command.completers(compl.nodes)
@command.completers(compl.online_nodes)
def do_standby(self, context, *args):
"""
usage: standby [<node>] [<lifetime>]
Expand Down Expand Up @@ -422,7 +422,7 @@ def do_standby(self, context, *args):
logger.info("standby node %s", node)

@command.wait
@command.completers(compl.nodes)
@command.completers(compl.standby_nodes)
def do_online(self, context, *args):
"""
usage: online [<node>]
Expand Down
4 changes: 2 additions & 2 deletions crmsh/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2866,15 +2866,15 @@ def is_dlm_running():
Check if dlm ra controld is running
"""
from . import xmlutil
return xmlutil.CrmMonXmlParser.is_resource_started(constants.DLM_CONTROLD_RA)
return xmlutil.CrmMonXmlParser().is_resource_started(constants.DLM_CONTROLD_RA)


def is_dlm_configured():
"""
Check if dlm configured
"""
from . import xmlutil
return xmlutil.CrmMonXmlParser.is_resource_configured(constants.DLM_CONTROLD_RA)
return xmlutil.CrmMonXmlParser().is_resource_configured(constants.DLM_CONTROLD_RA)


def is_quorate():
Expand Down
73 changes: 31 additions & 42 deletions crmsh/xmlutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1515,72 +1515,61 @@ def __init__(self, peer=None):
Init function
when peer set, parse peer node's results
"""
self.xml_elem = None
self.peer = peer
self.xml_elem = self._load()

def _load(self):
"""
Load xml output of crm_mon
"""
rc, output, stderr = sh.cluster_shell().get_rc_stdout_stderr_without_input(self.peer, constants.CRM_MON_XML_OUTPUT)
self.xml_elem = text2elem(output)
_, output, _ = sh.cluster_shell().get_rc_stdout_stderr_without_input(self.peer, constants.CRM_MON_XML_OUTPUT)
return text2elem(output)

@classmethod
def is_node_online(cls, node):
def is_node_online(self, node):
"""
Check if node online
Check if a node is online
"""
cls_inst = cls()
cls_inst._load()
elem_list = cls_inst.xml_elem.xpath('//node[@name="{}" and @online="true"]'.format(node))
return len(elem_list) != 0
xpath = f'//node[@name="{node}" and @online="true"]'
return bool(self.xml_elem.xpath(xpath))

@classmethod
def is_resource_configured(cls, ra_type, peer=None):
def get_node_list(self, attr=None):
"""
Check if the RA configured
Get a list of nodes based on the given attribute
"""
cls_inst = cls(peer=peer)
cls_inst._load()
elem_list = cls_inst.xml_elem.xpath('//resource[@resource_agent="{}"]'.format(ra_type))
return len(elem_list) != 0
attr_dict = {
'standby': '[@standby="true"]',
'online': '[@standby="false"]'
}
xpath_str = f'//node{attr_dict.get(attr, "")}'
return [e.get('name') for e in self.xml_elem.xpath(xpath_str)]

def is_resource_configured(self, ra_type):
"""
Check if the RA is configured
"""
xpath = f'//resource[@resource_agent="{ra_type}"]'
return bool(self.xml_elem.xpath(xpath))

@classmethod
def is_any_resource_running(cls, peer=None):
def is_any_resource_running(self):
"""
Check if any RA is running
"""
cls_inst = cls(peer=peer)
cls_inst._load()
elem_list = cls_inst.xml_elem.xpath('//resource[@active="true"]')
return len(elem_list) != 0
xpath = '//resource[@active="true"]'
return bool(self.xml_elem.xpath(xpath))

@classmethod
def is_resource_started(cls, ra, peer=None):
def is_resource_started(self, ra):
"""
Check if the RA started(in all clone instances if configured as clone)
@ra could be resource id or resource type
"""
cls_inst = cls(peer=peer)
cls_inst._load()
elem_list = cls_inst.xml_elem.xpath('//resource[(@id="{ra}" or @resource_agent="{ra}") and @active="true"]'.format(ra=ra))
# Stopped or not exist
if not elem_list:
return False
# Starting will return False
return all([True if elem.get('role') == 'Started' else False for elem in elem_list])
xpath = f'//resource[(@id="{ra}" or @resource_agent="{ra}") and @active="true" and @role="Started"]'
return bool(self.xml_elem.xpath(xpath))

@classmethod
def get_resource_id_list_via_type(cls, ra_type, peer=None):
def get_resource_id_list_via_type(self, ra_type):
"""
Given configured ra type, get the ra id list
"""
id_list = []
cls_inst = cls(peer=peer)
cls_inst._load()
elem_list = cls_inst.xml_elem.xpath('//resource[@resource_agent="{ra_type}"]'.format(ra_type=ra_type))
if not elem_list:
return id_list
return [elem.get('id') for elem in elem_list]
xpath = f'//resource[@resource_agent="{ra_type}"]'
return [elem.get('id') for elem in self.xml_elem.xpath(xpath)]
# vim:ts=4:sw=4:et:
7 changes: 3 additions & 4 deletions test/unittests/test_ocfs2.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,16 +445,15 @@ def test_join_ocfs2_return(self, mock_find):
@mock.patch('crmsh.utils.compare_uuid_with_peer_dev')
@mock.patch('crmsh.utils.is_dev_a_plain_raw_disk_or_partition')
@mock.patch('crmsh.ocfs2.OCFS2Manager._verify_packages')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_resource_configured')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
@mock.patch('crmsh.log.LoggerUtils.status_long')
@mock.patch('crmsh.ocfs2.OCFS2Manager._find_target_on_join')
def test_join_ocfs2(self, mock_find, mock_long, mock_configured, mock_verify_packages, mock_is_mapper, mock_compare):
def test_join_ocfs2(self, mock_find, mock_long, mock_parser, mock_verify_packages, mock_is_mapper, mock_compare):
mock_find.return_value = "/dev/sda2"
mock_configured.return_value = False
mock_parser("node1").is_resource_configured.return_value = False
mock_is_mapper.return_value = True
self.ocfs2_inst3.join_ocfs2("node1")
mock_find.assert_called_once_with("node1")
mock_configured.assert_called_once_with(constants.LVMLOCKD_RA, "node1")
mock_verify_packages.assert_called_once_with(False)
mock_is_mapper.assert_called_once_with("/dev/sda2", "node1")
mock_compare.assert_called_once_with(["/dev/sda2"], "node1")
Expand Down
14 changes: 6 additions & 8 deletions test/unittests/test_qdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,30 @@ def test_evaluate_qdevice_quorum_effect_reload(mock_get_dict, mock_quorate):
mock_quorate.assert_called_once_with(3, 2)


@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_any_resource_running')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
@mock.patch('crmsh.utils.calculate_quorate_status')
@mock.patch('crmsh.utils.get_quorum_votes_dict')
def test_evaluate_qdevice_quorum_effect_later(mock_get_dict, mock_quorate, mock_ra_running):
def test_evaluate_qdevice_quorum_effect_later(mock_get_dict, mock_quorate, mock_parser):
mock_get_dict.return_value = {'Expected': '2', 'Total': '2'}
mock_quorate.return_value = False
mock_ra_running.return_value = True
mock_parser().is_any_resource_running.return_value = True
res = qdevice.evaluate_qdevice_quorum_effect(qdevice.QDEVICE_REMOVE)
assert res == qdevice.QdevicePolicy.QDEVICE_RESTART_LATER
mock_get_dict.assert_called_once_with()
mock_quorate.assert_called_once_with(2, 1)
mock_ra_running.assert_called_once_with()


@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_any_resource_running')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
@mock.patch('crmsh.utils.calculate_quorate_status')
@mock.patch('crmsh.utils.get_quorum_votes_dict')
def test_evaluate_qdevice_quorum_effect(mock_get_dict, mock_quorate, mock_ra_running):
def test_evaluate_qdevice_quorum_effect(mock_get_dict, mock_quorate, mock_parser):
mock_get_dict.return_value = {'Expected': '2', 'Total': '2'}
mock_quorate.return_value = False
mock_ra_running.return_value = False
mock_parser().is_any_resource_running.return_value = False
res = qdevice.evaluate_qdevice_quorum_effect(qdevice.QDEVICE_REMOVE)
assert res == qdevice.QdevicePolicy.QDEVICE_RESTART
mock_get_dict.assert_called_once_with()
mock_quorate.assert_called_once_with(2, 1)
mock_ra_running.assert_called_once_with()


@mock.patch('crmsh.lock.RemoteLock')
Expand Down
26 changes: 12 additions & 14 deletions test/unittests/test_sbd.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,9 +618,9 @@ def test_sbd_init(self, mock_package, mock_watchdog, mock_get_device, mock_initi
@mock.patch('crmsh.bootstrap.wait_for_cluster')
@mock.patch('crmsh.utils.cluster_run_cmd')
@mock.patch('logging.Logger.info')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_any_resource_running')
def test_restart_cluster_on_needed_no_ra_running(self, mock_ra_running, mock_status, mock_cluster_run, mock_wait, mock_config_sbd_ra):
mock_ra_running.return_value = False
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
def test_restart_cluster_on_needed_no_ra_running(self, mock_parser, mock_status, mock_cluster_run, mock_wait, mock_config_sbd_ra):
mock_parser().is_any_resource_running.return_value = False
self.sbd_inst._restart_cluster_and_configure_sbd_ra()
mock_status.assert_called_once_with("Restarting cluster service")
mock_cluster_run.assert_called_once_with("crm cluster restart")
Expand All @@ -629,9 +629,9 @@ def test_restart_cluster_on_needed_no_ra_running(self, mock_ra_running, mock_sta

@mock.patch('crmsh.sbd.SBDTimeout.get_stonith_timeout')
@mock.patch('logging.Logger.warning')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_any_resource_running')
def test_restart_cluster_on_needed_diskless(self, mock_ra_running, mock_warn, mock_get_timeout):
mock_ra_running.return_value = True
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
def test_restart_cluster_on_needed_diskless(self, mock_parser, mock_warn, mock_get_timeout):
mock_parser().is_any_resource_running.return_value = True
mock_get_timeout.return_value = 60
self.sbd_inst_diskless.timeout_inst = mock.Mock(stonith_watchdog_timeout=-1)
self.sbd_inst_diskless._restart_cluster_and_configure_sbd_ra()
Expand All @@ -642,14 +642,13 @@ def test_restart_cluster_on_needed_diskless(self, mock_ra_running, mock_warn, mo

@mock.patch('crmsh.sbd.SBDManager.configure_sbd_resource_and_properties')
@mock.patch('logging.Logger.warning')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_any_resource_running')
def test_restart_cluster_on_needed(self, mock_ra_running, mock_warn, mock_config_sbd_ra):
mock_ra_running.return_value = True
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
def test_restart_cluster_on_needed(self, mock_parser, mock_warn, mock_config_sbd_ra):
mock_parser().is_any_resource_running.return_value = True
self.sbd_inst._restart_cluster_and_configure_sbd_ra()
mock_warn.assert_has_calls([
mock.call("To start sbd.service, need to restart cluster service manually on each node"),
])
mock_config_sbd_ra.assert_called_once_with()

@mock.patch('crmsh.bootstrap.invoke')
def test_enable_sbd_service_init(self, mock_invoke):
Expand Down Expand Up @@ -677,16 +676,16 @@ def test_configure_sbd_resource_and_properties_not_installed(self, mock_package)
@mock.patch('crmsh.sbd.SBDTimeout.adjust_sbd_timeout_related_cluster_configuration')
@mock.patch('crmsh.utils.set_property')
@mock.patch('crmsh.sh.ClusterShell.get_stdout_or_raise_error')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_resource_configured')
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
@mock.patch('crmsh.service_manager.ServiceManager.service_is_enabled')
@mock.patch('crmsh.utils.package_is_installed')
def test_configure_sbd_resource_and_properties(
self,
mock_package, mock_enabled, mock_configured, mock_run, mock_set_property, sbd_adjust, mock_is_active,
mock_package, mock_enabled, mock_parser, mock_run, mock_set_property, sbd_adjust, mock_is_active,
):
mock_package.return_value = True
mock_enabled.return_value = True
mock_configured.return_value = False
mock_parser().is_resource_configured.return_value = False
mock_is_active.return_value = False
self.sbd_inst._context = mock.Mock(cluster_is_running=True)
self.sbd_inst._get_sbd_device_from_config = mock.Mock()
Expand All @@ -696,7 +695,6 @@ def test_configure_sbd_resource_and_properties(

mock_package.assert_called_once_with("sbd")
mock_enabled.assert_called_once_with("sbd.service")
mock_configured.assert_called_once_with(sbd.SBDManager.SBD_RA)
mock_run.assert_called_once_with("crm configure primitive {} {}".format(sbd.SBDManager.SBD_RA_ID, sbd.SBDManager.SBD_RA))
mock_set_property.assert_called_once_with("stonith-enabled", "true")

Expand Down
8 changes: 4 additions & 4 deletions test/unittests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1327,11 +1327,11 @@ def test_set_dlm_option(mock_get_dict, mock_run):
mock_run.assert_called_once_with('dlm_tool set_config "key2=test"')


@mock.patch('crmsh.xmlutil.CrmMonXmlParser.is_resource_configured')
def test_is_dlm_configured(mock_configured):
mock_configured.return_value = True
@mock.patch('crmsh.xmlutil.CrmMonXmlParser')
def test_is_dlm_configured(mock_parser):
mock_parser().is_resource_configured.return_value = True
assert utils.is_dlm_configured() is True
mock_configured.assert_called_once_with(constants.DLM_CONTROLD_RA)
mock_parser().is_resource_configured.assert_called_once_with(constants.DLM_CONTROLD_RA)


@mock.patch('crmsh.sh.ClusterShell.get_stdout_or_raise_error')
Expand Down
Loading

0 comments on commit 38304a7

Please sign in to comment.