|
|
15f218 |
From e43d7324b9fc6933d8fa431e66c6236721724b98 Mon Sep 17 00:00:00 2001
|
|
|
15f218 |
From: Ivan Devat <idevat@redhat.com>
|
|
|
15f218 |
Date: Fri, 19 Aug 2016 02:57:38 +0200
|
|
|
15f218 |
Subject: [PATCH] squash bz1164402 Support for sbd configuration is
|
|
|
15f218 |
|
|
|
15f218 |
f2da8ad476c3 fix disable_service
|
|
|
15f218 |
|
|
|
15f218 |
9367e7162b7b fix code formating
|
|
|
15f218 |
|
|
|
15f218 |
57b618777d14 test: fix tests with parallel operations in SBD
|
|
|
15f218 |
|
|
|
15f218 |
f7b9fc15072c sbd: change error message
|
|
|
15f218 |
|
|
|
15f218 |
0dbdff4628d5 sbd: fix setting watchdog in config
|
|
|
15f218 |
|
|
|
15f218 |
1c5ccd3be588 sbd: ban changing sbd option SBD_PACEMAKER
|
|
|
15f218 |
|
|
|
15f218 |
733e28337589 sbd: add validation for SBD_WATCHDOG_TIMEOUT option
|
|
|
15f218 |
|
|
|
15f218 |
9951f3262ef1 docs: add watchdog option to node add command
|
|
|
15f218 |
|
|
|
15f218 |
d79592e05158 lib: fix disabling service on systemd systems
|
|
|
15f218 |
|
|
|
15f218 |
17e4c5838842 sbd: set auto_tie_breaker whenever it is needed for SBD to work
|
|
|
15f218 |
|
|
|
15f218 |
1ed4c2e3bc38 lib: fix enabled ATB in corosync.conf detection
|
|
|
15f218 |
---
|
|
|
15f218 |
pcs/cluster.py | 54 +++++-
|
|
|
15f218 |
pcs/common/report_codes.py | 4 +-
|
|
|
15f218 |
pcs/lib/commands/quorum.py | 41 ++++-
|
|
|
15f218 |
pcs/lib/commands/sbd.py | 30 ++-
|
|
|
15f218 |
pcs/lib/corosync/config_facade.py | 15 +-
|
|
|
15f218 |
pcs/lib/external.py | 4 +-
|
|
|
15f218 |
pcs/lib/reports.py | 27 +++
|
|
|
15f218 |
pcs/lib/sbd.py | 78 +++++++-
|
|
|
15f218 |
pcs/pcs.8 | 4 +-
|
|
|
15f218 |
pcs/quorum.py | 3 +-
|
|
|
15f218 |
pcs/stonith.py | 4 +-
|
|
|
15f218 |
pcs/test/test_lib_commands_quorum.py | 205 ++++++++++++++++++++-
|
|
|
15f218 |
pcs/test/test_lib_commands_sbd.py | 134 +++++++++++++-
|
|
|
15f218 |
pcs/test/test_lib_corosync_config_facade.py | 28 +++
|
|
|
15f218 |
pcs/test/test_lib_external.py | 22 ++-
|
|
|
15f218 |
pcs/test/test_lib_sbd.py | 272 +++++++++++++++++++++++++++-
|
|
|
15f218 |
pcs/usage.py | 3 +
|
|
|
15f218 |
pcs/utils.py | 25 ++-
|
|
|
15f218 |
pcsd/pcs.rb | 10 +-
|
|
|
15f218 |
19 files changed, 908 insertions(+), 55 deletions(-)
|
|
|
15f218 |
|
|
|
15f218 |
diff --git a/pcs/cluster.py b/pcs/cluster.py
|
|
|
15f218 |
index 90fec63..577e08e 100644
|
|
|
15f218 |
--- a/pcs/cluster.py
|
|
|
15f218 |
+++ b/pcs/cluster.py
|
|
|
15f218 |
@@ -43,6 +43,7 @@ from pcs.lib import (
|
|
|
15f218 |
reports as lib_reports,
|
|
|
15f218 |
)
|
|
|
15f218 |
from pcs.lib.booth import sync as booth_sync
|
|
|
15f218 |
+from pcs.lib.nodes_task import check_corosync_offline_on_nodes
|
|
|
15f218 |
from pcs.lib.commands.quorum import _add_device_model_net
|
|
|
15f218 |
from pcs.lib.corosync import (
|
|
|
15f218 |
config_parser as corosync_conf_utils,
|
|
|
15f218 |
@@ -1328,6 +1329,36 @@ def get_cib(argv):
|
|
|
15f218 |
except IOError as e:
|
|
|
15f218 |
utils.err("Unable to write to file '%s', %s" % (filename, e.strerror))
|
|
|
15f218 |
|
|
|
15f218 |
+
|
|
|
15f218 |
+def _ensure_cluster_is_offline_if_atb_should_be_enabled(
|
|
|
15f218 |
+ lib_env, node_num_modifier, skip_offline_nodes=False
|
|
|
15f218 |
+):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Check if cluster is offline if auto tie breaker should be enabled.
|
|
|
15f218 |
+ Raises LibraryError if ATB needs to be enabled cluster is not offline.
|
|
|
15f218 |
+
|
|
|
15f218 |
+ lib_env -- LibraryEnvironment
|
|
|
15f218 |
+ node_num_modifier -- number which wil be added to the number of nodes in
|
|
|
15f218 |
+ cluster when determining whenever ATB is needed.
|
|
|
15f218 |
+ skip_offline_nodes -- if True offline nodes will be skipped
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ corosync_conf = lib_env.get_corosync_conf()
|
|
|
15f218 |
+ if lib_sbd.atb_has_to_be_enabled(
|
|
|
15f218 |
+ lib_env.cmd_runner(), corosync_conf, node_num_modifier
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ print(
|
|
|
15f218 |
+ "Warning: auto_tie_breaker quorum option will be enabled to make "
|
|
|
15f218 |
+ "SBD fencing effecive after this change. Cluster has to be offline "
|
|
|
15f218 |
+ "to be able to make this change."
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ check_corosync_offline_on_nodes(
|
|
|
15f218 |
+ lib_env.node_communicator(),
|
|
|
15f218 |
+ lib_env.report_processor,
|
|
|
15f218 |
+ corosync_conf.get_nodes(),
|
|
|
15f218 |
+ skip_offline_nodes
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
def cluster_node(argv):
|
|
|
15f218 |
if len(argv) != 2:
|
|
|
15f218 |
usage.cluster()
|
|
|
15f218 |
@@ -1363,6 +1394,9 @@ def cluster_node(argv):
|
|
|
15f218 |
msg += ", use --force to override"
|
|
|
15f218 |
utils.err(msg)
|
|
|
15f218 |
|
|
|
15f218 |
+ lib_env = utils.get_lib_env()
|
|
|
15f218 |
+ modifiers = utils.get_modificators()
|
|
|
15f218 |
+
|
|
|
15f218 |
if add_node == True:
|
|
|
15f218 |
wait = False
|
|
|
15f218 |
wait_timeout = None
|
|
|
15f218 |
@@ -1385,11 +1419,9 @@ def cluster_node(argv):
|
|
|
15f218 |
if not canAdd:
|
|
|
15f218 |
utils.err("Unable to add '%s' to cluster: %s" % (node0, error))
|
|
|
15f218 |
|
|
|
15f218 |
- lib_env = utils.get_lib_env()
|
|
|
15f218 |
report_processor = lib_env.report_processor
|
|
|
15f218 |
node_communicator = lib_env.node_communicator()
|
|
|
15f218 |
node_addr = NodeAddresses(node0, node1)
|
|
|
15f218 |
- modifiers = utils.get_modificators()
|
|
|
15f218 |
try:
|
|
|
15f218 |
if lib_sbd.is_sbd_enabled(utils.cmd_runner()):
|
|
|
15f218 |
if "--watchdog" not in utils.pcs_options:
|
|
|
15f218 |
@@ -1400,6 +1432,10 @@ def cluster_node(argv):
|
|
|
15f218 |
else:
|
|
|
15f218 |
watchdog = utils.pcs_options["--watchdog"][0]
|
|
|
15f218 |
|
|
|
15f218 |
+ _ensure_cluster_is_offline_if_atb_should_be_enabled(
|
|
|
15f218 |
+ lib_env, 1, modifiers["skip_offline_nodes"]
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
report_processor.process(lib_reports.sbd_check_started())
|
|
|
15f218 |
lib_sbd.check_sbd_on_node(
|
|
|
15f218 |
report_processor, node_communicator, node_addr, watchdog
|
|
|
15f218 |
@@ -1407,12 +1443,15 @@ def cluster_node(argv):
|
|
|
15f218 |
sbd_cfg = environment_file_to_dict(
|
|
|
15f218 |
lib_sbd.get_local_sbd_config()
|
|
|
15f218 |
)
|
|
|
15f218 |
- sbd_cfg["SBD_WATCHDOG_DEV"] = watchdog
|
|
|
15f218 |
report_processor.process(
|
|
|
15f218 |
lib_reports.sbd_config_distribution_started()
|
|
|
15f218 |
)
|
|
|
15f218 |
lib_sbd.set_sbd_config_on_node(
|
|
|
15f218 |
- report_processor, node_communicator, node_addr, sbd_cfg
|
|
|
15f218 |
+ report_processor,
|
|
|
15f218 |
+ node_communicator,
|
|
|
15f218 |
+ node_addr,
|
|
|
15f218 |
+ sbd_cfg,
|
|
|
15f218 |
+ watchdog
|
|
|
15f218 |
)
|
|
|
15f218 |
report_processor.process(lib_reports.sbd_enabling_started())
|
|
|
15f218 |
lib_sbd.enable_sbd_service_on_node(
|
|
|
15f218 |
@@ -1549,6 +1588,13 @@ def cluster_node(argv):
|
|
|
15f218 |
)
|
|
|
15f218 |
# else the node seems to be stopped already, we're ok to proceed
|
|
|
15f218 |
|
|
|
15f218 |
+ try:
|
|
|
15f218 |
+ _ensure_cluster_is_offline_if_atb_should_be_enabled(
|
|
|
15f218 |
+ lib_env, -1, modifiers["skip_offline_nodes"]
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ except LibraryError as e:
|
|
|
15f218 |
+ utils.process_library_reports(e.args)
|
|
|
15f218 |
+
|
|
|
15f218 |
nodesRemoved = False
|
|
|
15f218 |
c_nodes = utils.getNodesFromCorosyncConf()
|
|
|
15f218 |
destroy_cluster([node0], keep_going=("--force" in utils.pcs_options))
|
|
|
15f218 |
diff --git a/pcs/common/report_codes.py b/pcs/common/report_codes.py
|
|
|
15f218 |
index e71d418..672c2e3 100644
|
|
|
15f218 |
--- a/pcs/common/report_codes.py
|
|
|
15f218 |
+++ b/pcs/common/report_codes.py
|
|
|
15f218 |
@@ -10,9 +10,9 @@ FORCE_ACTIVE_RRP = "ACTIVE_RRP"
|
|
|
15f218 |
FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE = "FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE"
|
|
|
15f218 |
FORCE_BOOTH_REMOVE_FROM_CIB = "FORCE_BOOTH_REMOVE_FROM_CIB"
|
|
|
15f218 |
FORCE_BOOTH_DESTROY = "FORCE_BOOTH_DESTROY"
|
|
|
15f218 |
-FORCE_FILE_OVERWRITE = "FORCE_FILE_OVERWRITE"
|
|
|
15f218 |
FORCE_CONSTRAINT_DUPLICATE = "CONSTRAINT_DUPLICATE"
|
|
|
15f218 |
FORCE_CONSTRAINT_MULTIINSTANCE_RESOURCE = "CONSTRAINT_MULTIINSTANCE_RESOURCE"
|
|
|
15f218 |
+FORCE_FILE_OVERWRITE = "FORCE_FILE_OVERWRITE"
|
|
|
15f218 |
FORCE_LOAD_THRESHOLD = "LOAD_THRESHOLD"
|
|
|
15f218 |
FORCE_OPTIONS = "OPTIONS"
|
|
|
15f218 |
FORCE_QDEVICE_MODEL = "QDEVICE_MODEL"
|
|
|
15f218 |
@@ -81,6 +81,7 @@ COROSYNC_NOT_RUNNING_CHECK_STARTED = "COROSYNC_NOT_RUNNING_CHECK_STARTED"
|
|
|
15f218 |
COROSYNC_NOT_RUNNING_CHECK_NODE_ERROR = "COROSYNC_NOT_RUNNING_CHECK_NODE_ERROR"
|
|
|
15f218 |
COROSYNC_NOT_RUNNING_ON_NODE = "COROSYNC_NOT_RUNNING_ON_NODE"
|
|
|
15f218 |
COROSYNC_OPTIONS_INCOMPATIBLE_WITH_QDEVICE = "COROSYNC_OPTIONS_INCOMPATIBLE_WITH_QDEVICE"
|
|
|
15f218 |
+COROSYNC_QUORUM_CANNOT_DISABLE_ATB_DUE_TO_SBD = "COROSYNC_QUORUM_CANNOT_DISABLE_ATB_DUE_TO_SBD"
|
|
|
15f218 |
COROSYNC_QUORUM_GET_STATUS_ERROR = "COROSYNC_QUORUM_GET_STATUS_ERROR"
|
|
|
15f218 |
COROSYNC_QUORUM_SET_EXPECTED_VOTES_ERROR = "COROSYNC_QUORUM_SET_EXPECTED_VOTES_ERROR"
|
|
|
15f218 |
COROSYNC_RUNNING_ON_NODE = "COROSYNC_RUNNING_ON_NODE"
|
|
|
15f218 |
@@ -179,5 +180,6 @@ UNABLE_TO_GET_SBD_CONFIG = "UNABLE_TO_GET_SBD_CONFIG"
|
|
|
15f218 |
UNABLE_TO_GET_SBD_STATUS = "UNABLE_TO_GET_SBD_STATUS"
|
|
|
15f218 |
UNKNOWN_COMMAND = 'UNKNOWN_COMMAND'
|
|
|
15f218 |
UNSUPPORTED_AGENT = 'UNSUPPORTED_AGENT'
|
|
|
15f218 |
+WATCHDOG_INVALID = "WATCHDOG_INVALID"
|
|
|
15f218 |
UNSUPPORTED_OPERATION_ON_NON_SYSTEMD_SYSTEMS = "UNSUPPORTED_OPERATION_ON_NON_SYSTEMD_SYSTEMS"
|
|
|
15f218 |
WATCHDOG_NOT_FOUND = "WATCHDOG_NOT_FOUND"
|
|
|
15f218 |
diff --git a/pcs/lib/commands/quorum.py b/pcs/lib/commands/quorum.py
|
|
|
15f218 |
index 7425e78..7fb7bb4 100644
|
|
|
15f218 |
--- a/pcs/lib/commands/quorum.py
|
|
|
15f218 |
+++ b/pcs/lib/commands/quorum.py
|
|
|
15f218 |
@@ -5,8 +5,9 @@ from __future__ import (
|
|
|
15f218 |
unicode_literals,
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
-from pcs.lib import reports
|
|
|
15f218 |
-from pcs.lib.errors import LibraryError
|
|
|
15f218 |
+from pcs.common import report_codes
|
|
|
15f218 |
+from pcs.lib import reports, sbd
|
|
|
15f218 |
+from pcs.lib.errors import LibraryError, ReportItemSeverity
|
|
|
15f218 |
from pcs.lib.corosync import (
|
|
|
15f218 |
live as corosync_live,
|
|
|
15f218 |
qdevice_net,
|
|
|
15f218 |
@@ -39,16 +40,50 @@ def get_config(lib_env):
|
|
|
15f218 |
"device": device,
|
|
|
15f218 |
}
|
|
|
15f218 |
|
|
|
15f218 |
-def set_options(lib_env, options, skip_offline_nodes=False):
|
|
|
15f218 |
+
|
|
|
15f218 |
+def _check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ runner, report_processor, corosync_conf, was_enabled, force=False
|
|
|
15f218 |
+):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Check whenever auto_tie_breaker can be changed without affecting SBD.
|
|
|
15f218 |
+ Raises LibraryError if change of ATB will affect SBD functionality.
|
|
|
15f218 |
+
|
|
|
15f218 |
+ runner -- CommandRunner
|
|
|
15f218 |
+ report_processor -- report processor
|
|
|
15f218 |
+ corosync_conf -- corosync conf facade
|
|
|
15f218 |
+ was_enabled -- True if ATB was enabled, False otherwise
|
|
|
15f218 |
+ force -- force change
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ if (
|
|
|
15f218 |
+ was_enabled
|
|
|
15f218 |
+ and
|
|
|
15f218 |
+ not corosync_conf.is_enabled_auto_tie_breaker()
|
|
|
15f218 |
+ and
|
|
|
15f218 |
+ sbd.is_auto_tie_breaker_needed(runner, corosync_conf)
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ report_processor.process(reports.quorum_cannot_disable_atb_due_to_sbd(
|
|
|
15f218 |
+ ReportItemSeverity.WARNING if force else ReportItemSeverity.ERROR,
|
|
|
15f218 |
+ None if force else report_codes.FORCE_OPTIONS
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+def set_options(lib_env, options, skip_offline_nodes=False, force=False):
|
|
|
15f218 |
"""
|
|
|
15f218 |
Set corosync quorum options, distribute and reload corosync.conf if live
|
|
|
15f218 |
lib_env LibraryEnvironment
|
|
|
15f218 |
options quorum options (dict)
|
|
|
15f218 |
skip_offline_nodes continue even if not all nodes are accessible
|
|
|
15f218 |
+ bool force force changes
|
|
|
15f218 |
"""
|
|
|
15f218 |
__ensure_not_cman(lib_env)
|
|
|
15f218 |
cfg = lib_env.get_corosync_conf()
|
|
|
15f218 |
+ atb_enabled = cfg.is_enabled_auto_tie_breaker()
|
|
|
15f218 |
cfg.set_quorum_options(lib_env.report_processor, options)
|
|
|
15f218 |
+ if lib_env.is_corosync_conf_live:
|
|
|
15f218 |
+ _check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ lib_env.cmd_runner(), lib_env.report_processor,
|
|
|
15f218 |
+ cfg, atb_enabled, force
|
|
|
15f218 |
+ )
|
|
|
15f218 |
lib_env.push_corosync_conf(cfg, skip_offline_nodes)
|
|
|
15f218 |
|
|
|
15f218 |
def status_text(lib_env):
|
|
|
15f218 |
diff --git a/pcs/lib/commands/sbd.py b/pcs/lib/commands/sbd.py
|
|
|
15f218 |
index 875758f..265ebb5 100644
|
|
|
15f218 |
--- a/pcs/lib/commands/sbd.py
|
|
|
15f218 |
+++ b/pcs/lib/commands/sbd.py
|
|
|
15f218 |
@@ -5,6 +5,7 @@ from __future__ import (
|
|
|
15f218 |
unicode_literals,
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
+import os
|
|
|
15f218 |
import json
|
|
|
15f218 |
|
|
|
15f218 |
from pcs import settings
|
|
|
15f218 |
@@ -44,7 +45,9 @@ def _validate_sbd_options(sbd_config, allow_unknown_opts=False):
|
|
|
15f218 |
"""
|
|
|
15f218 |
|
|
|
15f218 |
report_item_list = []
|
|
|
15f218 |
- unsupported_sbd_option_list = ["SBD_WATCHDOG_DEV", "SBD_OPTS"]
|
|
|
15f218 |
+ unsupported_sbd_option_list = [
|
|
|
15f218 |
+ "SBD_WATCHDOG_DEV", "SBD_OPTS", "SBD_PACEMAKER"
|
|
|
15f218 |
+ ]
|
|
|
15f218 |
allowed_sbd_options = [
|
|
|
15f218 |
"SBD_DELAY_START", "SBD_STARTMODE", "SBD_WATCHDOG_TIMEOUT"
|
|
|
15f218 |
]
|
|
|
15f218 |
@@ -62,6 +65,17 @@ def _validate_sbd_options(sbd_config, allow_unknown_opts=False):
|
|
|
15f218 |
Severities.WARNING if allow_unknown_opts else Severities.ERROR,
|
|
|
15f218 |
None if allow_unknown_opts else report_codes.FORCE_OPTIONS
|
|
|
15f218 |
))
|
|
|
15f218 |
+ if "SBD_WATCHDOG_TIMEOUT" in sbd_config:
|
|
|
15f218 |
+ report_item = reports.invalid_option_value(
|
|
|
15f218 |
+ "SBD_WATCHDOG_TIMEOUT",
|
|
|
15f218 |
+ sbd_config["SBD_WATCHDOG_TIMEOUT"],
|
|
|
15f218 |
+ "nonnegative integer"
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ try:
|
|
|
15f218 |
+ if int(sbd_config["SBD_WATCHDOG_TIMEOUT"]) < 0:
|
|
|
15f218 |
+ report_item_list.append(report_item)
|
|
|
15f218 |
+ except (ValueError, TypeError):
|
|
|
15f218 |
+ report_item_list.append(report_item)
|
|
|
15f218 |
|
|
|
15f218 |
return report_item_list
|
|
|
15f218 |
|
|
|
15f218 |
@@ -81,6 +95,9 @@ def _get_full_watchdog_list(node_list, default_watchdog, watchdog_dict):
|
|
|
15f218 |
report_item_list = []
|
|
|
15f218 |
|
|
|
15f218 |
for node_name, watchdog in watchdog_dict.items():
|
|
|
15f218 |
+ if not watchdog or not os.path.isabs(watchdog):
|
|
|
15f218 |
+ report_item_list.append(reports.invalid_watchdog_path(watchdog))
|
|
|
15f218 |
+ continue
|
|
|
15f218 |
try:
|
|
|
15f218 |
full_dict[node_list.find_by_label(node_name)] = watchdog
|
|
|
15f218 |
except NodeNotFound:
|
|
|
15f218 |
@@ -140,6 +157,14 @@ def enable_sbd(
|
|
|
15f218 |
full_watchdog_dict
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
+ # enable ATB if needed
|
|
|
15f218 |
+ corosync_conf = lib_env.get_corosync_conf()
|
|
|
15f218 |
+ if sbd.atb_has_to_be_enabled(lib_env.cmd_runner(), corosync_conf):
|
|
|
15f218 |
+ corosync_conf.set_quorum_options(
|
|
|
15f218 |
+ lib_env.report_processor, {"auto_tie_breaker": "1"}
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ lib_env.push_corosync_conf(corosync_conf, ignore_offline_nodes)
|
|
|
15f218 |
+
|
|
|
15f218 |
# distribute SBD configuration
|
|
|
15f218 |
config = sbd.get_default_sbd_config()
|
|
|
15f218 |
config.update(sbd_options)
|
|
|
15f218 |
@@ -147,7 +172,8 @@ def enable_sbd(
|
|
|
15f218 |
lib_env.report_processor,
|
|
|
15f218 |
lib_env.node_communicator(),
|
|
|
15f218 |
online_nodes,
|
|
|
15f218 |
- config
|
|
|
15f218 |
+ config,
|
|
|
15f218 |
+ full_watchdog_dict
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
# remove cluster prop 'stonith_watchdog_timeout'
|
|
|
15f218 |
diff --git a/pcs/lib/corosync/config_facade.py b/pcs/lib/corosync/config_facade.py
|
|
|
15f218 |
index 600a89b..be621c0 100644
|
|
|
15f218 |
--- a/pcs/lib/corosync/config_facade.py
|
|
|
15f218 |
+++ b/pcs/lib/corosync/config_facade.py
|
|
|
15f218 |
@@ -129,6 +129,16 @@ class ConfigFacade(object):
|
|
|
15f218 |
options[name] = value
|
|
|
15f218 |
return options
|
|
|
15f218 |
|
|
|
15f218 |
+ def is_enabled_auto_tie_breaker(self):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Returns True if auto tie braker option is enabled, False otherwise.
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ auto_tie_breaker = "0"
|
|
|
15f218 |
+ for quorum in self.config.get_sections("quorum"):
|
|
|
15f218 |
+ for attr in quorum.get_attributes("auto_tie_breaker"):
|
|
|
15f218 |
+ auto_tie_breaker = attr[1]
|
|
|
15f218 |
+ return auto_tie_breaker == "1"
|
|
|
15f218 |
+
|
|
|
15f218 |
def __validate_quorum_options(self, options):
|
|
|
15f218 |
report_items = []
|
|
|
15f218 |
has_qdevice = self.has_quorum_device()
|
|
|
15f218 |
@@ -488,10 +498,7 @@ class ConfigFacade(object):
|
|
|
15f218 |
# get relevant status
|
|
|
15f218 |
has_quorum_device = self.has_quorum_device()
|
|
|
15f218 |
has_two_nodes = len(self.get_nodes()) == 2
|
|
|
15f218 |
- auto_tie_breaker = False
|
|
|
15f218 |
- for quorum in self.config.get_sections("quorum"):
|
|
|
15f218 |
- for attr in quorum.get_attributes("auto_tie_breaker"):
|
|
|
15f218 |
- auto_tie_breaker = attr[1] != "0"
|
|
|
15f218 |
+ auto_tie_breaker = self.is_enabled_auto_tie_breaker()
|
|
|
15f218 |
# update two_node
|
|
|
15f218 |
if has_two_nodes and not auto_tie_breaker and not has_quorum_device:
|
|
|
15f218 |
quorum_section_list = self.__ensure_section(self.config, "quorum")
|
|
|
15f218 |
diff --git a/pcs/lib/external.py b/pcs/lib/external.py
|
|
|
15f218 |
index 25e071f..08bf2bb 100644
|
|
|
15f218 |
--- a/pcs/lib/external.py
|
|
|
15f218 |
+++ b/pcs/lib/external.py
|
|
|
15f218 |
@@ -135,13 +135,13 @@ def disable_service(runner, service, instance=None):
|
|
|
15f218 |
instance -- instance name, it ha no effect on not systemd systems.
|
|
|
15f218 |
If None no instance name will be used.
|
|
|
15f218 |
"""
|
|
|
15f218 |
+ if not is_service_installed(runner, service):
|
|
|
15f218 |
+ return
|
|
|
15f218 |
if is_systemctl():
|
|
|
15f218 |
output, retval = runner.run([
|
|
|
15f218 |
"systemctl", "disable", _get_service_name(service, instance)
|
|
|
15f218 |
])
|
|
|
15f218 |
else:
|
|
|
15f218 |
- if not is_service_installed(runner, service):
|
|
|
15f218 |
- return
|
|
|
15f218 |
output, retval = runner.run(["chkconfig", service, "off"])
|
|
|
15f218 |
if retval != 0:
|
|
|
15f218 |
raise DisableServiceError(service, output.rstrip(), instance)
|
|
|
15f218 |
diff --git a/pcs/lib/reports.py b/pcs/lib/reports.py
|
|
|
15f218 |
index eac95c7..568bb7e 100644
|
|
|
15f218 |
--- a/pcs/lib/reports.py
|
|
|
15f218 |
+++ b/pcs/lib/reports.py
|
|
|
15f218 |
@@ -1701,6 +1701,19 @@ def watchdog_not_found(node, watchdog):
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
+def invalid_watchdog_path(watchdog):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ watchdog path is not absolut path
|
|
|
15f218 |
+
|
|
|
15f218 |
+ watchdog -- watchdog device path
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ return ReportItem.error(
|
|
|
15f218 |
+ report_codes.WATCHDOG_INVALID,
|
|
|
15f218 |
+ "Watchdog path '{watchdog}' is invalid.",
|
|
|
15f218 |
+ info={"watchdog": watchdog}
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
def unable_to_get_sbd_status(node, reason):
|
|
|
15f218 |
"""
|
|
|
15f218 |
there was (communication or parsing) failure during obtaining status of SBD
|
|
|
15f218 |
@@ -1901,3 +1914,17 @@ def live_environment_required(forbidden_options):
|
|
|
15f218 |
"options_string": ", ".join(forbidden_options),
|
|
|
15f218 |
}
|
|
|
15f218 |
)
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+def quorum_cannot_disable_atb_due_to_sbd(
|
|
|
15f218 |
+ severity=ReportItemSeverity.ERROR, forceable=None
|
|
|
15f218 |
+):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Quorum option auto_tie_breaker cannot be disbled due to SBD.
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ return ReportItem(
|
|
|
15f218 |
+ report_codes.COROSYNC_QUORUM_CANNOT_DISABLE_ATB_DUE_TO_SBD,
|
|
|
15f218 |
+ severity,
|
|
|
15f218 |
+ "unable to disable auto_tie_breaker: SBD fencing will have no effect",
|
|
|
15f218 |
+ forceable=forceable
|
|
|
15f218 |
+ )
|
|
|
15f218 |
diff --git a/pcs/lib/sbd.py b/pcs/lib/sbd.py
|
|
|
15f218 |
index 4488a73..c9f013b 100644
|
|
|
15f218 |
--- a/pcs/lib/sbd.py
|
|
|
15f218 |
+++ b/pcs/lib/sbd.py
|
|
|
15f218 |
@@ -46,6 +46,50 @@ def _run_parallel_and_raise_lib_error_on_failure(func, param_list):
|
|
|
15f218 |
raise LibraryError(*report_list)
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
+def is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ runner, corosync_conf_facade, node_number_modifier=0
|
|
|
15f218 |
+):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Returns True whenever quorum option auto tie breaker is needed to be enabled
|
|
|
15f218 |
+ for proper working of SBD fencing. False if it is not needed.
|
|
|
15f218 |
+
|
|
|
15f218 |
+ runner -- command runner
|
|
|
15f218 |
+ corosync_conf_facade --
|
|
|
15f218 |
+ node_number_modifier -- this value vill be added to current number of nodes.
|
|
|
15f218 |
+ This can be useful to test whenever is ATB needed when adding/removeing
|
|
|
15f218 |
+ node.
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ return (
|
|
|
15f218 |
+ not corosync_conf_facade.has_quorum_device()
|
|
|
15f218 |
+ and
|
|
|
15f218 |
+ (len(corosync_conf_facade.get_nodes()) + node_number_modifier) % 2 == 0
|
|
|
15f218 |
+ and
|
|
|
15f218 |
+ is_sbd_installed(runner)
|
|
|
15f218 |
+ and
|
|
|
15f218 |
+ is_sbd_enabled(runner)
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+def atb_has_to_be_enabled(runner, corosync_conf_facade, node_number_modifier=0):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Return True whenever quorum option auto tie breaker has to be enabled for
|
|
|
15f218 |
+ proper working of SBD fencing. False if it's not needed or it is already
|
|
|
15f218 |
+ enabled.
|
|
|
15f218 |
+
|
|
|
15f218 |
+ runner -- command runner
|
|
|
15f218 |
+ corosync_conf_facade --
|
|
|
15f218 |
+ node_number_modifier -- this value vill be added to current number of nodes.
|
|
|
15f218 |
+ This can be useful to test whenever is ATB needed when adding/removeing
|
|
|
15f218 |
+ node.
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ return (
|
|
|
15f218 |
+ is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ runner, corosync_conf_facade, node_number_modifier
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ and
|
|
|
15f218 |
+ not corosync_conf_facade.is_enabled_auto_tie_breaker()
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
def check_sbd(communicator, node, watchdog):
|
|
|
15f218 |
"""
|
|
|
15f218 |
Check SBD on specified 'node' and existence of specified watchdog.
|
|
|
15f218 |
@@ -123,18 +167,23 @@ def set_sbd_config(communicator, node, config):
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
-def set_sbd_config_on_node(report_processor, node_communicator, node, config):
|
|
|
15f218 |
+def set_sbd_config_on_node(
|
|
|
15f218 |
+ report_processor, node_communicator, node, config, watchdog
|
|
|
15f218 |
+):
|
|
|
15f218 |
"""
|
|
|
15f218 |
- Send SBD configuration to 'node'. Also puts correct node name into
|
|
|
15f218 |
- SBD_OPTS option (SBD_OPTS="-n <node_name>").
|
|
|
15f218 |
+ Send SBD configuration to 'node' with specified watchdog set. Also puts
|
|
|
15f218 |
+ correct node name into SBD_OPTS option (SBD_OPTS="-n <node_name>").
|
|
|
15f218 |
|
|
|
15f218 |
report_processor --
|
|
|
15f218 |
node_communicator -- NodeCommunicator
|
|
|
15f218 |
node -- NodeAddresses
|
|
|
15f218 |
config -- dictionary in format: <SBD config option>: <value>
|
|
|
15f218 |
+ watchdog -- path to watchdog device
|
|
|
15f218 |
"""
|
|
|
15f218 |
config = dict(config)
|
|
|
15f218 |
config["SBD_OPTS"] = '"-n {node_name}"'.format(node_name=node.label)
|
|
|
15f218 |
+ if watchdog:
|
|
|
15f218 |
+ config["SBD_WATCHDOG_DEV"] = watchdog
|
|
|
15f218 |
set_sbd_config(node_communicator, node, dict_to_environment_file(config))
|
|
|
15f218 |
report_processor.process(
|
|
|
15f218 |
reports.sbd_config_accepted_by_node(node.label)
|
|
|
15f218 |
@@ -142,7 +191,7 @@ def set_sbd_config_on_node(report_processor, node_communicator, node, config):
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
def set_sbd_config_on_all_nodes(
|
|
|
15f218 |
- report_processor, node_communicator, node_list, config
|
|
|
15f218 |
+ report_processor, node_communicator, node_list, config, watchdog_dict
|
|
|
15f218 |
):
|
|
|
15f218 |
"""
|
|
|
15f218 |
Send SBD configuration 'config' to all nodes in 'node_list'. Option
|
|
|
15f218 |
@@ -153,12 +202,20 @@ def set_sbd_config_on_all_nodes(
|
|
|
15f218 |
node_communicator -- NodeCommunicator
|
|
|
15f218 |
node_list -- NodeAddressesList
|
|
|
15f218 |
config -- dictionary in format: <SBD config option>: <value>
|
|
|
15f218 |
+ watchdog_dict -- dictionary of watchdogs where key is NodeAdresses object
|
|
|
15f218 |
+ and value is path to watchdog
|
|
|
15f218 |
"""
|
|
|
15f218 |
report_processor.process(reports.sbd_config_distribution_started())
|
|
|
15f218 |
_run_parallel_and_raise_lib_error_on_failure(
|
|
|
15f218 |
set_sbd_config_on_node,
|
|
|
15f218 |
[
|
|
|
15f218 |
- ([report_processor, node_communicator, node, config], {})
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ [
|
|
|
15f218 |
+ report_processor, node_communicator, node, config,
|
|
|
15f218 |
+ watchdog_dict.get(node)
|
|
|
15f218 |
+ ],
|
|
|
15f218 |
+ {}
|
|
|
15f218 |
+ )
|
|
|
15f218 |
for node in node_list
|
|
|
15f218 |
]
|
|
|
15f218 |
)
|
|
|
15f218 |
@@ -362,3 +419,14 @@ def is_sbd_enabled(runner):
|
|
|
15f218 |
runner -- CommandRunner
|
|
|
15f218 |
"""
|
|
|
15f218 |
return external.is_service_enabled(runner, "sbd")
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+def is_sbd_installed(runner):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Check if SBD service is installed in local system.
|
|
|
15f218 |
+ Reurns True id SBD service is installed. False otherwise.
|
|
|
15f218 |
+
|
|
|
15f218 |
+ runner -- CommandRunner
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ return external.is_service_installed(runner, "sbd")
|
|
|
15f218 |
+
|
|
|
15f218 |
diff --git a/pcs/pcs.8 b/pcs/pcs.8
|
|
|
15f218 |
index 9064054..7a054ca 100644
|
|
|
15f218 |
--- a/pcs/pcs.8
|
|
|
15f218 |
+++ b/pcs/pcs.8
|
|
|
15f218 |
@@ -271,8 +271,8 @@ Upgrade the CIB to conform to the latest version of the document schema.
|
|
|
15f218 |
edit [scope=<scope> | \fB\-\-config\fR]
|
|
|
15f218 |
Edit the cib in the editor specified by the $EDITOR environment variable and push out any changes upon saving. Specify scope to edit a specific section of the CIB. Valid values of the scope are: configuration, nodes, resources, constraints, crm_config, rsc_defaults, op_defaults. \fB\-\-config\fR is the same as scope=configuration. Use of \fB\-\-config\fR is recommended. Do not specify a scope if you need to edit the whole CIB or be warned in the case of outdated CIB.
|
|
|
15f218 |
.TP
|
|
|
15f218 |
-node add <node[,node\-altaddr]> [\fB\-\-start\fR [\fB\-\-wait\fR[=<n>]]] [\fB\-\-enable\fR]
|
|
|
15f218 |
-Add the node to corosync.conf and corosync on all nodes in the cluster and sync the new corosync.conf to the new node. If \fB\-\-start\fR is specified also start corosync/pacemaker on the new node, if \fB\-\-wait\fR is sepcified wait up to 'n' seconds for the new node to start. If \fB\-\-enable\fR is specified enable corosync/pacemaker on new node. When using Redundant Ring Protocol (RRP) with udpu transport, specify the ring 0 address first followed by a ',' and then the ring 1 address.
|
|
|
15f218 |
+node add <node[,node\-altaddr]> [\fB\-\-start\fR [\fB\-\-wait\fR[=<n>]]] [\fB\-\-enable\fR] [\fB\-\-watchdog\fR=<watchdog\-path>]
|
|
|
15f218 |
+Add the node to corosync.conf and corosync on all nodes in the cluster and sync the new corosync.conf to the new node. If \fB\-\-start\fR is specified also start corosync/pacemaker on the new node, if \fB\-\-wait\fR is sepcified wait up to 'n' seconds for the new node to start. If \fB\-\-enable\fR is specified enable corosync/pacemaker on new node. When using Redundant Ring Protocol (RRP) with udpu transport, specify the ring 0 address first followed by a ',' and then the ring 1 address. Use \fB\-\-watchdog\fR to specify path to watchdog on newly added node, when SBD is enabled in cluster.
|
|
|
15f218 |
.TP
|
|
|
15f218 |
node remove <node>
|
|
|
15f218 |
Shutdown specified node and remove it from pacemaker and corosync on all other nodes in the cluster.
|
|
|
15f218 |
diff --git a/pcs/quorum.py b/pcs/quorum.py
|
|
|
15f218 |
index 1c2d41d..6cd06ca 100644
|
|
|
15f218 |
--- a/pcs/quorum.py
|
|
|
15f218 |
+++ b/pcs/quorum.py
|
|
|
15f218 |
@@ -121,7 +121,8 @@ def quorum_update_cmd(lib, argv, modificators):
|
|
|
15f218 |
|
|
|
15f218 |
lib.quorum.set_options(
|
|
|
15f218 |
options,
|
|
|
15f218 |
- skip_offline_nodes=modificators["skip_offline_nodes"]
|
|
|
15f218 |
+ skip_offline_nodes=modificators["skip_offline_nodes"],
|
|
|
15f218 |
+ force=modificators["force"]
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
def quorum_device_add_cmd(lib, argv, modificators):
|
|
|
15f218 |
diff --git a/pcs/stonith.py b/pcs/stonith.py
|
|
|
15f218 |
index 93332ef..23f3800 100644
|
|
|
15f218 |
--- a/pcs/stonith.py
|
|
|
15f218 |
+++ b/pcs/stonith.py
|
|
|
15f218 |
@@ -495,7 +495,7 @@ def _sbd_parse_watchdogs(watchdog_list):
|
|
|
15f218 |
for watchdog_node in watchdog_list:
|
|
|
15f218 |
if "@" not in watchdog_node:
|
|
|
15f218 |
if default_watchdog:
|
|
|
15f218 |
- raise CmdLineInputError("Multiple default watchdogs.")
|
|
|
15f218 |
+ raise CmdLineInputError("Multiple watchdog definitions.")
|
|
|
15f218 |
default_watchdog = watchdog_node
|
|
|
15f218 |
else:
|
|
|
15f218 |
watchdog, node_name = watchdog_node.rsplit("@", 1)
|
|
|
15f218 |
@@ -553,7 +553,7 @@ def sbd_config(lib, argv, modifiers):
|
|
|
15f218 |
|
|
|
15f218 |
config = config_list[0]["config"]
|
|
|
15f218 |
|
|
|
15f218 |
- filtered_options = ["SBD_WATCHDOG_DEV", "SBD_OPTS"]
|
|
|
15f218 |
+ filtered_options = ["SBD_WATCHDOG_DEV", "SBD_OPTS", "SBD_PACEMAKER"]
|
|
|
15f218 |
for key, val in config.items():
|
|
|
15f218 |
if key in filtered_options:
|
|
|
15f218 |
continue
|
|
|
15f218 |
diff --git a/pcs/test/test_lib_commands_quorum.py b/pcs/test/test_lib_commands_quorum.py
|
|
|
15f218 |
index 826251a..d286a8f 100644
|
|
|
15f218 |
--- a/pcs/test/test_lib_commands_quorum.py
|
|
|
15f218 |
+++ b/pcs/test/test_lib_commands_quorum.py
|
|
|
15f218 |
@@ -25,6 +25,7 @@ from pcs.lib.errors import (
|
|
|
15f218 |
LibraryError,
|
|
|
15f218 |
ReportItemSeverity as severity,
|
|
|
15f218 |
)
|
|
|
15f218 |
+from pcs.lib.corosync.config_facade import ConfigFacade
|
|
|
15f218 |
from pcs.lib.external import NodeCommunicationException
|
|
|
15f218 |
from pcs.lib.node import NodeAddresses, NodeAddressesList
|
|
|
15f218 |
|
|
|
15f218 |
@@ -146,23 +147,201 @@ class GetQuorumConfigTest(TestCase, CmanMixin):
|
|
|
15f218 |
self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
+@mock.patch("pcs.lib.sbd.is_auto_tie_breaker_needed")
|
|
|
15f218 |
+class CheckIfAtbCanBeDisabledTest(TestCase):
|
|
|
15f218 |
+ def setUp(self):
|
|
|
15f218 |
+ self.mock_reporter = MockLibraryReportProcessor()
|
|
|
15f218 |
+ self.mock_runner = "cmd_runner"
|
|
|
15f218 |
+ self.mock_corosync_conf = mock.MagicMock(spec_set=ConfigFacade)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_disabled_atb_disabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, False
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_disabled_atb_enabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, False
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_enable_atb_disabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_enabled_atb_enabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_disabled_atb_disabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, False
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_disabled_atb_enabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, False
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_enable_atb_disabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ report_item = (
|
|
|
15f218 |
+ severity.ERROR,
|
|
|
15f218 |
+ report_codes.COROSYNC_QUORUM_CANNOT_DISABLE_ATB_DUE_TO_SBD,
|
|
|
15f218 |
+ {},
|
|
|
15f218 |
+ report_codes.FORCE_OPTIONS
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ assert_raise_library_error(
|
|
|
15f218 |
+ lambda: lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner,
|
|
|
15f218 |
+ self.mock_reporter,
|
|
|
15f218 |
+ self.mock_corosync_conf,
|
|
|
15f218 |
+ True
|
|
|
15f218 |
+ ),
|
|
|
15f218 |
+ report_item
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ assert_report_item_list_equal(
|
|
|
15f218 |
+ self.mock_reporter.report_item_list, [report_item]
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_enabled_atb_enabled(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_disabled_atb_disabled_force(
|
|
|
15f218 |
+ self, mock_atb_needed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf,
|
|
|
15f218 |
+ False, force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_disabled_atb_enabled_force(
|
|
|
15f218 |
+ self, mock_atb_needed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf,
|
|
|
15f218 |
+ False, force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_enable_atb_disabled_force(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, True,
|
|
|
15f218 |
+ force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_no_need_was_enabled_atb_enabled_force(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = False
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, True,
|
|
|
15f218 |
+ force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_disabled_atb_disabled_force(
|
|
|
15f218 |
+ self, mock_atb_needed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf,
|
|
|
15f218 |
+ False, force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_disabled_atb_enabled_force(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf,
|
|
|
15f218 |
+ False, force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_enable_atb_disabled_force(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, True,
|
|
|
15f218 |
+ force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ assert_report_item_list_equal(
|
|
|
15f218 |
+ self.mock_reporter.report_item_list,
|
|
|
15f218 |
+ [(
|
|
|
15f218 |
+ severity.WARNING,
|
|
|
15f218 |
+ report_codes.COROSYNC_QUORUM_CANNOT_DISABLE_ATB_DUE_TO_SBD,
|
|
|
15f218 |
+ {},
|
|
|
15f218 |
+ None
|
|
|
15f218 |
+ )]
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_was_enabled_atb_enabled_force(self, mock_atb_needed):
|
|
|
15f218 |
+ mock_atb_needed.return_value = True
|
|
|
15f218 |
+ self.mock_corosync_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ lib._check_if_atb_can_be_disabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_reporter, self.mock_corosync_conf, True,
|
|
|
15f218 |
+ force=True
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+@mock.patch("pcs.lib.commands.quorum._check_if_atb_can_be_disabled")
|
|
|
15f218 |
@mock.patch.object(LibraryEnvironment, "push_corosync_conf")
|
|
|
15f218 |
@mock.patch.object(LibraryEnvironment, "get_corosync_conf_data")
|
|
|
15f218 |
+@mock.patch.object(LibraryEnvironment, "cmd_runner")
|
|
|
15f218 |
class SetQuorumOptionsTest(TestCase, CmanMixin):
|
|
|
15f218 |
def setUp(self):
|
|
|
15f218 |
self.mock_logger = mock.MagicMock(logging.Logger)
|
|
|
15f218 |
self.mock_reporter = MockLibraryReportProcessor()
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.env.is_cman_cluster", lambda self: True)
|
|
|
15f218 |
- def test_disabled_on_cman(self, mock_get_corosync, mock_push_corosync):
|
|
|
15f218 |
+ def test_disabled_on_cman(
|
|
|
15f218 |
+ self, mock_runner, mock_get_corosync, mock_push_corosync, mock_check
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
lib_env = LibraryEnvironment(self.mock_logger, self.mock_reporter)
|
|
|
15f218 |
self.assert_disabled_on_cman(lambda: lib.set_options(lib_env, {}))
|
|
|
15f218 |
mock_get_corosync.assert_not_called()
|
|
|
15f218 |
mock_push_corosync.assert_not_called()
|
|
|
15f218 |
+ mock_check.assert_not_called()
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.env.is_cman_cluster", lambda self: True)
|
|
|
15f218 |
def test_enabled_on_cman_if_not_live(
|
|
|
15f218 |
- self, mock_get_corosync, mock_push_corosync
|
|
|
15f218 |
+ self, mock_runner, mock_get_corosync, mock_push_corosync, mock_check
|
|
|
15f218 |
):
|
|
|
15f218 |
original_conf = "invalid {\nconfig: stop after cman test"
|
|
|
15f218 |
mock_get_corosync.return_value = original_conf
|
|
|
15f218 |
@@ -182,11 +361,16 @@ class SetQuorumOptionsTest(TestCase, CmanMixin):
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
mock_push_corosync.assert_not_called()
|
|
|
15f218 |
+ mock_check.assert_not_called()
|
|
|
15f218 |
+ mock_runner.assert_not_called()
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.env.is_cman_cluster", lambda self: False)
|
|
|
15f218 |
- def test_success(self, mock_get_corosync, mock_push_corosync):
|
|
|
15f218 |
+ def test_success(
|
|
|
15f218 |
+ self, mock_runner, mock_get_corosync, mock_push_corosync, mock_check
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
original_conf = open(rc("corosync-3nodes.conf")).read()
|
|
|
15f218 |
mock_get_corosync.return_value = original_conf
|
|
|
15f218 |
+ mock_runner.return_value = "cmd_runner"
|
|
|
15f218 |
lib_env = LibraryEnvironment(self.mock_logger, self.mock_reporter)
|
|
|
15f218 |
|
|
|
15f218 |
new_options = {"wait_for_all": "1"}
|
|
|
15f218 |
@@ -201,9 +385,16 @@ class SetQuorumOptionsTest(TestCase, CmanMixin):
|
|
|
15f218 |
)
|
|
|
15f218 |
)
|
|
|
15f218 |
self.assertEqual([], self.mock_reporter.report_item_list)
|
|
|
15f218 |
+ self.assertEqual(1, mock_check.call_count)
|
|
|
15f218 |
+ self.assertEqual("cmd_runner", mock_check.call_args[0][0])
|
|
|
15f218 |
+ self.assertEqual(self.mock_reporter, mock_check.call_args[0][1])
|
|
|
15f218 |
+ self.assertFalse(mock_check.call_args[0][3])
|
|
|
15f218 |
+ self.assertFalse(mock_check.call_args[0][4])
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.env.is_cman_cluster", lambda self: False)
|
|
|
15f218 |
- def test_bad_options(self, mock_get_corosync, mock_push_corosync):
|
|
|
15f218 |
+ def test_bad_options(
|
|
|
15f218 |
+ self, mock_runner, mock_get_corosync, mock_push_corosync, mock_check
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
original_conf = open(rc("corosync.conf")).read()
|
|
|
15f218 |
mock_get_corosync.return_value = original_conf
|
|
|
15f218 |
lib_env = LibraryEnvironment(self.mock_logger, self.mock_reporter)
|
|
|
15f218 |
@@ -228,9 +419,12 @@ class SetQuorumOptionsTest(TestCase, CmanMixin):
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
mock_push_corosync.assert_not_called()
|
|
|
15f218 |
+ mock_check.assert_not_called()
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.env.is_cman_cluster", lambda self: False)
|
|
|
15f218 |
- def test_bad_config(self, mock_get_corosync, mock_push_corosync):
|
|
|
15f218 |
+ def test_bad_config(
|
|
|
15f218 |
+ self, mock_runner, mock_get_corosync, mock_push_corosync, mock_check
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
original_conf = "invalid {\nconfig: this is"
|
|
|
15f218 |
mock_get_corosync.return_value = original_conf
|
|
|
15f218 |
lib_env = LibraryEnvironment(self.mock_logger, self.mock_reporter)
|
|
|
15f218 |
@@ -246,6 +440,7 @@ class SetQuorumOptionsTest(TestCase, CmanMixin):
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
mock_push_corosync.assert_not_called()
|
|
|
15f218 |
+ mock_check.assert_not_called()
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.commands.quorum.corosync_live.get_quorum_status_text")
|
|
|
15f218 |
diff --git a/pcs/test/test_lib_commands_sbd.py b/pcs/test/test_lib_commands_sbd.py
|
|
|
15f218 |
index 9a96757..0663082 100644
|
|
|
15f218 |
--- a/pcs/test/test_lib_commands_sbd.py
|
|
|
15f218 |
+++ b/pcs/test/test_lib_commands_sbd.py
|
|
|
15f218 |
@@ -35,6 +35,15 @@ from pcs.lib.external import (
|
|
|
15f218 |
import pcs.lib.commands.sbd as cmd_sbd
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
+def _assert_equal_list_of_dictionaries_without_order(expected, actual):
|
|
|
15f218 |
+ for item in actual:
|
|
|
15f218 |
+ if item not in expected:
|
|
|
15f218 |
+ raise AssertionError("Given but not expected: {0}".format(item))
|
|
|
15f218 |
+ for item in expected:
|
|
|
15f218 |
+ if item not in actual:
|
|
|
15f218 |
+ raise AssertionError("Expected but not given: {0}".format(item))
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
class CommandSbdTest(TestCase):
|
|
|
15f218 |
def setUp(self):
|
|
|
15f218 |
self.mock_env = mock.MagicMock(spec_set=LibraryEnvironment)
|
|
|
15f218 |
@@ -234,7 +243,8 @@ class ValidateSbdOptionsTest(TestCase):
|
|
|
15f218 |
"SBD_STARTMODE": "clean",
|
|
|
15f218 |
"SBD_WATCHDOG_DEV": "/dev/watchdog",
|
|
|
15f218 |
"SBD_UNKNOWN": "",
|
|
|
15f218 |
- "SBD_OPTS": " "
|
|
|
15f218 |
+ "SBD_OPTS": " ",
|
|
|
15f218 |
+ "SBD_PACEMAKER": "false",
|
|
|
15f218 |
}
|
|
|
15f218 |
|
|
|
15f218 |
assert_report_item_list_equal(
|
|
|
15f218 |
@@ -272,6 +282,90 @@ class ValidateSbdOptionsTest(TestCase):
|
|
|
15f218 |
"allowed_str": self.allowed_sbd_options_str
|
|
|
15f218 |
},
|
|
|
15f218 |
None
|
|
|
15f218 |
+ ),
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ Severities.ERROR,
|
|
|
15f218 |
+ report_codes.INVALID_OPTION,
|
|
|
15f218 |
+ {
|
|
|
15f218 |
+ "option_name": "SBD_PACEMAKER",
|
|
|
15f218 |
+ "option_type": None,
|
|
|
15f218 |
+ "allowed": self.allowed_sbd_options,
|
|
|
15f218 |
+ "allowed_str": self.allowed_sbd_options_str
|
|
|
15f218 |
+ },
|
|
|
15f218 |
+ None
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ ]
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_watchdog_timeout_is_not_present(self):
|
|
|
15f218 |
+ config = {
|
|
|
15f218 |
+ "SBD_DELAY_START": "yes",
|
|
|
15f218 |
+ "SBD_STARTMODE": "clean"
|
|
|
15f218 |
+ }
|
|
|
15f218 |
+ self.assertEqual([], cmd_sbd._validate_sbd_options(config))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_watchdog_timeout_is_nonnegative_int(self):
|
|
|
15f218 |
+ config = {
|
|
|
15f218 |
+ "SBD_WATCHDOG_TIMEOUT": "-1",
|
|
|
15f218 |
+ }
|
|
|
15f218 |
+
|
|
|
15f218 |
+ assert_report_item_list_equal(
|
|
|
15f218 |
+ cmd_sbd._validate_sbd_options(config),
|
|
|
15f218 |
+ [
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ Severities.ERROR,
|
|
|
15f218 |
+ report_codes.INVALID_OPTION_VALUE,
|
|
|
15f218 |
+ {
|
|
|
15f218 |
+ "option_name": "SBD_WATCHDOG_TIMEOUT",
|
|
|
15f218 |
+ "option_value": "-1",
|
|
|
15f218 |
+ "allowed_values": "nonnegative integer",
|
|
|
15f218 |
+ "allowed_values_str": "nonnegative integer",
|
|
|
15f218 |
+ },
|
|
|
15f218 |
+ None
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ ]
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_watchdog_timeout_is_not_int(self):
|
|
|
15f218 |
+ config = {
|
|
|
15f218 |
+ "SBD_WATCHDOG_TIMEOUT": "not int",
|
|
|
15f218 |
+ }
|
|
|
15f218 |
+
|
|
|
15f218 |
+ assert_report_item_list_equal(
|
|
|
15f218 |
+ cmd_sbd._validate_sbd_options(config),
|
|
|
15f218 |
+ [
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ Severities.ERROR,
|
|
|
15f218 |
+ report_codes.INVALID_OPTION_VALUE,
|
|
|
15f218 |
+ {
|
|
|
15f218 |
+ "option_name": "SBD_WATCHDOG_TIMEOUT",
|
|
|
15f218 |
+ "option_value": "not int",
|
|
|
15f218 |
+ "allowed_values": "nonnegative integer",
|
|
|
15f218 |
+ "allowed_values_str": "nonnegative integer",
|
|
|
15f218 |
+ },
|
|
|
15f218 |
+ None
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ ]
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_watchdog_timeout_is_none(self):
|
|
|
15f218 |
+ config = {
|
|
|
15f218 |
+ "SBD_WATCHDOG_TIMEOUT": None,
|
|
|
15f218 |
+ }
|
|
|
15f218 |
+
|
|
|
15f218 |
+ assert_report_item_list_equal(
|
|
|
15f218 |
+ cmd_sbd._validate_sbd_options(config),
|
|
|
15f218 |
+ [
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ Severities.ERROR,
|
|
|
15f218 |
+ report_codes.INVALID_OPTION_VALUE,
|
|
|
15f218 |
+ {
|
|
|
15f218 |
+ "option_name": "SBD_WATCHDOG_TIMEOUT",
|
|
|
15f218 |
+ "option_value": None,
|
|
|
15f218 |
+ "allowed_values": "nonnegative integer",
|
|
|
15f218 |
+ "allowed_values_str": "nonnegative integer",
|
|
|
15f218 |
+ },
|
|
|
15f218 |
+ None
|
|
|
15f218 |
)
|
|
|
15f218 |
]
|
|
|
15f218 |
)
|
|
|
15f218 |
@@ -325,6 +419,35 @@ class GetFullWatchdogListTest(TestCase):
|
|
|
15f218 |
)
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
+ def test_invalid_watchdogs(self):
|
|
|
15f218 |
+ watchdog_dict = {
|
|
|
15f218 |
+ self.node_list[1].label: "",
|
|
|
15f218 |
+ self.node_list[2].label: None,
|
|
|
15f218 |
+ self.node_list[3].label: "not/abs/path",
|
|
|
15f218 |
+ self.node_list[4].label: "/dev/watchdog"
|
|
|
15f218 |
+
|
|
|
15f218 |
+ }
|
|
|
15f218 |
+ assert_raise_library_error(
|
|
|
15f218 |
+ lambda: cmd_sbd._get_full_watchdog_list(
|
|
|
15f218 |
+ self.node_list, "/dev/dog", watchdog_dict
|
|
|
15f218 |
+ ),
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ Severities.ERROR,
|
|
|
15f218 |
+ report_codes.WATCHDOG_INVALID,
|
|
|
15f218 |
+ {"watchdog": ""}
|
|
|
15f218 |
+ ),
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ Severities.ERROR,
|
|
|
15f218 |
+ report_codes.WATCHDOG_INVALID,
|
|
|
15f218 |
+ {"watchdog": None}
|
|
|
15f218 |
+ ),
|
|
|
15f218 |
+ (
|
|
|
15f218 |
+ Severities.ERROR,
|
|
|
15f218 |
+ report_codes.WATCHDOG_INVALID,
|
|
|
15f218 |
+ {"watchdog": "not/abs/path"}
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.commands.sbd._get_cluster_nodes")
|
|
|
15f218 |
@mock.patch("pcs.lib.sbd.check_sbd")
|
|
|
15f218 |
@@ -393,8 +516,7 @@ class GetClusterSbdStatusTest(CommandSbdTest):
|
|
|
15f218 |
}
|
|
|
15f218 |
}
|
|
|
15f218 |
]
|
|
|
15f218 |
-
|
|
|
15f218 |
- self.assertEqual(
|
|
|
15f218 |
+ _assert_equal_list_of_dictionaries_without_order(
|
|
|
15f218 |
expected, cmd_sbd.get_cluster_sbd_status(self.mock_env)
|
|
|
15f218 |
)
|
|
|
15f218 |
mock_get_nodes.assert_called_once_with(self.mock_env)
|
|
|
15f218 |
@@ -447,7 +569,7 @@ class GetClusterSbdStatusTest(CommandSbdTest):
|
|
|
15f218 |
}
|
|
|
15f218 |
]
|
|
|
15f218 |
|
|
|
15f218 |
- self.assertEqual(
|
|
|
15f218 |
+ _assert_equal_list_of_dictionaries_without_order(
|
|
|
15f218 |
expected, cmd_sbd.get_cluster_sbd_status(self.mock_env)
|
|
|
15f218 |
)
|
|
|
15f218 |
mock_get_nodes.assert_called_once_with(self.mock_env)
|
|
|
15f218 |
@@ -538,7 +660,7 @@ OPTION= value
|
|
|
15f218 |
}
|
|
|
15f218 |
]
|
|
|
15f218 |
|
|
|
15f218 |
- self.assertEqual(
|
|
|
15f218 |
+ _assert_equal_list_of_dictionaries_without_order(
|
|
|
15f218 |
expected, cmd_sbd.get_cluster_sbd_config(self.mock_env)
|
|
|
15f218 |
)
|
|
|
15f218 |
mock_get_nodes.assert_called_once_with(self.mock_env)
|
|
|
15f218 |
@@ -589,7 +711,7 @@ invalid value
|
|
|
15f218 |
}
|
|
|
15f218 |
]
|
|
|
15f218 |
|
|
|
15f218 |
- self.assertEqual(
|
|
|
15f218 |
+ _assert_equal_list_of_dictionaries_without_order(
|
|
|
15f218 |
expected, cmd_sbd.get_cluster_sbd_config(self.mock_env)
|
|
|
15f218 |
)
|
|
|
15f218 |
mock_get_nodes.assert_called_once_with(self.mock_env)
|
|
|
15f218 |
diff --git a/pcs/test/test_lib_corosync_config_facade.py b/pcs/test/test_lib_corosync_config_facade.py
|
|
|
15f218 |
index 4a35fd9..91f7b40 100644
|
|
|
15f218 |
--- a/pcs/test/test_lib_corosync_config_facade.py
|
|
|
15f218 |
+++ b/pcs/test/test_lib_corosync_config_facade.py
|
|
|
15f218 |
@@ -281,6 +281,34 @@ quorum {
|
|
|
15f218 |
self.assertFalse(facade.need_qdevice_reload)
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
+class IsEnabledAutoTieBreaker(TestCase):
|
|
|
15f218 |
+ def test_enabled(self):
|
|
|
15f218 |
+ config = """\
|
|
|
15f218 |
+quorum {
|
|
|
15f218 |
+ auto_tie_breaker: 1
|
|
|
15f218 |
+}
|
|
|
15f218 |
+"""
|
|
|
15f218 |
+ facade = lib.ConfigFacade.from_string(config)
|
|
|
15f218 |
+ self.assertTrue(facade.is_enabled_auto_tie_breaker())
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_disabled(self):
|
|
|
15f218 |
+ config = """\
|
|
|
15f218 |
+quorum {
|
|
|
15f218 |
+ auto_tie_breaker: 0
|
|
|
15f218 |
+}
|
|
|
15f218 |
+"""
|
|
|
15f218 |
+ facade = lib.ConfigFacade.from_string(config)
|
|
|
15f218 |
+ self.assertFalse(facade.is_enabled_auto_tie_breaker())
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_no_value(self):
|
|
|
15f218 |
+ config = """\
|
|
|
15f218 |
+quorum {
|
|
|
15f218 |
+}
|
|
|
15f218 |
+"""
|
|
|
15f218 |
+ facade = lib.ConfigFacade.from_string(config)
|
|
|
15f218 |
+ self.assertFalse(facade.is_enabled_auto_tie_breaker())
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
class SetQuorumOptionsTest(TestCase):
|
|
|
15f218 |
def get_two_node(self, facade):
|
|
|
15f218 |
two_node = None
|
|
|
15f218 |
diff --git a/pcs/test/test_lib_external.py b/pcs/test/test_lib_external.py
|
|
|
15f218 |
index a4ec0f9..b0ffdbb 100644
|
|
|
15f218 |
--- a/pcs/test/test_lib_external.py
|
|
|
15f218 |
+++ b/pcs/test/test_lib_external.py
|
|
|
15f218 |
@@ -1012,12 +1012,14 @@ Copyright (c) 2006-2009 Red Hat, Inc.
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
@mock.patch("pcs.lib.external.is_systemctl")
|
|
|
15f218 |
+@mock.patch("pcs.lib.external.is_service_installed")
|
|
|
15f218 |
class DisableServiceTest(TestCase):
|
|
|
15f218 |
def setUp(self):
|
|
|
15f218 |
self.mock_runner = mock.MagicMock(spec_set=lib.CommandRunner)
|
|
|
15f218 |
self.service = "service_name"
|
|
|
15f218 |
|
|
|
15f218 |
- def test_systemctl(self, mock_systemctl):
|
|
|
15f218 |
+ def test_systemctl(self, mock_is_installed, mock_systemctl):
|
|
|
15f218 |
+ mock_is_installed.return_value = True
|
|
|
15f218 |
mock_systemctl.return_value = True
|
|
|
15f218 |
self.mock_runner.run.return_value = ("", 0)
|
|
|
15f218 |
lib.disable_service(self.mock_runner, self.service)
|
|
|
15f218 |
@@ -1025,7 +1027,8 @@ class DisableServiceTest(TestCase):
|
|
|
15f218 |
["systemctl", "disable", self.service + ".service"]
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
- def test_systemctl_failed(self, mock_systemctl):
|
|
|
15f218 |
+ def test_systemctl_failed(self, mock_is_installed, mock_systemctl):
|
|
|
15f218 |
+ mock_is_installed.return_value = True
|
|
|
15f218 |
mock_systemctl.return_value = True
|
|
|
15f218 |
self.mock_runner.run.return_value = ("", 1)
|
|
|
15f218 |
self.assertRaises(
|
|
|
15f218 |
@@ -1036,7 +1039,6 @@ class DisableServiceTest(TestCase):
|
|
|
15f218 |
["systemctl", "disable", self.service + ".service"]
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
- @mock.patch("pcs.lib.external.is_service_installed")
|
|
|
15f218 |
def test_not_systemctl(self, mock_is_installed, mock_systemctl):
|
|
|
15f218 |
mock_is_installed.return_value = True
|
|
|
15f218 |
mock_systemctl.return_value = False
|
|
|
15f218 |
@@ -1046,7 +1048,6 @@ class DisableServiceTest(TestCase):
|
|
|
15f218 |
["chkconfig", self.service, "off"]
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
- @mock.patch("pcs.lib.external.is_service_installed")
|
|
|
15f218 |
def test_not_systemctl_failed(self, mock_is_installed, mock_systemctl):
|
|
|
15f218 |
mock_is_installed.return_value = True
|
|
|
15f218 |
mock_systemctl.return_value = False
|
|
|
15f218 |
@@ -1059,7 +1060,14 @@ class DisableServiceTest(TestCase):
|
|
|
15f218 |
["chkconfig", self.service, "off"]
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
- @mock.patch("pcs.lib.external.is_service_installed")
|
|
|
15f218 |
+ def test_systemctl_not_installed(
|
|
|
15f218 |
+ self, mock_is_installed, mock_systemctl
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_is_installed.return_value = False
|
|
|
15f218 |
+ mock_systemctl.return_value = True
|
|
|
15f218 |
+ lib.disable_service(self.mock_runner, self.service)
|
|
|
15f218 |
+ self.assertEqual(self.mock_runner.run.call_count, 0)
|
|
|
15f218 |
+
|
|
|
15f218 |
def test_not_systemctl_not_installed(
|
|
|
15f218 |
self, mock_is_installed, mock_systemctl
|
|
|
15f218 |
):
|
|
|
15f218 |
@@ -1068,7 +1076,8 @@ class DisableServiceTest(TestCase):
|
|
|
15f218 |
lib.disable_service(self.mock_runner, self.service)
|
|
|
15f218 |
self.assertEqual(self.mock_runner.run.call_count, 0)
|
|
|
15f218 |
|
|
|
15f218 |
- def test_instance_systemctl(self, mock_systemctl):
|
|
|
15f218 |
+ def test_instance_systemctl(self, mock_is_installed, mock_systemctl):
|
|
|
15f218 |
+ mock_is_installed.return_value = True
|
|
|
15f218 |
mock_systemctl.return_value = True
|
|
|
15f218 |
self.mock_runner.run.return_value = ("", 0)
|
|
|
15f218 |
lib.disable_service(self.mock_runner, self.service, instance="test")
|
|
|
15f218 |
@@ -1078,7 +1087,6 @@ class DisableServiceTest(TestCase):
|
|
|
15f218 |
"{0}@{1}.service".format(self.service, "test")
|
|
|
15f218 |
])
|
|
|
15f218 |
|
|
|
15f218 |
- @mock.patch("pcs.lib.external.is_service_installed")
|
|
|
15f218 |
def test_instance_not_systemctl(self, mock_is_installed, mock_systemctl):
|
|
|
15f218 |
mock_is_installed.return_value = True
|
|
|
15f218 |
mock_systemctl.return_value = False
|
|
|
15f218 |
diff --git a/pcs/test/test_lib_sbd.py b/pcs/test/test_lib_sbd.py
|
|
|
15f218 |
index e3c1401..fd29484 100644
|
|
|
15f218 |
--- a/pcs/test/test_lib_sbd.py
|
|
|
15f218 |
+++ b/pcs/test/test_lib_sbd.py
|
|
|
15f218 |
@@ -28,6 +28,7 @@ from pcs.lib.external import (
|
|
|
15f218 |
NodeConnectionException,
|
|
|
15f218 |
)
|
|
|
15f218 |
import pcs.lib.sbd as lib_sbd
|
|
|
15f218 |
+from pcs.lib.corosync.config_facade import ConfigFacade as CorosyncConfigFacade
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
class TestException(Exception):
|
|
|
15f218 |
@@ -85,6 +86,246 @@ class RunParallelAndRaiseLibErrorOnFailureTest(TestCase):
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
+@mock.patch("pcs.lib.sbd.is_sbd_installed")
|
|
|
15f218 |
+@mock.patch("pcs.lib.sbd.is_sbd_enabled")
|
|
|
15f218 |
+class IsAutoTieBreakerNeededTest(TestCase):
|
|
|
15f218 |
+ def setUp(self):
|
|
|
15f218 |
+ self.runner = "runner"
|
|
|
15f218 |
+ self.mock_corosync_conf = mock.MagicMock(spec_set=CorosyncConfigFacade)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def _set_ret_vals(self, nodes, qdevice):
|
|
|
15f218 |
+ self.mock_corosync_conf.get_nodes.return_value = nodes
|
|
|
15f218 |
+ self.mock_corosync_conf.has_quorum_device.return_value = qdevice
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_even_nodes_has_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], True)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_even_nodes_no_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], False)
|
|
|
15f218 |
+ self.assertTrue(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_not_installed_even_nodes_no_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = False
|
|
|
15f218 |
+ mock_installed.return_value = False
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_odd_nodes_has_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], True)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_odd_nodes_no_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_disabled_even_nodes_has_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = False
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], True)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_disabled_even_nodes_no_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = False
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_disabled_odd_nodes_has_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = False
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], True)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_disabled_odd_nodes_no_qdevice(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = False
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_odd_nodes_no_qdevice_plus_node(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], False)
|
|
|
15f218 |
+ self.assertTrue(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, 1
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_not_installed_odd_nodes_no_qdevice_plus_node(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = False
|
|
|
15f218 |
+ mock_installed.return_value = False
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, 1
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_odd_nodes_no_qdevice_minus_node(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], False)
|
|
|
15f218 |
+ self.assertTrue(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, -1
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_odd_nodes_no_qdevice_plus_2_nodes(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, 2
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_odd_nodes_no_qdevice_minus_2_nodes(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, -2
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_even_nodes_no_qdevice_plus_node(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, 1
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_even_nodes_no_qdevice_minus_node(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], False)
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, -1
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_even_nodes_no_qdevice_plus_2_nodes(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2], False)
|
|
|
15f218 |
+ self.assertTrue(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, 2
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_sbd_enabled_even_nodes_no_qdevice_minus_2_nodes(
|
|
|
15f218 |
+ self, mock_enabled, mock_installed
|
|
|
15f218 |
+ ):
|
|
|
15f218 |
+ mock_enabled.return_value = True
|
|
|
15f218 |
+ mock_installed.return_value = True
|
|
|
15f218 |
+ self._set_ret_vals([1, 2, 3, 4], False)
|
|
|
15f218 |
+ self.assertTrue(lib_sbd.is_auto_tie_breaker_needed(
|
|
|
15f218 |
+ self.runner, self.mock_corosync_conf, -2
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+@mock.patch("pcs.lib.sbd.is_auto_tie_breaker_needed")
|
|
|
15f218 |
+class AtbHasToBeEnabledTest(TestCase):
|
|
|
15f218 |
+ def setUp(self):
|
|
|
15f218 |
+ self.mock_runner = "runner"
|
|
|
15f218 |
+ self.mock_conf = mock.MagicMock(spec_set=CorosyncConfigFacade)
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_is_enabled(self, mock_is_needed):
|
|
|
15f218 |
+ mock_is_needed.return_value = True
|
|
|
15f218 |
+ self.mock_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.atb_has_to_be_enabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, 1
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+ mock_is_needed.assert_called_once_with(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, 1
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_needed_is_disabled(self, mock_is_needed):
|
|
|
15f218 |
+ mock_is_needed.return_value = True
|
|
|
15f218 |
+ self.mock_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ self.assertTrue(lib_sbd.atb_has_to_be_enabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, -1
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+ mock_is_needed.assert_called_once_with(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, -1
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_not_needed_is_enabled(self, mock_is_needed):
|
|
|
15f218 |
+ mock_is_needed.return_value = False
|
|
|
15f218 |
+ self.mock_conf.is_enabled_auto_tie_breaker.return_value = True
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.atb_has_to_be_enabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, 2
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+ mock_is_needed.assert_called_once_with(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, 2
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_atb_not_needed_is_disabled(self, mock_is_needed):
|
|
|
15f218 |
+ mock_is_needed.return_value = False
|
|
|
15f218 |
+ self.mock_conf.is_enabled_auto_tie_breaker.return_value = False
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.atb_has_to_be_enabled(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, -2
|
|
|
15f218 |
+ ))
|
|
|
15f218 |
+ mock_is_needed.assert_called_once_with(
|
|
|
15f218 |
+ self.mock_runner, self.mock_conf, -2
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
class CheckSbdTest(TestCase):
|
|
|
15f218 |
def test_success(self):
|
|
|
15f218 |
mock_communicator = mock.MagicMock(spec_set=NodeCommunicator)
|
|
|
15f218 |
@@ -316,11 +557,11 @@ class SetSbdConfigOnNodeTest(TestCase):
|
|
|
15f218 |
}
|
|
|
15f218 |
cfg_out = """# This file has been generated by pcs.
|
|
|
15f218 |
SBD_OPTS="-n node1"
|
|
|
15f218 |
-SBD_WATCHDOG_DEV=/dev/watchdog
|
|
|
15f218 |
+SBD_WATCHDOG_DEV=/my/watchdog
|
|
|
15f218 |
SBD_WATCHDOG_TIMEOUT=0
|
|
|
15f218 |
"""
|
|
|
15f218 |
lib_sbd.set_sbd_config_on_node(
|
|
|
15f218 |
- self.mock_rep, self.mock_com, self.node, cfg_in
|
|
|
15f218 |
+ self.mock_rep, self.mock_com, self.node, cfg_in, "/my/watchdog"
|
|
|
15f218 |
)
|
|
|
15f218 |
mock_set_sbd_cfg.assert_called_once_with(
|
|
|
15f218 |
self.mock_com, self.node, cfg_out
|
|
|
15f218 |
@@ -340,17 +581,24 @@ class SetSbdConfigOnAllNodesTest(TestCase):
|
|
|
15f218 |
def test_success(self, mock_func):
|
|
|
15f218 |
mock_com = mock.MagicMock(spec_set=NodeCommunicator)
|
|
|
15f218 |
mock_rep = MockLibraryReportProcessor()
|
|
|
15f218 |
- node_list = [NodeAddresses("node" + str(i)) for i in range(5)]
|
|
|
15f218 |
+ watchdog_dict = dict([
|
|
|
15f218 |
+ (NodeAddresses("node" + str(i)), "/dev/watchdog" + str(i))
|
|
|
15f218 |
+ for i in range(5)
|
|
|
15f218 |
+ ])
|
|
|
15f218 |
+ node_list = list(watchdog_dict.keys())
|
|
|
15f218 |
config = {
|
|
|
15f218 |
"opt1": "val1",
|
|
|
15f218 |
"opt2": "val2"
|
|
|
15f218 |
}
|
|
|
15f218 |
lib_sbd.set_sbd_config_on_all_nodes(
|
|
|
15f218 |
- mock_rep, mock_com, node_list, config
|
|
|
15f218 |
+ mock_rep, mock_com, node_list, config, watchdog_dict
|
|
|
15f218 |
)
|
|
|
15f218 |
mock_func.assert_called_once_with(
|
|
|
15f218 |
lib_sbd.set_sbd_config_on_node,
|
|
|
15f218 |
- [([mock_rep, mock_com, node, config], {}) for node in node_list]
|
|
|
15f218 |
+ [
|
|
|
15f218 |
+ ([mock_rep, mock_com, node, config, watchdog_dict[node]], {})
|
|
|
15f218 |
+ for node in node_list
|
|
|
15f218 |
+ ]
|
|
|
15f218 |
)
|
|
|
15f218 |
|
|
|
15f218 |
|
|
|
15f218 |
@@ -594,3 +842,17 @@ class IsSbdEnabledTest(TestCase):
|
|
|
15f218 |
mock_obj = mock.MagicMock()
|
|
|
15f218 |
mock_is_service_enabled.return_value = True
|
|
|
15f218 |
self.assertTrue(lib_sbd.is_sbd_enabled(mock_obj))
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
+@mock.patch("pcs.lib.external.is_service_installed")
|
|
|
15f218 |
+class IsSbdInstalledTest(TestCase):
|
|
|
15f218 |
+ def test_installed(self, mock_is_service_installed):
|
|
|
15f218 |
+ mock_obj = mock.MagicMock()
|
|
|
15f218 |
+ mock_is_service_installed.return_value = True
|
|
|
15f218 |
+ self.assertTrue(lib_sbd.is_sbd_installed(mock_obj))
|
|
|
15f218 |
+
|
|
|
15f218 |
+ def test_not_installed(self, mock_is_service_installed):
|
|
|
15f218 |
+ mock_obj = mock.MagicMock()
|
|
|
15f218 |
+ mock_is_service_installed.return_value = False
|
|
|
15f218 |
+ self.assertFalse(lib_sbd.is_sbd_installed(mock_obj))
|
|
|
15f218 |
+
|
|
|
15f218 |
diff --git a/pcs/usage.py b/pcs/usage.py
|
|
|
15f218 |
index b11a5fa..9ebbca9 100644
|
|
|
15f218 |
--- a/pcs/usage.py
|
|
|
15f218 |
+++ b/pcs/usage.py
|
|
|
15f218 |
@@ -683,6 +683,7 @@ Commands:
|
|
|
15f218 |
the whole CIB or be warned in the case of outdated CIB.
|
|
|
15f218 |
|
|
|
15f218 |
node add <node[,node-altaddr]> [--start [--wait[=<n>]]] [--enable]
|
|
|
15f218 |
+ [--watchdog=<watchdog-path>]
|
|
|
15f218 |
Add the node to corosync.conf and corosync on all nodes in the cluster
|
|
|
15f218 |
and sync the new corosync.conf to the new node. If --start is
|
|
|
15f218 |
specified also start corosync/pacemaker on the new node, if --wait is
|
|
|
15f218 |
@@ -690,6 +691,8 @@ Commands:
|
|
|
15f218 |
is specified enable corosync/pacemaker on new node.
|
|
|
15f218 |
When using Redundant Ring Protocol (RRP) with udpu transport, specify
|
|
|
15f218 |
the ring 0 address first followed by a ',' and then the ring 1 address.
|
|
|
15f218 |
+ Use --watchdog to specify path to watchdog on newly added node, when SBD
|
|
|
15f218 |
+ is enabled in cluster.
|
|
|
15f218 |
|
|
|
15f218 |
node remove <node>
|
|
|
15f218 |
Shutdown specified node and remove it from pacemaker and corosync on
|
|
|
15f218 |
diff --git a/pcs/utils.py b/pcs/utils.py
|
|
|
15f218 |
index 53cc0b0..a7ff7ca 100644
|
|
|
15f218 |
--- a/pcs/utils.py
|
|
|
15f218 |
+++ b/pcs/utils.py
|
|
|
15f218 |
@@ -32,7 +32,7 @@ from pcs.cli.common.reports import (
|
|
|
15f218 |
LibraryReportProcessorToConsole as LibraryReportProcessorToConsole,
|
|
|
15f218 |
)
|
|
|
15f218 |
from pcs.common.tools import simple_cache
|
|
|
15f218 |
-from pcs.lib import reports
|
|
|
15f218 |
+from pcs.lib import reports, sbd
|
|
|
15f218 |
from pcs.lib.env import LibraryEnvironment
|
|
|
15f218 |
from pcs.lib.errors import LibraryError
|
|
|
15f218 |
from pcs.lib.external import (
|
|
|
15f218 |
@@ -574,6 +574,23 @@ def getCorosyncActiveNodes():
|
|
|
15f218 |
|
|
|
15f218 |
return nodes_active
|
|
|
15f218 |
|
|
|
15f218 |
+
|
|
|
15f218 |
+def _enable_auto_tie_breaker_for_sbd(corosync_conf):
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ Enable auto tie breaker in specified corosync conf if it is needed by SBD.
|
|
|
15f218 |
+
|
|
|
15f218 |
+ corosync_conf -- parsed corosync conf
|
|
|
15f218 |
+ """
|
|
|
15f218 |
+ try:
|
|
|
15f218 |
+ corosync_facade = corosync_conf_facade(corosync_conf)
|
|
|
15f218 |
+ if sbd.atb_has_to_be_enabled(cmd_runner(), corosync_facade):
|
|
|
15f218 |
+ corosync_facade.set_quorum_options(
|
|
|
15f218 |
+ get_report_processor(), {"auto_tie_breaker": "1"}
|
|
|
15f218 |
+ )
|
|
|
15f218 |
+ except LibraryError as e:
|
|
|
15f218 |
+ process_library_reports(e.args)
|
|
|
15f218 |
+
|
|
|
15f218 |
+
|
|
|
15f218 |
# Add node specified to corosync.conf and reload corosync.conf (if running)
|
|
|
15f218 |
def addNodeToCorosync(node):
|
|
|
15f218 |
# Before adding, make sure node isn't already in corosync.conf
|
|
|
15f218 |
@@ -600,6 +617,9 @@ def addNodeToCorosync(node):
|
|
|
15f218 |
new_node.add_attribute("ring1_addr", node1)
|
|
|
15f218 |
new_node.add_attribute("nodeid", new_nodeid)
|
|
|
15f218 |
|
|
|
15f218 |
+ # enable ATB if it's needed
|
|
|
15f218 |
+ _enable_auto_tie_breaker_for_sbd(corosync_conf)
|
|
|
15f218 |
+
|
|
|
15f218 |
corosync_conf = autoset_2node_corosync(corosync_conf)
|
|
|
15f218 |
setCorosyncConf(str(corosync_conf))
|
|
|
15f218 |
return True
|
|
|
15f218 |
@@ -667,6 +687,9 @@ def removeNodeFromCorosync(node):
|
|
|
15f218 |
removed_node = True
|
|
|
15f218 |
|
|
|
15f218 |
if removed_node:
|
|
|
15f218 |
+ # enable ATB if it's needed
|
|
|
15f218 |
+ _enable_auto_tie_breaker_for_sbd(corosync_conf)
|
|
|
15f218 |
+
|
|
|
15f218 |
corosync_conf = autoset_2node_corosync(corosync_conf)
|
|
|
15f218 |
setCorosyncConf(str(corosync_conf))
|
|
|
15f218 |
|
|
|
15f218 |
diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb
|
|
|
15f218 |
index d46cd62..137bb3d 100644
|
|
|
15f218 |
--- a/pcsd/pcs.rb
|
|
|
15f218 |
+++ b/pcsd/pcs.rb
|
|
|
15f218 |
@@ -1995,14 +1995,14 @@ def enable_service(service)
|
|
|
15f218 |
end
|
|
|
15f218 |
|
|
|
15f218 |
def disable_service(service)
|
|
|
15f218 |
+ # fails when the service is not installed, so we need to check it beforehand
|
|
|
15f218 |
+ if not is_service_installed?(service)
|
|
|
15f218 |
+ return true
|
|
|
15f218 |
+ end
|
|
|
15f218 |
+
|
|
|
15f218 |
if ISSYSTEMCTL
|
|
|
15f218 |
- # returns success even if the service is not installed
|
|
|
15f218 |
cmd = ['systemctl', 'disable', "#{service}.service"]
|
|
|
15f218 |
else
|
|
|
15f218 |
- if not is_service_installed?(service)
|
|
|
15f218 |
- return true
|
|
|
15f218 |
- end
|
|
|
15f218 |
- # fails when the service is not installed, so we need to check it beforehand
|
|
|
15f218 |
cmd = ['chkconfig', service, 'off']
|
|
|
15f218 |
end
|
|
|
15f218 |
_, _, retcode = run_cmd(PCSAuth.getSuperuserAuth(), *cmd)
|
|
|
15f218 |
--
|
|
|
15f218 |
1.8.3.1
|
|
|
15f218 |
|