From db91fd68f1baa7b19f06dc8156822430decce4e7 Mon Sep 17 00:00:00 2001 From: Miroslav Lisik Date: Thu, 2 Sep 2021 10:29:59 +0200 Subject: [PATCH 1/2] update --- Makefile.am | 9 +- configure.ac | 5 + pcs/config.py | 13 +- pcs/lib/communication/corosync.py | 8 +- pcs/utils.py | 4 +- pcs_test/suite.py | 70 ++ .../cluster/test_add_nodes_validation.py | 18 +- .../test_stonith_update_scsi_devices.py | 11 +- pcs_test/tier0/lib/test_env_corosync.py | 618 ++++++++-------- pcs_test/tier1/legacy/test_constraints.py | 76 +- pcs_test/tier1/legacy/test_resource.py | 48 +- pcs_test/tier1/legacy/test_stonith.py | 71 +- .../tools/command_env/config_http_corosync.py | 23 +- pcs_test/tools/fixture_cib.py | 65 ++ pcsd/Makefile.am | 1 - pcsd/capabilities.xml | 7 - pcsd/fenceagent.rb | 59 -- pcsd/pcs.rb | 15 - pcsd/pcsd.rb | 671 +----------------- pcsd/remote.rb | 559 +-------------- pcsd/resource.rb | 3 - pcsd/rserver.rb | 1 - pcsd/test/test_resource.rb | 4 - 23 files changed, 634 insertions(+), 1725 deletions(-) delete mode 100644 pcsd/fenceagent.rb diff --git a/Makefile.am b/Makefile.am index 6aede970..34692969 100644 --- a/Makefile.am +++ b/Makefile.am @@ -188,8 +188,13 @@ endif pylint: if DEV_TESTS +if PARALLEL_PYLINT +pylint_options = --jobs=0 +else +pylint_options = +endif export PYTHONPATH=${abs_top_builddir}/${PCS_BUNDLED_DIR_LOCAL}/packages && \ - $(TIME) $(PYTHON) -m pylint --rcfile pylintrc --persistent=n --reports=n --score=n --disable similarities ${PCS_PYTHON_PACKAGES} + $(TIME) $(PYTHON) -m pylint --rcfile pylintrc --persistent=n --reports=n --score=n --disable similarities ${pylint_options} ${PCS_PYTHON_PACKAGES} endif @@ -213,7 +218,7 @@ endif tests_tier0: export PYTHONPATH=${abs_top_builddir}/${PCS_BUNDLED_DIR_LOCAL}/packages && \ - $(PYTHON) ${abs_builddir}/pcs_test/suite.py $(python_test_options) --tier0 + $(PYTHON) ${abs_builddir}/pcs_test/suite.py ${python_test_options} --tier0 tests_tier1: if EXECUTE_TIER1_TESTS diff --git a/configure.ac b/configure.ac index f7b9d1ad..75d65616 100644 --- a/configure.ac +++ b/configure.ac @@ -148,6 +148,11 @@ AC_ARG_ENABLE([parallel-tests], [parallel_tests="yes"]) AM_CONDITIONAL([PARALLEL_TESTS], [test "x$parallel_tests" = "xyes"]) +AC_ARG_ENABLE([parallel-pylint], + [AS_HELP_STRING([--enable-parallel-pylint], [Enable running pylint in multiple threads (default: no)])], + [parallel_pylint="yes"]) +AM_CONDITIONAL([PARALLEL_PYLINT], [test "x$parallel_pylint" = "xyes"]) + AC_ARG_ENABLE([local-build], [AS_HELP_STRING([--enable-local-build], [Download and install all dependencies as user / bundles])], [local_build="yes"]) diff --git a/pcs/config.py b/pcs/config.py index a0290499..a3e7e164 100644 --- a/pcs/config.py +++ b/pcs/config.py @@ -345,12 +345,13 @@ def config_restore_remote(infile_name, infile_obj): err_msgs.append(output) continue _status = json.loads(output) - if ( - _status["corosync"] - or _status["pacemaker"] - or - # not supported by older pcsd, do not fail if not present - _status.get("pacemaker_remote", False) + if any( + _status["node"]["services"][service_name]["running"] + for service_name in ( + "corosync", + "pacemaker", + "pacemaker_remote", + ) ): err_msgs.append( "Cluster is currently running on node %s. You need to stop " diff --git a/pcs/lib/communication/corosync.py b/pcs/lib/communication/corosync.py index fab8e38f..e2a2949c 100644 --- a/pcs/lib/communication/corosync.py +++ b/pcs/lib/communication/corosync.py @@ -28,7 +28,7 @@ class CheckCorosyncOffline( self._set_skip_offline(skip_offline_targets) def _get_request_data(self): - return RequestData("remote/status") + return RequestData("remote/status", [("version", "2")]) def _process_response(self, response): report_item = self._get_response_report(response) @@ -53,7 +53,7 @@ class CheckCorosyncOffline( return try: status = response.data - if not json.loads(status)["corosync"]: + if not json.loads(status)["node"]["corosync"]: report_item = ReportItem.info( reports.messages.CorosyncNotRunningOnNode(node_label), ) @@ -94,7 +94,7 @@ class GetCorosyncOnlineTargets( self._corosync_online_target_list = [] def _get_request_data(self): - return RequestData("remote/status") + return RequestData("remote/status", [("version", "2")]) def _process_response(self, response): report_item = self._get_response_report(response) @@ -103,7 +103,7 @@ class GetCorosyncOnlineTargets( return try: status = response.data - if json.loads(status)["corosync"]: + if json.loads(status)["node"]["corosync"]: self._corosync_online_target_list.append( response.request.target ) diff --git a/pcs/utils.py b/pcs/utils.py index ef778b52..7774016e 100644 --- a/pcs/utils.py +++ b/pcs/utils.py @@ -186,7 +186,9 @@ def checkStatus(node): Commandline options: * --request-timeout - timeout for HTTP requests """ - return sendHTTPRequest(node, "remote/status", None, False, False) + return sendHTTPRequest( + node, "remote/status", urlencode({"version": "2"}), False, False + ) # Check and see if we're authorized (faster than a status check) diff --git a/pcs_test/suite.py b/pcs_test/suite.py index 75ab66cd..bd98b8b0 100644 --- a/pcs_test/suite.py +++ b/pcs_test/suite.py @@ -1,6 +1,8 @@ import importlib import os import sys +from threading import Thread +import time import unittest try: @@ -84,6 +86,67 @@ def discover_tests( return unittest.TestLoader().loadTestsFromNames(explicitly_enumerated_tests) +def tier1_fixtures_needed(test_list): + for test_name in tests_from_suite(test_list): + if test_name.startswith("pcs_test.tier1.legacy."): + return True + return False + + +def run_tier1_fixtures(run_concurrently=True): + # pylint: disable=import-outside-toplevel + from pcs_test.tier1.legacy.test_constraints import ( + CONSTRAINT_TEST_CIB_FIXTURE, + ) + from pcs_test.tier1.legacy.test_resource import RESOURCE_TEST_CIB_FIXTURE + from pcs_test.tier1.legacy.test_stonith import ( + STONITH_LEVEL_TEST_CIB_FIXTURE, + ) + + fixture_instances = [ + CONSTRAINT_TEST_CIB_FIXTURE, + RESOURCE_TEST_CIB_FIXTURE, + STONITH_LEVEL_TEST_CIB_FIXTURE, + ] + print("Preparing tier1 fixtures...") + time_start = time.time() + if run_concurrently: + thread_list = [] + for instance in fixture_instances: + thread = Thread(target=instance.set_up) + thread.daemon = True + thread.start() + thread_list.append(thread) + timeout_counter = 30 # 30 * 10s = 5min + while thread_list: + if timeout_counter < 0: + raise AssertionError("Fixture threads seem to be stuck :(") + for thread in thread_list: + thread.join(timeout=10) + sys.stdout.write(".") + sys.stdout.flush() + timeout_counter -= 1 + if not thread.is_alive(): + thread_list.remove(thread) + continue + + else: + for instance in fixture_instances: + instance.set_up() + time_stop = time.time() + time_taken = time_stop - time_start + sys.stdout.write("Tier1 fixtures prepared in %.3fs\n" % (time_taken)) + sys.stdout.flush() + + def cleanup(): + print("Cleaning tier1 fixtures...", end=" ") + for instance in fixture_instances: + instance.clean_up() + print("done") + + return cleanup + + def main(): # pylint: disable=import-outside-toplevel if "BUNDLED_LIB_LOCATION" in os.environ: @@ -141,6 +204,11 @@ def main(): sys.exit() tests_to_run = discovered_tests + tier1_fixtures_cleanup = None + if tier1_fixtures_needed(tests_to_run): + tier1_fixtures_cleanup = run_tier1_fixtures( + run_concurrently=run_concurrently + ) if run_concurrently: tests_to_run = ConcurrentTestSuite( discovered_tests, @@ -174,6 +242,8 @@ def main(): verbosity=2 if "-v" in sys.argv else 1, resultclass=ResultClass ) test_result = test_runner.run(tests_to_run) + if tier1_fixtures_cleanup: + tier1_fixtures_cleanup() if not test_result.wasSuccessful(): sys.exit(1) diff --git a/pcs_test/tier0/lib/commands/cluster/test_add_nodes_validation.py b/pcs_test/tier0/lib/commands/cluster/test_add_nodes_validation.py index c66a5dff..69cdeed2 100644 --- a/pcs_test/tier0/lib/commands/cluster/test_add_nodes_validation.py +++ b/pcs_test/tier0/lib/commands/cluster/test_add_nodes_validation.py @@ -14,6 +14,9 @@ from pcs_test.tier0.lib.commands.cluster.test_add_nodes import ( ) from pcs_test.tools import fixture from pcs_test.tools.command_env import get_env_tools +from pcs_test.tools.command_env.config_http_corosync import ( + corosync_running_check_response, +) from pcs_test.tools.custom_mock import patch_getaddrinfo from pcs import settings @@ -1170,7 +1173,10 @@ class ClusterStatus(TestCase): .local.read_sbd_config(name_sufix="_2") .http.corosync.check_corosync_offline( communication_list=[ - {"label": "node1", "output": '{"corosync":true}'}, + { + "label": "node1", + "output": corosync_running_check_response(True), + }, {"label": "node2", "output": "an error"}, { "label": "node3", @@ -1178,8 +1184,14 @@ class ClusterStatus(TestCase): "errno": 7, "error_msg": "an error", }, - {"label": "node4", "output": '{"corosync":true}'}, - {"label": "node5", "output": '{"corosync":false}'}, + { + "label": "node4", + "output": corosync_running_check_response(True), + }, + { + "label": "node5", + "output": corosync_running_check_response(False), + }, ] ) .local.get_host_info(new_nodes) diff --git a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py index 3bc51325..593757d8 100644 --- a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py +++ b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py @@ -4,6 +4,9 @@ from unittest import mock, TestCase from pcs_test.tools import fixture from pcs_test.tools.command_env import get_env_tools +from pcs_test.tools.command_env.config_http_corosync import ( + corosync_running_check_response, +) from pcs_test.tools.misc import get_test_resource as rc from pcs import settings @@ -1013,7 +1016,7 @@ class TestUpdateScsiDevicesFailures(TestCase): communication_list=[ dict( label=self.existing_nodes[0], - output='{"corosync":true}', + output=corosync_running_check_response(True), ), ] + [ @@ -1052,11 +1055,11 @@ class TestUpdateScsiDevicesFailures(TestCase): communication_list=[ dict( label=self.existing_nodes[0], - output='{"corosync":true}', + output=corosync_running_check_response(True), ), dict( label=self.existing_nodes[1], - output='{"corosync":false}', + output=corosync_running_check_response(False), ), dict( label=self.existing_nodes[2], @@ -1122,7 +1125,7 @@ class TestUpdateScsiDevicesFailures(TestCase): ), dict( label=self.existing_nodes[2], - output='{"corosync":false}', + output=corosync_running_check_response(False), ), ] ) diff --git a/pcs_test/tier0/lib/test_env_corosync.py b/pcs_test/tier0/lib/test_env_corosync.py index dafc63a0..7063ee80 100644 --- a/pcs_test/tier0/lib/test_env_corosync.py +++ b/pcs_test/tier0/lib/test_env_corosync.py @@ -14,6 +14,9 @@ from pcs.lib.corosync.config_parser import ( from pcs_test.tools import fixture from pcs_test.tools.assertions import assert_raise_library_error from pcs_test.tools.command_env import get_env_tools +from pcs_test.tools.command_env.config_http_corosync import ( + corosync_running_check_response, +) class PushCorosyncConfLiveBase(TestCase): @@ -92,12 +95,11 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): ) def test_dont_need_stopped_cluster(self): - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ).http.corosync.reload_corosync_conf( - node_labels=self.node_labels[:1] - ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + node_labels=self.node_labels[:1] ) self.env_assistant.get_env().push_corosync_conf( self.corosync_conf_facade @@ -114,26 +116,19 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): node="node-2", ), fixture.info( - report_codes.COROSYNC_CONFIG_RELOADED, node="node-1" + report_codes.COROSYNC_CONFIG_RELOADED, + node="node-1", ), ] ) def test_dont_need_stopped_cluster_error(self): - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, - communication_list=[ - { - "label": "node-1", - }, - { - "label": "node-2", - "response_code": 400, - "output": "Failed", - }, - ], - ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, + communication_list=[ + {"label": "node-1"}, + {"label": "node-2", "response_code": 400, "output": "Failed"}, + ], ) env = self.env_assistant.get_env() self.env_assistant.assert_raise_library_error( @@ -162,35 +157,28 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): ) def test_dont_need_stopped_cluster_error_skip_offline(self): - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, - communication_list=[ + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, + communication_list=[ + { + "label": "node-1", + "response_code": 400, + "output": "Failed", + }, + {"label": "node-2"}, + ], + ) + self.config.http.corosync.reload_corosync_conf( + communication_list=[ + [ { - "label": "node-1", + "label": self.node_labels[0], "response_code": 400, "output": "Failed", }, - { - "label": "node-2", - }, ], - ).http.corosync.reload_corosync_conf( - communication_list=[ - [ - { - "label": self.node_labels[0], - "response_code": 400, - "output": "Failed", - }, - ], - [ - { - "label": self.node_labels[1], - }, - ], - ] - ) + [{"label": self.node_labels[1]}], + ] ) self.env_assistant.get_env().push_corosync_conf( self.corosync_conf_facade, skip_offline_nodes=True @@ -219,33 +207,29 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): reason="Failed", ), fixture.info( - report_codes.COROSYNC_CONFIG_RELOADED, node="node-2" + report_codes.COROSYNC_CONFIG_RELOADED, + node="node-2", ), ] ) def test_reload_on_another_node(self): - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ).http.corosync.reload_corosync_conf( - communication_list=[ - [ - { - "label": self.node_labels[0], - "response_code": 200, - "output": json.dumps( - dict(code="not_running", message="not running") - ), - }, - ], - [ - { - "label": self.node_labels[1], - }, - ], - ] - ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + communication_list=[ + [ + { + "label": self.node_labels[0], + "response_code": 200, + "output": json.dumps( + dict(code="not_running", message="not running") + ), + }, + ], + [{"label": self.node_labels[1]}], + ] ) self.env_assistant.get_env().push_corosync_conf( self.corosync_conf_facade @@ -266,35 +250,35 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): node="node-1", ), fixture.info( - report_codes.COROSYNC_CONFIG_RELOADED, node="node-2" + report_codes.COROSYNC_CONFIG_RELOADED, + node="node-2", ), ] ) def test_reload_not_successful(self): - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ).http.corosync.reload_corosync_conf( - communication_list=[ - [ - { - "label": self.node_labels[0], - "response_code": 200, - "output": json.dumps( - dict(code="not_running", message="not running") - ), - }, - ], - [ - { - "label": self.node_labels[1], - "response_code": 200, - "output": "not a json", - }, - ], - ] - ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + communication_list=[ + [ + { + "label": self.node_labels[0], + "response_code": 200, + "output": json.dumps( + dict(code="not_running", message="not running") + ), + }, + ], + [ + { + "label": self.node_labels[1], + "response_code": 200, + "output": "not a json", + }, + ], + ] ) self.env_assistant.assert_raise_library_error( lambda: self.env_assistant.get_env().push_corosync_conf( @@ -318,7 +302,8 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): node="node-1", ), fixture.warn( - report_codes.INVALID_RESPONSE_FORMAT, node="node-2" + report_codes.INVALID_RESPONSE_FORMAT, + node="node-2", ), fixture.error( report_codes.UNABLE_TO_PERFORM_OPERATION_ON_ANY_NODE @@ -327,23 +312,22 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): ) def test_reload_corosync_not_running_anywhere(self): - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ).http.corosync.reload_corosync_conf( - communication_list=[ - [ - { - "label": node, - "response_code": 200, - "output": json.dumps( - dict(code="not_running", message="not running") - ), - }, - ] - for node in self.node_labels + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + communication_list=[ + [ + { + "label": node, + "response_code": 200, + "output": json.dumps( + dict(code="not_running", message="not running") + ), + }, ] - ) + for node in self.node_labels + ] ) self.env_assistant.get_env().push_corosync_conf( self.corosync_conf_facade @@ -372,12 +356,11 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): def test_need_stopped_cluster(self): self.corosync_conf_facade.need_stopped_cluster = True - ( - self.config.http.corosync.check_corosync_offline( - node_labels=self.node_labels - ).http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ) + self.config.http.corosync.check_corosync_offline( + node_labels=self.node_labels + ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels ) self.env_assistant.get_env().push_corosync_conf( self.corosync_conf_facade @@ -407,21 +390,14 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): def test_need_stopped_cluster_not_stopped(self): self.corosync_conf_facade.need_stopped_cluster = True - ( - self.config.http.corosync.check_corosync_offline( - communication_list=[ - { - "label": self.node_labels[0], - "output": '{"corosync":true}', - } - ] - + [ - { - "label": node, - } - for node in self.node_labels[1:] - ] - ) + self.config.http.corosync.check_corosync_offline( + communication_list=[ + { + "label": self.node_labels[0], + "output": corosync_running_check_response(True), + } + ] + + [{"label": node} for node in self.node_labels[1:]] ) env = self.env_assistant.get_env() self.env_assistant.assert_raise_library_error( @@ -445,18 +421,14 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): # If we know for sure that corosync is running, skip_offline doesn't # matter. self.corosync_conf_facade.need_stopped_cluster = True - ( - self.config.http.corosync.check_corosync_offline( - communication_list=[ - dict( - label="node-1", - output='{"corosync":true}', - ), - dict( - label="node-2", - ), - ] - ) + self.config.http.corosync.check_corosync_offline( + communication_list=[ + dict( + label="node-1", + output=corosync_running_check_response(True), + ), + dict(label="node-2"), + ] ) env = self.env_assistant.get_env() self.env_assistant.assert_raise_library_error( @@ -481,19 +453,17 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): def test_need_stopped_cluster_json_error(self): self.corosync_conf_facade.need_stopped_cluster = True - ( - self.config.http.corosync.check_corosync_offline( - communication_list=[ - dict(label="node-1", output="{"), # not valid json - dict( - label="node-2", - # The expected key (/corosync) is missing, we don't - # care about version 2 status key - # (/services/corosync/running) - output='{"services":{"corosync":{"running":true}}}', - ), - ] - ) + self.config.http.corosync.check_corosync_offline( + communication_list=[ + dict(label="node-1", output="{"), # not valid json + dict( + label="node-2", + # The expected key (/corosync) is missing, tested code + # doesn't care about a new key added in version 2 status + # (/services/corosync/running) + output='{"services":{"corosync":{"running":true}}}', + ), + ] ) env = self.env_assistant.get_env() self.env_assistant.assert_raise_library_error( @@ -517,19 +487,15 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): def test_need_stopped_cluster_comunnication_failure(self): self.corosync_conf_facade.need_stopped_cluster = True - ( - self.config.http.corosync.check_corosync_offline( - communication_list=[ - dict( - label="node-1", - ), - dict( - label="node-2", - response_code=401, - output="""{"notauthorized":"true"}""", - ), - ] - ) + self.config.http.corosync.check_corosync_offline( + communication_list=[ + dict(label="node-1"), + dict( + label="node-2", + response_code=401, + output='{"notauthorized":"true"}', + ), + ] ) env = self.env_assistant.get_env() self.env_assistant.assert_raise_library_error( @@ -560,29 +526,26 @@ class PushCorosyncConfLiveNoQdeviceTest(PushCorosyncConfLiveBase): def test_need_stopped_cluster_comunnication_failures_skip_offline(self): # If we don't know if corosync is running, skip_offline matters. self.corosync_conf_facade.need_stopped_cluster = True - ( - self.config.http.corosync.check_corosync_offline( - communication_list=[ - dict( - label="node-1", - response_code=401, - output="""{"notauthorized":"true"}""", - ), - dict(label="node-2", output="{"), # not valid json - ] - ).http.corosync.set_corosync_conf( - self.corosync_conf_text, - communication_list=[ - dict( - label="node-1", - response_code=401, - output="""{"notauthorized":"true"}""", - ), - dict( - label="node-2", - ), - ], - ) + self.config.http.corosync.check_corosync_offline( + communication_list=[ + dict( + label="node-1", + response_code=401, + output='{"notauthorized":"true"}', + ), + dict(label="node-2", output="{"), # not valid json + ] + ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, + communication_list=[ + dict( + label="node-1", + response_code=401, + output='{"notauthorized":"true"}', + ), + dict(label="node-2"), + ], ) self.env_assistant.get_env().push_corosync_conf( self.corosync_conf_facade, skip_offline_nodes=True @@ -662,15 +625,17 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): def test_qdevice_reload(self): self.corosync_conf_facade.need_qdevice_reload = True - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ) - .http.corosync.reload_corosync_conf( - node_labels=self.node_labels[:1] - ) - .http.corosync.qdevice_client_stop(node_labels=self.node_labels) - .http.corosync.qdevice_client_start(node_labels=self.node_labels) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + node_labels=self.node_labels[:1] + ) + self.config.http.corosync.qdevice_client_stop( + node_labels=self.node_labels + ) + self.config.http.corosync.qdevice_client_start( + node_labels=self.node_labels ) self.env_assistant.get_env().push_corosync_conf( @@ -689,7 +654,8 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): node="node-2", ), fixture.info( - report_codes.COROSYNC_CONFIG_RELOADED, node="node-1" + report_codes.COROSYNC_CONFIG_RELOADED, + node="node-1", ), fixture.info(report_codes.QDEVICE_CLIENT_RELOAD_STARTED), fixture.info( @@ -725,34 +691,34 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): def test_qdevice_reload_corosync_stopped(self): self.corosync_conf_facade.need_qdevice_reload = True - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ) - .http.corosync.reload_corosync_conf( - communication_list=[ - [ - { - "label": label, - "response_code": 200, - "output": json.dumps( - dict(code="not_running", message="") - ), - }, - ] - for label in self.node_labels - ] - ) - .http.corosync.qdevice_client_stop(node_labels=self.node_labels) - .http.corosync.qdevice_client_start( - communication_list=[ + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + communication_list=[ + [ { "label": label, - "output": "corosync is not running, skipping", - } - for label in self.node_labels + "response_code": 200, + "output": json.dumps( + dict(code="not_running", message="") + ), + }, ] - ) + for label in self.node_labels + ] + ) + self.config.http.corosync.qdevice_client_stop( + node_labels=self.node_labels + ) + self.config.http.corosync.qdevice_client_start( + communication_list=[ + { + "label": label, + "output": "corosync is not running, skipping", + } + for label in self.node_labels + ] ) self.env_assistant.get_env().push_corosync_conf( @@ -816,38 +782,28 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): # This also tests that failing to stop qdevice on a node doesn't prevent # starting qdevice on the same node. self.corosync_conf_facade.need_qdevice_reload = True - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ) - .http.corosync.reload_corosync_conf( - node_labels=self.node_labels[:1] - ) - .http.corosync.qdevice_client_stop( - communication_list=[ - dict( - label="node-1", - ), - dict( - label="node-2", - response_code=400, - output="error", - ), - ] - ) - .http.corosync.qdevice_client_start( - communication_list=[ - dict( - label="node-1", - errno=8, - error_msg="failure", - was_connected=False, - ), - dict( - label="node-2", - ), - ] - ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + node_labels=self.node_labels[:1] + ) + self.config.http.corosync.qdevice_client_stop( + communication_list=[ + dict(label="node-1"), + dict(label="node-2", response_code=400, output="error"), + ] + ) + self.config.http.corosync.qdevice_client_start( + communication_list=[ + dict( + label="node-1", + errno=8, + error_msg="failure", + was_connected=False, + ), + dict(label="node-2"), + ] ) env = self.env_assistant.get_env() @@ -867,7 +823,8 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): node="node-2", ), fixture.info( - report_codes.COROSYNC_CONFIG_RELOADED, node="node-1" + report_codes.COROSYNC_CONFIG_RELOADED, + node="node-1", ), fixture.info(report_codes.QDEVICE_CLIENT_RELOAD_STARTED), fixture.info( @@ -903,62 +860,46 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): def test_qdevice_reload_failures_skip_offline(self): self.corosync_conf_facade.need_qdevice_reload = True - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, - communication_list=[ - dict( - label="node-1", - ), - dict( - label="node-2", - errno=8, - error_msg="failure", - was_connected=False, - ), + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, + communication_list=[ + dict(label="node-1"), + dict( + label="node-2", + errno=8, + error_msg="failure", + was_connected=False, + ), + ], + ) + self.config.http.corosync.reload_corosync_conf( + communication_list=[ + [ + { + "label": self.node_labels[0], + "response_code": 400, + "output": "Failed", + }, ], - ) - .http.corosync.reload_corosync_conf( - communication_list=[ - [ - { - "label": self.node_labels[0], - "response_code": 400, - "output": "Failed", - }, - ], - [ - { - "label": self.node_labels[1], - }, - ], - ] - ) - .http.corosync.qdevice_client_stop( - communication_list=[ - dict( - label="node-1", - ), - dict( - label="node-2", - response_code=400, - output="error", - ), - ] - ) - .http.corosync.qdevice_client_start( - communication_list=[ - dict( - label="node-1", - errno=8, - error_msg="failure", - was_connected=False, - ), - dict( - label="node-2", - ), - ] - ) + [{"label": self.node_labels[1]}], + ] + ) + self.config.http.corosync.qdevice_client_stop( + communication_list=[ + dict(label="node-1"), + dict(label="node-2", response_code=400, output="error"), + ] + ) + self.config.http.corosync.qdevice_client_start( + communication_list=[ + dict( + label="node-1", + errno=8, + error_msg="failure", + was_connected=False, + ), + dict(label="node-2"), + ] ) env = self.env_assistant.get_env() @@ -990,7 +931,8 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): reason="Failed", ), fixture.info( - report_codes.COROSYNC_CONFIG_RELOADED, node="node-2" + report_codes.COROSYNC_CONFIG_RELOADED, + node="node-2", ), fixture.info(report_codes.QDEVICE_CLIENT_RELOAD_STARTED), fixture.info( @@ -1024,29 +966,28 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): def test_reload_not_successful(self): self.corosync_conf_facade.need_qdevice_reload = True - ( - self.config.http.corosync.set_corosync_conf( - self.corosync_conf_text, node_labels=self.node_labels - ).http.corosync.reload_corosync_conf( - communication_list=[ - [ - { - "label": self.node_labels[0], - "response_code": 200, - "output": json.dumps( - dict(code="not_running", message="not running") - ), - }, - ], - [ - { - "label": self.node_labels[1], - "response_code": 200, - "output": "not a json", - }, - ], - ] - ) + self.config.http.corosync.set_corosync_conf( + self.corosync_conf_text, node_labels=self.node_labels + ) + self.config.http.corosync.reload_corosync_conf( + communication_list=[ + [ + { + "label": self.node_labels[0], + "response_code": 200, + "output": json.dumps( + dict(code="not_running", message="not running") + ), + }, + ], + [ + { + "label": self.node_labels[1], + "response_code": 200, + "output": "not a json", + }, + ], + ] ) self.env_assistant.assert_raise_library_error( lambda: self.env_assistant.get_env().push_corosync_conf( @@ -1070,7 +1011,8 @@ class PushCorosyncConfLiveWithQdeviceTest(PushCorosyncConfLiveBase): node="node-1", ), fixture.warn( - report_codes.INVALID_RESPONSE_FORMAT, node="node-2" + report_codes.INVALID_RESPONSE_FORMAT, + node="node-2", ), fixture.error( report_codes.UNABLE_TO_PERFORM_OPERATION_ON_ANY_NODE diff --git a/pcs_test/tier1/legacy/test_constraints.py b/pcs_test/tier1/legacy/test_constraints.py index 36924925..49b413a8 100644 --- a/pcs_test/tier1/legacy/test_constraints.py +++ b/pcs_test/tier1/legacy/test_constraints.py @@ -13,9 +13,11 @@ from pcs_test.tools.assertions import ( from pcs_test.tools.bin_mock import get_mock_settings from pcs_test.tools.cib import get_assert_pcs_effect_mixin from pcs_test.tools.fixture_cib import ( + CachedCibFixture, fixture_master_xml, fixture_to_cib, wrap_element_by_master, + wrap_element_by_master_file, ) from pcs_test.tools.misc import ( get_test_resource as rc, @@ -23,7 +25,6 @@ from pcs_test.tools.misc import ( skip_unless_crm_rule, outdent, ParametrizedTestMetaClass, - write_data_to_tmpfile, write_file_to_tmpfile, ) from pcs_test.tools.pcs_runner import pcs, PcsRunner @@ -54,70 +55,63 @@ empty_cib = rc("cib-empty-3.7.xml") large_cib = rc("cib-large.xml") -@skip_unless_crm_rule() -class ConstraintTest(unittest.TestCase): - def setUp(self): - self.temp_cib = get_tmp_file("tier1_constraints") - write_file_to_tmpfile(empty_cib, self.temp_cib) - self.temp_corosync_conf = None - - def tearDown(self): - self.temp_cib.close() - if self.temp_corosync_conf: - self.temp_corosync_conf.close() - - def fixture_resources(self): - write_data_to_tmpfile(self.fixture_cib_cache(), self.temp_cib) - - def fixture_cib_cache(self): - if not hasattr(self.__class__, "cib_cache"): - self.__class__.cib_cache = self.fixture_cib() - return self.__class__.cib_cache - - def fixture_cib(self): - write_file_to_tmpfile(empty_cib, self.temp_cib) - self.setupClusterA() - self.temp_cib.flush() - self.temp_cib.seek(0) - cib_content = self.temp_cib.read() - self.temp_cib.seek(0) - write_file_to_tmpfile(empty_cib, self.temp_cib) - return cib_content - - # Sets up a cluster with Resources, groups, master/slave resource and clones - def setupClusterA(self): +class ConstraintTestCibFixture(CachedCibFixture): + def _setup_cib(self): line = "resource create D1 ocf:heartbeat:Dummy".split() - output, returnVal = pcs(self.temp_cib.name, line) + output, returnVal = pcs(self.cache_path, line) assert returnVal == 0 and output == "" line = "resource create D2 ocf:heartbeat:Dummy".split() - output, returnVal = pcs(self.temp_cib.name, line) + output, returnVal = pcs(self.cache_path, line) assert returnVal == 0 and output == "" line = "resource create D3 ocf:heartbeat:Dummy".split() - output, returnVal = pcs(self.temp_cib.name, line) + output, returnVal = pcs(self.cache_path, line) assert returnVal == 0 and output == "" line = "resource create D4 ocf:heartbeat:Dummy".split() - output, returnVal = pcs(self.temp_cib.name, line) + output, returnVal = pcs(self.cache_path, line) assert returnVal == 0 and output == "" line = "resource create D5 ocf:heartbeat:Dummy".split() - output, returnVal = pcs(self.temp_cib.name, line) + output, returnVal = pcs(self.cache_path, line) assert returnVal == 0 and output == "" line = "resource create D6 ocf:heartbeat:Dummy".split() - output, returnVal = pcs(self.temp_cib.name, line) + output, returnVal = pcs(self.cache_path, line) assert returnVal == 0 and output == "" line = "resource clone D3".split() - output, returnVal = pcs(self.temp_cib.name, line) + output, returnVal = pcs(self.cache_path, line) assert returnVal == 0 and output == "" # pcs no longer allows turning resources into masters but supports # existing ones. In order to test it, we need to put a master in the # CIB without pcs. - wrap_element_by_master(self.temp_cib, "D4", master_id="Master") + wrap_element_by_master_file(self.cache_path, "D4", master_id="Master") + + +CONSTRAINT_TEST_CIB_FIXTURE = ConstraintTestCibFixture( + "fixture_tier1_constraints", empty_cib +) + + +@skip_unless_crm_rule() +class ConstraintTest(unittest.TestCase): + def setUp(self): + self.temp_cib = get_tmp_file("tier1_constraints") + write_file_to_tmpfile(empty_cib, self.temp_cib) + self.temp_corosync_conf = None + + def tearDown(self): + self.temp_cib.close() + if self.temp_corosync_conf: + self.temp_corosync_conf.close() + + def fixture_resources(self): + write_file_to_tmpfile( + CONSTRAINT_TEST_CIB_FIXTURE.cache_path, self.temp_cib + ) def testConstraintRules(self): self.fixture_resources() diff --git a/pcs_test/tier1/legacy/test_resource.py b/pcs_test/tier1/legacy/test_resource.py index 8b043260..ecf0d23d 100644 --- a/pcs_test/tier1/legacy/test_resource.py +++ b/pcs_test/tier1/legacy/test_resource.py @@ -12,8 +12,10 @@ from pcs_test.tools.assertions import ( from pcs_test.tools.bin_mock import get_mock_settings from pcs_test.tools.cib import get_assert_pcs_effect_mixin from pcs_test.tools.fixture_cib import ( + CachedCibFixture, fixture_master_xml, fixture_to_cib, + wrap_element_by_master_file, wrap_element_by_master, ) from pcs_test.tools.misc import ( @@ -154,21 +156,8 @@ class ResourceDescribe(TestCase, AssertPcsMixin): ) -class Resource(TestCase, AssertPcsMixin): - def setUp(self): - self.temp_cib = get_tmp_file("tier1_resource") - self.temp_large_cib = get_tmp_file("tier1_resource_large") - write_file_to_tmpfile(empty_cib, self.temp_cib) - write_file_to_tmpfile(large_cib, self.temp_large_cib) - self.pcs_runner = PcsRunner(self.temp_cib.name) - self.pcs_runner.mock_settings = get_mock_settings("crm_resource_binary") - - def tearDown(self): - self.temp_cib.close() - self.temp_large_cib.close() - - # Setups up a cluster with Resources, groups, master/slave resource & clones - def setupClusterA(self): +class ResourceTestCibFixture(CachedCibFixture): + def _setup_cib(self): self.assert_pcs_success( ( "resource create --no-default-ops ClusterIP ocf:heartbeat:IPaddr2" @@ -215,7 +204,34 @@ class Resource(TestCase, AssertPcsMixin): # pcs no longer allows turning resources into masters but supports # existing ones. In order to test it, we need to put a master in the # CIB without pcs. - wrap_element_by_master(self.temp_cib, "ClusterIP5", master_id="Master") + wrap_element_by_master_file( + self.cache_path, "ClusterIP5", master_id="Master" + ) + + +RESOURCE_TEST_CIB_FIXTURE = ResourceTestCibFixture( + "fixture_tier1_resource", empty_cib +) + + +class Resource(TestCase, AssertPcsMixin): + def setUp(self): + self.temp_cib = get_tmp_file("tier1_resource") + self.temp_large_cib = get_tmp_file("tier1_resource_large") + write_file_to_tmpfile(empty_cib, self.temp_cib) + write_file_to_tmpfile(large_cib, self.temp_large_cib) + self.pcs_runner = PcsRunner(self.temp_cib.name) + self.pcs_runner.mock_settings = get_mock_settings("crm_resource_binary") + + def tearDown(self): + self.temp_cib.close() + self.temp_large_cib.close() + + # Setups up a cluster with Resources, groups, master/slave resource & clones + def setupClusterA(self): + write_file_to_tmpfile( + RESOURCE_TEST_CIB_FIXTURE.cache_path, self.temp_cib + ) def testCaseInsensitive(self): o, r = pcs( diff --git a/pcs_test/tier1/legacy/test_stonith.py b/pcs_test/tier1/legacy/test_stonith.py index b3def2d4..f6b93f01 100644 --- a/pcs_test/tier1/legacy/test_stonith.py +++ b/pcs_test/tier1/legacy/test_stonith.py @@ -8,6 +8,7 @@ from pcs.common.str_tools import indent from pcs_test.tier1.cib_resource.common import ResourceTest from pcs_test.tools.assertions import AssertPcsMixin from pcs_test.tools.bin_mock import get_mock_settings +from pcs_test.tools.fixture_cib import CachedCibFixture from pcs_test.tools.misc import ( get_test_resource as rc, get_tmp_file, @@ -840,6 +841,46 @@ _fixture_stonith_level_cache = None _fixture_stonith_level_cache_lock = Lock() +class StonithLevelTestCibFixture(CachedCibFixture): + def _fixture_stonith_resource(self, name): + self.assert_pcs_success( + [ + "stonith", + "create", + name, + "fence_apc", + "pcmk_host_list=rh7-1 rh7-2", + "ip=i", + "username=u", + ] + ) + + def _setup_cib(self): + self._fixture_stonith_resource("F1") + self._fixture_stonith_resource("F2") + self._fixture_stonith_resource("F3") + + self.assert_pcs_success("stonith level add 1 rh7-1 F1".split()) + self.assert_pcs_success("stonith level add 2 rh7-1 F2".split()) + self.assert_pcs_success("stonith level add 2 rh7-2 F1".split()) + self.assert_pcs_success("stonith level add 1 rh7-2 F2".split()) + self.assert_pcs_success("stonith level add 4 regexp%rh7-\\d F3".split()) + self.assert_pcs_success( + "stonith level add 3 regexp%rh7-\\d F2 F1".split() + ) + self.assert_pcs_success( + "stonith level add 5 attrib%fencewith=levels1 F3 F2".split() + ) + self.assert_pcs_success( + "stonith level add 6 attrib%fencewith=levels2 F3 F1".split() + ) + + +STONITH_LEVEL_TEST_CIB_FIXTURE = StonithLevelTestCibFixture( + "fixture_tier1_stonith_level_tests", rc("cib-empty-withnodes.xml") +) + + class LevelTestsBase(TestCase, AssertPcsMixin): def setUp(self): self.temp_cib = get_tmp_file("tier1_test_stonith_level") @@ -877,26 +918,11 @@ class LevelTestsBase(TestCase, AssertPcsMixin): _fixture_stonith_level_cache = self.fixture_cib_config() return _fixture_stonith_level_cache - def fixture_cib_config(self): - self.fixture_stonith_resource("F1") - self.fixture_stonith_resource("F2") - self.fixture_stonith_resource("F3") - - self.assert_pcs_success("stonith level add 1 rh7-1 F1".split()) - self.assert_pcs_success("stonith level add 2 rh7-1 F2".split()) - self.assert_pcs_success("stonith level add 2 rh7-2 F1".split()) - self.assert_pcs_success("stonith level add 1 rh7-2 F2".split()) - self.assert_pcs_success("stonith level add 4 regexp%rh7-\\d F3".split()) - self.assert_pcs_success( - "stonith level add 3 regexp%rh7-\\d F2 F1".split() - ) - self.assert_pcs_success( - "stonith level add 5 attrib%fencewith=levels1 F3 F2".split() - ) - self.assert_pcs_success( - "stonith level add 6 attrib%fencewith=levels2 F3 F1".split() - ) - + @staticmethod + def fixture_cib_config(): + cib_content = "" + with open(STONITH_LEVEL_TEST_CIB_FIXTURE.cache_path, "r") as cib_file: + cib_content = cib_file.read() config = outdent( """\ Target: rh7-1 @@ -914,12 +940,7 @@ class LevelTestsBase(TestCase, AssertPcsMixin): Level 6 - F3,F1 """ ) - config_lines = config.splitlines() - self.temp_cib.flush() - self.temp_cib.seek(0) - cib_content = self.temp_cib.read() - self.temp_cib.seek(0) return cib_content, config, config_lines diff --git a/pcs_test/tools/command_env/config_http_corosync.py b/pcs_test/tools/command_env/config_http_corosync.py index cdaf65ff..7f84f406 100644 --- a/pcs_test/tools/command_env/config_http_corosync.py +++ b/pcs_test/tools/command_env/config_http_corosync.py @@ -6,6 +6,23 @@ from pcs_test.tools.command_env.mock_node_communicator import ( ) +def corosync_running_check_response(running): + return json.dumps( + { + "node": { + "corosync": running, + "services": { + "corosync": { + "installed": True, + "enabled": not running, + "running": running, + } + }, + } + } + ) + + class CorosyncShortcuts: def __init__(self, calls): self.__calls = calls @@ -29,7 +46,8 @@ class CorosyncShortcuts: node_labels, communication_list, action="remote/status", - output='{"corosync":false}', + param_list=[("version", "2")], + output=corosync_running_check_response(False), ) def get_corosync_online_targets( @@ -51,7 +69,8 @@ class CorosyncShortcuts: node_labels, communication_list, action="remote/status", - output='{"corosync":true}', + param_list=[("version", "2")], + output=corosync_running_check_response(True), ) def get_corosync_conf( diff --git a/pcs_test/tools/fixture_cib.py b/pcs_test/tools/fixture_cib.py index 730b0e33..602491c8 100644 --- a/pcs_test/tools/fixture_cib.py +++ b/pcs_test/tools/fixture_cib.py @@ -3,7 +3,14 @@ import os from unittest import mock from lxml import etree +from pcs_test.tools.assertions import AssertPcsMixin from pcs_test.tools.custom_mock import MockLibraryReportProcessor +from pcs_test.tools.misc import ( + get_test_resource, + get_tmp_file, + write_file_to_tmpfile, +) +from pcs_test.tools.pcs_runner import PcsRunner from pcs_test.tools.xml import etree_to_str from pcs import settings @@ -12,6 +19,54 @@ from pcs.lib.external import CommandRunner # pylint: disable=line-too-long +class CachedCibFixture(AssertPcsMixin): + def __init__(self, cache_name, empty_cib_path): + self._empty_cib_path = empty_cib_path + self._cache_name = cache_name + self._cache_path = None + self._pcs_runner = None + + def _setup_cib(self): + raise NotImplementedError() + + def set_up(self): + fixture_dir = get_test_resource("temp_fixtures") + os.makedirs(fixture_dir, exist_ok=True) + self._cache_path = os.path.join(fixture_dir, self._cache_name) + self._pcs_runner = PcsRunner(self._cache_path) + + with open(self._empty_cib_path, "r") as template_file, open( + self.cache_path, "w" + ) as cache_file: + cache_file.write(template_file.read()) + self._setup_cib() + + def clean_up(self): + if os.path.isfile(self.cache_path): + os.unlink(self.cache_path) + + @property + def cache_path(self): + if self._cache_path is None: + raise AssertionError("Cache has not been initiialized") + return self._cache_path + + # methods for supporting assert_pcs_success + @property + def pcs_runner(self): + if self._pcs_runner is None: + raise AssertionError("Cache has not been initialized") + return self._pcs_runner + + def assertEqual(self, first, second, msg=None): + # pylint: disable=invalid-name + # pylint: disable=no-self-use + if first != second: + raise AssertionError( + f"{msg}\n{first} != {second}" if msg else f"{first} != {second}" + ) + + def wrap_element_by_master(cib_file, resource_id, master_id=None): cib_file.seek(0) cib_tree = etree.parse(cib_file, etree.XMLParser(huge_tree=True)).getroot() @@ -49,6 +104,16 @@ def wrap_element_by_master(cib_file, resource_id, master_id=None): ) +def wrap_element_by_master_file(filepath, resource_id, master_id=None): + cib_tmp = get_tmp_file("wrap_by_master") + write_file_to_tmpfile(filepath, cib_tmp) + wrap_element_by_master(cib_tmp, resource_id, master_id=master_id) + cib_tmp.seek(0) + with open(filepath, "w") as target: + target.write(cib_tmp.read()) + cib_tmp.close() + + def fixture_master_xml(name, all_ops=True, meta_dict=None): default_ops = f""" - - - Restart one host machine or the local host machine if no host specified. - - daemon urls: node_restart - - diff --git a/pcsd/fenceagent.rb b/pcsd/fenceagent.rb deleted file mode 100644 index 4a3ba07d..00000000 --- a/pcsd/fenceagent.rb +++ /dev/null @@ -1,59 +0,0 @@ -def getFenceAgents(auth_user) - fence_agent_list = {} - stdout, stderr, retval = run_cmd( - auth_user, PCS, "stonith", "list", "--nodesc" - ) - if retval != 0 - $logger.error("Error running 'pcs stonith list --nodesc") - $logger.error(stdout + stderr) - return {} - end - - agents = stdout - agents.each { |a| - fa = FenceAgent.new - fa.name = a.chomp - fence_agent_list[fa.name] = fa - } - return fence_agent_list -end - -class FenceAgent - attr_accessor :name, :resource_class, :required_options, :optional_options, :advanced_options, :info - def initialize(name=nil, required_options={}, optional_options={}, resource_class=nil, advanced_options={}) - @name = name - @required_options = {} - @optional_options = {} - @required_options = required_options - @optional_options = optional_options - @advanced_options = advanced_options - @resource_class = nil - end - - def type - name - end - - def to_json(options = {}) - JSON.generate({ - :full_name => "stonith:#{name}", - :class => 'stonith', - :provider => nil, - :type => name, - }) - end - - def long_desc - if info && info.length >= 2 - return info[1] - end - return "" - end - - def short_desc - if info && info.length >= 1 - return info[0] - end - return "" - end -end diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb index 9e26c607..1507bdf5 100644 --- a/pcsd/pcs.rb +++ b/pcsd/pcs.rb @@ -1514,21 +1514,6 @@ def allowed_for_superuser(auth_user) return true end -def get_default_overview_node_list(clustername) - nodes = get_cluster_nodes clustername - node_list = [] - nodes.each { |node| - node_list << { - 'error_list' => [], - 'warning_list' => [], - 'status' => 'unknown', - 'quorum' => false, - 'name' => node - } - } - return node_list -end - def enable_service(service) result = run_pcs_internal( PCSAuth.getSuperuserAuth(), diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb index bf91e906..3297fc5e 100644 --- a/pcsd/pcsd.rb +++ b/pcsd/pcsd.rb @@ -11,7 +11,6 @@ require 'cgi' require 'bootstrap.rb' require 'resource.rb' require 'remote.rb' -require 'fenceagent.rb' require 'cluster.rb' require 'config.rb' require 'pcs.rb' @@ -54,14 +53,14 @@ end before do # nobody is logged in yet @auth_user = nil - @tornado_session_username = Thread.current[:tornado_username] - @tornado_session_groups = Thread.current[:tornado_groups] - @tornado_is_authenticated = Thread.current[:tornado_is_authenticated] if(request.path.start_with?('/remote/') and request.path != "/remote/auth") or request.path == '/run_pcs' or request.path.start_with?('/api/') # Sets @auth_user to a hash containing info about logged in user or halts # the request processing if login credentials are incorrect. - protect_by_token! + @auth_user = PCSAuth.loginByToken(request.cookies) + unless @auth_user + halt [401, '{"notauthorized":"true"}'] + end else # Set a sane default: nobody is logged in, but we do not need to check both # for nil and empty username (if auth_user and auth_user[:username]) @@ -120,37 +119,6 @@ def run_cfgsync end end -helpers do - def is_ajax? - return request.env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' - end - - def protect_by_token! - @auth_user = PCSAuth.loginByToken(request.cookies) - unless @auth_user - halt [401, '{"notauthorized":"true"}'] - end - end - - def getParamList(params) - param_line = [] - meta_options = [] - params.each { |param, val| - if param.start_with?("_res_paramne_") or (param.start_with?("_res_paramempty_") and val != "") - myparam = param.sub(/^_res_paramne_/,"").sub(/^_res_paramempty_/,"") - param_line << "#{myparam}=#{val}" - end - if param == "disabled" - meta_options << 'meta' << 'target-role=Stopped' - end - if param == "force" and val - param_line << "--force" - end - } - return param_line + meta_options - end -end - get '/remote/?:command?' do return remote(params, request, @auth_user) end @@ -675,10 +643,6 @@ post '/manage/auth_gui_against_nodes' do ] end -get '/clusters_overview' do - clusters_overview(params, request, getAuthUser()) -end - get '/imported-cluster-list' do imported_cluster_list(params, request, getAuthUser()) end @@ -693,190 +657,11 @@ post '/managec/:cluster/permissions_save/?' do ) end -get '/managec/:cluster/status_all' do - auth_user = getAuthUser() - status_all(params, request, auth_user, get_cluster_nodes(params[:cluster])) -end - get '/managec/:cluster/cluster_status' do auth_user = getAuthUser() cluster_status_gui(auth_user, params[:cluster]) end -get '/managec/:cluster/cluster_properties' do - auth_user = getAuthUser() - cluster = params[:cluster] - unless cluster - return 200, {} - end - code, out = send_cluster_request_with_token(auth_user, cluster, 'get_cib') - if code == 403 - return [403, 'Permission denied'] - elsif code != 200 - return [400, 'getting CIB failed'] - end - begin - properties = getAllSettings(nil, REXML::Document.new(out)) - code, out = send_cluster_request_with_token( - auth_user, cluster, 'get_cluster_properties_definition' - ) - - if code == 403 - return [403, 'Permission denied'] - elsif code == 404 - definition = { - 'batch-limit' => { - 'name' => 'batch-limit', - 'source' => 'pacemaker-schedulerd', - 'default' => '0', - 'type' => 'integer', - 'shortdesc' => 'The number of jobs that pacemaker is allowed to execute in parallel.', - 'longdesc' => 'The "correct" value will depend on the speed and load of your network and cluster nodes.', - 'readable_name' => 'Batch Limit', - 'advanced' => false - }, - 'no-quorum-policy' => { - 'name' => 'no-quorum-policy', - 'source' => 'pacemaker-schedulerd', - 'default' => 'stop', - 'type' => 'enum', - 'enum' => ['stop', 'freeze', 'ignore', 'suicide'], - 'shortdesc' => 'What to do when the cluster does not have quorum.', - 'longdesc' => 'Allowed values: - * ignore - continue all resource management - * freeze - continue resource management, but don\'t recover resources from nodes not in the affected partition - * stop - stop all resources in the affected cluster partition - * suicide - fence all nodes in the affected cluster partition', - 'readable_name' => 'No Quorum Policy', - 'advanced' => false - }, - 'symmetric-cluster' => { - 'name' => 'symmetric-cluster', - 'source' => 'pacemaker-schedulerd', - 'default' => 'true', - 'type' => 'boolean', - 'shortdesc' => 'All resources can run anywhere by default.', - 'longdesc' => 'All resources can run anywhere by default.', - 'readable_name' => 'Symmetric', - 'advanced' => false - }, - 'stonith-enabled' => { - 'name' => 'stonith-enabled', - 'source' => 'pacemaker-schedulerd', - 'default' => 'true', - 'type' => 'boolean', - 'shortdesc' => 'Failed nodes are STONITH\'d', - 'longdesc' => 'Failed nodes are STONITH\'d', - 'readable_name' => 'Stonith Enabled', - 'advanced' => false - }, - 'stonith-action' => { - 'name' => 'stonith-action', - 'source' => 'pacemaker-schedulerd', - 'default' => 'reboot', - 'type' => 'enum', - 'enum' => ['reboot', 'poweroff', 'off'], - 'shortdesc' => 'Action to send to STONITH device', - 'longdesc' => 'Action to send to STONITH device Allowed values: reboot, poweroff, off', - 'readable_name' => 'Stonith Action', - 'advanced' => false - }, - 'cluster-delay' => { - 'name' => 'cluster-delay', - 'source' => 'pacemaker-schedulerd', - 'default' => '60s', - 'type' => 'time', - 'shortdesc' => 'Round trip delay over the network (excluding action execution)', - 'longdesc' => 'The "correct" value will depend on the speed and load of your network and cluster nodes.', - 'readable_name' => 'Cluster Delay', - 'advanced' => false - }, - 'stop-orphan-resources' => { - 'name' => 'stop-orphan-resources', - 'source' => 'pacemaker-schedulerd', - 'default' => 'true', - 'type' => 'boolean', - 'shortdesc' => 'Should deleted resources be stopped', - 'longdesc' => 'Should deleted resources be stopped', - 'readable_name' => 'Stop Orphan Resources', - 'advanced' => false - }, - 'stop-orphan-actions' => { - 'name' => 'stop-orphan-actions', - 'source' => 'pacemaker-schedulerd', - 'default' => 'true', - 'type' => 'boolean', - 'shortdesc' => 'Should deleted actions be cancelled', - 'longdesc' => 'Should deleted actions be cancelled', - 'readable_name' => 'Stop Orphan Actions', - 'advanced' => false - }, - 'start-failure-is-fatal' => { - 'name' => 'start-failure-is-fatal', - 'source' => 'pacemaker-schedulerd', - 'default' => 'true', - 'type' => 'boolean', - 'shortdesc' => 'Always treat start failures as fatal', - 'longdesc' => 'This was the old default. However when set to FALSE, the cluster will instead use the resource\'s failcount and value for resource-failure-stickiness', - 'readable_name' => 'Start Failure is Fatal', - 'advanced' => false - }, - 'pe-error-series-max' => { - 'name' => 'pe-error-series-max', - 'source' => 'pacemaker-schedulerd', - 'default' => '-1', - 'type' => 'integer', - 'shortdesc' => 'The number of PE inputs resulting in ERRORs to save', - 'longdesc' => 'Zero to disable, -1 to store unlimited.', - 'readable_name' => 'PE Error Storage', - 'advanced' => false - }, - 'pe-warn-series-max' => { - 'name' => 'pe-warn-series-max', - 'source' => 'pacemaker-schedulerd', - 'default' => '5000', - 'type' => 'integer', - 'shortdesc' => 'The number of PE inputs resulting in WARNINGs to save', - 'longdesc' => 'Zero to disable, -1 to store unlimited.', - 'readable_name' => 'PE Warning Storage', - 'advanced' => false - }, - 'pe-input-series-max' => { - 'name' => 'pe-input-series-max', - 'source' => 'pacemaker-schedulerd', - 'default' => '4000', - 'type' => 'integer', - 'shortdesc' => 'The number of other PE inputs to save', - 'longdesc' => 'Zero to disable, -1 to store unlimited.', - 'readable_name' => 'PE Input Storage', - 'advanced' => false - }, - 'enable-acl' => { - 'name' => 'enable-acl', - 'source' => 'pacemaker-based', - 'default' => 'false', - 'type' => 'boolean', - 'shortdesc' => 'Enable CIB ACL', - 'longdesc' => 'Should pacemaker use ACLs to determine access to cluster', - 'readable_name' => 'Enable ACLs', - 'advanced' => false - }, - } - elsif code != 200 - return [400, 'getting properties definition failed'] - else - definition = JSON.parse(out) - end - - definition.each { |name, prop| - prop['value'] = properties[name] - } - return [200, JSON.generate(definition)] - rescue - return [400, 'unable to get cluster properties'] - end -end - get '/managec/:cluster/get_resource_agent_metadata' do auth_user = getAuthUser() cluster = params[:cluster] @@ -888,69 +673,7 @@ get '/managec/:cluster/get_resource_agent_metadata' do false, {:resource_agent => resource_agent} ) - if code != 404 - return [code, out] - end - - code, out = send_cluster_request_with_token( - auth_user, - cluster, - 'resource_metadata', - false, - { - :resourcename => resource_agent, - :new => true - } - ) - if code != 200 - return [400, 'Unable to get meta-data of specified resource agent.'] - end - desc_regex = Regexp.new( - '[^"]*)"' - ) - parameters_regex = Regexp.new( - ']*>(?[\s\S]*)' + - '
Optional Arguments:
(?[\S\s]*)' + - '' - ) - parameter_regex = Regexp.new( - ']*>[\s]*\s*' + - '(?[^<\s]*)\s*\s*\s*' + - ' resource_agent, - :shortdesc => html2plain(desc[:short]), - :longdesc => html2plain(desc[:long]), - :parameters => [] - } - - parameters = parameters_regex.match(out) - parameters[:required].scan(parameter_regex) { |match| - result[:parameters] << { - :name => html2plain(match[1]), - :longdesc => html2plain(match[0]), - :shortdesc => html2plain(match[2]), - :type => 'string', - :required => true - } - } - parameters[:optional].scan(parameter_regex) { |match| - result[:parameters] << { - :name => html2plain(match[1]), - :longdesc => html2plain(match[0]), - :shortdesc => html2plain(match[2]), - :type => 'string', - :required => false - } - } - return [200, JSON.generate(result)] + return [code, out] end get '/managec/:cluster/get_fence_agent_metadata' do @@ -964,90 +687,7 @@ get '/managec/:cluster/get_fence_agent_metadata' do false, {:fence_agent => fence_agent} ) - if code != 404 - return [code, out] - end - - code, out = send_cluster_request_with_token( - auth_user, - cluster, - 'fence_device_metadata', - false, - { - :resourcename => fence_agent.sub('stonith:', ''), - :new => true - } - ) - if code != 200 - return [400, 'Unable to get meta-data of specified fence agent.'] - end - desc_regex = Regexp.new( - '[^"]*)"' - ) - parameters_regex = Regexp.new( - ']*>(?[\s\S]*)' + - '
Optional Arguments:
(?[\S\s]*)' + - '
Advanced Arguments:
(?[\S\s]*)' + - '' - ) - required_parameter_regex = Regexp.new( - ']*>[\s]*' + - '\s* (?[^<\s]*)\s*\s*\s*' + - '\s* (?[^<\s]*)\s*\s*\s*' + - ' fence_agent, - :shortdesc => '', - :longdesc => '', - :parameters => [] - } - - # pcsd in version 0.9.137 (and older) does not provide description for - # fence agents - desc = desc_regex.match(out) - if desc - result[:shortdesc] = html2plain(desc[:short]) - result[:longdesc] = html2plain(desc[:long]) - end - - parameters = parameters_regex.match(out) - parameters[:required].scan(required_parameter_regex) { |match| - result[:parameters] << { - :name => html2plain(match[1]), - :longdesc => html2plain(match[0]), - :shortdesc => html2plain(match[2]), - :type => 'string', - :required => true, - :advanced => false - } - } - parameters[:optional].scan(other_parameter_regex) { |match| - result[:parameters] << { - :name => html2plain(match[0]), - :longdesc => '', - :shortdesc => html2plain(match[1]), - :type => 'string', - :required => false, - :advanced => false - } - } - parameters[:advanced].scan(other_parameter_regex) { |match| - result[:parameters] << { - :name => html2plain(match[0]), - :longdesc => '', - :shortdesc => html2plain(match[1]), - :type => 'string', - :required => false, - :advanced => true - } - } - return [200, JSON.generate(result)] + return [code, out] end post '/managec/:cluster/fix_auth_of_cluster' do @@ -1123,7 +763,6 @@ def pcs_compatibility_layer_known_hosts_add( known_hosts = get_known_hosts().select { |name, obj| host_list.include?(name) } - # try the new endpoint provided by pcs-0.10 known_hosts_request_data = {} known_hosts.each { |host_name, host_obj| known_hosts_request_data[host_name] = { @@ -1149,50 +788,14 @@ def pcs_compatibility_layer_known_hosts_add( ) end - # a remote host supports the endpoint; success - if retval == 200 - return 'success' - end - - # a remote host supports the endpoint; error - if retval != 404 - return 'error' - end - - # a remote host does not support the endpoint - # fallback to the old endpoint provided by pcs-0.9 since 0.9.140 - request_data = {} - known_hosts.each { |host_name, host_obj| - addr = host_obj.first_dest()['addr'] - port = host_obj.first_dest()['port'] - request_data["node:#{host_name}"] = host_obj.token - request_data["port:#{host_name}"] = port - request_data["node:#{addr}"] = host_obj.token - request_data["port:#{addr}"] = port - } - if is_cluster_request - retval, _out = send_cluster_request_with_token( - auth_user, target, '/save_tokens', true, request_data - ) - else - retval, _out = send_request_with_token( - auth_user, target, '/save_tokens', true, request_data - ) - end - - # a remote host supports the endpoint; success if retval == 200 return 'success' end - # a remote host supports the endpoint; error - if retval != 404 - return 'error' + if retval == 404 + return 'not_supported' end - - # a remote host does not support any of the endpoints - # there's nothing we can do about it - return 'not_supported' + return 'error' end def pcs_compatibility_layer_get_cluster_known_hosts(cluster_name, target_node) @@ -1200,11 +803,9 @@ def pcs_compatibility_layer_get_cluster_known_hosts(cluster_name, target_node) known_hosts = [] auth_user = PCSAuth.getSuperuserAuth() - # try the new endpoint provided by pcs-0.10 retval, out = send_request_with_token( auth_user, target_node, '/get_cluster_known_hosts' ) - # a remote host supports /get_cluster_known_hosts; data downloaded if retval == 200 begin JSON.parse(out).each { |name, data| @@ -1222,159 +823,21 @@ def pcs_compatibility_layer_get_cluster_known_hosts(cluster_name, target_node) "cannot get authentication info from cluster '#{cluster_name}'" ) end - return known_hosts, warning_messages - end - - # a remote host supports /get_cluster_known_hosts; an error occured - if retval != 404 + elsif retval == 404 warning_messages << ( "Unable to automatically authenticate against cluster nodes: " + - "cannot get authentication info from cluster '#{cluster_name}'" + "cluster '#{cluster_name}' is running an old version of pcs/pcsd" ) - return known_hosts, warning_messages - end - - # a remote host does not support /get_cluster_known_hosts - # fallback to the old endpoint provided by pcs-0.9 since 0.9.140 - retval, out = send_request_with_token( - auth_user, target_node, '/get_cluster_tokens', false, {'with_ports' => '1'} - ) - - # a remote host supports /get_cluster_tokens; data downloaded - if retval == 200 - begin - data = JSON.parse(out) - expected_keys = ['tokens', 'ports'] - if expected_keys.all? {|i| data.has_key?(i) and data[i].class == Hash} - # new format - new_tokens = data["tokens"] || {} - new_ports = data["ports"] || {} - else - # old format - new_tokens = data - new_ports = {} - end - new_tokens.each { |name_addr, token| - known_hosts << PcsKnownHost.new( - name_addr, - token, - [ - { - 'addr' => name_addr, - 'port' => (new_ports[name_addr] || PCSD_DEFAULT_PORT), - } - ] - ) - } - rescue => e - $logger.error "Unable to parse the response of /get_cluster_tokens: #{e}" - known_hosts = [] - warning_messages << ( - "Unable to automatically authenticate against cluster nodes: " + - "cannot get authentication info from cluster '#{cluster_name}'" - ) - end - return known_hosts, warning_messages - end - - # a remote host supports /get_cluster_tokens; an error occured - if retval != 404 + else warning_messages << ( "Unable to automatically authenticate against cluster nodes: " + "cannot get authentication info from cluster '#{cluster_name}'" ) - return known_hosts, warning_messages end - # a remote host does not support /get_cluster_tokens - # there's nothing we can do about it - warning_messages << ( - "Unable to automatically authenticate against cluster nodes: " + - "cluster '#{cluster_name}' is running an old version of pcs/pcsd" - ) return known_hosts, warning_messages end -def pcs_0_9_142_resource_change_group(auth_user, params) - parameters = { - :resource_id => params[:resource_id], - :resource_group => '', - :_orig_resource_group => '', - } - parameters[:resource_group] = params[:group_id] if params[:group_id] - if params[:old_group_id] - parameters[:_orig_resource_group] = params[:old_group_id] - end - return send_cluster_request_with_token( - auth_user, params[:cluster], 'update_resource', true, parameters - ) -end - -def pcs_0_9_142_resource_clone(auth_user, params) - parameters = { - :resource_id => params[:resource_id], - :resource_clone => true, - :_orig_resource_clone => 'false', - } - return send_cluster_request_with_token( - auth_user, params[:cluster], 'update_resource', true, parameters - ) -end - -def pcs_0_9_142_resource_unclone(auth_user, params) - parameters = { - :resource_id => params[:resource_id], - :resource_clone => nil, - :_orig_resource_clone => 'true', - } - return send_cluster_request_with_token( - auth_user, params[:cluster], 'update_resource', true, parameters - ) -end - -def pcs_0_9_142_resource_master(auth_user, params) - parameters = { - :resource_id => params[:resource_id], - :resource_ms => true, - :_orig_resource_ms => 'false', - } - return send_cluster_request_with_token( - auth_user, params[:cluster], 'update_resource', true, parameters - ) -end - -# There is a bug in pcs-0.9.138 and older in processing the standby and -# unstandby request. JS of that pcsd always sent nodename in "node" -# parameter, which caused pcsd daemon to run the standby command locally with -# param["node"] as node name. This worked fine if the local cluster was -# managed from JS, as pacemaker simply put the requested node into standby. -# However it didn't work for managing non-local clusters, as the command was -# run on the local cluster everytime. Pcsd daemon would send the request to a -# remote cluster if the param["name"] variable was set, and that never -# happened. That however wouldn't work either, as then the required parameter -# "node" wasn't sent in the request causing an exception on the receiving -# node. This is fixed in commit 053f63ca109d9ef9e7f0416e90aab8e140480f5b -# -# In order to be able to put nodes running pcs-0.9.138 into standby, the -# nodename must be sent in "node" param, and the "name" must not be sent. -def pcs_0_9_138_node_standby(auth_user, params) - translated_params = { - 'node' => params[:name], - } - return send_cluster_request_with_token( - auth_user, params[:cluster], 'node_standby', true, translated_params - ) -end - -def pcs_0_9_138_node_unstandby(auth_user, params) - translated_params = { - 'node' => params[:name], - } - return send_cluster_request_with_token( - auth_user, params[:cluster], 'node_unstandby', true, translated_params - ) -end - def pcs_0_10_6_get_avail_resource_agents(code, out) if code != 200 return code, out @@ -1421,99 +884,9 @@ post '/managec/:cluster/?*' do if params[:cluster] request = "/" + params[:splat].join("/") - # backward compatibility layer BEGIN - translate_for_version = { - '/node_standby' => [ - [[0, 9, 138], method(:pcs_0_9_138_node_standby)], - ], - '/node_unstandby' => [ - [[0, 9, 138], method(:pcs_0_9_138_node_unstandby)], - ], - } - if translate_for_version.key?(request) - target_pcsd_version = [0, 0, 0] - version_code, version_out = send_cluster_request_with_token( - auth_user, params[:cluster], 'get_sw_versions' - ) - if version_code == 200 - begin - versions = JSON.parse(version_out) - target_pcsd_version = versions['pcs'] if versions['pcs'] - rescue JSON::ParserError - end - end - translate_function = nil - translate_for_version[request].each { |pair| - if (target_pcsd_version <=> pair[0]) != 1 # target <= pair - translate_function = pair[1] - break - end - } - end - # backward compatibility layer END - - if translate_function - code, out = translate_function.call(auth_user, params) - else - code, out = send_cluster_request_with_token( - auth_user, params[:cluster], request, true, params, true, raw_data - ) - end - - # backward compatibility layer BEGIN - if code == 404 - case request - # supported since pcs-0.9.143 (tree view of resources) - when '/resource_change_group', 'resource_change_group' - code, out = pcs_0_9_142_resource_change_group(auth_user, params) - # supported since pcs-0.9.143 (tree view of resources) - when '/resource_clone', 'resource_clone' - code, out = pcs_0_9_142_resource_clone(auth_user, params) - # supported since pcs-0.9.143 (tree view of resources) - when '/resource_unclone', 'resource_unclone' - code, out = pcs_0_9_142_resource_unclone(auth_user, params) - # supported since pcs-0.9.143 (tree view of resources) - when '/resource_master', 'resource_master' - # defaults to true for old pcsds without capabilities defined - supports_resource_master = true - capabilities_code, capabilities_out = send_cluster_request_with_token( - auth_user, params[:cluster], 'capabilities' - ) - if capabilities_code == 200 - begin - capabilities_json = JSON.parse(capabilities_out) - supports_resource_master = capabilities_json[:pcsd_capabilities].include?( - 'pcmk.resource.master' - ) - rescue JSON::ParserError - end - end - if supports_resource_master - code, out = pcs_0_9_142_resource_master(auth_user, params) - end - else - redirection = { - # constraints removal for pcs-0.9.137 and older - "/remove_constraint_remote" => "/resource_cmd/rm_constraint", - # constraints removal for pcs-0.9.137 and older - "/remove_constraint_rule_remote" => "/resource_cmd/rm_constraint_rule" - } - if redirection.key?(request) - code, out = send_cluster_request_with_token( - auth_user, - params[:cluster], - redirection[request], - true, - params, - false, - raw_data - ) - end - end - end - # backward compatibility layer END - - return code, out + return send_cluster_request_with_token( + auth_user, params[:cluster], request, true, params, true, raw_data + ) end end @@ -1548,17 +921,3 @@ get '*' do redirect "Bad URL" call(env.merge("PATH_INFO" => '/nodes')) end - -def html2plain(text) - return CGI.unescapeHTML(text).gsub(/]*>/, "\n") -end - -helpers do - def h(text) - Rack::Utils.escape_html(text) - end - - def nl2br(text) - text.gsub(/\n/, "
") - end -end diff --git a/pcsd/remote.rb b/pcsd/remote.rb index 1c019e98..e36f651f 100644 --- a/pcsd/remote.rb +++ b/pcsd/remote.rb @@ -25,14 +25,14 @@ def remote(params, request, auth_user) remote_cmd_without_pacemaker = { :capabilities => method(:capabilities), :status => method(:node_status), - :status_all => method(:status_all), :cluster_status => method(:cluster_status_remote), :cluster_status_plaintext => method(:cluster_status_plaintext), :auth => method(:auth), :check_auth => method(:check_auth), + # lib api: + # /api/v1/cluster-setup/v1 :cluster_setup => method(:cluster_setup), :get_quorum_info => method(:get_quorum_info), - :get_cib => method(:get_cib), :get_corosync_conf => method(:get_corosync_conf_remote), :set_corosync_conf => method(:set_corosync_conf), :get_sync_capabilities => method(:get_sync_capabilities), @@ -45,14 +45,6 @@ def remote(params, request, auth_user) :cluster_start => method(:cluster_start), :cluster_stop => method(:cluster_stop), :config_restore => method(:config_restore), - # TODO deprecated, remove, not used anymore - :node_restart => method(:node_restart), - # lib api: - # /api/v1/node-standby-unstandby/v1 - :node_standby => method(:node_standby), - # lib api: - # /api/v1/node-standby-unstandby/v1 - :node_unstandby => method(:node_unstandby), :cluster_enable => method(:cluster_enable), :cluster_disable => method(:cluster_disable), :get_sw_versions => method(:get_sw_versions), @@ -69,12 +61,6 @@ def remote(params, request, auth_user) :sbd_enable => method(:sbd_enable), :remove_stonith_watchdog_timeout=> method(:remove_stonith_watchdog_timeout), :set_stonith_watchdog_timeout_to_zero => method(:set_stonith_watchdog_timeout_to_zero), - # lib api: - # /api/v1/sbd-enable-sbd/v1 - :remote_enable_sbd => method(:remote_enable_sbd), - # lib api: - # /api/v1/sbd-disable-sbd/v1 - :remote_disable_sbd => method(:remote_disable_sbd), :qdevice_net_get_ca_certificate => method(:qdevice_net_get_ca_certificate), # lib api: # /api/v1/qdevice-qdevice-net-sign-certificate-request/v1 @@ -100,9 +86,6 @@ def remote(params, request, auth_user) # lib api: # /api/v1/resource-agent-list-agents/v1 :get_avail_resource_agents => method(:get_avail_resource_agents), - # lib api: - # /api/v1/stonith-agent-list-agents/v1 - :get_avail_fence_agents => method(:get_avail_fence_agents), } remote_cmd_with_pacemaker = { :pacemaker_node_status => method(:remote_pacemaker_node_status), @@ -159,18 +142,6 @@ def remote(params, request, auth_user) :get_fence_agent_metadata => method(:get_fence_agent_metadata), :manage_resource => method(:manage_resource), :unmanage_resource => method(:unmanage_resource), - # lib api: - # /api/v1/alert-create-alert/v1 - :create_alert => method(:create_alert), - # lib api: - # /api/v1/alert-update-alert/v1 - :update_alert => method(:update_alert), - :create_recipient => method(:create_recipient), - :update_recipient => method(:update_recipient), - # lib api: - # /api/v1/alert-remove-alert/v1 - # /api/v1/alert-remove-recipient/v1 - :remove_alerts_and_recipients => method("remove_alerts_and_recipients"), } command = params[:command].to_sym @@ -193,6 +164,24 @@ def remote(params, request, auth_user) end end +def _get_param_list(params) + param_line = [] + meta_options = [] + params.each { |param, val| + if param.start_with?("_res_paramne_") or (param.start_with?("_res_paramempty_") and val != "") + myparam = param.sub(/^_res_paramne_/,"").sub(/^_res_paramempty_/,"") + param_line << "#{myparam}=#{val}" + end + if param == "disabled" + meta_options << 'meta' << 'target-role=Stopped' + end + if param == "force" and val + param_line << "--force" + end + } + return param_line + meta_options +end + def capabilities(params, request, auth_user) return JSON.generate({ :pcsd_capabilities => CAPABILITIES_PCSD, @@ -394,53 +383,6 @@ def config_restore(params, request, auth_user) end end -# TODO deprecated, remove, not used anymore -def node_restart(params, request, auth_user) - if params[:name] - code, response = send_request_with_token( - auth_user, params[:name], 'node_restart', true - ) - else - if not allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - $logger.info "Restarting Node" - output = `/sbin/reboot` - $logger.debug output - return output - end -end - -def node_standby(params, request, auth_user) - if params[:name] - code, response = send_request_with_token( - auth_user, params[:name], 'node_standby', true - ) - else - if not allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - $logger.info "Standby Node" - stdout, stderr, retval = run_cmd(auth_user, PCS, "node", "standby") - return stdout - end -end - -def node_unstandby(params, request, auth_user) - if params[:name] - code, response = send_request_with_token( - auth_user, params[:name], 'node_unstandby', true - ) - else - if not allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - $logger.info "Unstandby Node" - stdout, stderr, retval = run_cmd(auth_user, PCS, "node", "unstandby") - return stdout - end -end - def cluster_enable(params, request, auth_user) if params[:name] code, response = send_request_with_token( @@ -491,21 +433,6 @@ def get_quorum_info(params, request, auth_user) end end -def get_cib(params, request, auth_user) - if not allowed_for_local_cluster(auth_user, Permissions::READ) - return 403, 'Permission denied' - end - cib, stderr, retval = run_cmd(auth_user, CIBADMIN, "-Ql") - if retval != 0 - if not pacemaker_running? - return [400, '{"pacemaker_not_running":true}'] - end - return [500, "Unable to get CIB: " + cib.to_s + stderr.to_s] - else - return [200, cib] - end -end - def get_corosync_conf_remote(params, request, auth_user) if not allowed_for_local_cluster(auth_user, Permissions::READ) return 403, 'Permission denied' @@ -912,66 +839,6 @@ def node_status(params, request, auth_user) return [400, "Unsupported version '#{version}' of status requested"] end -def status_all(params, request, auth_user, nodes=[], dont_update_config=false) - if nodes == nil - return JSON.generate({"error" => "true"}) - end - - final_response = {} - threads = [] - forbidden_nodes = {} - nodes.each {|node| - threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger| - Thread.current[:pcsd_logger_container] = logger - code, response = send_request_with_token(auth_user, node, 'status') - if 403 == code - forbidden_nodes[node] = true - end - begin - final_response[node] = JSON.parse(response) - rescue JSON::ParserError => e - final_response[node] = {"bad_json" => true} - $logger.info("ERROR: Parse Error when parsing status JSON from #{node}") - end - if final_response[node] and final_response[node]["notoken"] == true - $logger.error("ERROR: bad token for #{node}") - end - } - } - threads.each { |t| t.join } - if forbidden_nodes.length > 0 - return 403, 'Permission denied' - end - - # Get full list of nodes and see if we need to update the configuration - node_list = [] - final_response.each { |fr,n| - node_list += n["corosync_offline"] if n["corosync_offline"] - node_list += n["corosync_online"] if n["corosync_online"] - node_list += n["pacemaker_offline"] if n["pacemaker_offline"] - node_list += n["pacemaker_online"] if n["pacemaker_online"] - } - - node_list.uniq! - if node_list.length > 0 - config = PCSConfig.new(Cfgsync::PcsdSettings.from_file().text()) - old_node_list = config.get_nodes(params[:cluster]) - if !(dont_update_config or config.cluster_nodes_equal?(params[:cluster], node_list)) - $logger.info("Updating node list for: #{params[:cluster]} #{old_node_list}->#{node_list}") - config.update_cluster(params[:cluster], node_list) - sync_config = Cfgsync::PcsdSettings.from_text(config.text()) - # on version conflict just go on, config will be corrected eventually - # by displaying the cluster in the web UI - Cfgsync::save_sync_new_version( - sync_config, get_corosync_nodes_names(), $cluster_name, true - ) - return status_all(params, request, auth_user, node_list, true) - end - end - $logger.debug("NODE LIST: " + node_list.inspect) - return JSON.generate(final_response) -end - def imported_cluster_list(params, request, auth_user) config = PCSConfig.new(Cfgsync::PcsdSettings.from_file().text()) imported_clusters = {"cluster_list" => []} @@ -981,173 +848,6 @@ def imported_cluster_list(params, request, auth_user) return JSON.generate(imported_clusters) end -def clusters_overview(params, request, auth_user) - cluster_map = {} - forbidden_clusters = {} - threads = [] - config = PCSConfig.new(Cfgsync::PcsdSettings.from_file().text()) - config.clusters.each { |cluster| - threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger| - Thread.current[:pcsd_logger_container] = logger - cluster_map[cluster.name] = { - 'cluster_name' => cluster.name, - 'error_list' => [ - {'message' => 'Unable to connect to the cluster. Request timeout.'} - ], - 'warning_list' => [], - 'status' => 'unknown', - 'node_list' => get_default_overview_node_list(cluster.name), - 'resource_list' => [] - } - overview_cluster = nil - online, offline, not_authorized_nodes = is_auth_against_nodes( - auth_user, - get_cluster_nodes(cluster.name), - 3 - ) - not_supported = false - forbidden = false - cluster_nodes_auth = (online + offline).uniq - cluster_nodes_all = (cluster_nodes_auth + not_authorized_nodes).uniq - nodes_not_in_cluster = [] - for node in cluster_nodes_auth - code, response = send_request_with_token( - auth_user, node, 'cluster_status', true, {}, true, nil, 8 - ) - if code == 404 - not_supported = true - next - end - if 403 == code - forbidden = true - forbidden_clusters[cluster.name] = true - break - end - begin - parsed_response = JSON.parse(response) - if parsed_response['noresponse'] or parsed_response['pacemaker_not_running'] - next - elsif parsed_response['notoken'] or parsed_response['notauthorized'] - next - elsif parsed_response['cluster_name'] != cluster.name - # queried node is not in the cluster (any more) - nodes_not_in_cluster << node - next - else - overview_cluster = parsed_response - break - end - rescue JSON::ParserError - end - end - - if cluster_nodes_all.sort == nodes_not_in_cluster.sort - overview_cluster = { - 'cluster_name' => cluster.name, - 'error_list' => [], - 'warning_list' => [], - 'status' => 'unknown', - 'node_list' => [], - 'resource_list' => [] - } - end - - if not overview_cluster - overview_cluster = { - 'cluster_name' => cluster.name, - 'error_list' => [], - 'warning_list' => [], - 'status' => 'unknown', - 'node_list' => get_default_overview_node_list(cluster.name), - 'resource_list' => [] - } - if not_supported - overview_cluster['warning_list'] = [ - { - 'message' => 'Cluster is running an old version of pcs/pcsd which does not provide data for the dashboard.', - }, - ] - else - if forbidden - overview_cluster['error_list'] = [ - { - 'message' => 'You do not have permissions to view the cluster.', - 'type' => 'forbidden', - }, - ] - overview_cluster['node_list'] = [] - else - overview_cluster['error_list'] = [ - { - 'message' => 'Unable to connect to the cluster.', - }, - ] - end - end - end - if not_authorized_nodes.length > 0 - overview_cluster['warning_list'] << { - 'message' => 'GUI is not authorized against node(s) '\ - + not_authorized_nodes.join(', '), - 'type' => 'nodes_not_authorized', - 'node_list' => not_authorized_nodes, - } - end - - overview_cluster['node_list'].each { |node| - if node['status_version'] == '1' - overview_cluster['warning_list'] << { - :message => 'Some nodes are running old version of pcs/pcsd.' - } - break - end - } - - cluster_map[cluster.name] = overview_cluster - } - } - - begin - Timeout::timeout(18) { - threads.each { |t| t.join } - } - rescue Timeout::Error - threads.each { |t| t.exit } - end - - # update clusters in PCSConfig - not_current_data = false - config = PCSConfig.new(Cfgsync::PcsdSettings.from_file().text()) - cluster_map.each { |cluster, values| - next if forbidden_clusters[cluster] - nodes = [] - values['node_list'].each { |node| - nodes << node['name'] - } - if !config.cluster_nodes_equal?(cluster, nodes) - $logger.info("Updating node list for: #{cluster} #{config.get_nodes(cluster)}->#{nodes}") - config.update_cluster(cluster, nodes) - not_current_data = true - end - } - if not_current_data - sync_config = Cfgsync::PcsdSettings.from_text(config.text()) - # on version conflict just go on, config will be corrected eventually - # by displaying the cluster in the web UI - Cfgsync::save_sync_new_version( - sync_config, get_corosync_nodes_names(), $cluster_name, true - ) - end - - overview = { - 'not_current_data' => not_current_data, - 'cluster_list' => cluster_map.values.sort { |a, b| - a['clustername'] <=> b['clustername'] - } - } - return JSON.generate(overview) -end - def auth(params, request, auth_user) # User authentication using username and password is done in python part of # pcsd. We will get here only if credentials are correct, so we just need to @@ -1220,7 +920,7 @@ def update_resource (params, request, auth_user) return 403, 'Permission denied' end - param_line = getParamList(params) + param_line = _get_param_list(params) if not params[:resource_id] cmd = [PCS, "resource", "create", params[:name], params[:resource_type]] cmd += param_line @@ -1320,7 +1020,7 @@ def update_fence_device(params, request, auth_user) $logger.info "Updating fence device" $logger.info params - param_line = getParamList(params) + param_line = _get_param_list(params) $logger.info param_line if not params[:resource_id] @@ -1353,14 +1053,6 @@ def get_avail_resource_agents(params, request, auth_user) return JSON.generate(getResourceAgents(auth_user).map{|a| [a, get_resource_agent_name_structure(a)]}.to_h) end -def get_avail_fence_agents(params, request, auth_user) - if not allowed_for_local_cluster(auth_user, Permissions::READ) - return 403, 'Permission denied' - end - agents = getFenceAgents(auth_user) - return JSON.generate(agents) -end - def remove_resource(params, request, auth_user) if not allowed_for_local_cluster(auth_user, Permissions::WRITE) return 403, 'Permission denied' @@ -1740,18 +1432,6 @@ def update_cluster_settings(params, request, auth_user) to_update = [] current = getAllSettings(auth_user) - # We need to be able to set cluster properties also from older version GUI. - # This code handles proper processing of checkboxes. - # === backward compatibility layer start === - params['hidden'].each { |prop, val| - next if prop == 'hidden_input' - unless properties.include?(prop) - properties[prop] = val - to_update << prop - end - } - # === backward compatibility layer end === - properties.each { |prop, val| val.strip! if not current.include?(prop) and val != '' # add @@ -2236,62 +1916,6 @@ def set_stonith_watchdog_timeout_to_zero(param, request, auth_user) end end -def remote_enable_sbd(params, request, auth_user) - unless allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - - arg_list = [] - - if ['true', '1', 'on'].include?(params[:ignore_offline_nodes]) - arg_list << '--skip-offline' - end - - params[:watchdog].each do |node, watchdog| - unless watchdog.strip.empty? - arg_list << "watchdog=#{watchdog.strip}@#{node}" - end - end - - params[:config].each do |option, value| - unless value.empty? - arg_list << "#{option}=#{value}" - end - end - - _, stderr, retcode = run_cmd( - auth_user, PCS, 'stonith', 'sbd', 'enable', *arg_list - ) - - if retcode != 0 - return [400, "Unable to enable sbd in cluster:\n#{stderr.join('')}"] - end - - return [200, 'Sbd has been enabled.'] -end - -def remote_disable_sbd(params, request, auth_user) - unless allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - - arg_list = [] - - if ['true', '1', 'on'].include?(params[:ignore_offline_nodes]) - arg_list << '--skip-offline' - end - - _, stderr, retcode = run_cmd( - auth_user, PCS, 'stonith', 'sbd', 'disable', *arg_list - ) - - if retcode != 0 - return [400, "Unable to disable sbd in cluster:\n#{stderr.join('')}"] - end - - return [200, 'Sbd has been disabled.'] -end - def qdevice_net_get_ca_certificate(params, request, auth_user) unless allowed_for_local_cluster(auth_user, Permissions::READ) return 403, 'Permission denied' @@ -2697,145 +2321,6 @@ def manage_services(params, request, auth_user) end end -def _hash_to_argument_list(hash) - result = [] - if hash.kind_of?(Hash) - hash.each {|key, value| - value = '' if value.nil? - result << "#{key}=#{value}" - } - end - return result -end - -def create_alert(params, request, auth_user) - unless allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - path = params[:path] - unless path - return [400, 'Missing required parameter: path'] - end - alert_id = params[:alert_id] - description = params[:description] - meta_attr_list = _hash_to_argument_list(params[:meta_attr]) - instance_attr_list = _hash_to_argument_list(params[:instance_attr]) - cmd = [PCS, 'alert', 'create', "path=#{path}"] - cmd << "id=#{alert_id}" if alert_id and alert_id != '' - cmd << "description=#{description}" if description and description != '' - cmd += ['options', *instance_attr_list] if instance_attr_list.any? - cmd += ['meta', *meta_attr_list] if meta_attr_list.any? - output, stderr, retval = run_cmd(auth_user, *cmd) - if retval != 0 - return [400, "Unable to create alert: #{stderr.join("\n")}"] - end - return [200, 'Alert created'] -end - -def update_alert(params, request, auth_user) - unless allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - alert_id = params[:alert_id] - unless alert_id - return [400, 'Missing required parameter: alert_id'] - end - path = params[:path] - description = params[:description] - meta_attr_list = _hash_to_argument_list(params[:meta_attr]) - instance_attr_list = _hash_to_argument_list(params[:instance_attr]) - cmd = [PCS, 'alert', 'update', alert_id] - cmd << "path=#{path}" if path - cmd << "description=#{description}" if description - cmd += ['options', *instance_attr_list] if instance_attr_list.any? - cmd += ['meta', *meta_attr_list] if meta_attr_list.any? - output, stderr, retval = run_cmd(auth_user, *cmd) - if retval != 0 - return [400, "Unable to update alert: #{stderr.join("\n")}"] - end - return [200, 'Alert updated'] -end - -def remove_alerts_and_recipients(params, request, auth_user) - unless allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - alert_list = params[:alert_list] - recipient_list = params[:recipient_list] - if recipient_list.kind_of?(Array) and recipient_list.any? - output, stderr, retval = run_cmd( - auth_user, PCS, 'alert', 'recipient', 'remove', *recipient_list - ) - if retval != 0 - return [400, "Unable to remove recipients: #{stderr.join("\n")}"] - end - end - if alert_list.kind_of?(Array) and alert_list.any? - output, stderr, retval = run_cmd( - auth_user, PCS, 'alert', 'remove', *alert_list - ) - if retval != 0 - return [400, "Unable to remove alerts: #{stderr.join("\n")}"] - end - end - return [200, 'All removed'] -end - -def create_recipient(params, request, auth_user) - unless allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - alert_id = params[:alert_id] - if not alert_id or alert_id.strip! == '' - return [400, 'Missing required paramter: alert_id'] - end - value = params[:value] - if not value or value == '' - return [400, 'Missing required paramter: value'] - end - recipient_id = params[:recipient_id] - description = params[:description] - meta_attr_list = _hash_to_argument_list(params[:meta_attr]) - instance_attr_list = _hash_to_argument_list(params[:instance_attr]) - cmd = [PCS, 'alert', 'recipient', 'add', alert_id, "value=#{value}"] - cmd << "id=#{recipient_id}" if recipient_id and recipient_id != '' - cmd << "description=#{description}" if description and description != '' - cmd += ['options', *instance_attr_list] if instance_attr_list.any? - cmd += ['meta', *meta_attr_list] if meta_attr_list.any? - output, stderr, retval = run_cmd(auth_user, *cmd) - if retval != 0 - return [400, "Unable to create recipient: #{stderr.join("\n")}"] - end - return [200, 'Recipient created'] -end - -def update_recipient(params, request, auth_user) - unless allowed_for_local_cluster(auth_user, Permissions::WRITE) - return 403, 'Permission denied' - end - recipient_id = params[:recipient_id] - if not recipient_id or recipient_id.strip! == '' - return [400, 'Missing required paramter: recipient_id'] - end - value = params[:value] - if value and value.strip! == '' - return [400, 'Parameter value canot be empty string'] - end - description = params[:description] - meta_attr_list = _hash_to_argument_list(params[:meta_attr]) - instance_attr_list = _hash_to_argument_list(params[:instance_attr]) - cmd = [PCS, 'alert', 'recipient', 'update', recipient_id] - cmd << "value=#{value}" if value - cmd << "description=#{description}" if description - cmd += ['options', *instance_attr_list] if instance_attr_list.any? - cmd += ['meta', *meta_attr_list] if meta_attr_list.any? - output, stderr, retval = run_cmd(auth_user, *cmd) - if retval != 0 - return [400, "Unable to update recipient: #{stderr.join("\n")}"] - end - return [200, 'Recipient updated'] -end - def pcsd_success(msg) $logger.info(msg) return [200, msg] diff --git a/pcsd/resource.rb b/pcsd/resource.rb index e49422f8..27894cc9 100644 --- a/pcsd/resource.rb +++ b/pcsd/resource.rb @@ -103,11 +103,8 @@ def get_resource_agent_name_structure(agent_name) match = expression.match(agent_name) if match provider = match.names.include?('provider') ? match[:provider] : nil - class_provider = provider.nil? ? match[:standard] : "#{match[:standard]}:#{provider}" return { :full_name => agent_name, - # TODO remove, this is only used by the old web UI - :class_provider => class_provider, :class => match[:standard], :provider => provider, :type => match[:type], diff --git a/pcsd/rserver.rb b/pcsd/rserver.rb index c37f9df4..e2c5e2a1 100644 --- a/pcsd/rserver.rb +++ b/pcsd/rserver.rb @@ -26,7 +26,6 @@ class TornadoCommunicationMiddleware session = JSON.parse(Base64.strict_decode64(env["HTTP_X_PCSD_PAYLOAD"])) Thread.current[:tornado_username] = session["username"] Thread.current[:tornado_groups] = session["groups"] - Thread.current[:tornado_is_authenticated] = session["is_authenticated"] end status, headers, body = @app.call(env) diff --git a/pcsd/test/test_resource.rb b/pcsd/test/test_resource.rb index 1eb0d3aa..97679eca 100644 --- a/pcsd/test/test_resource.rb +++ b/pcsd/test/test_resource.rb @@ -8,7 +8,6 @@ class GetResourceAgentNameStructure < Test::Unit::TestCase get_resource_agent_name_structure('standard:provider:type'), { :full_name => 'standard:provider:type', - :class_provider => 'standard:provider', :class => 'standard', :provider => 'provider', :type => 'type', @@ -21,7 +20,6 @@ class GetResourceAgentNameStructure < Test::Unit::TestCase get_resource_agent_name_structure('standard:type'), { :full_name => 'standard:type', - :class_provider => 'standard', :class => 'standard', :provider => nil, :type => 'type', @@ -34,7 +32,6 @@ class GetResourceAgentNameStructure < Test::Unit::TestCase get_resource_agent_name_structure('systemd:service@instance:name'), { :full_name => 'systemd:service@instance:name', - :class_provider => 'systemd', :class => 'systemd', :provider => nil, :type => 'service@instance:name', @@ -47,7 +44,6 @@ class GetResourceAgentNameStructure < Test::Unit::TestCase get_resource_agent_name_structure('service:service@instance:name'), { :full_name => 'service:service@instance:name', - :class_provider => 'service', :class => 'service', :provider => nil, :type => 'service@instance:name', -- 2.31.1