diff --git a/.gitignore b/.gitignore index 103ac1c..c9d343c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,21 @@ SOURCES/backports-3.23.0.gem +SOURCES/childprocess-4.1.0.gem SOURCES/dacite-1.6.0.tar.gz SOURCES/daemons-1.4.1.gem -SOURCES/ethon-0.15.0.gem +SOURCES/ethon-0.16.0.gem SOURCES/eventmachine-1.2.7.gem SOURCES/ffi-1.15.5.gem -SOURCES/mustermann-1.1.1.gem -SOURCES/open4-1.3.4-1.gem -SOURCES/pcs-0.11.3.tar.gz -SOURCES/pcs-web-ui-0.1.14.tar.gz -SOURCES/pcs-web-ui-node-modules-0.1.14.tar.xz +SOURCES/mustermann-3.0.0.gem +SOURCES/pcs-0.11.4.tar.gz +SOURCES/pcs-web-ui-0.1.16.tar.gz +SOURCES/pcs-web-ui-node-modules-0.1.16.tar.xz SOURCES/pyagentx-0.4.pcs.2.tar.gz -SOURCES/rack-2.2.3.1.gem -SOURCES/rack-protection-2.2.0.gem -SOURCES/rack-test-1.1.0.gem +SOURCES/rack-2.2.5.gem +SOURCES/rack-protection-3.0.5.gem +SOURCES/rack-test-2.0.2.gem SOURCES/ruby2_keywords-0.0.5.gem -SOURCES/sinatra-2.2.0.gem +SOURCES/sinatra-3.0.5.gem SOURCES/thin-1.8.1.gem -SOURCES/tilt-2.0.10.gem -SOURCES/tornado-6.1.0.tar.gz +SOURCES/tilt-2.0.11.gem +SOURCES/tornado-6.2.0.tar.gz SOURCES/webrick-1.7.0.gem diff --git a/.pcs.metadata b/.pcs.metadata index 1000040..90cd2c4 100644 --- a/.pcs.metadata +++ b/.pcs.metadata @@ -1,21 +1,21 @@ 0e11246385a9e0a4bc122b74fb74fe536a234f81 SOURCES/backports-3.23.0.gem +81639c8886342e01d189c10a6beab6ad0526dc4e SOURCES/childprocess-4.1.0.gem 31546c37fbdc6270d5097687619e9c0db6f1c05c SOURCES/dacite-1.6.0.tar.gz 4795a8962cc1608bfec0d91fa4d438c7cfe90c62 SOURCES/daemons-1.4.1.gem -29697a34b8cd9df4a77c650e2a38d0a36cdeee8b SOURCES/ethon-0.15.0.gem +5b56a68268708c474bef04550639ded3add5e946 SOURCES/ethon-0.16.0.gem 7a5b2896e210fac9759c786ee4510f265f75b481 SOURCES/eventmachine-1.2.7.gem 97632b7975067266c0b39596de0a4c86d9330658 SOURCES/ffi-1.15.5.gem -50a4e37904485810cb05e27d75c9783e5a8f3402 SOURCES/mustermann-1.1.1.gem -41a7fe9f8e3e02da5ae76c821b89c5b376a97746 SOURCES/open4-1.3.4-1.gem -c16c4623928ba85b0b94b8cbdec6396be85d3672 SOURCES/pcs-0.11.3.tar.gz -4f518dcca24972f19d3e377931595602106f049d SOURCES/pcs-web-ui-0.1.14.tar.gz -b3e77ed1e3bb07b8e0ed34ae80fc88b9be0f49a4 SOURCES/pcs-web-ui-node-modules-0.1.14.tar.xz +e892678aaf02ccb27f3a6cd58482cda00aea6ce8 SOURCES/mustermann-3.0.0.gem +b7aecf2f71777395b2b3bb79012de3e658383d4e SOURCES/pcs-0.11.4.tar.gz +62565f6f573d40a733662f5b9274caa4d275b0de SOURCES/pcs-web-ui-0.1.16.tar.gz +c079fc5427f91afcefec34c3dc5597eba916effc SOURCES/pcs-web-ui-node-modules-0.1.16.tar.xz 3176b2f2b332c2b6bf79fe882e83feecf3d3f011 SOURCES/pyagentx-0.4.pcs.2.tar.gz -be609467c819d263c138c417548431b81f8da216 SOURCES/rack-2.2.3.1.gem -21cfac2453436c6856da31e741bbfa59da4973e1 SOURCES/rack-protection-2.2.0.gem -b80bc5ca38a885e747271675ba91dd3d02136bf1 SOURCES/rack-test-1.1.0.gem +3ad7b27b68d5dd893ce91f216bb2685ae6c9846a SOURCES/rack-2.2.5.gem +b311f9d60fc3ac0e20078a5aca7c51efa404727c SOURCES/rack-protection-3.0.5.gem +3c669527ecbcb9f915a83983ec89320c356e1fe3 SOURCES/rack-test-2.0.2.gem d017b9e4d1978e0b3ccc3e2a31493809e4693cd3 SOURCES/ruby2_keywords-0.0.5.gem -5f0d7e63f9d8683f39ad23afe7e00b99602b87cc SOURCES/sinatra-2.2.0.gem +2a2fb3c121c6e5adc6f29d7e06cef66cdda303f1 SOURCES/sinatra-3.0.5.gem 1ac6292a98e17247b7bb847a35ff868605256f7b SOURCES/thin-1.8.1.gem -d265c822a6b228392d899e9eb5114613d65e6967 SOURCES/tilt-2.0.10.gem -c23c617c7a0205e465bebad5b8cdf289ae8402a2 SOURCES/tornado-6.1.0.tar.gz +360d77c80d2851a538fb13d43751093115c34712 SOURCES/tilt-2.0.11.gem +9e809453db3a3347b7c0e7837a189833247e0828 SOURCES/tornado-6.2.0.tar.gz 10ba51035928541b7713415f1f2e3a41114972fc SOURCES/webrick-1.7.0.gem diff --git a/SOURCES/01-smoke-test-fix.patch b/SOURCES/01-smoke-test-fix.patch new file mode 100644 index 0000000..c986ce9 --- /dev/null +++ b/SOURCES/01-smoke-test-fix.patch @@ -0,0 +1,215 @@ +From 2f1b9d7f225530dfc88af57d364547d9ad425172 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular +Date: Thu, 24 Nov 2022 15:10:20 +0100 +Subject: [PATCH] smoke test improvements + +--- + .gitignore | 1 + + .gitlab-ci.yml | 1 + + configure.ac | 2 +- + pcs/Makefile.am | 1 - + pcs/api_v2_client.in | 22 -------------------- + pcs_test/Makefile.am | 1 + + pcs_test/api_v2_client.in | 20 +++++++++++++++++++ + {pcs => pcs_test}/api_v2_client.py | 0 + pcs_test/smoke.sh.in | 32 +++++++++++++++++++----------- + 9 files changed, 44 insertions(+), 36 deletions(-) + delete mode 100644 pcs/api_v2_client.in + create mode 100644 pcs_test/api_v2_client.in + rename {pcs => pcs_test}/api_v2_client.py (100%) + +diff --git a/.gitignore b/.gitignore +index b368a048..8dd3d5be 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -21,6 +21,7 @@ requirements.txt + setup.py + setup.cfg + pcs/api_v2_client ++pcs_test/api_v2_client + pcs/pcs + pcs/pcs_internal + pcs/settings.py +diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml +index d4e0074d..3d797729 100644 +--- a/.gitlab-ci.yml ++++ b/.gitlab-ci.yml +@@ -126,6 +126,7 @@ python_smoke_tests: + - ./autogen.sh + - ./configure --enable-local-build + - make ++ - rm -rf pcs + - pcs_test/smoke.sh + artifacts: + paths: +diff --git a/configure.ac b/configure.ac +index bc8abb39..b61c1b25 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -578,10 +578,10 @@ AC_CONFIG_FILES([Makefile + pcsd/settings.rb + pcsd/logrotate/pcsd]) + +-AC_CONFIG_FILES([pcs/api_v2_client], [chmod +x pcs/api_v2_client]) + AC_CONFIG_FILES([pcs/pcs], [chmod +x pcs/pcs]) + AC_CONFIG_FILES([pcs/pcs_internal], [chmod +x pcs/pcs_internal]) + AC_CONFIG_FILES([pcs/snmp/pcs_snmp_agent], [chmod +x pcs/snmp/pcs_snmp_agent]) ++AC_CONFIG_FILES([pcs_test/api_v2_client], [chmod +x pcs_test/api_v2_client]) + AC_CONFIG_FILES([pcs_test/smoke.sh], [chmod +x pcs_test/smoke.sh]) + AC_CONFIG_FILES([pcs_test/pcs_for_tests], [chmod +x pcs_test/pcs_for_tests]) + AC_CONFIG_FILES([pcs_test/suite], [chmod +x pcs_test/suite]) +diff --git a/pcs/Makefile.am b/pcs/Makefile.am +index 5c5104b4..f562b32c 100644 +--- a/pcs/Makefile.am ++++ b/pcs/Makefile.am +@@ -20,7 +20,6 @@ EXTRA_DIST = \ + acl.py \ + alert.py \ + app.py \ +- api_v2_client.py \ + cli/booth/command.py \ + cli/booth/env.py \ + cli/booth/__init__.py \ +diff --git a/pcs/api_v2_client.in b/pcs/api_v2_client.in +deleted file mode 100644 +index 93336c31..00000000 +--- a/pcs/api_v2_client.in ++++ /dev/null +@@ -1,22 +0,0 @@ +-#!@PYTHON@ +- +-import os.path +-import sys +- +-CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +- +-# We prevent to import some module from this dir instead of e.g. standard module. +-# There is no reason to import anything from this module. +-sys.path.remove(CURRENT_DIR) +- +-# Add pcs package. +-PACKAGE_DIR = os.path.dirname(CURRENT_DIR) +-BUNDLED_PACKAGES_DIR = os.path.join( +- PACKAGE_DIR, "@PCS_BUNDLED_DIR_LOCAL@", "packages" +-) +-sys.path.insert(0, BUNDLED_PACKAGES_DIR) +-sys.path.insert(0, PACKAGE_DIR) +- +-from pcs import api_v2_client +- +-api_v2_client.main() +diff --git a/pcs_test/Makefile.am b/pcs_test/Makefile.am +index 89a23e05..6f497a0e 100644 +--- a/pcs_test/Makefile.am ++++ b/pcs_test/Makefile.am +@@ -57,6 +57,7 @@ EXTRA_DIST = \ + resources/transitions01.xml \ + resources/transitions02.xml \ + suite.py \ ++ api_v2_client.py \ + tier0/cli/booth/__init__.py \ + tier0/cli/booth/test_env.py \ + tier0/cli/cluster/__init__.py \ +diff --git a/pcs_test/api_v2_client.in b/pcs_test/api_v2_client.in +new file mode 100644 +index 00000000..73a22324 +--- /dev/null ++++ b/pcs_test/api_v2_client.in +@@ -0,0 +1,20 @@ ++#!@PYTHON@ ++import os.path ++import sys ++ ++CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) ++ ++TEST_INSTALLED = os.environ.get("PCS_TEST.TEST_INSTALLED", "0") == "1" ++ ++if TEST_INSTALLED: ++ BUNDLED_PACKAGES_DIR = os.path.join("@PCS_BUNDLED_DIR@", "packages") ++else: ++ PACKAGE_DIR = os.path.dirname(CURRENT_DIR) ++ sys.path.insert(0, PACKAGE_DIR) ++ BUNDLED_PACKAGES_DIR = os.path.join(PACKAGE_DIR, "@PCS_BUNDLED_DIR_LOCAL@", "packages") ++ ++sys.path.insert(0, BUNDLED_PACKAGES_DIR) ++ ++from api_v2_client import main ++ ++main() +diff --git a/pcs/api_v2_client.py b/pcs_test/api_v2_client.py +similarity index 100% +rename from pcs/api_v2_client.py +rename to pcs_test/api_v2_client.py +diff --git a/pcs_test/smoke.sh.in b/pcs_test/smoke.sh.in +index 42321777..b845b6d6 100755 +--- a/pcs_test/smoke.sh.in ++++ b/pcs_test/smoke.sh.in +@@ -1,6 +1,8 @@ + #!@BASH@ + set -ex + ++SCRIPT_DIR="$(dirname -- "$(realpath -- "$0")")" ++ + cluster_user=hacluster + cluster_user_password=qa57Jk27eP + pcsd_socket_path="@LOCALSTATEDIR@/run/pcsd.socket" +@@ -15,13 +17,15 @@ if pidof systemd | grep "\b1\b"; then + pcs cluster setup cluster-name localhost --debug + fi + ++output_file=$(mktemp) ++token_file=$(mktemp) ++ + # Sanity check of API V0 + token=$(python3 -c "import json; print(json.load(open('@LOCALSTATEDIR@/lib/pcsd/known-hosts'))['known_hosts']['localhost']['token']);") +-curl -kb "token=${token}" https://localhost:2224/remote/cluster_status_plaintext -d 'data_json={}' > output.json +-cat output.json; echo "" +-python3 -c "import json; import sys; json.load(open('output.json'))['status'] == 'exception' and (sys.exit(1))"; ++curl -kb "token=${token}" https://localhost:2224/remote/cluster_status_plaintext -d 'data_json={}' > "${output_file}" ++cat "${output_file}"; echo "" ++python3 -c "import json; import sys; json.load(open('${output_file}'))['status'] == 'exception' and (sys.exit(1))"; + +-token_file=$(mktemp) + dd if=/dev/urandom bs=32 count=1 status=none | base64 > "${token_file}" + custom_localhost_node_name="custom-node-name" + +@@ -30,24 +34,28 @@ pcs pcsd accept_token "${token_file}" + pcs pcsd status "${custom_localhost_node_name}" | grep "${custom_localhost_node_name}: Online" + + # Sanity check of API V1 +-curl -kb "token=${token}" https://localhost:2224/api/v1/resource-agent-get-agents-list/v1 --data '{}' > output.json +-cat output.json; echo "" +-python3 -c "import json; import sys; json.load(open('output.json'))['status'] != 'success' and (sys.exit(1))"; ++curl -kb "token=${token}" https://localhost:2224/api/v1/resource-agent-get-agents-list/v1 --data '{}' > "${output_file}" ++cat "${output_file}"; echo "" ++python3 -c "import json; import sys; json.load(open('${output_file}'))['status'] != 'success' and (sys.exit(1))"; + + # Sanity check of API V2 + # async +-pcs/api_v2_client resource_agent.get_agent_metadata '{"agent_name":{"standard":"ocf","provider":"pacemaker","type":"Dummy"}}' ++env "PCS_TEST.TEST_INSTALLED=1" ${SCRIPT_DIR}/api_v2_client resource_agent.get_agent_metadata '{"agent_name":{"standard":"ocf","provider":"pacemaker","type":"Dummy"}}' + + # sync +-pcs/api_v2_client --sync resource_agent.get_agent_metadata '{"agent_name":{"standard":"ocf","provider":"pacemaker","type":"Stateful"}}' ++env "PCS_TEST.TEST_INSTALLED=1" ${SCRIPT_DIR}/api_v2_client --sync resource_agent.get_agent_metadata '{"agent_name":{"standard":"ocf","provider":"pacemaker","type":"Stateful"}}' + + # unix socket test +-curl --unix-socket "${pcsd_socket_path}" http:/something/api/v1/resource-agent-get-agents-list/v1 --data '{}' > output.json +-cat output.json; echo "" +-python3 -c "import json; import sys; json.load(open('output.json'))['status'] != 'success' and (sys.exit(1))"; ++curl --unix-socket "${pcsd_socket_path}" http:/something/api/v1/resource-agent-get-agents-list/v1 --data '{}' > "${output_file}" ++cat "${output_file}"; echo "" ++python3 -c "import json; import sys; json.load(open('${output_file}'))['status'] != 'success' and (sys.exit(1))"; + + # make sure socket is not accessible by all users + useradd testuser + su testuser + ! curl --unix-socket "${pcsd_socket_path}" http:/something/api/v1/resource-agent-get-agents-list/v1 --data '{}' ++ ++# cleanup ++rm "${token_file}" ++rm "${output_file}" + exit 0 +-- +2.38.1 + diff --git a/SOURCES/02-smoke-test-fix.patch b/SOURCES/02-smoke-test-fix.patch new file mode 100644 index 0000000..eefa289 --- /dev/null +++ b/SOURCES/02-smoke-test-fix.patch @@ -0,0 +1,30 @@ +From 4692f1032b7954d56f76f243283d519e8453ff58 Mon Sep 17 00:00:00 2001 +From: Miroslav Lisik +Date: Tue, 29 Nov 2022 17:46:08 +0100 +Subject: [PATCH 1/3] fix smoke test + +--- + pcs_test/smoke.sh.in | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/pcs_test/smoke.sh.in b/pcs_test/smoke.sh.in +index b845b6d6..e9466efd 100755 +--- a/pcs_test/smoke.sh.in ++++ b/pcs_test/smoke.sh.in +@@ -52,10 +52,11 @@ python3 -c "import json; import sys; json.load(open('${output_file}'))['status'] + + # make sure socket is not accessible by all users + useradd testuser +-su testuser +-! curl --unix-socket "${pcsd_socket_path}" http:/something/api/v1/resource-agent-get-agents-list/v1 --data '{}' ++su testuser -c '! curl --unix-socket '"${pcsd_socket_path}"' http:/something/api/v1/resource-agent-get-agents-list/v1 --data '\''{}'\''' + + # cleanup + rm "${token_file}" + rm "${output_file}" ++pcs cluster destroy --force ++userdel -r testuser + exit 0 +-- +2.38.1 + diff --git a/SOURCES/bz2026725-01-booth-sync-check-whether-etc-booth-exists.patch b/SOURCES/bz2026725-01-booth-sync-check-whether-etc-booth-exists.patch deleted file mode 100644 index 1a8dc60..0000000 --- a/SOURCES/bz2026725-01-booth-sync-check-whether-etc-booth-exists.patch +++ /dev/null @@ -1,46 +0,0 @@ -From f863c38497eb716141c9e585ddb418191e9fc2a4 Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek -Date: Fri, 15 Jul 2022 15:55:57 +0200 -Subject: [PATCH 3/3] booth sync: check whether /etc/booth exists - ---- - pcsd/pcsd_file.rb | 6 +----- - pcsd/remote.rb | 4 ++++ - 2 files changed, 5 insertions(+), 5 deletions(-) - -diff --git a/pcsd/pcsd_file.rb b/pcsd/pcsd_file.rb -index d82b55d2..394db59a 100644 ---- a/pcsd/pcsd_file.rb -+++ b/pcsd/pcsd_file.rb -@@ -112,12 +112,8 @@ module PcsdFile - end - end - -- def dir() -- return BOOTH_CONFIG_DIR -- end -- - def full_file_name() -- @full_file_name ||= File.join(self.dir, @file[:name]) -+ @full_file_name ||= File.join(BOOTH_CONFIG_DIR, @file[:name]) - end - end - -diff --git a/pcsd/remote.rb b/pcsd/remote.rb -index 854674eb..144d25f3 100644 ---- a/pcsd/remote.rb -+++ b/pcsd/remote.rb -@@ -2090,6 +2090,10 @@ def booth_set_config(params, request, auth_user) - check_permissions(auth_user, Permissions::WRITE) - data = check_request_data_for_json(params, auth_user) - -+ if not File.directory?(BOOTH_CONFIG_DIR) -+ raise "Configuration directory for booth '/etc/booth' is missing. Is booth installed?" -+ end -+ - PcsdExchangeFormat::validate_item_map_is_Hash('files', data) - PcsdExchangeFormat::validate_item_is_Hash('file', :config, data[:config]) - if data[:authfile] --- -2.35.3 - diff --git a/SOURCES/bz2058243-01-code-formatting.patch b/SOURCES/bz2058243-01-code-formatting.patch deleted file mode 100644 index 7868ae8..0000000 --- a/SOURCES/bz2058243-01-code-formatting.patch +++ /dev/null @@ -1,2491 +0,0 @@ -From 8859f3450df675579d24e4bd3d5e12aeefbaef54 Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek -Date: Thu, 14 Jul 2022 16:22:39 +0200 -Subject: [PATCH 1/3] code formatting - ---- - pcs_test/tier0/lib/commands/test_booth.py | 1684 +++++++++------------ - 1 file changed, 723 insertions(+), 961 deletions(-) - -diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py -index 6dc0cf3e..2b20a199 100644 ---- a/pcs_test/tier0/lib/commands/test_booth.py -+++ b/pcs_test/tier0/lib/commands/test_booth.py -@@ -211,22 +211,23 @@ class ConfigSetup(TestCase, FixtureMixin): - ) - ) - self.env_assist.assert_reports( -- [fixture.error(reports.codes.BOOTH_EVEN_PEERS_NUM, number=4)] -+ [ -+ fixture.error(reports.codes.BOOTH_EVEN_PEERS_NUM, number=4), -+ ] - ) - - def fixture_config_success(self, instance_name="booth"): -- ( -- self.config.raw_file.write( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(instance_name), -- RANDOM_KEY, -- name="raw_file.write.key", -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- self.fixture_cfg_content(self.fixture_key_path(instance_name)), -- name="raw_file.write.cfg", -- ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(instance_name), -+ RANDOM_KEY, -+ name="raw_file.write.key", -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ self.fixture_cfg_content(self.fixture_key_path(instance_name)), -+ name="raw_file.write.cfg", - ) - - def test_success_default_instance(self): -@@ -248,19 +249,18 @@ class ConfigSetup(TestCase, FixtureMixin): - ) - - def test_files_exist_config(self): -- ( -- self.config.raw_file.write( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- RANDOM_KEY, -- name="raw_file.write.key", -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content(), -- already_exists=True, -- name="raw_file.write.cfg", -- ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ RANDOM_KEY, -+ name="raw_file.write.key", -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content(), -+ already_exists=True, -+ name="raw_file.write.cfg", - ) - - self.env_assist.assert_raise_library_error( -@@ -283,14 +283,12 @@ class ConfigSetup(TestCase, FixtureMixin): - ) - - def test_files_exist_key(self): -- ( -- self.config.raw_file.write( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- RANDOM_KEY, -- already_exists=True, -- name="raw_file.write.key", -- ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ RANDOM_KEY, -+ already_exists=True, -+ name="raw_file.write.key", - ) - - self.env_assist.assert_raise_library_error( -@@ -313,20 +311,19 @@ class ConfigSetup(TestCase, FixtureMixin): - ) - - def test_files_exist_forced(self): -- ( -- self.config.raw_file.write( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- RANDOM_KEY, -- can_overwrite=True, -- name="raw_file.write.key", -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content(), -- can_overwrite=True, -- name="raw_file.write.cfg", -- ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ RANDOM_KEY, -+ can_overwrite=True, -+ name="raw_file.write.key", -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content(), -+ can_overwrite=True, -+ name="raw_file.write.cfg", - ) - - commands.config_setup( -@@ -337,7 +334,6 @@ class ConfigSetup(TestCase, FixtureMixin): - ) - - def _assert_write_config_error(self, error, booth_dir_exists): -- - self.config.raw_file.write( - file_type_codes.BOOTH_KEY, - self.fixture_key_path(), -@@ -444,11 +440,7 @@ class ConfigSetup(TestCase, FixtureMixin): - ) - env = self.env_assist.get_env() - -- commands.config_setup( -- env, -- self.sites, -- self.arbitrators, -- ) -+ commands.config_setup(env, self.sites, self.arbitrators) - - self.assertEqual( - env.get_booth_env(name="").export(), -@@ -491,38 +483,34 @@ class ConfigDestroy(TestCase, FixtureMixin): - self.env_assist, self.config = get_env_tools(self) - - def fixture_config_booth_not_used(self, instance_name="booth"): -- ( -- self.config.runner.cib.load() -- .services.is_running( -- "booth", instance=instance_name, return_value=False -- ) -- .services.is_enabled( -- "booth", instance=instance_name, return_value=False -- ) -+ self.config.runner.cib.load() -+ self.config.services.is_running( -+ "booth", instance=instance_name, return_value=False -+ ) -+ self.config.services.is_enabled( -+ "booth", instance=instance_name, return_value=False - ) - - def fixture_config_success(self, instance_name="booth"): - self.fixture_config_booth_not_used(instance_name) -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- content=self.fixture_cfg_content( -- self.fixture_key_path(instance_name) -- ), -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(instance_name), -- fail_if_file_not_found=False, -- name="raw_file.remove.key", -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- fail_if_file_not_found=True, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ content=self.fixture_cfg_content( -+ self.fixture_key_path(instance_name) -+ ), -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(instance_name), -+ fail_if_file_not_found=False, -+ name="raw_file.remove.key", -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ fail_if_file_not_found=True, -+ name="raw_file.remove.cfg", - ) - - def test_invalid_instance(self): -@@ -540,9 +528,7 @@ class ConfigDestroy(TestCase, FixtureMixin): - - def test_success_default_instance(self): - self.fixture_config_success() -- commands.config_destroy( -- self.env_assist.get_env(), -- ) -+ commands.config_destroy(self.env_assist.get_env()) - - def test_success_custom_instance(self): - instance_name = "my_booth" -@@ -553,23 +539,20 @@ class ConfigDestroy(TestCase, FixtureMixin): - - def test_success_no_booth_key(self): - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=bytes(), -- ).raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- fail_if_file_not_found=True, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=bytes(), - ) -- -- commands.config_destroy( -- self.env_assist.get_env(), -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ fail_if_file_not_found=True, -+ name="raw_file.remove.cfg", - ) - -+ commands.config_destroy(self.env_assist.get_env()) -+ - def test_not_live_booth(self): - self.config.env.set_booth( - { -@@ -603,9 +586,7 @@ class ConfigDestroy(TestCase, FixtureMixin): - [ - fixture.error( - reports.codes.LIVE_ENVIRONMENT_REQUIRED, -- forbidden_options=[ -- file_type_codes.CIB, -- ], -+ forbidden_options=[file_type_codes.CIB], - ), - ], - expected_in_processor=False, -@@ -640,20 +621,16 @@ class ConfigDestroy(TestCase, FixtureMixin): - def test_booth_config_in_use(self): - instance_name = "booth" - -- ( -- self.config.runner.cib.load(resources=self.fixture_cib_resources()) -- .services.is_running( -- "booth", instance=instance_name, return_value=True -- ) -- .services.is_enabled( -- "booth", instance=instance_name, return_value=True -- ) -+ self.config.runner.cib.load(resources=self.fixture_cib_resources()) -+ self.config.services.is_running( -+ "booth", instance=instance_name, return_value=True -+ ) -+ self.config.services.is_enabled( -+ "booth", instance=instance_name, return_value=True - ) - - self.env_assist.assert_raise_library_error( -- lambda: commands.config_destroy( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_destroy(self.env_assist.get_env()), - ) - - self.env_assist.assert_reports( -@@ -682,18 +659,14 @@ class ConfigDestroy(TestCase, FixtureMixin): - def test_cannot_read_config(self): - error = "an error" - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- exception_msg=error, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ exception_msg=error, - ) - - self.env_assist.assert_raise_library_error( -- lambda: commands.config_destroy( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_destroy(self.env_assist.get_env()), - ) - self.env_assist.assert_reports( - [ -@@ -711,17 +684,16 @@ class ConfigDestroy(TestCase, FixtureMixin): - def test_cannot_read_config_forced(self): - error = "an error" - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- exception_msg=error, -- ).raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- fail_if_file_not_found=True, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ exception_msg=error, -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ fail_if_file_not_found=True, -+ name="raw_file.remove.cfg", - ) - - commands.config_destroy( -@@ -742,18 +714,14 @@ class ConfigDestroy(TestCase, FixtureMixin): - - def test_config_parse_error(self): - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content="invalid config".encode("utf-8"), -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content="invalid config".encode("utf-8"), - ) - - self.env_assist.assert_raise_library_error( -- lambda: commands.config_destroy( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_destroy(self.env_assist.get_env()), - ) - self.env_assist.assert_reports( - [ -@@ -768,17 +736,16 @@ class ConfigDestroy(TestCase, FixtureMixin): - - def test_config_parse_error_forced(self): - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content="invalid config".encode("utf-8"), -- ).raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- fail_if_file_not_found=True, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content="invalid config".encode("utf-8"), -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ fail_if_file_not_found=True, -+ name="raw_file.remove.cfg", - ) - - commands.config_destroy( -@@ -797,52 +764,45 @@ class ConfigDestroy(TestCase, FixtureMixin): - - def test_key_already_deleted(self): - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- fail_if_file_not_found=False, -- file_not_found_exception=True, -- name="raw_file.remove.key", -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- fail_if_file_not_found=True, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), - ) -- -- commands.config_destroy( -- self.env_assist.get_env(), -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ fail_if_file_not_found=False, -+ file_not_found_exception=True, -+ name="raw_file.remove.key", -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ fail_if_file_not_found=True, -+ name="raw_file.remove.cfg", - ) - -+ commands.config_destroy(self.env_assist.get_env()) -+ - def test_cannot_delete_key(self): - error = "an error" - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ).raw_file.remove( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- fail_if_file_not_found=False, -- exception_msg=error, -- name="raw_file.remove.key", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ fail_if_file_not_found=False, -+ exception_msg=error, -+ name="raw_file.remove.key", - ) - - self.env_assist.assert_raise_library_error( -- lambda: commands.config_destroy( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_destroy(self.env_assist.get_env()), - ) - self.env_assist.assert_reports( - [ -@@ -860,25 +820,23 @@ class ConfigDestroy(TestCase, FixtureMixin): - def test_cannot_delete_key_forced(self): - error = "an error" - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- fail_if_file_not_found=False, -- exception_msg=error, -- name="raw_file.remove.key", -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- fail_if_file_not_found=True, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ fail_if_file_not_found=False, -+ exception_msg=error, -+ name="raw_file.remove.key", -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ fail_if_file_not_found=True, -+ name="raw_file.remove.cfg", - ) - - commands.config_destroy( -@@ -900,31 +858,27 @@ class ConfigDestroy(TestCase, FixtureMixin): - def test_cannot_delete_config_forced(self): - error = "an error" - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- fail_if_file_not_found=False, -- name="raw_file.remove.key", -- ) -- .raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- fail_if_file_not_found=True, -- exception_msg=error, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ fail_if_file_not_found=False, -+ name="raw_file.remove.key", -+ ) -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ fail_if_file_not_found=True, -+ exception_msg=error, -+ name="raw_file.remove.cfg", - ) - - self.env_assist.assert_raise_library_error( -- lambda: commands.config_destroy( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_destroy(self.env_assist.get_env()), - ) - self.env_assist.assert_reports( - [ -@@ -941,22 +895,19 @@ class ConfigDestroy(TestCase, FixtureMixin): - def test_keyfile_outside_of_booth_dir(self): - key_path = "/tmp/pcs_test/booth.key" - self.fixture_config_booth_not_used() -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=f"authfile = {key_path}".encode("utf-8"), -- ).raw_file.remove( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- fail_if_file_not_found=True, -- name="raw_file.remove.cfg", -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=f"authfile = {key_path}".encode("utf-8"), - ) -- -- commands.config_destroy( -- self.env_assist.get_env(), -+ self.config.raw_file.remove( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ fail_if_file_not_found=True, -+ name="raw_file.remove.cfg", - ) -+ -+ commands.config_destroy(self.env_assist.get_env()) - self.env_assist.assert_reports( - [ - fixture.warn( -@@ -987,29 +938,23 @@ class ConfigText(TestCase, FixtureMixin): - - def test_success_default_instance(self): - config_content = "my config content".encode("utf-8") -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=config_content, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=config_content, - ) - self.assertEqual( -- commands.config_text( -- self.env_assist.get_env(), -- ), -+ commands.config_text(self.env_assist.get_env()), - config_content, - ) - - def test_success_custom_instance(self): - instance_name = "my_booth" - config_content = "my config content".encode("utf-8") -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- content=config_content, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ content=config_content, - ) - self.assertEqual( - commands.config_text( -@@ -1029,9 +974,7 @@ class ConfigText(TestCase, FixtureMixin): - } - ) - self.env_assist.assert_raise_library_error( -- lambda: commands.config_text( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_text(self.env_assist.get_env()), - [ - fixture.error( - reports.codes.LIVE_ENVIRONMENT_REQUIRED, -@@ -1046,17 +989,13 @@ class ConfigText(TestCase, FixtureMixin): - - def test_cannot_read_config(self): - error = "an error" -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- exception_msg=error, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ exception_msg=error, - ) - self.env_assist.assert_raise_library_error( -- lambda: commands.config_text( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_text(self.env_assist.get_env()), - ) - self.env_assist.assert_reports( - [ -@@ -1073,12 +1012,10 @@ class ConfigText(TestCase, FixtureMixin): - def test_remote_success(self): - instance_name = "my_booth" - config_content = "my config content" -- ( -- self.config.http.booth.get_config( -- instance_name, -- config_data=config_content, -- node_labels=["node1"], -- ) -+ self.config.http.booth.get_config( -+ instance_name, -+ config_data=config_content, -+ node_labels=["node1"], - ) - self.assertEqual( - commands.config_text( -@@ -1095,22 +1032,19 @@ class ConfigText(TestCase, FixtureMixin): - server_error = ( - "some error like 'config does not exist' or 'instance name invalid'" - ) -- ( -- self.config.http.booth.get_config( -- instance_name, -- communication_list=[ -- dict( -- label=node_name, -- response_code=400, -- output=server_error, -- ) -- ], -- ) -+ self.config.http.booth.get_config( -+ instance_name, -+ communication_list=[ -+ dict( -+ label=node_name, -+ response_code=400, -+ output=server_error, -+ ) -+ ], - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_text( -- self.env_assist.get_env(), -- node_name=node_name, -+ self.env_assist.get_env(), node_name=node_name - ), - ) - self.env_assist.assert_reports( -@@ -1127,21 +1061,18 @@ class ConfigText(TestCase, FixtureMixin): - def test_remote_bad_response(self): - instance_name = "booth" - node_name = "node1" -- ( -- self.config.http.booth.get_config( -- instance_name, -- communication_list=[ -- dict( -- label=node_name, -- output="not a json", -- ) -- ], -- ) -+ self.config.http.booth.get_config( -+ instance_name, -+ communication_list=[ -+ dict( -+ label=node_name, -+ output="not a json", -+ ) -+ ], - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_text( -- self.env_assist.get_env(), -- node_name=node_name, -+ self.env_assist.get_env(), node_name=node_name - ), - ) - self.env_assist.assert_reports( -@@ -1157,23 +1088,20 @@ class ConfigText(TestCase, FixtureMixin): - instance_name = "booth" - node_name = "node1" - error = "an error" -- ( -- self.config.http.booth.get_config( -- instance_name, -- communication_list=[ -- dict( -- label=node_name, -- was_connected=False, -- errno=1, -- error_msg=error, -- ) -- ], -- ) -+ self.config.http.booth.get_config( -+ instance_name, -+ communication_list=[ -+ dict( -+ label=node_name, -+ was_connected=False, -+ errno=1, -+ error_msg=error, -+ ) -+ ], - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_text( -- self.env_assist.get_env(), -- node_name=node_name, -+ self.env_assist.get_env(), node_name=node_name - ), - ) - self.env_assist.assert_reports( -@@ -1208,22 +1136,21 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - ) - - def fixture_config_success(self, instance_name="booth"): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- content=self.fixture_cfg_content( -- self.fixture_key_path(instance_name) -- ), -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- self.fixture_cfg_content( -- self.fixture_key_path(instance_name), -- ticket_list=[["ticketA", []]], -- ), -- can_overwrite=True, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ content=self.fixture_cfg_content( -+ self.fixture_key_path(instance_name) -+ ), -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ self.fixture_cfg_content( -+ self.fixture_key_path(instance_name), -+ ticket_list=[["ticketA", []]], -+ ), -+ can_overwrite=True, - ) - - def test_success_default_instance(self): -@@ -1268,21 +1195,20 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - ) - - def test_success_ticket_options(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content( -- ticket_list=[ -- ["ticketA", [("retries", "10"), ("timeout", "20")]] -- ] -- ), -- can_overwrite=True, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content( -+ ticket_list=[ -+ ["ticketA", [("retries", "10"), ("timeout", "20")]] -+ ] -+ ), -+ can_overwrite=True, - ) - commands.config_ticket_add( - self.env_assist.get_env(), -@@ -1291,12 +1217,10 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - ) - - def test_ticket_already_exists(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(ticket_list=[["ticketA", []]]), -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(ticket_list=[["ticketA", []]]), - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_add( -@@ -1316,12 +1240,10 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - ) - - def test_validator_errors(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_add( -@@ -1356,19 +1278,16 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - ) - - def test_invalid_ticket_options_forced(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content( -- ticket_list=[["ticketA", [("a", "A")]]] -- ), -- can_overwrite=True, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content(ticket_list=[["ticketA", [("a", "A")]]]), -+ can_overwrite=True, - ) - commands.config_ticket_add( - self.env_assist.get_env(), -@@ -1399,12 +1318,10 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - ) - - def test_config_parse_error(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content="invalid config".encode("utf-8"), -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content="invalid config".encode("utf-8"), - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_add( -@@ -1425,12 +1342,10 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - - def test_cannot_read_config(self): - error = "an error" -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- exception_msg=error, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ exception_msg=error, - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_add( -@@ -1480,18 +1395,17 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - - def test_cannot_write_config(self): - error = "an error" -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content(ticket_list=[["ticketA", []]]), -- can_overwrite=True, -- exception_msg=error, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content(ticket_list=[["ticketA", []]]), -+ can_overwrite=True, -+ exception_msg=error, - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_add( -@@ -1532,46 +1446,40 @@ class ConfigTicketRemove(TestCase, FixtureMixin): - ) - - def fixture_config_success(self, instance_name="booth"): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- self.fixture_cfg_content( -- self.fixture_key_path(instance_name), -- ticket_list=[ -- ["ticketA", []], -- ["ticketB", []], -- ["ticketC", []], -- ], -- ), -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- self.fixture_cfg_content( -- self.fixture_key_path(instance_name), -- ticket_list=[ -- ["ticketA", []], -- ["ticketC", []], -- ], -- ), -- can_overwrite=True, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ self.fixture_cfg_content( -+ self.fixture_key_path(instance_name), -+ ticket_list=[ -+ ["ticketA", []], -+ ["ticketB", []], -+ ["ticketC", []], -+ ], -+ ), -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ self.fixture_cfg_content( -+ self.fixture_key_path(instance_name), -+ ticket_list=[ -+ ["ticketA", []], -+ ["ticketC", []], -+ ], -+ ), -+ can_overwrite=True, - ) - - def test_success_default_instance(self): - self.fixture_config_success() -- commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -- ) -+ commands.config_ticket_remove(self.env_assist.get_env(), "ticketB") - - def test_success_custom_instance(self): - instance_name = "my_booth" - self.fixture_config_success(instance_name=instance_name) - commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -- instance_name=instance_name, -+ self.env_assist.get_env(), "ticketB", instance_name=instance_name - ) - - def test_success_not_live(self): -@@ -1602,51 +1510,44 @@ class ConfigTicketRemove(TestCase, FixtureMixin): - ) - - def test_success_ticket_options(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content( -- ticket_list=[ -- ["ticketA", [("a1", "A1"), ("a2", "A2")]], -- ["ticketB", [("b1", "B1"), ("b2", "B2")]], -- ["ticketC", [("c1", "C1"), ("c2", "C2")]], -- ] -- ), -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content( -- ticket_list=[ -- ["ticketA", [("a1", "A1"), ("a2", "A2")]], -- ["ticketC", [("c1", "C1"), ("c2", "C2")]], -- ] -- ), -- can_overwrite=True, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content( -+ ticket_list=[ -+ ["ticketA", [("a1", "A1"), ("a2", "A2")]], -+ ["ticketB", [("b1", "B1"), ("b2", "B2")]], -+ ["ticketC", [("c1", "C1"), ("c2", "C2")]], -+ ] -+ ), - ) -- commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content( -+ ticket_list=[ -+ ["ticketA", [("a1", "A1"), ("a2", "A2")]], -+ ["ticketC", [("c1", "C1"), ("c2", "C2")]], -+ ] -+ ), -+ can_overwrite=True, - ) -+ commands.config_ticket_remove(self.env_assist.get_env(), "ticketB") - - def test_ticket_does_not_exist(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content( -- ticket_list=[ -- ["ticketA", []], -- ["ticketC", []], -- ] -- ), -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content( -+ ticket_list=[ -+ ["ticketA", []], -+ ["ticketC", []], -+ ] -+ ), - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -+ self.env_assist.get_env(), "ticketB" - ) - ) - self.env_assist.assert_reports( -@@ -1659,17 +1560,14 @@ class ConfigTicketRemove(TestCase, FixtureMixin): - ) - - def test_config_parse_error(self): -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content="invalid config".encode("utf-8"), -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content="invalid config".encode("utf-8"), - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -+ self.env_assist.get_env(), "ticketB" - ) - ) - self.env_assist.assert_reports( -@@ -1684,17 +1582,14 @@ class ConfigTicketRemove(TestCase, FixtureMixin): - - def test_cannot_read_config(self): - error = "an error" -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- exception_msg=error, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ exception_msg=error, - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -+ self.env_assist.get_env(), "ticketB" - ) - ) - self.env_assist.assert_reports( -@@ -1719,8 +1614,7 @@ class ConfigTicketRemove(TestCase, FixtureMixin): - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -+ self.env_assist.get_env(), "ticketB" - ) - ) - self.env_assist.assert_reports( -@@ -1737,27 +1631,25 @@ class ConfigTicketRemove(TestCase, FixtureMixin): - - def test_cannot_write_config(self): - error = "an error" -- ( -- self.config.raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content( -- ticket_list=[ -- ["ticketB", []], -- ] -- ), -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- self.fixture_cfg_content(), -- can_overwrite=True, -- exception_msg=error, -- ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content( -+ ticket_list=[ -+ ["ticketB", []], -+ ] -+ ), -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ self.fixture_cfg_content(), -+ can_overwrite=True, -+ exception_msg=error, - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_ticket_remove( -- self.env_assist.get_env(), -- "ticketB", -+ self.env_assist.get_env(), "ticketB" - ) - ) - self.env_assist.assert_reports( -@@ -1792,40 +1684,35 @@ class CreateInCluster(TestCase, FixtureMixin): - ) - - def fixture_config_success(self, instance_name="booth"): -- ( -- self.config.runner.cib.load() -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- content=self.fixture_cfg_content( -- self.fixture_key_path(instance_name) -- ), -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:heartbeat:IPaddr2", -- name="runner.pcmk.load_agent.ipaddr2", -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:pacemaker:booth-site", -- name="runner.pcmk.load_agent.booth-site", -- ) -- .env.push_cib(resources=self.fixture_cib_booth_group(instance_name)) -+ self.config.runner.cib.load() -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ content=self.fixture_cfg_content( -+ self.fixture_key_path(instance_name) -+ ), -+ ) -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:heartbeat:IPaddr2", -+ name="runner.pcmk.load_agent.ipaddr2", -+ ) -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:pacemaker:booth-site", -+ name="runner.pcmk.load_agent.booth-site", -+ ) -+ self.config.env.push_cib( -+ resources=self.fixture_cib_booth_group(instance_name) - ) - - def test_success_default_instance(self): - self.fixture_config_success() -- commands.create_in_cluster( -- self.env_assist.get_env(), -- self.site_ip, -- ) -+ commands.create_in_cluster(self.env_assist.get_env(), self.site_ip) - - def test_success_custom_instance(self): - instance_name = "my_booth" - self.fixture_config_success(instance_name=instance_name) - commands.create_in_cluster( -- self.env_assist.get_env(), -- self.site_ip, -- instance_name=instance_name, -+ self.env_assist.get_env(), self.site_ip, instance_name=instance_name - ) - - def test_success_not_live_cib(self): -@@ -1833,29 +1720,24 @@ class CreateInCluster(TestCase, FixtureMixin): - env = dict(CIB_file=tmp_file) - with open(rc("cib-empty.xml")) as cib_file: - self.config.env.set_cib_data(cib_file.read(), cib_tempfile=tmp_file) -- ( -- self.config.runner.cib.load(env=env) -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:heartbeat:IPaddr2", -- name="runner.pcmk.load_agent.ipaddr2", -- env=env, -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:pacemaker:booth-site", -- name="runner.pcmk.load_agent.booth-site", -- env=env, -- ) -- .env.push_cib(resources=self.fixture_cib_booth_group()) -+ self.config.runner.cib.load(env=env) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), - ) -- commands.create_in_cluster( -- self.env_assist.get_env(), -- self.site_ip, -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:heartbeat:IPaddr2", -+ name="runner.pcmk.load_agent.ipaddr2", -+ env=env, - ) -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:pacemaker:booth-site", -+ name="runner.pcmk.load_agent.booth-site", -+ env=env, -+ ) -+ self.config.env.push_cib(resources=self.fixture_cib_booth_group()) -+ commands.create_in_cluster(self.env_assist.get_env(), self.site_ip) - - def test_not_live_booth(self): - self.config.env.set_booth( -@@ -1867,8 +1749,7 @@ class CreateInCluster(TestCase, FixtureMixin): - ) - self.env_assist.assert_raise_library_error( - lambda: commands.create_in_cluster( -- self.env_assist.get_env(), -- self.site_ip, -+ self.env_assist.get_env(), self.site_ip - ), - [ - fixture.error( -@@ -1883,19 +1764,15 @@ class CreateInCluster(TestCase, FixtureMixin): - ) - - def test_booth_resource_already_created(self): -- ( -- self.config.runner.cib.load( -- resources=self.fixture_cib_booth_group() -- ).raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -+ self.config.runner.cib.load(resources=self.fixture_cib_booth_group()) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), - ) - self.env_assist.assert_raise_library_error( - lambda: commands.create_in_cluster( -- self.env_assist.get_env(), -- self.site_ip, -+ self.env_assist.get_env(), self.site_ip - ) - ) - self.env_assist.assert_reports( -@@ -1904,17 +1781,14 @@ class CreateInCluster(TestCase, FixtureMixin): - - def test_booth_config_does_not_exist(self): - error = "an error" -- ( -- self.config.runner.cib.load().raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- exception_msg=error, -- ) -+ self.config.runner.cib.load().raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ exception_msg=error, - ) - self.env_assist.assert_raise_library_error( - lambda: commands.create_in_cluster( -- self.env_assist.get_env(), -- self.site_ip, -+ self.env_assist.get_env(), self.site_ip - ) - ) - self.env_assist.assert_reports( -@@ -1959,28 +1833,25 @@ class CreateInCluster(TestCase, FixtureMixin): - ) - - def test_booth_agent_missing(self): -- ( -- self.config.runner.cib.load() -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:heartbeat:IPaddr2", -- name="runner.pcmk.load_agent.ipaddr2", -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:pacemaker:booth-site", -- agent_is_missing=True, -- name="runner.pcmk.load_agent.booth-site", -- stderr=REASON, -- ) -+ self.config.runner.cib.load() -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:heartbeat:IPaddr2", -+ name="runner.pcmk.load_agent.ipaddr2", -+ ) -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:pacemaker:booth-site", -+ agent_is_missing=True, -+ name="runner.pcmk.load_agent.booth-site", -+ stderr=REASON, - ) - self.env_assist.assert_raise_library_error( - lambda: commands.create_in_cluster( -- self.env_assist.get_env(), -- self.site_ip, -+ self.env_assist.get_env(), self.site_ip - ) - ) - self.env_assist.assert_reports( -@@ -1993,30 +1864,28 @@ class CreateInCluster(TestCase, FixtureMixin): - ), - ] - ) -- -- def test_agents_missing_forced(self): -- ( -- self.config.runner.cib.load() -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:heartbeat:IPaddr2", -- agent_is_missing=True, -- name="runner.pcmk.load_agent.ipaddr2", -- stderr=REASON, -- ) -- .runner.pcmk.load_agent( -- agent_name="ocf:pacemaker:booth-site", -- agent_is_missing=True, -- name="runner.pcmk.load_agent.booth-site", -- stderr=REASON, -- ) -- .env.push_cib( -- resources=self.fixture_cib_booth_group(default_operations=True) -- ) -+ -+ def test_agents_missing_forced(self): -+ self.config.runner.cib.load() -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ ) -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:heartbeat:IPaddr2", -+ agent_is_missing=True, -+ name="runner.pcmk.load_agent.ipaddr2", -+ stderr=REASON, -+ ) -+ self.config.runner.pcmk.load_agent( -+ agent_name="ocf:pacemaker:booth-site", -+ agent_is_missing=True, -+ name="runner.pcmk.load_agent.booth-site", -+ stderr=REASON, -+ ) -+ self.config.env.push_cib( -+ resources=self.fixture_cib_booth_group(default_operations=True) - ) - commands.create_in_cluster( - self.env_assist.get_env(), -@@ -2061,10 +1930,9 @@ class RemoveFromCluster(TestCase, FixtureMixin): - self.resource_remove.assert_not_called() - - def test_success_default_instance(self): -- (self.config.runner.cib.load(resources=self.fixture_cib_booth_group())) -+ self.config.runner.cib.load(resources=self.fixture_cib_booth_group()) - commands.remove_from_cluster( -- self.env_assist.get_env(), -- self.resource_remove, -+ self.env_assist.get_env(), self.resource_remove - ) - self.resource_remove.assert_has_calls( - [ -@@ -2075,10 +1943,8 @@ class RemoveFromCluster(TestCase, FixtureMixin): - - def test_success_custom_instance(self): - instance_name = "my_booth" -- ( -- self.config.runner.cib.load( -- resources=self.fixture_cib_booth_group(instance_name) -- ) -+ self.config.runner.cib.load( -+ resources=self.fixture_cib_booth_group(instance_name) - ) - commands.remove_from_cluster( - self.env_assist.get_env(), -@@ -2099,16 +1965,12 @@ class RemoveFromCluster(TestCase, FixtureMixin): - cib_xml_man.append_to_first_tag_name( - "resources", self.fixture_cib_booth_group(wrap_in_resources=False) - ) -- ( -- self.config -- # This makes env.is_cib_live return False -- .env.set_cib_data(str(cib_xml_man), cib_tempfile=tmp_file) -- # This instructs the runner to actually return our mocked cib -- .runner.cib.load_content(str(cib_xml_man), env=env) -- ) -+ # This makes env.is_cib_live return False -+ self.config.env.set_cib_data(str(cib_xml_man), cib_tempfile=tmp_file) -+ # This instructs the runner to actually return our mocked cib -+ self.config.runner.cib.load_content(str(cib_xml_man), env=env) - commands.remove_from_cluster( -- self.env_assist.get_env(), -- self.resource_remove, -+ self.env_assist.get_env(), self.resource_remove - ) - self.resource_remove.assert_has_calls( - [ -@@ -2127,8 +1989,7 @@ class RemoveFromCluster(TestCase, FixtureMixin): - ) - self.env_assist.assert_raise_library_error( - lambda: commands.remove_from_cluster( -- self.env_assist.get_env(), -- self.resource_remove, -+ self.env_assist.get_env(), self.resource_remove - ), - [ - fixture.error( -@@ -2147,8 +2008,7 @@ class RemoveFromCluster(TestCase, FixtureMixin): - (self.config.runner.cib.load()) - self.env_assist.assert_raise_library_error( - lambda: commands.remove_from_cluster( -- self.env_assist.get_env(), -- self.resource_remove, -+ self.env_assist.get_env(), self.resource_remove - ), - ) - self.env_assist.assert_reports( -@@ -2162,15 +2022,10 @@ class RemoveFromCluster(TestCase, FixtureMixin): - self.resource_remove.assert_not_called() - - def test_more_booth_resources(self): -- ( -- self.config.runner.cib.load( -- resources=self.fixture_cib_more_resources() -- ) -- ) -+ self.config.runner.cib.load(resources=self.fixture_cib_more_resources()) - self.env_assist.assert_raise_library_error( - lambda: commands.remove_from_cluster( -- self.env_assist.get_env(), -- self.resource_remove, -+ self.env_assist.get_env(), self.resource_remove - ), - ) - self.env_assist.assert_reports( -@@ -2185,11 +2040,7 @@ class RemoveFromCluster(TestCase, FixtureMixin): - self.resource_remove.assert_not_called() - - def test_more_booth_resources_forced(self): -- ( -- self.config.runner.cib.load( -- resources=self.fixture_cib_more_resources() -- ) -- ) -+ self.config.runner.cib.load(resources=self.fixture_cib_more_resources()) - commands.remove_from_cluster( - self.env_assist.get_env(), - self.resource_remove, -@@ -2234,11 +2085,8 @@ class Restart(TestCase, FixtureMixin): - self.resource_restart.assert_not_called() - - def test_success_default_instance(self): -- (self.config.runner.cib.load(resources=self.fixture_cib_booth_group())) -- commands.restart( -- self.env_assist.get_env(), -- self.resource_restart, -- ) -+ self.config.runner.cib.load(resources=self.fixture_cib_booth_group()) -+ commands.restart(self.env_assist.get_env(), self.resource_restart) - self.resource_restart.assert_has_calls( - [ - mock.call(["booth-booth-service"]), -@@ -2247,10 +2095,8 @@ class Restart(TestCase, FixtureMixin): - - def test_success_custom_instance(self): - instance_name = "my_booth" -- ( -- self.config.runner.cib.load( -- resources=self.fixture_cib_booth_group(instance_name) -- ) -+ self.config.runner.cib.load( -+ resources=self.fixture_cib_booth_group(instance_name) - ) - commands.restart( - self.env_assist.get_env(), -@@ -2274,8 +2120,7 @@ class Restart(TestCase, FixtureMixin): - self.config.env.set_cib_data("") - self.env_assist.assert_raise_library_error( - lambda: commands.restart( -- self.env_assist.get_env(), -- self.resource_restart, -+ self.env_assist.get_env(), self.resource_restart - ), - [ - fixture.error( -@@ -2295,8 +2140,7 @@ class Restart(TestCase, FixtureMixin): - (self.config.runner.cib.load()) - self.env_assist.assert_raise_library_error( - lambda: commands.restart( -- self.env_assist.get_env(), -- self.resource_restart, -+ self.env_assist.get_env(), self.resource_restart - ), - ) - self.env_assist.assert_reports( -@@ -2310,15 +2154,10 @@ class Restart(TestCase, FixtureMixin): - self.resource_restart.assert_not_called() - - def test_more_booth_resources(self): -- ( -- self.config.runner.cib.load( -- resources=self.fixture_cib_more_resources() -- ) -- ) -+ self.config.runner.cib.load(resources=self.fixture_cib_more_resources()) - self.env_assist.assert_raise_library_error( - lambda: commands.restart( -- self.env_assist.get_env(), -- self.resource_restart, -+ self.env_assist.get_env(), self.resource_restart - ), - ) - self.env_assist.assert_reports( -@@ -2333,11 +2172,7 @@ class Restart(TestCase, FixtureMixin): - self.resource_restart.assert_not_called() - - def test_more_booth_resources_forced(self): -- ( -- self.config.runner.cib.load( -- resources=self.fixture_cib_more_resources() -- ) -- ) -+ self.config.runner.cib.load(resources=self.fixture_cib_more_resources()) - commands.restart( - self.env_assist.get_env(), - self.resource_restart, -@@ -2390,10 +2225,7 @@ class TicketGrantRevokeMixin(FixtureMixin): - ) - self.config.env.set_cib_data("") - self.env_assist.assert_raise_library_error( -- lambda: self.command( -- self.env_assist.get_env(), -- self.ticket, -- ), -+ lambda: self.command(self.env_assist.get_env(), self.ticket), - [ - fixture.error( - reports.codes.LIVE_ENVIRONMENT_REQUIRED, -@@ -2410,26 +2242,18 @@ class TicketGrantRevokeMixin(FixtureMixin): - def test_success_site_ip_specified(self): - self.get_booth_call()(self.ticket, self.site_ip) - self.command( -- self.env_assist.get_env(), -- self.ticket, -- site_ip=self.site_ip, -+ self.env_assist.get_env(), self.ticket, site_ip=self.site_ip - ) - - def test_success_site_ip_not_specified(self): - self.config.runner.cib.load(resources=self.fixture_cib_booth_group()) - self.get_booth_call()(self.ticket, self.site_ip) -- self.command( -- self.env_assist.get_env(), -- self.ticket, -- ) -+ self.command(self.env_assist.get_env(), self.ticket) - - def test_cannot_find_site_ip(self): - self.config.runner.cib.load() - self.env_assist.assert_raise_library_error( -- lambda: self.command( -- self.env_assist.get_env(), -- self.ticket, -- ), -+ lambda: self.command(self.env_assist.get_env(), self.ticket), - [ - fixture.error( - reports.codes.BOOTH_CANNOT_DETERMINE_LOCAL_SITE_IP, -@@ -2448,9 +2272,7 @@ class TicketGrantRevokeMixin(FixtureMixin): - ) - self.env_assist.assert_raise_library_error( - lambda: self.command( -- self.env_assist.get_env(), -- self.ticket, -- site_ip=self.site_ip, -+ self.env_assist.get_env(), self.ticket, site_ip=self.site_ip - ), - [ - fixture.error( -@@ -2496,34 +2318,30 @@ class ConfigSyncTest(TestCase, FixtureMixin): - self.fixture_key_path(instance_name) - ) - self.fixture_config_read_success(instance_name=instance_name) -- ( -- self.config.http.booth.send_config( -- instance_name, -- config_content.decode("utf-8"), -- authfile=os.path.basename(self.fixture_key_path(instance_name)), -- authfile_data=RANDOM_KEY, -- node_labels=self.node_list, -- ) -+ self.config.http.booth.send_config( -+ instance_name, -+ config_content.decode("utf-8"), -+ authfile=os.path.basename(self.fixture_key_path(instance_name)), -+ authfile_data=RANDOM_KEY, -+ node_labels=self.node_list, - ) - - def fixture_config_read_success(self, instance_name="booth"): - config_content = self.fixture_cfg_content( - self.fixture_key_path(instance_name) - ) -- ( -- self.config.corosync_conf.load() -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(instance_name), -- content=config_content, -- name="raw_file.read.conf", -- ) -- .raw_file.read( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(instance_name), -- content=RANDOM_KEY, -- name="raw_file.read.key", -- ) -+ self.config.corosync_conf.load() -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(instance_name), -+ content=config_content, -+ name="raw_file.read.conf", -+ ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(instance_name), -+ content=RANDOM_KEY, -+ name="raw_file.read.key", - ) - - def fixture_reports_success(self, instance_name="booth"): -@@ -2552,30 +2370,23 @@ class ConfigSyncTest(TestCase, FixtureMixin): - - def test_success_default_instance(self): - self.fixture_config_success() -- commands.config_sync( -- self.env_assist.get_env(), -- ) -+ commands.config_sync(self.env_assist.get_env()) - self.env_assist.assert_reports(self.fixture_reports_success()) - - def test_success_custom_instance(self): - instance_name = "my_booth" - self.fixture_config_success(instance_name=instance_name) - commands.config_sync( -- self.env_assist.get_env(), -- instance_name=instance_name, -+ self.env_assist.get_env(), instance_name=instance_name - ) - self.env_assist.assert_reports( -- self.fixture_reports_success( -- instance_name=instance_name, -- ) -+ self.fixture_reports_success(instance_name=instance_name) - ) - - def test_not_live_cib(self): - self.config.env.set_cib_data("") - self.env_assist.assert_raise_library_error( -- lambda: commands.config_sync( -- self.env_assist.get_env(), -- ), -+ lambda: commands.config_sync(self.env_assist.get_env()), - [ - fixture.error( - reports.codes.LIVE_ENVIRONMENT_REQUIRED, -@@ -2596,56 +2407,45 @@ class ConfigSyncTest(TestCase, FixtureMixin): - "key_path": "some key path", - } - ) -- ( -- self.config.corosync_conf.load( -- node_name_list=self.node_list -- ).http.booth.send_config( -- instance_name, -- config_data.decode("utf-8"), -- authfile=os.path.basename(key_path), -- authfile_data=key_data, -- node_labels=self.node_list, -- ) -+ self.config.corosync_conf.load(node_name_list=self.node_list) -+ self.config.http.booth.send_config( -+ instance_name, -+ config_data.decode("utf-8"), -+ authfile=os.path.basename(key_path), -+ authfile_data=key_data, -+ node_labels=self.node_list, - ) - - def test_not_live_booth_default_instance(self): - self.fixture_config_success_not_live() -- commands.config_sync( -- self.env_assist.get_env(), -- ) -+ commands.config_sync(self.env_assist.get_env()) - self.env_assist.assert_reports(self.fixture_reports_success()) - - def test_not_live_booth_custom_instance(self): - instance_name = "my_booth" - self.fixture_config_success_not_live(instance_name=instance_name) - commands.config_sync( -- self.env_assist.get_env(), -- instance_name=instance_name, -+ self.env_assist.get_env(), instance_name=instance_name - ) - self.env_assist.assert_reports( -- self.fixture_reports_success( -- instance_name=instance_name, -- ) -+ self.fixture_reports_success(instance_name=instance_name) - ) - - def test_some_node_names_missing(self): - nodes = ["rh7-2"] - self.fixture_config_read_success() -- ( -- self.config.corosync_conf.load( -- filename="corosync-some-node-names.conf", -- instead="corosync_conf.load", -- ).http.booth.send_config( -- "booth", -- self.fixture_cfg_content().decode("utf-8"), -- authfile=os.path.basename(self.fixture_key_path()), -- authfile_data=RANDOM_KEY, -- node_labels=nodes, -- ) -+ self.config.corosync_conf.load( -+ filename="corosync-some-node-names.conf", -+ instead="corosync_conf.load", - ) -- commands.config_sync( -- self.env_assist.get_env(), -+ self.config.http.booth.send_config( -+ "booth", -+ self.fixture_cfg_content().decode("utf-8"), -+ authfile=os.path.basename(self.fixture_key_path()), -+ authfile_data=RANDOM_KEY, -+ node_labels=nodes, - ) -+ commands.config_sync(self.env_assist.get_env()) - self.env_assist.assert_reports( - [ - fixture.info(reports.codes.BOOTH_CONFIG_DISTRIBUTION_STARTED), -@@ -2666,11 +2466,9 @@ class ConfigSyncTest(TestCase, FixtureMixin): - - def test_all_node_names_missing(self): - self.fixture_config_read_success() -- ( -- self.config.corosync_conf.load( -- filename="corosync-no-node-names.conf", -- instead="corosync_conf.load", -- ) -+ self.config.corosync_conf.load( -+ filename="corosync-no-node-names.conf", -+ instead="corosync_conf.load", - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_sync(self.env_assist.get_env()) -@@ -2687,23 +2485,21 @@ class ConfigSyncTest(TestCase, FixtureMixin): - - def test_node_failure(self): - self.fixture_config_read_success() -- ( -- self.config.http.booth.send_config( -- "booth", -- self.fixture_cfg_content().decode("utf-8"), -- authfile=os.path.basename(self.fixture_key_path()), -- authfile_data=RANDOM_KEY, -- communication_list=[ -- dict( -- label=self.node_list[0], -- response_code=400, -- output=self.reason, -- ), -- dict( -- label=self.node_list[1], -- ), -- ], -- ) -+ self.config.http.booth.send_config( -+ "booth", -+ self.fixture_cfg_content().decode("utf-8"), -+ authfile=os.path.basename(self.fixture_key_path()), -+ authfile_data=RANDOM_KEY, -+ communication_list=[ -+ dict( -+ label=self.node_list[0], -+ response_code=400, -+ output=self.reason, -+ ), -+ dict( -+ label=self.node_list[1], -+ ), -+ ], - ) - self.env_assist.assert_raise_library_error( - lambda: commands.config_sync(self.env_assist.get_env()), [] -@@ -2728,23 +2524,21 @@ class ConfigSyncTest(TestCase, FixtureMixin): - - def test_node_failure_skip_offline(self): - self.fixture_config_read_success() -- ( -- self.config.http.booth.send_config( -- "booth", -- self.fixture_cfg_content().decode("utf-8"), -- authfile=os.path.basename(self.fixture_key_path()), -- authfile_data=RANDOM_KEY, -- communication_list=[ -- dict( -- label=self.node_list[0], -- response_code=400, -- output=self.reason, -- ), -- dict( -- label=self.node_list[1], -- ), -- ], -- ) -+ self.config.http.booth.send_config( -+ "booth", -+ self.fixture_cfg_content().decode("utf-8"), -+ authfile=os.path.basename(self.fixture_key_path()), -+ authfile_data=RANDOM_KEY, -+ communication_list=[ -+ dict( -+ label=self.node_list[0], -+ response_code=400, -+ output=self.reason, -+ ), -+ dict( -+ label=self.node_list[1], -+ ), -+ ], - ) - - commands.config_sync(self.env_assist.get_env(), skip_offline_nodes=True) -@@ -2767,24 +2561,22 @@ class ConfigSyncTest(TestCase, FixtureMixin): - - def test_node_offline(self): - self.fixture_config_read_success() -- ( -- self.config.http.booth.send_config( -- "booth", -- self.fixture_cfg_content().decode("utf-8"), -- authfile=os.path.basename(self.fixture_key_path()), -- authfile_data=RANDOM_KEY, -- communication_list=[ -- dict( -- label=self.node_list[0], -- errno=1, -- error_msg=self.reason, -- was_connected=False, -- ), -- dict( -- label=self.node_list[1], -- ), -- ], -- ) -+ self.config.http.booth.send_config( -+ "booth", -+ self.fixture_cfg_content().decode("utf-8"), -+ authfile=os.path.basename(self.fixture_key_path()), -+ authfile_data=RANDOM_KEY, -+ communication_list=[ -+ dict( -+ label=self.node_list[0], -+ errno=1, -+ error_msg=self.reason, -+ was_connected=False, -+ ), -+ dict( -+ label=self.node_list[1], -+ ), -+ ], - ) - - self.env_assist.assert_raise_library_error( -@@ -2810,24 +2602,22 @@ class ConfigSyncTest(TestCase, FixtureMixin): - - def test_node_offline_skip_offline(self): - self.fixture_config_read_success() -- ( -- self.config.http.booth.send_config( -- "booth", -- self.fixture_cfg_content().decode("utf-8"), -- authfile=os.path.basename(self.fixture_key_path()), -- authfile_data=RANDOM_KEY, -- communication_list=[ -- dict( -- label=self.node_list[0], -- errno=1, -- error_msg=self.reason, -- was_connected=False, -- ), -- dict( -- label=self.node_list[1], -- ), -- ], -- ) -+ self.config.http.booth.send_config( -+ "booth", -+ self.fixture_cfg_content().decode("utf-8"), -+ authfile=os.path.basename(self.fixture_key_path()), -+ authfile_data=RANDOM_KEY, -+ communication_list=[ -+ dict( -+ label=self.node_list[0], -+ errno=1, -+ error_msg=self.reason, -+ was_connected=False, -+ ), -+ dict( -+ label=self.node_list[1], -+ ), -+ ], - ) - - commands.config_sync(self.env_assist.get_env(), skip_offline_nodes=True) -@@ -2849,15 +2639,13 @@ class ConfigSyncTest(TestCase, FixtureMixin): - ) - - def test_config_not_accessible(self): -- ( -- self.config.corosync_conf.load().raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- exception_msg=self.reason, -- ) -+ self.config.corosync_conf.load().raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ exception_msg=self.reason, - ) - self.env_assist.assert_raise_library_error( -- lambda: commands.config_sync(self.env_assist.get_env()), -+ lambda: commands.config_sync(self.env_assist.get_env()) - ) - self.env_assist.assert_reports( - [ -@@ -2881,7 +2669,7 @@ class ConfigSyncTest(TestCase, FixtureMixin): - ) - (self.config.corosync_conf.load()) - self.env_assist.assert_raise_library_error( -- lambda: commands.config_sync(self.env_assist.get_env()), -+ lambda: commands.config_sync(self.env_assist.get_env()) - ) - self.env_assist.assert_reports( - [ -@@ -2896,15 +2684,13 @@ class ConfigSyncTest(TestCase, FixtureMixin): - ) - - def test_config_parse_error(self): -- ( -- self.config.corosync_conf.load().raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content="invalid config".encode("utf-8"), -- ) -+ self.config.corosync_conf.load().raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content="invalid config".encode("utf-8"), - ) - self.env_assist.assert_raise_library_error( -- lambda: commands.config_sync(self.env_assist.get_env()), -+ lambda: commands.config_sync(self.env_assist.get_env()) - ) - self.env_assist.assert_reports( - [ -@@ -2924,9 +2710,9 @@ class ConfigSyncTest(TestCase, FixtureMixin): - "key_path": "some key path", - } - ) -- (self.config.corosync_conf.load()) -+ self.config.corosync_conf.load() - self.env_assist.assert_raise_library_error( -- lambda: commands.config_sync(self.env_assist.get_env()), -+ lambda: commands.config_sync(self.env_assist.get_env()) - ) - self.env_assist.assert_reports( - [ -@@ -2939,23 +2725,21 @@ class ConfigSyncTest(TestCase, FixtureMixin): - ) - - def test_authfile_not_accessible(self): -- ( -- self.config.corosync_conf.load() -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=self.fixture_cfg_content(), -- name="raw_file.read.conf", -- ) -- .raw_file.read( -- file_type_codes.BOOTH_KEY, -- self.fixture_key_path(), -- exception_msg=self.reason, -- name="raw_file.read.key", -- ) -+ self.config.corosync_conf.load() -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=self.fixture_cfg_content(), -+ name="raw_file.read.conf", -+ ) -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_KEY, -+ self.fixture_key_path(), -+ exception_msg=self.reason, -+ name="raw_file.read.key", - ) - self.env_assist.assert_raise_library_error( -- lambda: commands.config_sync(self.env_assist.get_env()), -+ lambda: commands.config_sync(self.env_assist.get_env()) - ) - self.env_assist.assert_reports( - [ -@@ -2977,9 +2761,9 @@ class ConfigSyncTest(TestCase, FixtureMixin): - "key_path": "some key path", - } - ) -- (self.config.corosync_conf.load()) -+ self.config.corosync_conf.load() - self.env_assist.assert_raise_library_error( -- lambda: commands.config_sync(self.env_assist.get_env()), -+ lambda: commands.config_sync(self.env_assist.get_env()) - ) - self.env_assist.assert_reports( - [ -@@ -2994,22 +2778,22 @@ class ConfigSyncTest(TestCase, FixtureMixin): - ) - - def test_no_authfile(self): -- ( -- self.config.corosync_conf.load() -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=bytes(), -- ) -- .http.booth.send_config( -- "booth", -- bytes().decode("utf-8"), -- node_labels=self.node_list, -- ) -+ self.config.corosync_conf.load() -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=bytes(), -+ ) -+ self.config.http.booth.send_config( -+ "booth", -+ bytes().decode("utf-8"), -+ node_labels=self.node_list, - ) - commands.config_sync(self.env_assist.get_env()) - self.env_assist.assert_reports( -- [fixture.info(reports.codes.BOOTH_CONFIG_DISTRIBUTION_STARTED)] -+ [ -+ fixture.info(reports.codes.BOOTH_CONFIG_DISTRIBUTION_STARTED), -+ ] - + [ - fixture.info( - reports.codes.BOOTH_CONFIG_ACCEPTED_BY_NODE, -@@ -3022,18 +2806,14 @@ class ConfigSyncTest(TestCase, FixtureMixin): - - def test_authfile_not_in_booth_dir(self): - config_content = "authfile=/etc/my_booth.key" -- ( -- self.config.corosync_conf.load() -- .raw_file.read( -- file_type_codes.BOOTH_CONFIG, -- self.fixture_cfg_path(), -- content=config_content.encode("utf-8"), -- ) -- .http.booth.send_config( -- "booth", -- config_content, -- node_labels=self.node_list, -- ) -+ self.config.corosync_conf.load() -+ self.config.raw_file.read( -+ file_type_codes.BOOTH_CONFIG, -+ self.fixture_cfg_path(), -+ content=config_content.encode("utf-8"), -+ ) -+ self.config.http.booth.send_config( -+ "booth", config_content, node_labels=self.node_list - ) - commands.config_sync(self.env_assist.get_env()) - self.env_assist.assert_reports( -@@ -3096,9 +2876,7 @@ class EnableDisableStartStopMixin(FixtureMixin): - ) - self.config.env.set_cib_data("") - self.env_assist.assert_raise_library_error( -- lambda: self.command( -- self.env_assist.get_env(), -- ), -+ lambda: self.command(self.env_assist.get_env()), - [ - fixture.error( - reports.codes.LIVE_ENVIRONMENT_REQUIRED, -@@ -3114,9 +2892,7 @@ class EnableDisableStartStopMixin(FixtureMixin): - - def test_success_default_instance(self): - self.get_external_call()("booth", instance="booth") -- self.command( -- self.env_assist.get_env(), -- ) -+ self.command(self.env_assist.get_env()) - self.env_assist.assert_reports( - [ - fixture.info( -@@ -3149,9 +2925,7 @@ class EnableDisableStartStopMixin(FixtureMixin): - err_msg = "some stderr\nsome stdout" - self.get_external_call()("booth", instance="booth", failure_msg=err_msg) - self.env_assist.assert_raise_library_error( -- lambda: self.command( -- self.env_assist.get_env(), -- ), -+ lambda: self.command(self.env_assist.get_env()), - [ - fixture.error( - reports.codes.SERVICE_ACTION_FAILED, -@@ -3226,17 +3000,16 @@ class PullConfigBase(TestCase, FixtureMixin): - class PullConfigSuccess(PullConfigBase): - def setUp(self): - super().setUp() -- ( -- self.config.http.booth.get_config( -- self.name, -- self.config_data.decode("utf-8"), -- node_labels=[self.node_name], -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.config_path, -- self.config_data, -- can_overwrite=True, -- ) -+ self.config.http.booth.get_config( -+ self.name, -+ self.config_data.decode("utf-8"), -+ node_labels=[self.node_name], -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.config_path, -+ self.config_data, -+ can_overwrite=True, - ) - - def test_success(self): -@@ -3265,17 +3038,16 @@ class PullConfigSuccessCustomInstance(TestCase, FixtureMixin): - ] - - def test_success(self): -- ( -- self.config.http.booth.get_config( -- self.name, -- self.config_data.decode("utf-8"), -- node_labels=[self.node_name], -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.config_path, -- self.config_data, -- can_overwrite=True, -- ) -+ self.config.http.booth.get_config( -+ self.name, -+ self.config_data.decode("utf-8"), -+ node_labels=[self.node_name], -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.config_path, -+ self.config_data, -+ can_overwrite=True, - ) - commands.pull_config( - self.env_assist.get_env(), -@@ -3514,34 +3286,31 @@ class PullConfigWithAuthfile(PullConfigBase): - self.authfile = os.path.basename(self.authfile_path) - self.authfile_data = b"auth" - -- ( -- self.config.http.booth.get_config( -- self.name, -- self.config_data.decode("utf-8"), -- authfile=self.authfile, -- authfile_data=self.authfile_data, -- node_labels=[self.node_name], -- ) -+ self.config.http.booth.get_config( -+ self.name, -+ self.config_data.decode("utf-8"), -+ authfile=self.authfile, -+ authfile_data=self.authfile_data, -+ node_labels=[self.node_name], - ) - - - class PullConfigWithAuthfileSuccess(PullConfigWithAuthfile): - def setUp(self): - super().setUp() -- ( -- self.config.raw_file.write( -- file_type_codes.BOOTH_KEY, -- self.authfile_path, -- self.authfile_data, -- can_overwrite=True, -- name="raw_file.write.key", -- ).raw_file.write( -- file_type_codes.BOOTH_CONFIG, -- self.config_path, -- self.config_data, -- can_overwrite=True, -- name="raw_file.write.cfg", -- ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_KEY, -+ self.authfile_path, -+ self.authfile_data, -+ can_overwrite=True, -+ name="raw_file.write.key", -+ ) -+ self.config.raw_file.write( -+ file_type_codes.BOOTH_CONFIG, -+ self.config_path, -+ self.config_data, -+ can_overwrite=True, -+ name="raw_file.write.cfg", - ) - - def test_success(self): -@@ -3642,13 +3411,13 @@ class GetStatus(TestCase): - - def assert_success(self, instance_name=None): - inner_name = instance_name or "booth" -- ( -- self.config.runner.booth.status_daemon( -- inner_name, stdout="daemon status" -- ) -- .runner.booth.status_tickets(inner_name, stdout="tickets status") -- .runner.booth.status_peers(inner_name, stdout="peers status") -+ self.config.runner.booth.status_daemon( -+ inner_name, stdout="daemon status" - ) -+ self.config.runner.booth.status_tickets( -+ inner_name, stdout="tickets status" -+ ) -+ self.config.runner.booth.status_peers(inner_name, stdout="peers status") - self.assertEqual( - commands.get_status( - self.env_assist.get_env(), instance_name=instance_name -@@ -3667,10 +3436,8 @@ class GetStatus(TestCase): - self.assert_success(instance_name="my_booth") - - def test_daemon_status_failure(self): -- ( -- self.config.runner.booth.status_daemon( -- "booth", stdout="some output", stderr="some error", returncode=1 -- ) -+ self.config.runner.booth.status_daemon( -+ "booth", stdout="some output", stderr="some error", returncode=1 - ) - self.env_assist.assert_raise_library_error( - lambda: commands.get_status(self.env_assist.get_env()), -@@ -3684,12 +3451,9 @@ class GetStatus(TestCase): - ) - - def test_ticket_status_failure(self): -- ( -- self.config.runner.booth.status_daemon( -- "booth", stdout="daemon status" -- ).runner.booth.status_tickets( -- "booth", stdout="some output", stderr="some error", returncode=1 -- ) -+ self.config.runner.booth.status_daemon("booth", stdout="daemon status") -+ self.config.runner.booth.status_tickets( -+ "booth", stdout="some output", stderr="some error", returncode=1 - ) - self.env_assist.assert_raise_library_error( - lambda: commands.get_status(self.env_assist.get_env()), -@@ -3703,14 +3467,12 @@ class GetStatus(TestCase): - ) - - def test_peers_status_failure(self): -- ( -- self.config.runner.booth.status_daemon( -- "booth", stdout="daemon status" -- ) -- .runner.booth.status_tickets("booth", stdout="tickets status") -- .runner.booth.status_peers( -- "booth", stdout="some output", stderr="some error", returncode=1 -- ) -+ self.config.runner.booth.status_daemon("booth", stdout="daemon status") -+ self.config.runner.booth.status_tickets( -+ "booth", stdout="tickets status" -+ ) -+ self.config.runner.booth.status_peers( -+ "booth", stdout="some output", stderr="some error", returncode=1 - ) - self.env_assist.assert_raise_library_error( - lambda: commands.get_status(self.env_assist.get_env()), --- -2.35.3 - diff --git a/SOURCES/bz2058243-02-make-booth-ticket-mode-value-case-insensitive.patch b/SOURCES/bz2058243-02-make-booth-ticket-mode-value-case-insensitive.patch deleted file mode 100644 index 212472d..0000000 --- a/SOURCES/bz2058243-02-make-booth-ticket-mode-value-case-insensitive.patch +++ /dev/null @@ -1,126 +0,0 @@ -From de4845ea2d22c7dd9f4539360b44a900b1cea193 Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek -Date: Thu, 14 Jul 2022 16:46:33 +0200 -Subject: [PATCH 2/3] make booth ticket mode value case insensitive - ---- - pcs/lib/booth/config_validators.py | 10 ++++++++ - pcs/lib/commands/booth.py | 14 +++++++++--- - pcs_test/tier0/lib/commands/test_booth.py | 28 ++++++++++++++++------- - 3 files changed, 41 insertions(+), 11 deletions(-) - -diff --git a/pcs/lib/booth/config_validators.py b/pcs/lib/booth/config_validators.py -index 99badc46..6c4a4ddc 100644 ---- a/pcs/lib/booth/config_validators.py -+++ b/pcs/lib/booth/config_validators.py -@@ -100,6 +100,16 @@ def remove_ticket(conf_facade, ticket_name): - return [] - - -+def ticket_options_normalization() -> validate.TypeNormalizeFunc: -+ return validate.option_value_normalization( -+ { -+ "mode": ( -+ lambda value: value.lower() if isinstance(value, str) else value -+ ) -+ } -+ ) -+ -+ - def validate_ticket_name(ticket_name: str) -> reports.ReportItemList: - if not __TICKET_NAME_RE.search(ticket_name): - return [ -diff --git a/pcs/lib/commands/booth.py b/pcs/lib/commands/booth.py -index e7891fbe..fc1454ce 100644 ---- a/pcs/lib/commands/booth.py -+++ b/pcs/lib/commands/booth.py -@@ -23,7 +23,10 @@ from pcs.common.reports.item import ( - ) - from pcs.common.services.errors import ManageServiceError - from pcs.common.str_tools import join_multilines --from pcs.lib import tools -+from pcs.lib import ( -+ tools, -+ validate, -+) - from pcs.lib.booth import ( - config_files, - config_validators, -@@ -329,17 +332,22 @@ def config_ticket_add( - booth_env = env.get_booth_env(instance_name) - try: - booth_conf = booth_env.config.read_to_facade() -+ options_pairs = validate.values_to_pairs( -+ options, config_validators.ticket_options_normalization() -+ ) - report_processor.report_list( - config_validators.add_ticket( - booth_conf, - ticket_name, -- options, -+ options_pairs, - allow_unknown_options=allow_unknown_options, - ) - ) - if report_processor.has_errors: - raise LibraryError() -- booth_conf.add_ticket(ticket_name, options) -+ booth_conf.add_ticket( -+ ticket_name, validate.pairs_to_values(options_pairs) -+ ) - booth_env.config.write_facade(booth_conf, can_overwrite=True) - except RawFileError as e: - report_processor.report(raw_file_error_report(e)) -diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py -index 2b20a199..12b169c2 100644 ---- a/pcs_test/tier0/lib/commands/test_booth.py -+++ b/pcs_test/tier0/lib/commands/test_booth.py -@@ -1194,7 +1194,7 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - }, - ) - -- def test_success_ticket_options(self): -+ def assert_success_ticket_options(self, options_command, options_config): - self.config.raw_file.read( - file_type_codes.BOOTH_CONFIG, - self.fixture_cfg_path(), -@@ -1203,17 +1203,29 @@ class ConfigTicketAdd(TestCase, FixtureMixin): - self.config.raw_file.write( - file_type_codes.BOOTH_CONFIG, - self.fixture_cfg_path(), -- self.fixture_cfg_content( -- ticket_list=[ -- ["ticketA", [("retries", "10"), ("timeout", "20")]] -- ] -- ), -+ self.fixture_cfg_content(ticket_list=[["ticketA", options_config]]), - can_overwrite=True, - ) - commands.config_ticket_add( -- self.env_assist.get_env(), -- "ticketA", -+ self.env_assist.get_env(), "ticketA", options_command -+ ) -+ -+ def test_success_ticket_options(self): -+ self.assert_success_ticket_options( - {"timeout": "20", "retries": "10"}, -+ [("retries", "10"), ("timeout", "20")], -+ ) -+ -+ def test_success_ticket_options_mode(self): -+ self.assert_success_ticket_options( -+ {"timeout": "20", "retries": "10", "mode": "manual"}, -+ [("mode", "manual"), ("retries", "10"), ("timeout", "20")], -+ ) -+ -+ def test_success_ticket_options_mode_case_insensitive(self): -+ self.assert_success_ticket_options( -+ {"timeout": "20", "retries": "10", "mode": "MaNuAl"}, -+ [("mode", "manual"), ("retries", "10"), ("timeout", "20")], - ) - - def test_ticket_already_exists(self): --- -2.35.3 - diff --git a/SOURCES/bz2102663-01-fix-pcs-resource-restart-traceback.patch b/SOURCES/bz2102663-01-fix-pcs-resource-restart-traceback.patch deleted file mode 100644 index 20f2d50..0000000 --- a/SOURCES/bz2102663-01-fix-pcs-resource-restart-traceback.patch +++ /dev/null @@ -1,24 +0,0 @@ -From eeeaac9a46f3568e0a116048068ff99d0dccf25c Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek -Date: Thu, 30 Jun 2022 14:49:16 +0200 -Subject: [PATCH] fix 'pcs resource restart' traceback - ---- - pcs/resource.py | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/pcs/resource.py b/pcs/resource.py -index eb59d703..8d402c22 100644 ---- a/pcs/resource.py -+++ b/pcs/resource.py -@@ -2596,7 +2596,6 @@ def resource_restart( - Options: - * --wait - """ -- del lib - modifiers.ensure_only_supported("--wait") - if not argv: - utils.err("You must specify a resource to restart") --- -2.35.3 - diff --git a/SOURCES/bz2148124-01-pcsd-systemd-killmode.patch b/SOURCES/bz2148124-01-pcsd-systemd-killmode.patch new file mode 100644 index 0000000..76e5d3e --- /dev/null +++ b/SOURCES/bz2148124-01-pcsd-systemd-killmode.patch @@ -0,0 +1,40 @@ +From d486d9c9bafbfc13be7ff86c0ae781feed184d52 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular +Date: Thu, 24 Nov 2022 08:15:13 +0100 +Subject: [PATCH 1/2] fix graceful termination of pcsd via systemd + +--- + CHANGELOG.md | 5 +++++ + pcsd/pcsd.service.in | 1 + + 2 files changed, 6 insertions(+) + +diff --git a/CHANGELOG.md b/CHANGELOG.md +index 7d3d606b..7927eae6 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -1,5 +1,10 @@ + # Change Log + ++## [Unreleased] ++ ++### Fixed ++- Graceful stopping pcsd service using `systemctl stop pcsd` command ++ + ## [0.11.4] - 2022-11-21 + + ### Security +diff --git a/pcsd/pcsd.service.in b/pcsd/pcsd.service.in +index 8591e750..dca5052d 100644 +--- a/pcsd/pcsd.service.in ++++ b/pcsd/pcsd.service.in +@@ -11,6 +11,7 @@ After=pcsd-ruby.service + EnvironmentFile=@CONF_DIR@/pcsd + ExecStart=@SBINDIR@/pcsd + Type=notify ++KillMode=mixed + + [Install] + WantedBy=multi-user.target +-- +2.38.1 + diff --git a/SOURCES/bz2151164-01-fix-displaying-bool-and-integer-values.patch b/SOURCES/bz2151164-01-fix-displaying-bool-and-integer-values.patch new file mode 100644 index 0000000..3e43f84 --- /dev/null +++ b/SOURCES/bz2151164-01-fix-displaying-bool-and-integer-values.patch @@ -0,0 +1,145 @@ +From 1edf85bdabadf10708f63c0767991c7f4150e842 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular +Date: Wed, 7 Dec 2022 15:53:25 +0100 +Subject: [PATCH 3/3] fix displaying bool and integer values in `pcs resource + config` command + +--- + CHANGELOG.md | 4 ++++ + pcs/cli/resource/output.py | 18 +++++++++--------- + pcs_test/resources/cib-resources.xml | 2 +- + pcs_test/tier1/legacy/test_resource.py | 3 ++- + pcs_test/tools/resources_dto.py | 4 ++-- + 5 files changed, 18 insertions(+), 13 deletions(-) + +diff --git a/CHANGELOG.md b/CHANGELOG.md +index ed2083af..378cca50 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -9,7 +9,11 @@ + + ### Fixed + - Graceful stopping pcsd service using `systemctl stop pcsd` command ++- Displaying bool and integer values in `pcs resource config` command ++ ([rhbz#2151164], [ghissue#604]) + ++[ghissue#604]: https://github.com/ClusterLabs/pcs/issues/604 ++[rhbz#2151164]: https://bugzilla.redhat.com/show_bug.cgi?id=2151164 + [rhbz#2151524]: https://bugzilla.redhat.com/show_bug.cgi?id=2151524 + + +diff --git a/pcs/cli/resource/output.py b/pcs/cli/resource/output.py +index daa713a0..dbdf009f 100644 +--- a/pcs/cli/resource/output.py ++++ b/pcs/cli/resource/output.py +@@ -69,9 +69,9 @@ def _resource_operation_to_pairs( + pairs.append(("interval-origin", operation_dto.interval_origin)) + if operation_dto.timeout: + pairs.append(("timeout", operation_dto.timeout)) +- if operation_dto.enabled: ++ if operation_dto.enabled is not None: + pairs.append(("enabled", _bool_to_cli_value(operation_dto.enabled))) +- if operation_dto.record_pending: ++ if operation_dto.record_pending is not None: + pairs.append( + ("record-pending", _bool_to_cli_value(operation_dto.record_pending)) + ) +@@ -474,13 +474,13 @@ def _resource_bundle_container_options_to_pairs( + options: CibResourceBundleContainerRuntimeOptionsDto, + ) -> List[Tuple[str, str]]: + option_list = [("image", options.image)] +- if options.replicas: ++ if options.replicas is not None: + option_list.append(("replicas", str(options.replicas))) +- if options.replicas_per_host: ++ if options.replicas_per_host is not None: + option_list.append( + ("replicas-per-host", str(options.replicas_per_host)) + ) +- if options.promoted_max: ++ if options.promoted_max is not None: + option_list.append(("promoted-max", str(options.promoted_max))) + if options.run_command: + option_list.append(("run-command", options.run_command)) +@@ -505,7 +505,7 @@ def _resource_bundle_network_options_to_pairs( + network_options.append( + ("ip-range-start", bundle_network_dto.ip_range_start) + ) +- if bundle_network_dto.control_port: ++ if bundle_network_dto.control_port is not None: + network_options.append( + ("control-port", str(bundle_network_dto.control_port)) + ) +@@ -513,7 +513,7 @@ def _resource_bundle_network_options_to_pairs( + network_options.append( + ("host-interface", bundle_network_dto.host_interface) + ) +- if bundle_network_dto.host_netmask: ++ if bundle_network_dto.host_netmask is not None: + network_options.append( + ("host-netmask", str(bundle_network_dto.host_netmask)) + ) +@@ -528,9 +528,9 @@ def _resource_bundle_port_mapping_to_pairs( + bundle_net_port_mapping_dto: CibResourceBundlePortMappingDto, + ) -> List[Tuple[str, str]]: + mapping = [] +- if bundle_net_port_mapping_dto.port: ++ if bundle_net_port_mapping_dto.port is not None: + mapping.append(("port", str(bundle_net_port_mapping_dto.port))) +- if bundle_net_port_mapping_dto.internal_port: ++ if bundle_net_port_mapping_dto.internal_port is not None: + mapping.append( + ("internal-port", str(bundle_net_port_mapping_dto.internal_port)) + ) +diff --git a/pcs_test/resources/cib-resources.xml b/pcs_test/resources/cib-resources.xml +index 1e256b42..9242fd4a 100644 +--- a/pcs_test/resources/cib-resources.xml ++++ b/pcs_test/resources/cib-resources.xml +@@ -53,7 +53,7 @@ + + + +- ++ + + + +diff --git a/pcs_test/tier1/legacy/test_resource.py b/pcs_test/tier1/legacy/test_resource.py +index c097a937..3ba32ec7 100644 +--- a/pcs_test/tier1/legacy/test_resource.py ++++ b/pcs_test/tier1/legacy/test_resource.py +@@ -774,7 +774,7 @@ Error: moni=tor does not appear to be a valid operation action + + o, r = pcs( + self.temp_cib.name, +- "resource create --no-default-ops OPTest ocf:heartbeat:Dummy op monitor interval=30s OCF_CHECK_LEVEL=1 op monitor interval=25s OCF_CHECK_LEVEL=1".split(), ++ "resource create --no-default-ops OPTest ocf:heartbeat:Dummy op monitor interval=30s OCF_CHECK_LEVEL=1 op monitor interval=25s OCF_CHECK_LEVEL=1 enabled=0".split(), + ) + ac(o, "") + assert r == 0 +@@ -791,6 +791,7 @@ Error: moni=tor does not appear to be a valid operation action + OCF_CHECK_LEVEL=1 + monitor: OPTest-monitor-interval-25s + interval=25s ++ enabled=0 + OCF_CHECK_LEVEL=1 + """ + ), +diff --git a/pcs_test/tools/resources_dto.py b/pcs_test/tools/resources_dto.py +index e010037e..af0b4ac3 100644 +--- a/pcs_test/tools/resources_dto.py ++++ b/pcs_test/tools/resources_dto.py +@@ -236,8 +236,8 @@ PRIMITIVE_R7 = CibResourcePrimitiveDto( + start_delay=None, + interval_origin=None, + timeout="20s", +- enabled=None, +- record_pending=None, ++ enabled=False, ++ record_pending=False, + role=None, + on_fail=None, + meta_attributes=[], +-- +2.38.1 + diff --git a/SOURCES/bz2151524-01-add-warning-when-updating-a-misconfigured-resource.patch b/SOURCES/bz2151524-01-add-warning-when-updating-a-misconfigured-resource.patch new file mode 100644 index 0000000..3b444b3 --- /dev/null +++ b/SOURCES/bz2151524-01-add-warning-when-updating-a-misconfigured-resource.patch @@ -0,0 +1,755 @@ +From e292dd4de2504da09901133fdab7ace5a97f9d73 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular +Date: Wed, 7 Dec 2022 11:33:25 +0100 +Subject: [PATCH 2/3] add warning when updating a misconfigured resource + +--- + CHANGELOG.md | 8 ++ + pcs/common/reports/codes.py | 3 + + pcs/common/reports/messages.py | 19 +++++ + pcs/lib/cib/resource/primitive.py | 84 ++++++++++++++----- + pcs/lib/pacemaker/live.py | 38 ++------- + .../tier0/common/reports/test_messages.py | 16 ++++ + .../cib/resource/test_primitive_validate.py | 56 +++++++------ + pcs_test/tier0/lib/pacemaker/test_live.py | 78 +++++------------ + pcs_test/tier1/legacy/test_stonith.py | 5 +- + 9 files changed, 169 insertions(+), 138 deletions(-) + +diff --git a/CHANGELOG.md b/CHANGELOG.md +index 7927eae6..ed2083af 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -2,9 +2,17 @@ + + ## [Unreleased] + ++### Added ++- Warning to `pcs resource|stonith update` commands about not using agent ++ self-validation feature when the resource is already misconfigured ++ ([rhbz#2151524]) ++ + ### Fixed + - Graceful stopping pcsd service using `systemctl stop pcsd` command + ++[rhbz#2151524]: https://bugzilla.redhat.com/show_bug.cgi?id=2151524 ++ ++ + ## [0.11.4] - 2022-11-21 + + ### Security +diff --git a/pcs/common/reports/codes.py b/pcs/common/reports/codes.py +index 76963733..90609f47 100644 +--- a/pcs/common/reports/codes.py ++++ b/pcs/common/reports/codes.py +@@ -44,6 +44,9 @@ AGENT_NAME_GUESS_FOUND_MORE_THAN_ONE = M("AGENT_NAME_GUESS_FOUND_MORE_THAN_ONE") + AGENT_NAME_GUESS_FOUND_NONE = M("AGENT_NAME_GUESS_FOUND_NONE") + AGENT_NAME_GUESSED = M("AGENT_NAME_GUESSED") + AGENT_SELF_VALIDATION_INVALID_DATA = M("AGENT_SELF_VALIDATION_INVALID_DATA") ++AGENT_SELF_VALIDATION_SKIPPED_UPDATED_RESOURCE_MISCONFIGURED = M( ++ "AGENT_SELF_VALIDATION_SKIPPED_UPDATED_RESOURCE_MISCONFIGURED" ++) + AGENT_SELF_VALIDATION_RESULT = M("AGENT_SELF_VALIDATION_RESULT") + BAD_CLUSTER_STATE_FORMAT = M("BAD_CLUSTER_STATE_FORMAT") + BOOTH_ADDRESS_DUPLICATION = M("BOOTH_ADDRESS_DUPLICATION") +diff --git a/pcs/common/reports/messages.py b/pcs/common/reports/messages.py +index ba748eb2..fbc4de62 100644 +--- a/pcs/common/reports/messages.py ++++ b/pcs/common/reports/messages.py +@@ -7494,6 +7494,25 @@ class AgentSelfValidationInvalidData(ReportItemMessage): + return f"Invalid validation data from agent: {self.reason}" + + ++@dataclass(frozen=True) ++class AgentSelfValidationSkippedUpdatedResourceMisconfigured(ReportItemMessage): ++ """ ++ Agent self validation is skipped when updating a resource as it is ++ misconfigured in its current state. ++ """ ++ ++ result: str ++ _code = codes.AGENT_SELF_VALIDATION_SKIPPED_UPDATED_RESOURCE_MISCONFIGURED ++ ++ @property ++ def message(self) -> str: ++ return ( ++ "The resource was misconfigured before the update, therefore agent " ++ "self-validation will not be run for the updated configuration. " ++ "Validation output of the original configuration:\n{result}" ++ ).format(result="\n".join(indent(self.result.splitlines()))) ++ ++ + @dataclass(frozen=True) + class ResourceCloneIncompatibleMetaAttributes(ReportItemMessage): + """ +diff --git a/pcs/lib/cib/resource/primitive.py b/pcs/lib/cib/resource/primitive.py +index d9940e9d..f9d52b9b 100644 +--- a/pcs/lib/cib/resource/primitive.py ++++ b/pcs/lib/cib/resource/primitive.py +@@ -357,6 +357,31 @@ def _is_ocf_or_stonith_agent(resource_agent_name: ResourceAgentName) -> bool: + return resource_agent_name.standard in ("stonith", "ocf") + + ++def _get_report_from_agent_self_validation( ++ is_valid: Optional[bool], ++ reason: str, ++ report_severity: reports.ReportItemSeverity, ++) -> reports.ReportItemList: ++ report_items = [] ++ if is_valid is None: ++ report_items.append( ++ reports.ReportItem( ++ report_severity, ++ reports.messages.AgentSelfValidationInvalidData(reason), ++ ) ++ ) ++ elif not is_valid or reason: ++ if is_valid: ++ report_severity = reports.ReportItemSeverity.warning() ++ report_items.append( ++ reports.ReportItem( ++ report_severity, ++ reports.messages.AgentSelfValidationResult(reason), ++ ) ++ ) ++ return report_items ++ ++ + def validate_resource_instance_attributes_create( + cmd_runner: CommandRunner, + resource_agent: ResourceAgentFacade, +@@ -405,16 +430,16 @@ def validate_resource_instance_attributes_create( + for report_item in report_items + ) + ): +- ( +- dummy_is_valid, +- agent_validation_reports, +- ) = validate_resource_instance_attributes_via_pcmk( +- cmd_runner, +- agent_name, +- instance_attributes, +- reports.get_severity(reports.codes.FORCE, force), ++ report_items.extend( ++ _get_report_from_agent_self_validation( ++ *validate_resource_instance_attributes_via_pcmk( ++ cmd_runner, ++ agent_name, ++ instance_attributes, ++ ), ++ reports.get_severity(reports.codes.FORCE, force), ++ ) + ) +- report_items.extend(agent_validation_reports) + return report_items + + +@@ -508,25 +533,40 @@ def validate_resource_instance_attributes_update( + ) + ): + ( +- is_valid, +- dummy_reports, ++ original_is_valid, ++ original_reason, + ) = validate_resource_instance_attributes_via_pcmk( + cmd_runner, + agent_name, + current_instance_attrs, +- reports.ReportItemSeverity.error(), + ) +- if is_valid: +- ( +- dummy_is_valid, +- agent_validation_reports, +- ) = validate_resource_instance_attributes_via_pcmk( +- cmd_runner, +- resource_agent.metadata.name, +- final_attrs, +- reports.get_severity(reports.codes.FORCE, force), ++ if original_is_valid: ++ report_items.extend( ++ _get_report_from_agent_self_validation( ++ *validate_resource_instance_attributes_via_pcmk( ++ cmd_runner, ++ resource_agent.metadata.name, ++ final_attrs, ++ ), ++ reports.get_severity(reports.codes.FORCE, force), ++ ) ++ ) ++ elif original_is_valid is None: ++ report_items.append( ++ reports.ReportItem.warning( ++ reports.messages.AgentSelfValidationInvalidData( ++ original_reason ++ ) ++ ) ++ ) ++ else: ++ report_items.append( ++ reports.ReportItem.warning( ++ reports.messages.AgentSelfValidationSkippedUpdatedResourceMisconfigured( ++ original_reason ++ ) ++ ) + ) +- report_items.extend(agent_validation_reports) + return report_items + + +diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py +index 6dab613e..fb1e0a4a 100644 +--- a/pcs/lib/pacemaker/live.py ++++ b/pcs/lib/pacemaker/live.py +@@ -884,8 +884,7 @@ def _validate_stonith_instance_attributes_via_pcmk( + cmd_runner: CommandRunner, + agent_name: ResourceAgentName, + instance_attributes: Mapping[str, str], +- not_valid_severity: reports.ReportItemSeverity, +-) -> tuple[Optional[bool], reports.ReportItemList]: ++) -> tuple[Optional[bool], str]: + cmd = [ + settings.stonith_admin, + "--validate", +@@ -899,7 +898,6 @@ def _validate_stonith_instance_attributes_via_pcmk( + cmd, + "./validate/command/output", + instance_attributes, +- not_valid_severity, + ) + + +@@ -907,8 +905,7 @@ def _validate_resource_instance_attributes_via_pcmk( + cmd_runner: CommandRunner, + agent_name: ResourceAgentName, + instance_attributes: Mapping[str, str], +- not_valid_severity: reports.ReportItemSeverity, +-) -> tuple[Optional[bool], reports.ReportItemList]: ++) -> tuple[Optional[bool], str]: + cmd = [ + settings.crm_resource_binary, + "--validate", +@@ -926,7 +923,6 @@ def _validate_resource_instance_attributes_via_pcmk( + cmd, + "./resource-agent-action/command/output", + instance_attributes, +- not_valid_severity, + ) + + +@@ -935,8 +931,7 @@ def _handle_instance_attributes_validation_via_pcmk( + cmd: StringSequence, + data_xpath: str, + instance_attributes: Mapping[str, str], +- not_valid_severity: reports.ReportItemSeverity, +-) -> tuple[Optional[bool], reports.ReportItemList]: ++) -> tuple[Optional[bool], str]: + full_cmd = list(cmd) + for key, value in sorted(instance_attributes.items()): + full_cmd.extend(["--option", f"{key}={value}"]) +@@ -945,12 +940,7 @@ def _handle_instance_attributes_validation_via_pcmk( + # dom = _get_api_result_dom(stdout) + dom = xml_fromstring(stdout) + except (etree.XMLSyntaxError, etree.DocumentInvalid) as e: +- return None, [ +- reports.ReportItem( +- not_valid_severity, +- reports.messages.AgentSelfValidationInvalidData(str(e)), +- ) +- ] ++ return None, str(e) + result = "\n".join( + "\n".join( + line.strip() for line in item.text.split("\n") if line.strip() +@@ -958,38 +948,22 @@ def _handle_instance_attributes_validation_via_pcmk( + for item in dom.iterfind(data_xpath) + if item.get("source") == "stderr" and item.text + ).strip() +- if return_value == 0: +- if result: +- return True, [ +- reports.ReportItem.warning( +- reports.messages.AgentSelfValidationResult(result) +- ) +- ] +- return True, [] +- return False, [ +- reports.ReportItem( +- not_valid_severity, +- reports.messages.AgentSelfValidationResult(result), +- ) +- ] ++ return return_value == 0, result + + + def validate_resource_instance_attributes_via_pcmk( + cmd_runner: CommandRunner, + resource_agent_name: ResourceAgentName, + instance_attributes: Mapping[str, str], +- not_valid_severity: reports.ReportItemSeverity, +-) -> tuple[Optional[bool], reports.ReportItemList]: ++) -> tuple[Optional[bool], str]: + if resource_agent_name.is_stonith: + return _validate_stonith_instance_attributes_via_pcmk( + cmd_runner, + resource_agent_name, + instance_attributes, +- not_valid_severity, + ) + return _validate_resource_instance_attributes_via_pcmk( + cmd_runner, + resource_agent_name, + instance_attributes, +- not_valid_severity, + ) +diff --git a/pcs_test/tier0/common/reports/test_messages.py b/pcs_test/tier0/common/reports/test_messages.py +index 64e74daa..b1e009ce 100644 +--- a/pcs_test/tier0/common/reports/test_messages.py ++++ b/pcs_test/tier0/common/reports/test_messages.py +@@ -5525,6 +5525,22 @@ class AgentSelfValidationInvalidData(NameBuildTest): + ) + + ++class AgentSelfValidationSkippedUpdatedResourceMisconfigured(NameBuildTest): ++ def test_message(self): ++ lines = list(f"line #{i}" for i in range(3)) ++ self.assert_message_from_report( ++ ( ++ "The resource was misconfigured before the update, therefore " ++ "agent self-validation will not be run for the updated " ++ "configuration. Validation output of the original " ++ "configuration:\n {}" ++ ).format("\n ".join(lines)), ++ reports.AgentSelfValidationSkippedUpdatedResourceMisconfigured( ++ "\n".join(lines) ++ ), ++ ) ++ ++ + class ResourceCloneIncompatibleMetaAttributes(NameBuildTest): + def test_with_provider(self): + attr = "attr_name" +diff --git a/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py b/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py +index 8b52314f..7a4e5c8f 100644 +--- a/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py ++++ b/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py +@@ -660,7 +660,6 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + attributes, +- reports.ReportItemSeverity.error(reports.codes.FORCE), + ) + + def test_force(self): +@@ -680,15 +679,14 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + attributes, +- reports.ReportItemSeverity.warning(), + ) + + def test_failure(self): + attributes = {"required": "value"} + facade = _fixture_ocf_agent() +- failure_reports = ["report1", "report2"] +- self.agent_self_validation_mock.return_value = False, failure_reports +- self.assertEqual( ++ failure_reason = "failure reason" ++ self.agent_self_validation_mock.return_value = False, failure_reason ++ assert_report_item_list_equal( + primitive.validate_resource_instance_attributes_create( + self.cmd_runner, + facade, +@@ -696,13 +694,18 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + etree.Element("resources"), + force=False, + ), +- failure_reports, ++ [ ++ fixture.error( ++ reports.codes.AGENT_SELF_VALIDATION_RESULT, ++ result=failure_reason, ++ force_code=reports.codes.FORCE, ++ ) ++ ], + ) + self.agent_self_validation_mock.assert_called_once_with( + self.cmd_runner, + facade.metadata.name, + attributes, +- reports.ReportItemSeverity.error(reports.codes.FORCE), + ) + + def test_stonith_check(self): +@@ -722,7 +725,6 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + attributes, +- reports.ReportItemSeverity.error(reports.codes.FORCE), + ) + + def test_nonexisting_agent(self): +@@ -1346,13 +1348,11 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + old_attributes, +- reports.ReportItemSeverity.error(), + ), + mock.call( + self.cmd_runner, + facade.metadata.name, + new_attributes, +- reports.ReportItemSeverity.error(reports.codes.FORCE), + ), + ], + ) +@@ -1379,13 +1379,11 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + old_attributes, +- reports.ReportItemSeverity.error(), + ), + mock.call( + self.cmd_runner, + facade.metadata.name, + new_attributes, +- reports.ReportItemSeverity.warning(), + ), + ], + ) +@@ -1393,13 +1391,13 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + def test_failure(self): + old_attributes = {"required": "old_value"} + new_attributes = {"required": "new_value"} +- failure_reports = ["report1", "report2"] ++ failure_reason = "failure reason" + facade = _fixture_ocf_agent() + self.agent_self_validation_mock.side_effect = ( +- (True, []), +- (False, failure_reports), ++ (True, ""), ++ (False, failure_reason), + ) +- self.assertEqual( ++ assert_report_item_list_equal( + primitive.validate_resource_instance_attributes_update( + self.cmd_runner, + facade, +@@ -1408,7 +1406,13 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._fixture_resources(old_attributes), + force=False, + ), +- failure_reports, ++ [ ++ fixture.error( ++ reports.codes.AGENT_SELF_VALIDATION_RESULT, ++ result=failure_reason, ++ force_code=reports.codes.FORCE, ++ ) ++ ], + ) + self.assertEqual( + self.agent_self_validation_mock.mock_calls, +@@ -1417,13 +1421,11 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + old_attributes, +- reports.ReportItemSeverity.error(), + ), + mock.call( + self.cmd_runner, + facade.metadata.name, + new_attributes, +- reports.ReportItemSeverity.error(reports.codes.FORCE), + ), + ], + ) +@@ -1450,13 +1452,11 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + old_attributes, +- reports.ReportItemSeverity.error(), + ), + mock.call( + self.cmd_runner, + facade.metadata.name, + new_attributes, +- reports.ReportItemSeverity.error(reports.codes.FORCE), + ), + ], + ) +@@ -1522,10 +1522,10 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + def test_current_attributes_failure(self): + old_attributes = {"required": "old_value"} + new_attributes = {"required": "new_value"} +- failure_reports = ["report1", "report2"] ++ failure_reason = "failure reason" + facade = _fixture_ocf_agent() +- self.agent_self_validation_mock.return_value = False, failure_reports +- self.assertEqual( ++ self.agent_self_validation_mock.return_value = False, failure_reason ++ assert_report_item_list_equal( + primitive.validate_resource_instance_attributes_update( + self.cmd_runner, + facade, +@@ -1534,7 +1534,12 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._fixture_resources(old_attributes), + force=False, + ), +- [], ++ [ ++ fixture.warn( ++ reports.codes.AGENT_SELF_VALIDATION_SKIPPED_UPDATED_RESOURCE_MISCONFIGURED, ++ result=failure_reason, ++ ) ++ ], + ) + self.assertEqual( + self.agent_self_validation_mock.mock_calls, +@@ -1543,7 +1548,6 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self.cmd_runner, + facade.metadata.name, + old_attributes, +- reports.ReportItemSeverity.error(), + ), + ], + ) +diff --git a/pcs_test/tier0/lib/pacemaker/test_live.py b/pcs_test/tier0/lib/pacemaker/test_live.py +index 1f37d759..c1363a65 100644 +--- a/pcs_test/tier0/lib/pacemaker/test_live.py ++++ b/pcs_test/tier0/lib/pacemaker/test_live.py +@@ -1706,16 +1706,15 @@ class HandleInstanceAttributesValidateViaPcmkTest(TestCase): + base_cmd = ["some", "command"] + ( + is_valid, +- report_list, ++ reason, + ) = lib._handle_instance_attributes_validation_via_pcmk( + runner, + base_cmd, + "result/output", + {"attr1": "val1", "attr2": "val2"}, +- not_valid_severity=Severity.info(), + ) + self.assertTrue(is_valid) +- self.assertEqual(report_list, []) ++ self.assertEqual(reason, "") + runner.run.assert_called_once_with( + base_cmd + ["--option", "attr1=val1", "--option", "attr2=val2"] + ) +@@ -1725,23 +1724,17 @@ class HandleInstanceAttributesValidateViaPcmkTest(TestCase): + base_cmd = ["some", "command"] + ( + is_valid, +- report_list, ++ reason, + ) = lib._handle_instance_attributes_validation_via_pcmk( + runner, + base_cmd, + "result/output", + {"attr1": "val1", "attr2": "val2"}, +- not_valid_severity=Severity.info(), + ) + self.assertIsNone(is_valid) +- assert_report_item_list_equal( +- report_list, +- [ +- fixture.info( +- report_codes.AGENT_SELF_VALIDATION_INVALID_DATA, +- reason="Start tag expected, '<' not found, line 1, column 1 (, line 1)", +- ) +- ], ++ self.assertEqual( ++ reason, ++ "Start tag expected, '<' not found, line 1, column 1 (, line 1)", + ) + runner.run.assert_called_once_with( + base_cmd + ["--option", "attr1=val1", "--option", "attr2=val2"] +@@ -1760,19 +1753,15 @@ class HandleInstanceAttributesValidateViaPcmkTest(TestCase): + base_cmd = ["some", "command"] + ( + is_valid, +- report_list, ++ reason, + ) = lib._handle_instance_attributes_validation_via_pcmk( + runner, + base_cmd, + "result/output", + {"attr1": "val1", "attr2": "val2"}, +- not_valid_severity=Severity.info(), + ) + self.assertTrue(is_valid) +- assert_report_item_list_equal( +- report_list, +- [], +- ) ++ self.assertEqual(reason, "") + runner.run.assert_called_once_with( + base_cmd + ["--option", "attr1=val1", "--option", "attr2=val2"] + ) +@@ -1791,23 +1780,15 @@ class HandleInstanceAttributesValidateViaPcmkTest(TestCase): + base_cmd = ["some", "command"] + ( + is_valid, +- report_list, ++ reason, + ) = lib._handle_instance_attributes_validation_via_pcmk( + runner, + base_cmd, + "result/output", + {"attr1": "val1", "attr2": "val2"}, +- not_valid_severity=Severity.info(), + ) + self.assertFalse(is_valid) +- assert_report_item_list_equal( +- report_list, +- [ +- fixture.info( +- report_codes.AGENT_SELF_VALIDATION_RESULT, result="" +- ) +- ], +- ) ++ self.assertEqual(reason, "") + runner.run.assert_called_once_with( + base_cmd + ["--option", "attr1=val1", "--option", "attr2=val2"] + ) +@@ -1835,23 +1816,17 @@ class HandleInstanceAttributesValidateViaPcmkTest(TestCase): + base_cmd = ["some", "command"] + ( + is_valid, +- report_list, ++ reason, + ) = lib._handle_instance_attributes_validation_via_pcmk( + runner, + base_cmd, + "result/output", + {"attr1": "val1", "attr2": "val2"}, +- not_valid_severity=Severity.info(), + ) + self.assertFalse(is_valid) +- assert_report_item_list_equal( +- report_list, +- [ +- fixture.info( +- report_codes.AGENT_SELF_VALIDATION_RESULT, +- result="first line\nImportant output\nand another line", +- ) +- ], ++ self.assertEqual( ++ reason, ++ "first line\nImportant output\nand another line", + ) + runner.run.assert_called_once_with( + base_cmd + ["--option", "attr1=val1", "--option", "attr2=val2"] +@@ -1879,23 +1854,17 @@ class HandleInstanceAttributesValidateViaPcmkTest(TestCase): + base_cmd = ["some", "command"] + ( + is_valid, +- report_list, ++ reason, + ) = lib._handle_instance_attributes_validation_via_pcmk( + runner, + base_cmd, + "result/output", + {"attr1": "val1", "attr2": "val2"}, +- not_valid_severity=Severity.info(), + ) + self.assertTrue(is_valid) +- assert_report_item_list_equal( +- report_list, +- [ +- fixture.warn( +- report_codes.AGENT_SELF_VALIDATION_RESULT, +- result="first line\nImportant output\nand another line", +- ) +- ], ++ self.assertEqual( ++ reason, ++ "first line\nImportant output\nand another line", + ) + runner.run.assert_called_once_with( + base_cmd + ["--option", "attr1=val1", "--option", "attr2=val2"] +@@ -1907,7 +1876,6 @@ class ValidateResourceInstanceAttributesViaPcmkTest(TestCase): + def setUp(self): + self.runner = mock.Mock() + self.attrs = dict(attra="val1", attrb="val2") +- self.severity = Severity.info() + patcher = mock.patch( + "pcs.lib.pacemaker.live._handle_instance_attributes_validation_via_pcmk" + ) +@@ -1921,7 +1889,7 @@ class ValidateResourceInstanceAttributesViaPcmkTest(TestCase): + ) + self.assertEqual( + lib._validate_resource_instance_attributes_via_pcmk( +- self.runner, agent, self.attrs, self.severity ++ self.runner, agent, self.attrs + ), + self.ret_val, + ) +@@ -1941,7 +1909,6 @@ class ValidateResourceInstanceAttributesViaPcmkTest(TestCase): + ], + "./resource-agent-action/command/output", + self.attrs, +- self.severity, + ) + + def test_without_provider(self): +@@ -1950,7 +1917,7 @@ class ValidateResourceInstanceAttributesViaPcmkTest(TestCase): + ) + self.assertEqual( + lib._validate_resource_instance_attributes_via_pcmk( +- self.runner, agent, self.attrs, self.severity ++ self.runner, agent, self.attrs + ), + self.ret_val, + ) +@@ -1968,7 +1935,6 @@ class ValidateResourceInstanceAttributesViaPcmkTest(TestCase): + ], + "./resource-agent-action/command/output", + self.attrs, +- self.severity, + ) + + +@@ -1978,7 +1944,6 @@ class ValidateStonithInstanceAttributesViaPcmkTest(TestCase): + def setUp(self): + self.runner = mock.Mock() + self.attrs = dict(attra="val1", attrb="val2") +- self.severity = Severity.info() + patcher = mock.patch( + "pcs.lib.pacemaker.live._handle_instance_attributes_validation_via_pcmk" + ) +@@ -1992,7 +1957,7 @@ class ValidateStonithInstanceAttributesViaPcmkTest(TestCase): + ) + self.assertEqual( + lib._validate_stonith_instance_attributes_via_pcmk( +- self.runner, agent, self.attrs, self.severity ++ self.runner, agent, self.attrs + ), + self.ret_val, + ) +@@ -2008,5 +1973,4 @@ class ValidateStonithInstanceAttributesViaPcmkTest(TestCase): + ], + "./validate/command/output", + self.attrs, +- self.severity, + ) +diff --git a/pcs_test/tier1/legacy/test_stonith.py b/pcs_test/tier1/legacy/test_stonith.py +index 8b31094b..7e7ec030 100644 +--- a/pcs_test/tier1/legacy/test_stonith.py ++++ b/pcs_test/tier1/legacy/test_stonith.py +@@ -1291,7 +1291,10 @@ class StonithTest(TestCase, AssertPcsMixin): + ), + ) + +- self.assert_pcs_success("stonith update test3 username=testA".split()) ++ self.assert_pcs_success( ++ "stonith update test3 username=testA".split(), ++ stdout_start="Warning: ", ++ ) + + self.assert_pcs_success( + "stonith config test2".split(), +-- +2.38.1 + diff --git a/SOURCES/bz2158790-01-fix-stonith-watchdog-timeout-validation.patch b/SOURCES/bz2158790-01-fix-stonith-watchdog-timeout-validation.patch new file mode 100644 index 0000000..faa3dac --- /dev/null +++ b/SOURCES/bz2158790-01-fix-stonith-watchdog-timeout-validation.patch @@ -0,0 +1,506 @@ +From bbf53f713189eb2233efa03bf3aa9c96eb79ba82 Mon Sep 17 00:00:00 2001 +From: Miroslav Lisik +Date: Thu, 5 Jan 2023 16:21:44 +0100 +Subject: [PATCH 2/2] Fix stonith-watchdog-timeout validation + +--- + CHANGELOG.md | 2 + + pcs/lib/cluster_property.py | 25 ++++- + pcs/lib/sbd.py | 15 ++- + .../lib/commands/test_cluster_property.py | 50 ++++++++-- + pcs_test/tier0/lib/test_cluster_property.py | 98 ++++++++++++++----- + pcs_test/tier1/test_cluster_property.py | 14 ++- + 6 files changed, 159 insertions(+), 45 deletions(-) + +diff --git a/CHANGELOG.md b/CHANGELOG.md +index 47212f00..0945d727 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -11,6 +11,7 @@ + - Graceful stopping pcsd service using `systemctl stop pcsd` command + - Displaying bool and integer values in `pcs resource config` command + ([rhbz#2151164], [ghissue#604]) ++- Allow time values in stonith-watchdog-time property ([rhbz#2158790]) + + ### Changed + - Resource/stonith agent self-validation of instance attributes is now +@@ -22,6 +23,7 @@ + [rhbz#2151164]: https://bugzilla.redhat.com/show_bug.cgi?id=2151164 + [rhbz#2151524]: https://bugzilla.redhat.com/show_bug.cgi?id=2151524 + [rhbz#2159454]: https://bugzilla.redhat.com/show_bug.cgi?id=2159454 ++[rhbz#2158790]: https://bugzilla.redhat.com/show_bug.cgi?id=2158790 + + + ## [0.11.4] - 2022-11-21 +diff --git a/pcs/lib/cluster_property.py b/pcs/lib/cluster_property.py +index 3bbc093d..d3c8a896 100644 +--- a/pcs/lib/cluster_property.py ++++ b/pcs/lib/cluster_property.py +@@ -7,6 +7,7 @@ from lxml.etree import _Element + + from pcs.common import reports + from pcs.common.services.interfaces import ServiceManagerInterface ++from pcs.common.tools import timeout_to_seconds + from pcs.common.types import StringSequence + from pcs.lib import ( + sbd, +@@ -37,8 +38,21 @@ def _validate_stonith_watchdog_timeout_property( + force: bool = False, + ) -> reports.ReportItemList: + report_list: reports.ReportItemList = [] ++ original_value = value ++ # if value is not empty, try to convert time interval string ++ if value: ++ seconds = timeout_to_seconds(value) ++ if seconds is None: ++ # returns empty list because this should be reported by ++ # ValueTimeInterval validator ++ return report_list ++ value = str(seconds) + if sbd.is_sbd_enabled(service_manager): +- report_list.extend(sbd.validate_stonith_watchdog_timeout(value, force)) ++ report_list.extend( ++ sbd.validate_stonith_watchdog_timeout( ++ validate.ValuePair(original_value, value), force ++ ) ++ ) + else: + if value not in ["", "0"]: + report_list.append( +@@ -123,9 +137,6 @@ def validate_set_cluster_properties( + # unknow properties are reported by NamesIn validator + continue + property_metadata = possible_properties_dict[property_name] +- if property_metadata.name == "stonith-watchdog-timeout": +- # needs extra validation +- continue + if property_metadata.type == "boolean": + validators.append( + validate.ValuePcmkBoolean( +@@ -153,9 +164,13 @@ def validate_set_cluster_properties( + ) + ) + elif property_metadata.type == "time": ++ # make stonith-watchdog-timeout value not forcable + validators.append( + validate.ValueTimeInterval( +- property_metadata.name, severity=severity ++ property_metadata.name, ++ severity=severity ++ if property_metadata.name != "stonith-watchdog-timeout" ++ else reports.ReportItemSeverity.error(), + ) + ) + report_list.extend( +diff --git a/pcs/lib/sbd.py b/pcs/lib/sbd.py +index 1e3cfb37..38cd8767 100644 +--- a/pcs/lib/sbd.py ++++ b/pcs/lib/sbd.py +@@ -1,6 +1,9 @@ + import re + from os import path +-from typing import Optional ++from typing import ( ++ Optional, ++ Union, ++) + + from pcs import settings + from pcs.common import reports +@@ -392,7 +395,10 @@ def _get_local_sbd_watchdog_timeout() -> int: + + + def validate_stonith_watchdog_timeout( +- stonith_watchdog_timeout: str, force: bool = False ++ stonith_watchdog_timeout: Union[ ++ validate.TypeOptionValue, validate.ValuePair ++ ], ++ force: bool = False, + ) -> reports.ReportItemList: + """ + Check sbd status and config when user is setting stonith-watchdog-timeout +@@ -401,6 +407,7 @@ def validate_stonith_watchdog_timeout( + + stonith_watchdog_timeout -- value to be validated + """ ++ stonith_watchdog_timeout = validate.ValuePair.get(stonith_watchdog_timeout) + severity = reports.get_severity(reports.codes.FORCE, force) + if _is_device_set_local(): + return ( +@@ -412,11 +419,11 @@ def validate_stonith_watchdog_timeout( + ), + ) + ] +- if stonith_watchdog_timeout not in ["", "0"] ++ if stonith_watchdog_timeout.normalized not in ["", "0"] + else [] + ) + +- if stonith_watchdog_timeout in ["", "0"]: ++ if stonith_watchdog_timeout.normalized in ["", "0"]: + return [ + reports.ReportItem( + severity, +diff --git a/pcs_test/tier0/lib/commands/test_cluster_property.py b/pcs_test/tier0/lib/commands/test_cluster_property.py +index 94c0938a..781222ab 100644 +--- a/pcs_test/tier0/lib/commands/test_cluster_property.py ++++ b/pcs_test/tier0/lib/commands/test_cluster_property.py +@@ -120,6 +120,34 @@ class StonithWatchdogTimeoutMixin(LoadMetadataMixin): + ) + self.env_assist.assert_reports([]) + ++ def _set_invalid_value(self, forced=False): ++ self.config.remove("services.is_enabled") ++ self.env_assist.assert_raise_library_error( ++ lambda: cluster_property.set_properties( ++ self.env_assist.get_env(), ++ {"stonith-watchdog-timeout": "15x"}, ++ [] if not forced else [reports.codes.FORCE], ++ ) ++ ) ++ self.env_assist.assert_reports( ++ [ ++ fixture.error( ++ reports.codes.INVALID_OPTION_VALUE, ++ option_name="stonith-watchdog-timeout", ++ option_value="15x", ++ allowed_values="time interval (e.g. 1, 2s, 3m, 4h, ...)", ++ cannot_be_empty=False, ++ forbidden_characters=None, ++ ), ++ ] ++ ) ++ ++ def test_set_invalid_value(self): ++ self._set_invalid_value(forced=False) ++ ++ def test_set_invalid_value_forced(self): ++ self._set_invalid_value(forced=True) ++ + + class TestSetStonithWatchdogTimeoutSBDIsDisabled( + StonithWatchdogTimeoutMixin, TestCase +@@ -132,6 +160,9 @@ class TestSetStonithWatchdogTimeoutSBDIsDisabled( + def test_set_zero(self): + self._set_success({"stonith-watchdog-timeout": "0"}) + ++ def test_set_zero_time_suffix(self): ++ self._set_success({"stonith-watchdog-timeout": "0s"}) ++ + def test_set_not_zero_or_empty(self): + self.env_assist.assert_raise_library_error( + lambda: cluster_property.set_properties( +@@ -231,12 +262,12 @@ class TestSetStonithWatchdogTimeoutSBDIsEnabledWatchdogOnly( + def test_set_zero_forced(self): + self.config.env.push_cib( + crm_config=fixture_crm_config_properties( +- [("cib-bootstrap-options", {"stonith-watchdog-timeout": "0"})] ++ [("cib-bootstrap-options", {"stonith-watchdog-timeout": "0s"})] + ) + ) + cluster_property.set_properties( + self.env_assist.get_env(), +- {"stonith-watchdog-timeout": "0"}, ++ {"stonith-watchdog-timeout": "0s"}, + [reports.codes.FORCE], + ) + self.env_assist.assert_reports( +@@ -271,7 +302,7 @@ class TestSetStonithWatchdogTimeoutSBDIsEnabledWatchdogOnly( + self.env_assist.assert_raise_library_error( + lambda: cluster_property.set_properties( + self.env_assist.get_env(), +- {"stonith-watchdog-timeout": "9"}, ++ {"stonith-watchdog-timeout": "9s"}, + [], + ) + ) +@@ -281,7 +312,7 @@ class TestSetStonithWatchdogTimeoutSBDIsEnabledWatchdogOnly( + reports.codes.STONITH_WATCHDOG_TIMEOUT_TOO_SMALL, + force_code=reports.codes.FORCE, + cluster_sbd_watchdog_timeout=10, +- entered_watchdog_timeout="9", ++ entered_watchdog_timeout="9s", + ) + ] + ) +@@ -289,12 +320,12 @@ class TestSetStonithWatchdogTimeoutSBDIsEnabledWatchdogOnly( + def test_too_small_forced(self): + self.config.env.push_cib( + crm_config=fixture_crm_config_properties( +- [("cib-bootstrap-options", {"stonith-watchdog-timeout": "9"})] ++ [("cib-bootstrap-options", {"stonith-watchdog-timeout": "9s"})] + ) + ) + cluster_property.set_properties( + self.env_assist.get_env(), +- {"stonith-watchdog-timeout": "9"}, ++ {"stonith-watchdog-timeout": "9s"}, + [reports.codes.FORCE], + ) + self.env_assist.assert_reports( +@@ -302,13 +333,13 @@ class TestSetStonithWatchdogTimeoutSBDIsEnabledWatchdogOnly( + fixture.warn( + reports.codes.STONITH_WATCHDOG_TIMEOUT_TOO_SMALL, + cluster_sbd_watchdog_timeout=10, +- entered_watchdog_timeout="9", ++ entered_watchdog_timeout="9s", + ) + ] + ) + + def test_more_than_timeout(self): +- self._set_success({"stonith-watchdog-timeout": "11"}) ++ self._set_success({"stonith-watchdog-timeout": "11s"}) + + + @mock.patch("pcs.lib.sbd.get_local_sbd_device_list", lambda: ["dev1", "dev2"]) +@@ -323,6 +354,9 @@ class TestSetStonithWatchdogTimeoutSBDIsEnabledSharedDevices( + def test_set_to_zero(self): + self._set_success({"stonith-watchdog-timeout": "0"}) + ++ def test_set_to_zero_time_suffix(self): ++ self._set_success({"stonith-watchdog-timeout": "0min"}) ++ + def test_set_not_zero_or_empty(self): + self.env_assist.assert_raise_library_error( + lambda: cluster_property.set_properties( +diff --git a/pcs_test/tier0/lib/test_cluster_property.py b/pcs_test/tier0/lib/test_cluster_property.py +index 2feb728d..8d6f90b1 100644 +--- a/pcs_test/tier0/lib/test_cluster_property.py ++++ b/pcs_test/tier0/lib/test_cluster_property.py +@@ -83,6 +83,7 @@ FIXTURE_VALID_OPTIONS_DICT = { + "integer_param": "10", + "percentage_param": "20%", + "select_param": "s3", ++ "stonith-watchdog-timeout": "0", + "time_param": "5min", + } + +@@ -96,6 +97,8 @@ FIXTURE_INVALID_OPTIONS_DICT = { + "have-watchdog": "100", + } + ++STONITH_WATCHDOG_TIMEOUT_UNSET_VALUES = ["", "0", "0s"] ++ + + def _fixture_parameter(name, param_type, default, enum_values): + return ResourceAgentParameter( +@@ -239,6 +242,7 @@ class TestValidateSetClusterProperties(TestCase): + sbd_enabled=False, + sbd_devices=False, + force=False, ++ valid_value=True, + ): + self.mock_is_sbd_enabled.return_value = sbd_enabled + self.mock_sbd_devices.return_value = ["devices"] if sbd_devices else [] +@@ -254,9 +258,13 @@ class TestValidateSetClusterProperties(TestCase): + ), + expected_report_list, + ) +- if "stonith-watchdog-timeout" in new_properties and ( +- new_properties["stonith-watchdog-timeout"] +- or "stonith-watchdog-timeout" in configured_properties ++ if ( ++ "stonith-watchdog-timeout" in new_properties ++ and ( ++ new_properties["stonith-watchdog-timeout"] ++ or "stonith-watchdog-timeout" in configured_properties ++ ) ++ and valid_value + ): + self.mock_is_sbd_enabled.assert_called_once_with( + self.mock_service_manager +@@ -266,7 +274,10 @@ class TestValidateSetClusterProperties(TestCase): + if sbd_devices: + self.mock_sbd_timeout.assert_not_called() + else: +- if new_properties["stonith-watchdog-timeout"] in ["", "0"]: ++ if ( ++ new_properties["stonith-watchdog-timeout"] ++ in STONITH_WATCHDOG_TIMEOUT_UNSET_VALUES ++ ): + self.mock_sbd_timeout.assert_not_called() + else: + self.mock_sbd_timeout.assert_called_once_with() +@@ -280,6 +291,8 @@ class TestValidateSetClusterProperties(TestCase): + self.mock_sbd_timeout.assert_not_called() + + self.mock_is_sbd_enabled.reset_mock() ++ self.mock_sbd_devices.reset_mock() ++ self.mock_sbd_timeout.reset_mock() + + def test_no_properties_to_set_or_unset(self): + self.assert_validate_set( +@@ -328,7 +341,7 @@ class TestValidateSetClusterProperties(TestCase): + ) + + def test_unset_stonith_watchdog_timeout_sbd_disabled(self): +- for value in ["0", ""]: ++ for value in STONITH_WATCHDOG_TIMEOUT_UNSET_VALUES: + with self.subTest(value=value): + self.assert_validate_set( + ["stonith-watchdog-timeout"], +@@ -349,22 +362,27 @@ class TestValidateSetClusterProperties(TestCase): + ) + + def test_set_ok_stonith_watchdog_timeout_sbd_enabled_without_devices(self): +- self.assert_validate_set( +- [], {"stonith-watchdog-timeout": "15"}, [], sbd_enabled=True +- ) ++ for value in ["15", "15s"]: ++ with self.subTest(value=value): ++ self.assert_validate_set( ++ [], ++ {"stonith-watchdog-timeout": value}, ++ [], ++ sbd_enabled=True, ++ ) + + def test_set_small_stonith_watchdog_timeout_sbd_enabled_without_devices( + self, + ): + self.assert_validate_set( + [], +- {"stonith-watchdog-timeout": "9"}, ++ {"stonith-watchdog-timeout": "9s"}, + [ + fixture.error( + reports.codes.STONITH_WATCHDOG_TIMEOUT_TOO_SMALL, + force_code=reports.codes.FORCE, + cluster_sbd_watchdog_timeout=10, +- entered_watchdog_timeout="9", ++ entered_watchdog_timeout="9s", + ) + ], + sbd_enabled=True, +@@ -387,28 +405,54 @@ class TestValidateSetClusterProperties(TestCase): + force=True, + ) + +- def test_set_not_a_number_stonith_watchdog_timeout_sbd_enabled_without_devices( ++ def _set_invalid_value_stonith_watchdog_timeout( ++ self, sbd_enabled=False, sbd_devices=False ++ ): ++ for value in ["invalid", "10x"]: ++ with self.subTest(value=value): ++ self.assert_validate_set( ++ [], ++ {"stonith-watchdog-timeout": value}, ++ [ ++ fixture.error( ++ reports.codes.INVALID_OPTION_VALUE, ++ option_name="stonith-watchdog-timeout", ++ option_value=value, ++ allowed_values="time interval (e.g. 1, 2s, 3m, 4h, ...)", ++ cannot_be_empty=False, ++ forbidden_characters=None, ++ ) ++ ], ++ sbd_enabled=sbd_enabled, ++ sbd_devices=sbd_devices, ++ valid_value=False, ++ ) ++ ++ def test_set_invalid_value_stonith_watchdog_timeout_sbd_enabled_without_devices( + self, + ): ++ self._set_invalid_value_stonith_watchdog_timeout( ++ sbd_enabled=True, sbd_devices=False ++ ) + +- self.assert_validate_set( +- [], +- {"stonith-watchdog-timeout": "invalid"}, +- [ +- fixture.error( +- reports.codes.STONITH_WATCHDOG_TIMEOUT_TOO_SMALL, +- force_code=reports.codes.FORCE, +- cluster_sbd_watchdog_timeout=10, +- entered_watchdog_timeout="invalid", +- ) +- ], +- sbd_enabled=True, ++ def test_set_invalid_value_stonith_watchdog_timeout_sbd_enabled_with_devices( ++ self, ++ ): ++ self._set_invalid_value_stonith_watchdog_timeout( ++ sbd_enabled=True, sbd_devices=True ++ ) ++ ++ def test_set_invalid_value_stonith_watchdog_timeout_sbd_disabled( ++ self, ++ ): ++ self._set_invalid_value_stonith_watchdog_timeout( ++ sbd_enabled=False, sbd_devices=False + ) + + def test_unset_stonith_watchdog_timeout_sbd_enabled_without_devices( + self, + ): +- for value in ["0", ""]: ++ for value in STONITH_WATCHDOG_TIMEOUT_UNSET_VALUES: + with self.subTest(value=value): + self.assert_validate_set( + ["stonith-watchdog-timeout"], +@@ -426,7 +470,7 @@ class TestValidateSetClusterProperties(TestCase): + def test_unset_stonith_watchdog_timeout_sbd_enabled_without_devices_forced( + self, + ): +- for value in ["0", ""]: ++ for value in STONITH_WATCHDOG_TIMEOUT_UNSET_VALUES: + with self.subTest(value=value): + self.assert_validate_set( + ["stonith-watchdog-timeout"], +@@ -459,7 +503,7 @@ class TestValidateSetClusterProperties(TestCase): + def test_set_stonith_watchdog_timeout_sbd_enabled_with_devices_forced(self): + self.assert_validate_set( + [], +- {"stonith-watchdog-timeout": 15}, ++ {"stonith-watchdog-timeout": "15s"}, + [ + fixture.warn( + reports.codes.STONITH_WATCHDOG_TIMEOUT_CANNOT_BE_SET, +@@ -472,7 +516,7 @@ class TestValidateSetClusterProperties(TestCase): + ) + + def test_unset_stonith_watchdog_timeout_sbd_enabled_with_devices(self): +- for value in ["0", ""]: ++ for value in STONITH_WATCHDOG_TIMEOUT_UNSET_VALUES: + with self.subTest(value=value): + self.assert_validate_set( + ["stonith-watchdog-timeout"], +diff --git a/pcs_test/tier1/test_cluster_property.py b/pcs_test/tier1/test_cluster_property.py +index 39d70b9d..cb2d8f5c 100644 +--- a/pcs_test/tier1/test_cluster_property.py ++++ b/pcs_test/tier1/test_cluster_property.py +@@ -169,7 +169,7 @@ class TestPropertySet(PropertyMixin, TestCase): + + def test_set_stonith_watchdog_timeout(self): + self.assert_pcs_fail( +- "property set stonith-watchdog-timeout=5".split(), ++ "property set stonith-watchdog-timeout=5s".split(), + stderr_full=( + "Error: stonith-watchdog-timeout can only be unset or set to 0 " + "while SBD is disabled\n" +@@ -179,6 +179,18 @@ class TestPropertySet(PropertyMixin, TestCase): + ) + self.assert_resources_xml_in_cib(UNCHANGED_CRM_CONFIG) + ++ def test_set_stonith_watchdog_timeout_invalid_value(self): ++ self.assert_pcs_fail( ++ "property set stonith-watchdog-timeout=5x".split(), ++ stderr_full=( ++ "Error: '5x' is not a valid stonith-watchdog-timeout value, use" ++ " time interval (e.g. 1, 2s, 3m, 4h, ...)\n" ++ "Error: Errors have occurred, therefore pcs is unable to " ++ "continue\n" ++ ), ++ ) ++ self.assert_resources_xml_in_cib(UNCHANGED_CRM_CONFIG) ++ + + class TestPropertyUnset(PropertyMixin, TestCase): + def test_success(self): +-- +2.39.0 + diff --git a/SOURCES/bz2159454-01-add-agent-validation-option.patch b/SOURCES/bz2159454-01-add-agent-validation-option.patch new file mode 100644 index 0000000..662850c --- /dev/null +++ b/SOURCES/bz2159454-01-add-agent-validation-option.patch @@ -0,0 +1,1473 @@ +From f60b24d7c70f63ceef0020a0a3b8a885aeccbdd1 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular +Date: Tue, 10 Jan 2023 15:57:33 +0100 +Subject: [PATCH 1/2] add '--agent-validation' option for enabling agent + self-validation feature + +--- + CHANGELOG.md | 7 + + pcs/cli/common/parse_args.py | 3 + + pcs/lib/cib/resource/primitive.py | 12 +- + pcs/lib/cib/resource/remote_node.py | 1 + + pcs/lib/commands/booth.py | 1 + + pcs/lib/commands/resource.py | 16 ++ + pcs/lib/commands/stonith.py | 8 + + pcs/pcs.8.in | 16 +- + pcs/resource.py | 12 +- + pcs/stonith.py | 3 + + pcs/usage.py | 32 ++-- + .../cib/resource/test_primitive_validate.py | 49 ++++++ + .../commands/resource/test_resource_create.py | 156 ++++++++++++------ + pcs_test/tier0/lib/commands/test_booth.py | 49 ------ + pcs_test/tier0/lib/commands/test_stonith.py | 24 +-- + pcs_test/tier1/cib_resource/test_create.py | 2 + + .../tier1/cib_resource/test_stonith_create.py | 2 - + pcs_test/tier1/legacy/test_resource.py | 8 +- + pcs_test/tier1/legacy/test_stonith.py | 25 ++- + 19 files changed, 280 insertions(+), 146 deletions(-) + +diff --git a/CHANGELOG.md b/CHANGELOG.md +index 378cca50..47212f00 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -12,9 +12,16 @@ + - Displaying bool and integer values in `pcs resource config` command + ([rhbz#2151164], [ghissue#604]) + ++### Changed ++- Resource/stonith agent self-validation of instance attributes is now ++ disabled by default, as many agents do not work with it properly. ++ Use flag '--agent-validation' to enable it in supported commands. ++ ([rhbz#2159454]) ++ + [ghissue#604]: https://github.com/ClusterLabs/pcs/issues/604 + [rhbz#2151164]: https://bugzilla.redhat.com/show_bug.cgi?id=2151164 + [rhbz#2151524]: https://bugzilla.redhat.com/show_bug.cgi?id=2151524 ++[rhbz#2159454]: https://bugzilla.redhat.com/show_bug.cgi?id=2159454 + + + ## [0.11.4] - 2022-11-21 +diff --git a/pcs/cli/common/parse_args.py b/pcs/cli/common/parse_args.py +index 663751aa..1c75f406 100644 +--- a/pcs/cli/common/parse_args.py ++++ b/pcs/cli/common/parse_args.py +@@ -93,6 +93,8 @@ PCS_LONG_OPTIONS = [ + f"{_OUTPUT_FORMAT_OPTION_STR}=", + # auth token + "token=", ++ # enable agent self validation ++ "agent-validation", + ] + + +@@ -484,6 +486,7 @@ class InputModifiers: + { + # boolean values + "--all": "--all" in options, ++ "--agent-validation": "--agent-validation" in options, + "--autodelete": "--autodelete" in options, + "--brief": "--brief" in options, + "--config": "--config" in options, +diff --git a/pcs/lib/cib/resource/primitive.py b/pcs/lib/cib/resource/primitive.py +index f9d52b9b..8d6e4c05 100644 +--- a/pcs/lib/cib/resource/primitive.py ++++ b/pcs/lib/cib/resource/primitive.py +@@ -137,6 +137,7 @@ def create( + resource_type: str = "resource", + # TODO remove this arg + do_not_report_instance_attribute_server_exists: bool = False, ++ enable_agent_self_validation: bool = False, + ): + # pylint: disable=too-many-arguments + # pylint: disable=too-many-locals +@@ -159,6 +160,8 @@ def create( + resource_type -- describes the resource for reports + do_not_report_instance_attribute_server_exists -- dirty fix due to + suboptimal architecture, TODO: fix the architecture and remove the param ++ enable_agent_self_validation -- if True, use agent self-validation feature ++ to validate instance attributes + """ + if raw_operation_list is None: + raw_operation_list = [] +@@ -200,6 +203,7 @@ def create( + instance_attributes, + resources_section, + force=allow_invalid_instance_attributes, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + # TODO remove this "if", see pcs.lib.cib.remote_node.create for details + if do_not_report_instance_attribute_server_exists: +@@ -388,6 +392,7 @@ def validate_resource_instance_attributes_create( + instance_attributes: Mapping[str, str], + resources_section: _Element, + force: bool = False, ++ enable_agent_self_validation: bool = False, + ) -> reports.ReportItemList: + report_items: reports.ReportItemList = [] + report_items += validate.ValidatorAll( +@@ -422,7 +427,8 @@ def validate_resource_instance_attributes_create( + ) + + if ( +- _is_ocf_or_stonith_agent(agent_name) ++ enable_agent_self_validation ++ and _is_ocf_or_stonith_agent(agent_name) + and resource_agent.metadata.agent_exists + and resource_agent.metadata.provides_self_validation + and not any( +@@ -450,6 +456,7 @@ def validate_resource_instance_attributes_update( + resource_id: str, + resources_section: _Element, + force: bool = False, ++ enable_agent_self_validation: bool = False, + ) -> reports.ReportItemList: + # pylint: disable=too-many-locals + # TODO This function currently accepts the updated resource as a string and +@@ -524,7 +531,8 @@ def validate_resource_instance_attributes_update( + ) + + if ( +- _is_ocf_or_stonith_agent(agent_name) ++ enable_agent_self_validation ++ and _is_ocf_or_stonith_agent(agent_name) + and resource_agent.metadata.agent_exists + and resource_agent.metadata.provides_self_validation + and not any( +diff --git a/pcs/lib/cib/resource/remote_node.py b/pcs/lib/cib/resource/remote_node.py +index c76e37a6..f65c5446 100644 +--- a/pcs/lib/cib/resource/remote_node.py ++++ b/pcs/lib/cib/resource/remote_node.py +@@ -253,4 +253,5 @@ def create( + # 3) call the validation from here and handle the results or config + # the validator before / when running it + do_not_report_instance_attribute_server_exists=True, ++ enable_agent_self_validation=False, + ) +diff --git a/pcs/lib/commands/booth.py b/pcs/lib/commands/booth.py +index ee91ea14..7b0ed4de 100644 +--- a/pcs/lib/commands/booth.py ++++ b/pcs/lib/commands/booth.py +@@ -480,6 +480,7 @@ def create_in_cluster( + env.cmd_runner(), + resources_section, + id_provider, ++ enable_agent_self_validation=False, + ) + agent_factory = ResourceAgentFacadeFactory( + env.cmd_runner(), report_processor +diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py +index 7c64ace7..41f1a1ac 100644 +--- a/pcs/lib/commands/resource.py ++++ b/pcs/lib/commands/resource.py +@@ -361,6 +361,7 @@ def create( + ensure_disabled: bool = False, + wait: WaitType = False, + allow_not_suitable_command: bool = False, ++ enable_agent_self_validation: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +@@ -394,6 +395,8 @@ def create( + pcs.lib.commands.remote_node); + in the case of remote/guest node forcible error is produced when this + flag is set to False and warning is produced otherwise ++ enable_agent_self_validation -- if True, use agent self-validation feature ++ to validate instance attributes + """ + runner = env.cmd_runner() + agent_factory = ResourceAgentFacadeFactory(runner, env.report_processor) +@@ -440,6 +443,7 @@ def create( + allow_invalid_operation, + allow_invalid_instance_attributes, + use_default_operations, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + if env.report_processor.has_errors: + raise LibraryError() +@@ -465,6 +469,7 @@ def create_as_clone( + wait: WaitType = False, + allow_not_suitable_command: bool = False, + allow_incompatible_clone_meta_attributes: bool = False, ++ enable_agent_self_validation: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +@@ -495,6 +500,8 @@ def create_as_clone( + allow_incompatible_clone_meta_attributes -- if True some incompatible clone + meta attributes are treated as a warning, or as a forceable error if + False ++ enable_agent_self_validation -- if True, use agent self-validation feature ++ to validate instance attributes + """ + runner = env.cmd_runner() + agent_factory = ResourceAgentFacadeFactory(runner, env.report_processor) +@@ -579,6 +586,7 @@ def create_as_clone( + allow_invalid_operation, + allow_invalid_instance_attributes, + use_default_operations, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + + clone_element = resource.clone.append_new( +@@ -609,6 +617,7 @@ def create_in_group( + put_after_adjacent: bool = False, + wait: WaitType = False, + allow_not_suitable_command: bool = False, ++ enable_agent_self_validation: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +@@ -637,6 +646,8 @@ def create_in_group( + adjacent resource + wait -- is flag for controlling waiting for pacemaker idle mechanism + allow_not_suitable_command -- turn forceable errors into warnings ++ enable_agent_self_validation -- if True, use agent self-validation feature ++ to validate instance attributes + """ + runner = env.cmd_runner() + agent_factory = ResourceAgentFacadeFactory(runner, env.report_processor) +@@ -712,6 +723,7 @@ def create_in_group( + allow_invalid_operation, + allow_invalid_instance_attributes, + use_default_operations, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + if ensure_disabled: + resource.common.disable(primitive_element, id_provider) +@@ -749,6 +761,7 @@ def create_into_bundle( + wait: WaitType = False, + allow_not_suitable_command: bool = False, + allow_not_accessible_resource: bool = False, ++ enable_agent_self_validation: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +@@ -776,6 +789,8 @@ def create_into_bundle( + wait -- is flag for controlling waiting for pacemaker idle mechanism + allow_not_suitable_command -- turn forceable errors into warnings + allow_not_accessible_resource -- turn forceable errors into warnings ++ enable_agent_self_validation -- if True, use agent self-validation feature ++ to validate instance attributes + """ + runner = env.cmd_runner() + agent_factory = ResourceAgentFacadeFactory(runner, env.report_processor) +@@ -823,6 +838,7 @@ def create_into_bundle( + allow_invalid_operation, + allow_invalid_instance_attributes, + use_default_operations, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + if ensure_disabled: + resource.common.disable(primitive_element, id_provider) +diff --git a/pcs/lib/commands/stonith.py b/pcs/lib/commands/stonith.py +index 42b5b270..3a3ea3b7 100644 +--- a/pcs/lib/commands/stonith.py ++++ b/pcs/lib/commands/stonith.py +@@ -115,6 +115,7 @@ def create( + use_default_operations: bool = True, + ensure_disabled: bool = False, + wait: WaitType = False, ++ enable_agent_self_validation: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +@@ -137,6 +138,8 @@ def create( + operations (specified in a stonith agent) + ensure_disabled -- flag that keeps resource in target-role "Stopped" + wait -- flag for controlling waiting for pacemaker idle mechanism ++ enable_agent_self_validation -- if True, use agent self-validation feature ++ to validate instance attributes + """ + runner = env.cmd_runner() + agent_factory = ResourceAgentFacadeFactory(runner, env.report_processor) +@@ -173,6 +176,7 @@ def create( + allow_invalid_instance_attributes=allow_invalid_instance_attributes, + use_default_operations=use_default_operations, + resource_type="stonith", ++ enable_agent_self_validation=enable_agent_self_validation, + ) + if ensure_disabled: + resource.common.disable(stonith_element, id_provider) +@@ -195,6 +199,7 @@ def create_in_group( + adjacent_resource_id: Optional[str] = None, + put_after_adjacent: bool = False, + wait: WaitType = False, ++ enable_agent_self_validation: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +@@ -222,6 +227,8 @@ def create_in_group( + put_after_adjacent -- is flag to put a newly create resource befor/after + adjacent stonith + wait -- flag for controlling waiting for pacemaker idle mechanism ++ enable_agent_self_validation -- if True, use agent self-validation feature ++ to validate instance attributes + """ + runner = env.cmd_runner() + agent_factory = ResourceAgentFacadeFactory(runner, env.report_processor) +@@ -288,6 +295,7 @@ def create_in_group( + allow_invalid_operation, + allow_invalid_instance_attributes, + use_default_operations, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + if ensure_disabled: + resource.common.disable(stonith_element, id_provider) +diff --git a/pcs/pcs.8.in b/pcs/pcs.8.in +index 7bbd1ae2..3be8465e 100644 +--- a/pcs/pcs.8.in ++++ b/pcs/pcs.8.in +@@ -95,8 +95,8 @@ Show list of all available resource agents (if filter is provided then only reso + describe [:[:]] [\fB\-\-full\fR] + Show options for the specified resource. If \fB\-\-full\fR is specified, all options including advanced and deprecated ones are shown. + .TP +-create [:[:]] [resource options] [\fBop\fR [ ]...] [\fBmeta\fR ...] [\fBclone\fR [] [] | promotable [] [] | \fB\-\-group\fR [\fB\-\-before\fR | \fB\-\-after\fR ] | \fBbundle\fR ] [\fB\-\-disabled\fR] [\fB\-\-no\-default\-ops] [\fB\-\-wait\fR[=n]] +-Create specified resource. If \fBclone\fR is used a clone resource is created. If \fBpromotable\fR is used a promotable clone resource is created. If \fB\-\-group\fR is specified the resource is added to the group named. You can use \fB\-\-before\fR or \fB\-\-after\fR to specify the position of the added resource relatively to some resource already existing in the group. If \fBbundle\fR is specified, resource will be created inside of the specified bundle. If \fB\-\-disabled\fR is specified the resource is not started automatically. If \fB\-\-no\-default\-ops\fR is specified, only monitor operations are created for the resource and all other operations use default settings. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the resource to start and then return 0 if the resource is started, or 1 if the resource has not yet started. If 'n' is not specified it defaults to 60 minutes. ++create [:[:]] [resource options] [\fBop\fR [ ]...] [\fBmeta\fR ...] [\fBclone\fR [] [] | promotable [] [] | \fB\-\-group\fR [\fB\-\-before\fR | \fB\-\-after\fR ] | \fBbundle\fR ] [\fB\-\-disabled\fR] [\fB\-\-agent\-validation\fR] [\fB\-\-no\-default\-ops\fR] [\fB\-\-wait\fR[=n]] ++Create specified resource. If \fBclone\fR is used a clone resource is created. If \fBpromotable\fR is used a promotable clone resource is created. If \fB\-\-group\fR is specified the resource is added to the group named. You can use \fB\-\-before\fR or \fB\-\-after\fR to specify the position of the added resource relatively to some resource already existing in the group. If \fBbundle\fR is specified, resource will be created inside of the specified bundle. If \fB\-\-disabled\fR is specified the resource is not started automatically. If \fB\-\-agent\-validation\fR is specified, resource agent validate\-all action will be used to validate resource options. If \fB\-\-no\-default\-ops\fR is specified, only monitor operations are created for the resource and all other operations use default settings. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the resource to start and then return 0 if the resource is started, or 1 if the resource has not yet started. If 'n' is not specified it defaults to 60 minutes. + + Example: Create a new resource called 'VirtualIP' with IP address 192.168.0.99, netmask of 32, monitored everything 30 seconds, on eth2: pcs resource create VirtualIP ocf:heartbeat:IPaddr2 ip=192.168.0.99 cidr_netmask=32 nic=eth2 op monitor interval=30s + .TP +@@ -204,11 +204,13 @@ List available OCF resource agent providers. + agents [standard[:provider]] + List available agents optionally filtered by standard and provider. + .TP +-update [resource options] [op [ ]...] [meta ...] [\fB\-\-wait\fR[=n]] ++update [resource options] [op [ ]...] [meta ...] [\fB\-\-agent\-validation\fR] [\fB\-\-wait\fR[=n]] + Add, remove or change options of specified resource, clone or multi\-state resource. Unspecified options will be kept unchanged. If you wish to remove an option, set it to empty value, i.e. 'option_name='. + + If an operation (op) is specified it will update the first found operation with the same action on the specified resource. If no operation with that action exists then a new operation will be created. (WARNING: all existing options on the updated operation will be reset if not specified.) If you want to create multiple monitor operations you should use the 'op add' & 'op remove' commands. + ++If \fB\-\-agent\-validation\fR is specified, resource agent validate\-all action will be used to validate resource options. ++ + If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the changes to take effect and then return 0 if the changes have been processed or 1 otherwise. If 'n' is not specified it defaults to 60 minutes. + .TP + op add [operation properties] +@@ -689,8 +691,8 @@ Show list of all available stonith agents (if filter is provided then only stoni + describe [\fB\-\-full\fR] + Show options for specified stonith agent. If \fB\-\-full\fR is specified, all options including advanced and deprecated ones are shown. + .TP +-create [stonith device options] [op [ ]...] [meta ...] [\fB\-\-group\fR [\fB\-\-before\fR | \fB\-\-after\fR ]] [\fB\-\-disabled\fR] [\fB\-\-wait\fR[=n]] +-Create stonith device with specified type and options. If \fB\-\-group\fR is specified the stonith device is added to the group named. You can use \fB\-\-before\fR or \fB\-\-after\fR to specify the position of the added stonith device relatively to some stonith device already existing in the group. If\fB\-\-disabled\fR is specified the stonith device is not used. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the stonith device to start and then return 0 if the stonith device is started, or 1 if the stonith device has not yet started. If 'n' is not specified it defaults to 60 minutes. ++create [stonith device options] [op [ ]...] [meta ...] [\fB\-\-group\fR [\fB\-\-before\fR | \fB\-\-after\fR ]] [\fB\-\-disabled\fR] [\fB\-\-agent\-validation\fR] [\fB\-\-wait\fR[=n]] ++Create stonith device with specified type and options. If \fB\-\-group\fR is specified the stonith device is added to the group named. You can use \fB\-\-before\fR or \fB\-\-after\fR to specify the position of the added stonith device relatively to some stonith device already existing in the group. If\fB\-\-disabled\fR is specified the stonith device is not used. If \fB\-\-agent\-validation\fR is specified, stonith agent validate\-all action will be used to validate stonith device options. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the stonith device to start and then return 0 if the stonith device is started, or 1 if the stonith device has not yet started. If 'n' is not specified it defaults to 60 minutes. + + Example: Create a device for nodes node1 and node2 + .br +@@ -700,11 +702,13 @@ Example: Use port p1 for node n1 and ports p2 and p3 for node n2 + .br + pcs stonith create MyFence fence_virt 'pcmk_host_map=n1:p1;n2:p2,p3' + .TP +-update [stonith options] [op [ ]...] [meta ...] [\fB\-\-wait\fR[=n]] ++update [stonith options] [op [ ]...] [meta ...] [\fB\-\-agent\-validation\fR] [\fB\-\-wait\fR[=n]] + Add, remove or change options of specified stonith device. Unspecified options will be kept unchanged. If you wish to remove an option, set it to empty value, i.e. 'option_name='. + + If an operation (op) is specified it will update the first found operation with the same action on the specified stonith device. If no operation with that action exists then a new operation will be created. (WARNING: all existing options on the updated operation will be reset if not specified.) If you want to create multiple monitor operations you should use the 'op add' & 'op remove' commands. + ++If \fB\-\-agent\-validation\fR is specified, stonith agent validate\-all action will be used to validate stonith device options. ++ + If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the changes to take effect and then return 0 if the changes have been processed or 1 otherwise. If 'n' is not specified it defaults to 60 minutes. + .TP + update\-scsi\-devices (set [...]) | (add [...] delete|remove [...] ) +diff --git a/pcs/resource.py b/pcs/resource.py +index b265b2d2..d8c61633 100644 +--- a/pcs/resource.py ++++ b/pcs/resource.py +@@ -624,6 +624,7 @@ def _format_desc(indentation, desc): + def resource_create(lib, argv, modifiers): + """ + Options: ++ * --agent-validation - use agent self validation of instance attributes + * --before - specified resource inside a group before which new resource + will be placed inside the group + * --after - specified resource inside a group after which new resource +@@ -637,6 +638,7 @@ def resource_create(lib, argv, modifiers): + * -f - CIB file + """ + modifiers.ensure_only_supported( ++ "--agent-validation", + "--before", + "--after", + "--group", +@@ -699,6 +701,7 @@ def resource_create(lib, argv, modifiers): + use_default_operations=not modifiers.get("--no-default-ops"), + wait=modifiers.get("--wait"), + allow_not_suitable_command=modifiers.get("--force"), ++ enable_agent_self_validation=modifiers.get("--agent-validation"), + ) + + clone_id = parts.get("clone_id", None) +@@ -966,6 +969,7 @@ def update_cmd(lib: Any, argv: List[str], modifiers: InputModifiers) -> None: + """ + Options: + * -f - CIB file ++ * --agent-validation - use agent self validation of instance attributes + * --wait + * --force - allow invalid options, do not fail if not possible to get + agent metadata, allow not suitable command +@@ -982,11 +986,14 @@ def resource_update(args: List[str], modifiers: InputModifiers) -> None: + """ + Commandline options: + * -f - CIB file ++ * --agent-validation - use agent self validation of instance attributes + * --wait + * --force - allow invalid options, do not fail if not possible to get + agent metadata, allow not suitable command + """ +- modifiers.ensure_only_supported("-f", "--wait", "--force") ++ modifiers.ensure_only_supported( ++ "-f", "--wait", "--force", "--agent-validation" ++ ) + if len(args) < 2: + raise CmdLineInputError() + res_id = args.pop(0) +@@ -1052,6 +1059,9 @@ def resource_update(args: List[str], modifiers: InputModifiers) -> None: + res_id, + get_resources(lib_pacemaker.get_cib(cib_xml)), + force=bool(modifiers.get("--force")), ++ enable_agent_self_validation=bool( ++ modifiers.get("--agent-validation") ++ ), + ) + if report_list: + process_library_reports(report_list) +diff --git a/pcs/stonith.py b/pcs/stonith.py +index 8ba6066c..ef97b41b 100644 +--- a/pcs/stonith.py ++++ b/pcs/stonith.py +@@ -148,6 +148,7 @@ def stonith_create(lib, argv, modifiers): + instance attributes + * --disabled - created resource will be disabled + * --no-default-ops - do not add default operations ++ * --agent-validation - use agent self validation of instance attributes + * --wait + * -f - CIB file + """ +@@ -158,6 +159,7 @@ def stonith_create(lib, argv, modifiers): + "--force", + "--disabled", + "--no-default-ops", ++ "--agent-validation", + "--wait", + "-f", + ) +@@ -191,6 +193,7 @@ def stonith_create(lib, argv, modifiers): + ensure_disabled=modifiers.get("--disabled"), + use_default_operations=not modifiers.get("--no-default-ops"), + wait=modifiers.get("--wait"), ++ enable_agent_self_validation=modifiers.get("--agent-validation"), + ) + + if not modifiers.get("--group"): +diff --git a/pcs/usage.py b/pcs/usage.py +index 073e2b1e..b2c46669 100644 +--- a/pcs/usage.py ++++ b/pcs/usage.py +@@ -680,16 +680,17 @@ _RESOURCE_UPDATE_CMD = "update" + _RESOURCE_UPDATE_SYNTAX = _unwrap( + """ + <{obj} id> [{obj} options] [op [ ]...] +- [meta ...] [--wait[=n]] ++ [meta ...] [--agent-validation] [--wait[=n]] + """ + ) + + + def _resource_update_desc_fn(is_stonith: bool) -> Iterable[str]: + if is_stonith: +- obj = obj_long = "stonith device" ++ agent_type = "stonith" ++ obj = obj_long = f"{agent_type} device" + else: +- obj = "resource" ++ obj = agent_type = "resource" + obj_long = "resource, clone or multi-state resource" + return ( + f""" +@@ -707,6 +708,11 @@ def _resource_update_desc_fn(is_stonith: bool) -> Iterable[str]: + you should use the 'op add' & 'op remove' commands. + """, + "", ++ f""" ++ If --agent-validation is specified, {agent_type} agent validate-all ++ action will be used to validate {obj} options. ++ """, ++ "", + """ + If --wait is specified, pcs will wait up to 'n' seconds for the changes + to take effect and then return 0 if the changes have been processed or +@@ -778,7 +784,8 @@ Commands: + [clone [] [] | + promotable [] [] | + --group [--before | --after ] | +- bundle ] [--disabled] [--no-default-ops] [--wait[=n]] ++ bundle ] [--disabled] [--agent-validation] ++ [--no-default-ops] [--wait[=n]] + Create specified resource. If clone is used a clone resource is + created. If promotable is used a promotable clone resource is created. + If --group is specified the resource is added to the group named. You +@@ -786,12 +793,13 @@ Commands: + resource relatively to some resource already existing in the group. If + bundle is used, the resource will be created inside of the specified + bundle. If --disabled is specified the resource is not started +- automatically. If --no-default-ops is specified, only monitor +- operations are created for the resource and all other operations use +- default settings. If --wait is specified, pcs will wait up to 'n' +- seconds for the resource to start and then return 0 if the resource is +- started, or 1 if the resource has not yet started. If 'n' is not +- specified it defaults to 60 minutes. ++ automatically. If --agent-validation is specified, resource agent ++ validate-all action will be used to validate resource options. If ++ --no-default-ops is specified, only monitor operations are created for ++ the resource and all other operations use default settings. If --wait ++ is specified, pcs will wait up to 'n' seconds for the resource to start ++ and then return 0 if the resource is started, or 1 if the resource has ++ not yet started. If 'n' is not specified it defaults to 60 minutes. + Example: Create a new resource called 'VirtualIP' with IP address + 192.168.0.99, netmask of 32, monitored everything 30 seconds, + on eth2: +@@ -1844,13 +1852,15 @@ Commands: + [op [ + ]...] [meta ...] + [--group [--before | --after ]] +- [--disabled] [--wait[=n]] ++ [--disabled] [--agent-validation] [--wait[=n]] + Create stonith device with specified type and options. + If --group is specified the stonith device is added to the group named. + You can use --before or --after to specify the position of the added + stonith device relatively to some stonith device already existing in the + group. + If --disabled is specified the stonith device is not used. ++ If --agent-validation is specified, stonith agent validate-all action ++ will be used to validate stonith device options. + If --wait is specified, pcs will wait up to 'n' seconds for the stonith + device to start and then return 0 if the stonith device is started, or 1 + if the stonith device has not yet started. If 'n' is not specified it +diff --git a/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py b/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py +index 7a4e5c8f..0438ae5d 100644 +--- a/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py ++++ b/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py +@@ -643,6 +643,22 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + self.agent_self_validation_mock.return_value = True, [] + self.cmd_runner = mock.Mock() + ++ def test_disabled(self): ++ attributes = {"required": "value"} ++ facade = _fixture_ocf_agent() ++ self.assertEqual( ++ primitive.validate_resource_instance_attributes_create( ++ self.cmd_runner, ++ facade, ++ attributes, ++ etree.Element("resources"), ++ force=False, ++ enable_agent_self_validation=False, ++ ), ++ [], ++ ) ++ self.agent_self_validation_mock.assert_not_called() ++ + def test_success(self): + attributes = {"required": "value"} + facade = _fixture_ocf_agent() +@@ -653,6 +669,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -672,6 +689,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=True, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -693,6 +711,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -718,6 +737,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -737,6 +757,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -752,6 +773,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -767,6 +789,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -1326,6 +1349,24 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + etree.SubElement(nvset_el, "nvpair", dict(name=name, value=value)) + return resources_el + ++ def test_disabled(self): ++ old_attributes = {"required": "old_value"} ++ new_attributes = {"required": "new_value"} ++ facade = _fixture_ocf_agent() ++ self.assertEqual( ++ primitive.validate_resource_instance_attributes_update( ++ self.cmd_runner, ++ facade, ++ new_attributes, ++ self._NAME, ++ self._fixture_resources(old_attributes), ++ force=False, ++ enable_agent_self_validation=False, ++ ), ++ [], ++ ) ++ self.agent_self_validation_mock.assert_not_called() ++ + def test_success(self): + old_attributes = {"required": "old_value"} + new_attributes = {"required": "new_value"} +@@ -1338,6 +1379,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1369,6 +1411,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=True, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1405,6 +1448,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -1442,6 +1486,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1473,6 +1518,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1490,6 +1536,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1507,6 +1554,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -1533,6 +1581,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.warn( +diff --git a/pcs_test/tier0/lib/commands/resource/test_resource_create.py b/pcs_test/tier0/lib/commands/resource/test_resource_create.py +index 433a7351..a038d0f5 100644 +--- a/pcs_test/tier0/lib/commands/resource/test_resource_create.py ++++ b/pcs_test/tier0/lib/commands/resource/test_resource_create.py +@@ -30,7 +30,9 @@ def create( + allow_invalid_operation=False, + agent_name="ocf:heartbeat:Dummy", + allow_invalid_instance_attributes=False, ++ enable_agent_self_validation=False, + ): ++ # pylint: disable=too-many-arguments + return resource.create( + env, + "A", +@@ -42,6 +44,7 @@ def create( + ensure_disabled=disabled, + allow_invalid_operation=allow_invalid_operation, + allow_invalid_instance_attributes=allow_invalid_instance_attributes, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + + +@@ -51,6 +54,7 @@ def create_group( + disabled=False, + meta_attributes=None, + operation_list=None, ++ enable_agent_self_validation=False, + ): + return resource.create_in_group( + env, +@@ -62,6 +66,7 @@ def create_group( + instance_attributes={}, + wait=wait, + ensure_disabled=disabled, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + + +@@ -75,6 +80,7 @@ def create_clone( + clone_id=None, + agent="ocf:heartbeat:Dummy", + allow_incompatible_clone_meta_attributes=False, ++ enable_agent_self_validation=False, + ): + # pylint: disable=too-many-arguments + return resource.create_as_clone( +@@ -89,6 +95,7 @@ def create_clone( + wait=wait, + ensure_disabled=disabled, + allow_incompatible_clone_meta_attributes=allow_incompatible_clone_meta_attributes, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + + +@@ -99,6 +106,7 @@ def create_bundle( + meta_attributes=None, + allow_not_accessible_resource=False, + operation_list=None, ++ enable_agent_self_validation=False, + ): + return resource.create_into_bundle( + env, +@@ -111,6 +119,7 @@ def create_bundle( + wait=wait, + ensure_disabled=disabled, + allow_not_accessible_resource=allow_not_accessible_resource, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + + +@@ -395,13 +404,6 @@ class CreateRolesNormalization(TestCase): + agent_filename=agent_file_name, + ) + self.config.runner.cib.load(filename=cib_file) +- self.config.runner.pcmk.resource_agent_self_validation( +- {}, +- output="", +- standard="ocf", +- provider="pacemaker", +- agent_type="Stateful", +- ) + + def create(self, operation_list=None): + resource.create( +@@ -597,7 +599,6 @@ class Create(TestCase): + def test_simplest_resource(self): + self.config.runner.pcmk.load_agent() + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=fixture_cib_resources_xml_primitive_simplest + ) +@@ -619,7 +620,9 @@ class Create(TestCase): + returncode=1, + ) + self.env_assist.assert_raise_library_error( +- lambda: create(self.env_assist.get_env()), ++ lambda: create( ++ self.env_assist.get_env(), enable_agent_self_validation=True ++ ), + ) + self.env_assist.assert_reports( + [ +@@ -650,7 +653,9 @@ class Create(TestCase): + resources=fixture_cib_resources_xml_primitive_simplest + ) + create( +- self.env_assist.get_env(), allow_invalid_instance_attributes=True ++ self.env_assist.get_env(), ++ allow_invalid_instance_attributes=True, ++ enable_agent_self_validation=True, + ) + self.env_assist.assert_reports( + [ +@@ -670,7 +675,10 @@ class Create(TestCase): + returncode=0, + ) + self.env_assist.assert_raise_library_error( +- lambda: create(self.env_assist.get_env()), ++ lambda: create( ++ self.env_assist.get_env(), ++ enable_agent_self_validation=True, ++ ), + ) + self.env_assist.assert_reports( + [ +@@ -715,7 +723,6 @@ class Create(TestCase): + ) + self.config.runner.pcmk.load_agent() + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=fixture_cib_resources_xml_primitive_simplest + ) +@@ -878,7 +885,6 @@ class Create(TestCase): + def test_resource_with_operation(self): + self.config.runner.pcmk.load_agent() + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=""" + +@@ -924,14 +930,12 @@ class Create(TestCase): + ), + ) + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib(resources=self.fixture_sanitized_operation) + create(self.env_assist.get_env()) + + def test_sanitize_operation_id_from_user(self): + self.config.runner.pcmk.load_agent() + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib(resources=self.fixture_sanitized_operation) + create( + self.env_assist.get_env(), +@@ -1113,9 +1117,6 @@ class Create(TestCase): + + """, + ) +- self.config.runner.pcmk.resource_agent_self_validation( +- dict(state=1), output="" +- ) + self.config.env.push_cib( + resources=""" + +@@ -1201,7 +1202,6 @@ class Create(TestCase): + ) + self.config.runner.cib.upgrade() + self.config.runner.cib.load(filename="cib-empty-3.4.xml") +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=""" + +@@ -1257,7 +1257,6 @@ class CreateWait(TestCase): + self.env_assist, self.config = get_env_tools(test_case=self) + self.config.runner.pcmk.load_agent() + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=fixture_cib_resources_xml_primitive_simplest, + wait=TIMEOUT, +@@ -1387,15 +1386,9 @@ class CreateWait(TestCase): + + class CreateInGroup(TestCase): + def setUp(self): +- self.agent_self_validation_call_name = ( +- "runner.pcmk.resource_agent_self_validation" +- ) + self.env_assist, self.config = get_env_tools(test_case=self) + self.config.runner.pcmk.load_agent() + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation( +- {}, output="", name=self.agent_self_validation_call_name +- ) + + def test_simplest_resource(self): + ( +@@ -1438,7 +1431,6 @@ class CreateInGroup(TestCase): + create_group(self.env_assist.get_env(), wait=False) + + def test_cib_upgrade_on_onfail_demote(self): +- self.config.remove(self.agent_self_validation_call_name) + self.config.runner.cib.load( + filename="cib-empty-3.3.xml", + instead="runner.cib.load", +@@ -1446,7 +1438,6 @@ class CreateInGroup(TestCase): + ) + self.config.runner.cib.upgrade() + self.config.runner.cib.load(filename="cib-empty-3.4.xml") +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=""" + +@@ -1498,6 +1489,34 @@ class CreateInGroup(TestCase): + [fixture.info(reports.codes.CIB_UPGRADE_SUCCESSFUL)] + ) + ++ def test_resource_self_validation_failure(self): ++ self.config.runner.pcmk.resource_agent_self_validation( ++ {}, ++ output=""" ++ not ignored ++ this is ignored ++ ++ first issue ++ another one ++ ++ """, ++ returncode=1, ++ ) ++ self.env_assist.assert_raise_library_error( ++ lambda: create_group( ++ self.env_assist.get_env(), enable_agent_self_validation=True ++ ), ++ ) ++ self.env_assist.assert_reports( ++ [ ++ fixture.error( ++ reports.codes.AGENT_SELF_VALIDATION_RESULT, ++ result="not ignored\nfirst issue\nanother one", ++ force_code=reports.codes.FORCE, ++ ) ++ ] ++ ) ++ + def test_fail_wait(self): + self.config.env.push_cib( + resources=fixture_cib_resources_xml_group_simplest, +@@ -1641,15 +1660,9 @@ class CreateInGroup(TestCase): + + class CreateAsClone(TestCase): + def setUp(self): +- self.agent_self_validation_call_name = ( +- "runner.pcmk.resource_agent_self_validation" +- ) + self.env_assist, self.config = get_env_tools(test_case=self) + self.config.runner.pcmk.load_agent() + self.config.runner.cib.load() +- self.config.runner.pcmk.resource_agent_self_validation( +- {}, output="", name=self.agent_self_validation_call_name +- ) + + def test_simplest_resource(self): + ( +@@ -1659,6 +1672,34 @@ class CreateAsClone(TestCase): + ) + create_clone(self.env_assist.get_env(), wait=False) + ++ def test_resource_self_validation_failure(self): ++ self.config.runner.pcmk.resource_agent_self_validation( ++ {}, ++ output=""" ++ not ignored ++ this is ignored ++ ++ first issue ++ another one ++ ++ """, ++ returncode=1, ++ ) ++ self.env_assist.assert_raise_library_error( ++ lambda: create_clone( ++ self.env_assist.get_env(), enable_agent_self_validation=True ++ ), ++ ) ++ self.env_assist.assert_reports( ++ [ ++ fixture.error( ++ reports.codes.AGENT_SELF_VALIDATION_RESULT, ++ result="not ignored\nfirst issue\nanother one", ++ force_code=reports.codes.FORCE, ++ ) ++ ] ++ ) ++ + def test_custom_clone_id(self): + ( + self.config.env.push_cib( +@@ -1670,7 +1711,6 @@ class CreateAsClone(TestCase): + ) + + def test_custom_clone_id_error_invalid_id(self): +- self.config.remove(self.agent_self_validation_call_name) + self.env_assist.assert_raise_library_error( + lambda: create_clone( + self.env_assist.get_env(), wait=False, clone_id="1invalid" +@@ -1682,7 +1722,6 @@ class CreateAsClone(TestCase): + + def test_custom_clone_id_error_id_already_exist(self): + self.config.remove(name="runner.cib.load") +- self.config.remove(self.agent_self_validation_call_name) + self.config.runner.cib.load( + resources=""" + +@@ -1705,7 +1744,6 @@ class CreateAsClone(TestCase): + self.env_assist.assert_reports([fixture.report_id_already_exist("C")]) + + def test_cib_upgrade_on_onfail_demote(self): +- self.config.remove(self.agent_self_validation_call_name) + self.config.runner.cib.load( + filename="cib-empty-3.3.xml", + instead="runner.cib.load", +@@ -1713,7 +1751,6 @@ class CreateAsClone(TestCase): + ) + self.config.runner.cib.upgrade() + self.config.runner.cib.load(filename="cib-empty-3.4.xml") +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=""" + +@@ -2198,6 +2235,7 @@ class CreateAsCloneFailures(TestCase): + agent=agent.full_name, + clone_options={"promotable": "1"}, + allow_incompatible_clone_meta_attributes=True, ++ enable_agent_self_validation=True, + ), + ) + self.env_assist.assert_reports( +@@ -2363,7 +2401,6 @@ class CreateInToBundle(TestCase): + self.config.runner.cib.load( + filename="cib-empty-3.4.xml", resources=self.fixture_resources_pre + ) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=self.fixture_resource_post_simple_without_network.format( + network=""" +@@ -2393,13 +2430,11 @@ class CreateInToBundle(TestCase): + + def test_simplest_resource(self): + self.config.runner.cib.load(resources=self.fixture_resources_pre) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib(resources=self.fixture_resources_post_simple) + create_bundle(self.env_assist.get_env(), wait=False) + + def test_bundle_doesnt_exist(self): + self.config.runner.cib.load(resources=self.fixture_empty_resources) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.env_assist.assert_raise_library_error( + lambda: create_bundle(self.env_assist.get_env(), wait=False), + [ +@@ -2422,7 +2457,6 @@ class CreateInToBundle(TestCase): + + """ + ) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + + self.env_assist.assert_raise_library_error( + lambda: create_bundle(self.env_assist.get_env(), wait=False), +@@ -2448,7 +2482,6 @@ class CreateInToBundle(TestCase): + + """ + ) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.env_assist.assert_raise_library_error( + lambda: create_bundle(self.env_assist.get_env(), wait=False), + [ +@@ -2463,7 +2496,6 @@ class CreateInToBundle(TestCase): + + def test_wait_fail(self): + self.config.runner.cib.load(resources=self.fixture_resources_pre) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=self.fixture_resources_post_simple, + wait=TIMEOUT, +@@ -2488,7 +2520,6 @@ class CreateInToBundle(TestCase): + ) + def test_wait_ok_run_ok(self): + self.config.runner.cib.load(resources=self.fixture_resources_pre) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=self.fixture_resources_post_simple, wait=TIMEOUT + ) +@@ -2509,7 +2540,6 @@ class CreateInToBundle(TestCase): + ) + def test_wait_ok_run_fail(self): + self.config.runner.cib.load(resources=self.fixture_resources_pre) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=self.fixture_resources_post_simple, wait=TIMEOUT + ) +@@ -2534,7 +2564,6 @@ class CreateInToBundle(TestCase): + ) + def test_disabled_wait_ok_not_running(self): + self.config.runner.cib.load(resources=self.fixture_resources_pre) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=self.fixture_resources_post_disabled, wait=TIMEOUT + ) +@@ -2553,7 +2582,6 @@ class CreateInToBundle(TestCase): + ) + def test_disabled_wait_ok_running(self): + self.config.runner.cib.load(resources=self.fixture_resources_pre) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=self.fixture_resources_post_disabled, wait=TIMEOUT + ) +@@ -2581,7 +2609,6 @@ class CreateInToBundle(TestCase): + + """ + ) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.env_assist.assert_raise_library_error( + lambda: create_bundle(self.env_assist.get_env(), wait=False) + ) +@@ -2605,7 +2632,6 @@ class CreateInToBundle(TestCase): + + """ + ) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=( + self.fixture_resource_post_simple_without_network.format( +@@ -2638,7 +2664,6 @@ class CreateInToBundle(TestCase): + + """ + ) +- self.config.runner.pcmk.resource_agent_self_validation({}, output="") + self.config.env.push_cib( + resources=( + self.fixture_resource_post_simple_without_network.format( +@@ -2655,3 +2680,32 @@ class CreateInToBundle(TestCase): + self._test_with_network_defined( + '' + ) ++ ++ def test_resource_self_validation_failure(self): ++ self.config.runner.cib.load() ++ self.config.runner.pcmk.resource_agent_self_validation( ++ {}, ++ output=""" ++ not ignored ++ this is ignored ++ ++ first issue ++ another one ++ ++ """, ++ returncode=1, ++ ) ++ self.env_assist.assert_raise_library_error( ++ lambda: create_bundle( ++ self.env_assist.get_env(), enable_agent_self_validation=True ++ ), ++ ) ++ self.env_assist.assert_reports( ++ [ ++ fixture.error( ++ reports.codes.AGENT_SELF_VALIDATION_RESULT, ++ result="not ignored\nfirst issue\nanother one", ++ force_code=reports.codes.FORCE, ++ ) ++ ] ++ ) +diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py +index 220d058f..e0b6924e 100644 +--- a/pcs_test/tier0/lib/commands/test_booth.py ++++ b/pcs_test/tier0/lib/commands/test_booth.py +@@ -1754,30 +1754,10 @@ class CreateInCluster(TestCase, FixtureMixin): + agent_name="ocf:heartbeat:IPaddr2", + name="runner.pcmk.load_agent.ipaddr2", + ) +- self.config.runner.pcmk.resource_agent_self_validation( +- dict(ip=self.site_ip), +- standard="ocf", +- provider="heartbeat", +- agent_type="IPaddr2", +- output="", +- ) + self.config.runner.pcmk.load_agent( + agent_name="ocf:pacemaker:booth-site", + name="runner.pcmk.load_agent.booth-site", + ) +- self.config.runner.pcmk.resource_agent_self_validation( +- dict( +- config=os.path.join( +- settings.booth_config_dir, +- f"{instance_name}.conf", +- ) +- ), +- standard="ocf", +- provider="pacemaker", +- agent_type="booth-site", +- output="", +- name="runner.pcmk.agent_self_validation.booth-site", +- ) + self.config.env.push_cib( + resources=self.fixture_cib_booth_group(instance_name) + ) +@@ -1809,33 +1789,11 @@ class CreateInCluster(TestCase, FixtureMixin): + name="runner.pcmk.load_agent.ipaddr2", + env=env, + ) +- self.config.runner.pcmk.resource_agent_self_validation( +- dict(ip=self.site_ip), +- standard="ocf", +- provider="heartbeat", +- agent_type="IPaddr2", +- output="", +- env=env, +- ) + self.config.runner.pcmk.load_agent( + agent_name="ocf:pacemaker:booth-site", + name="runner.pcmk.load_agent.booth-site", + env=env, + ) +- self.config.runner.pcmk.resource_agent_self_validation( +- dict( +- config=os.path.join( +- settings.booth_config_dir, +- f"{constants.DEFAULT_INSTANCE_NAME}.conf", +- ) +- ), +- standard="ocf", +- provider="pacemaker", +- agent_type="booth-site", +- output="", +- env=env, +- name="runner.pcmk.agent_self_validation.booth-site", +- ) + self.config.env.push_cib(resources=self.fixture_cib_booth_group()) + commands.create_in_cluster(self.env_assist.get_env(), self.site_ip) + +@@ -1943,13 +1901,6 @@ class CreateInCluster(TestCase, FixtureMixin): + agent_name="ocf:heartbeat:IPaddr2", + name="runner.pcmk.load_agent.ipaddr2", + ) +- self.config.runner.pcmk.resource_agent_self_validation( +- dict(ip=self.site_ip), +- standard="ocf", +- provider="heartbeat", +- agent_type="IPaddr2", +- output="", +- ) + self.config.runner.pcmk.load_agent( + agent_name="ocf:pacemaker:booth-site", + agent_is_missing=True, +diff --git a/pcs_test/tier0/lib/commands/test_stonith.py b/pcs_test/tier0/lib/commands/test_stonith.py +index 65a0608f..eedd1c04 100644 +--- a/pcs_test/tier0/lib/commands/test_stonith.py ++++ b/pcs_test/tier0/lib/commands/test_stonith.py +@@ -108,9 +108,6 @@ class CreateMixin: + ) + self.config.runner.pcmk.load_fake_agent_metadata() + self.config.runner.cib.load() +- self.config.runner.pcmk.stonith_agent_self_validation( +- instance_attributes, agent_name, output="" +- ) + self.config.env.push_cib( + resources=self._expected_cib(expected_cib_simple) + ) +@@ -158,6 +155,7 @@ class CreateMixin: + operations=[], + meta_attributes={}, + instance_attributes=instance_attributes, ++ enable_agent_self_validation=True, + ), + ) + self.env_assist.assert_reports( +@@ -208,6 +206,7 @@ class CreateMixin: + meta_attributes={}, + instance_attributes=instance_attributes, + allow_invalid_instance_attributes=True, ++ enable_agent_self_validation=True, + ) + self.env_assist.assert_reports( + [ +@@ -245,6 +244,7 @@ class CreateMixin: + operations=[], + meta_attributes={}, + instance_attributes=instance_attributes, ++ enable_agent_self_validation=True, + ), + ) + self.env_assist.assert_reports( +@@ -266,9 +266,6 @@ class CreateMixin: + ) + self.config.runner.pcmk.load_fake_agent_metadata() + self.config.runner.cib.load() +- self.config.runner.pcmk.stonith_agent_self_validation( +- {}, agent_name, output="" +- ) + self.config.env.push_cib( + resources=self._expected_cib(expected_cib_unfencing) + ) +@@ -306,9 +303,6 @@ class CreateMixin: + ) + self.config.runner.pcmk.load_fake_agent_metadata() + self.config.runner.cib.load() +- self.config.runner.pcmk.stonith_agent_self_validation( +- instance_attributes, agent_name, output="" +- ) + self.config.env.push_cib(resources=self._expected_cib(expected_cib)) + + self._create( +@@ -334,9 +328,6 @@ class CreateMixin: + ) + self.config.runner.pcmk.load_fake_agent_metadata() + self.config.runner.cib.load() +- self.config.runner.pcmk.stonith_agent_self_validation( +- {}, agent_name, output="" +- ) + self.config.env.push_cib( + resources=self._expected_cib(expected_cib_operations) + ) +@@ -395,9 +386,6 @@ class CreateMixin: + ) + self.config.runner.pcmk.load_fake_agent_metadata() + self.config.runner.cib.load() +- self.config.runner.pcmk.stonith_agent_self_validation( +- instance_attributes, agent_name, output="" +- ) + self.config.env.push_cib( + resources=self._expected_cib(expected_cib_simple_forced) + ) +@@ -611,9 +599,6 @@ class CreateMixin: + ) + self.config.runner.pcmk.load_fake_agent_metadata() + self.config.runner.cib.load() +- self.config.runner.pcmk.stonith_agent_self_validation( +- instance_attributes, agent_name, output="" +- ) + self.config.env.push_cib( + resources=self._expected_cib(expected_cib_simple), wait=timeout + ) +@@ -727,9 +712,6 @@ class CreateInGroup(CreateMixin, TestCase): + ) + self.config.runner.pcmk.load_fake_agent_metadata() + self.config.runner.cib.load(resources=original_cib) +- self.config.runner.pcmk.stonith_agent_self_validation( +- instance_attributes, agent_name, output="" +- ) + self.config.env.push_cib(resources=expected_cib) + + stonith.create_in_group( +diff --git a/pcs_test/tier1/cib_resource/test_create.py b/pcs_test/tier1/cib_resource/test_create.py +index 3a088513..bbc1acaa 100644 +--- a/pcs_test/tier1/cib_resource/test_create.py ++++ b/pcs_test/tier1/cib_resource/test_create.py +@@ -751,7 +751,9 @@ class Promotable(TestCase, AssertPcsMixin): + ensure_disabled=False, + use_default_operations=True, + wait=False, ++ enable_agent_self_validation=False, + ): ++ # pylint: disable=too-many-arguments + options = locals() + del options["self"] + return options +diff --git a/pcs_test/tier1/cib_resource/test_stonith_create.py b/pcs_test/tier1/cib_resource/test_stonith_create.py +index 77277b75..ea429b64 100644 +--- a/pcs_test/tier1/cib_resource/test_stonith_create.py ++++ b/pcs_test/tier1/cib_resource/test_stonith_create.py +@@ -45,7 +45,6 @@ class PlainStonith(ResourceTest): + + + """, +- output_start="Warning: Validation result from agent:", + ) + + def test_error_when_not_valid_name(self): +@@ -249,7 +248,6 @@ class WithMeta(ResourceTest): + + + """, +- output_start="Warning: Validation result from agent:", + ) + + +diff --git a/pcs_test/tier1/legacy/test_resource.py b/pcs_test/tier1/legacy/test_resource.py +index 3ba32ec7..d53af88b 100644 +--- a/pcs_test/tier1/legacy/test_resource.py ++++ b/pcs_test/tier1/legacy/test_resource.py +@@ -5906,7 +5906,13 @@ class UpdateInstanceAttrs( + def test_agent_self_validation_failure(self): + self.fixture_resource() + self.assert_pcs_fail( +- ["resource", "update", "R", "fake=is_invalid=True"], ++ [ ++ "resource", ++ "update", ++ "R", ++ "fake=is_invalid=True", ++ "--agent-validation", ++ ], + stdout_start="Error: Validation result from agent (use --force to override):", + ) + +diff --git a/pcs_test/tier1/legacy/test_stonith.py b/pcs_test/tier1/legacy/test_stonith.py +index 7e7ec030..8ca84065 100644 +--- a/pcs_test/tier1/legacy/test_stonith.py ++++ b/pcs_test/tier1/legacy/test_stonith.py +@@ -1283,6 +1283,27 @@ class StonithTest(TestCase, AssertPcsMixin): + "Deleting Resource - apc-fencing\n", + ) + ++ self.assert_pcs_fail( ++ ( ++ "stonith create apc-fencing fence_apc ip=morph-apc username=apc " ++ "--agent-validation" ++ ).split(), ++ stdout_start="Error: Validation result from agent", ++ ) ++ ++ self.assert_pcs_success( ++ ( ++ "stonith create apc-fencing fence_apc ip=morph-apc username=apc " ++ "--agent-validation --force" ++ ).split(), ++ stdout_start="Warning: Validation result from agent", ++ ) ++ ++ self.assert_pcs_success( ++ "stonith remove apc-fencing".split(), ++ stdout_full="Deleting Resource - apc-fencing\n", ++ ) ++ + self.assert_pcs_fail( + "stonith update test3 bad_ipaddr=test username=login".split(), + stdout_regexp=( +@@ -1292,8 +1313,8 @@ class StonithTest(TestCase, AssertPcsMixin): + ) + + self.assert_pcs_success( +- "stonith update test3 username=testA".split(), +- stdout_start="Warning: ", ++ "stonith update test3 username=testA --agent-validation".split(), ++ stdout_start="Warning: The resource was misconfigured before the update,", + ) + + self.assert_pcs_success( +-- +2.39.0 + diff --git a/SOURCES/bz2166249-01-fix-stonith-watchdog-timeout-offline-update.patch b/SOURCES/bz2166249-01-fix-stonith-watchdog-timeout-offline-update.patch new file mode 100644 index 0000000..7ad384c --- /dev/null +++ b/SOURCES/bz2166249-01-fix-stonith-watchdog-timeout-offline-update.patch @@ -0,0 +1,311 @@ +From 166fd04bb5505f29463088080044689f15635018 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular +Date: Tue, 31 Jan 2023 17:44:16 +0100 +Subject: [PATCH] fix update of stonith-watchdog-timeout when cluster is not + running + +--- + pcs/lib/communication/sbd.py | 4 +- + .../lib/commands/sbd/test_disable_sbd.py | 10 ++-- + .../tier0/lib/commands/sbd/test_enable_sbd.py | 49 ++++++++++--------- + pcsd/pcs.rb | 17 +++++-- + 4 files changed, 48 insertions(+), 32 deletions(-) + +diff --git a/pcs/lib/communication/sbd.py b/pcs/lib/communication/sbd.py +index f31bf16f..83d912b2 100644 +--- a/pcs/lib/communication/sbd.py ++++ b/pcs/lib/communication/sbd.py +@@ -98,8 +98,8 @@ class StonithWatchdogTimeoutAction( + ) + if report_item is None: + self._on_success() +- return [] +- self._report(report_item) ++ else: ++ self._report(report_item) + return self._get_next_list() + + +diff --git a/pcs_test/tier0/lib/commands/sbd/test_disable_sbd.py b/pcs_test/tier0/lib/commands/sbd/test_disable_sbd.py +index 13135fb2..f8f165bf 100644 +--- a/pcs_test/tier0/lib/commands/sbd/test_disable_sbd.py ++++ b/pcs_test/tier0/lib/commands/sbd/test_disable_sbd.py +@@ -19,7 +19,7 @@ class DisableSbd(TestCase): + self.config.corosync_conf.load(filename=self.corosync_conf_name) + self.config.http.host.check_auth(node_labels=self.node_list) + self.config.http.pcmk.set_stonith_watchdog_timeout_to_zero( +- node_labels=self.node_list[:1] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.disable_sbd(node_labels=self.node_list) + disable_sbd(self.env_assist.get_env()) +@@ -56,7 +56,7 @@ class DisableSbd(TestCase): + self.config.corosync_conf.load(filename=self.corosync_conf_name) + self.config.http.host.check_auth(node_labels=self.node_list) + self.config.http.pcmk.set_stonith_watchdog_timeout_to_zero( +- node_labels=self.node_list[:1] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.disable_sbd(node_labels=self.node_list) + +@@ -158,7 +158,9 @@ class DisableSbd(TestCase): + ] + ) + self.config.http.pcmk.set_stonith_watchdog_timeout_to_zero( +- node_labels=online_nodes_list[:1] ++ communication_list=[ ++ [dict(label=node)] for node in self.node_list[1:] ++ ], + ) + self.config.http.sbd.disable_sbd(node_labels=online_nodes_list) + disable_sbd(self.env_assist.get_env(), ignore_offline_nodes=True) +@@ -291,7 +293,7 @@ class DisableSbd(TestCase): + self.config.corosync_conf.load(filename=self.corosync_conf_name) + self.config.http.host.check_auth(node_labels=self.node_list) + self.config.http.pcmk.set_stonith_watchdog_timeout_to_zero( +- node_labels=self.node_list[:1] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.disable_sbd( + communication_list=[ +diff --git a/pcs_test/tier0/lib/commands/sbd/test_enable_sbd.py b/pcs_test/tier0/lib/commands/sbd/test_enable_sbd.py +index be479a34..77863d5e 100644 +--- a/pcs_test/tier0/lib/commands/sbd/test_enable_sbd.py ++++ b/pcs_test/tier0/lib/commands/sbd/test_enable_sbd.py +@@ -130,7 +130,7 @@ class OddNumOfNodesSuccess(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -164,7 +164,7 @@ class OddNumOfNodesSuccess(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -218,7 +218,7 @@ class OddNumOfNodesDefaultsSuccess(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -248,7 +248,7 @@ class OddNumOfNodesDefaultsSuccess(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -351,7 +351,7 @@ class WatchdogValidations(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -407,7 +407,7 @@ class EvenNumOfNodes(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -443,7 +443,7 @@ class EvenNumOfNodes(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -480,7 +480,7 @@ class EvenNumOfNodes(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -513,7 +513,7 @@ class EvenNumOfNodes(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + self.config.http.sbd.enable_sbd(node_labels=self.node_list) + enable_sbd( +@@ -604,7 +604,9 @@ class OfflineNodes(TestCase): + node_labels=self.online_node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.online_node_list[0]] ++ communication_list=[ ++ [dict(label=node)] for node in self.online_node_list ++ ], + ) + self.config.http.sbd.enable_sbd(node_labels=self.online_node_list) + enable_sbd( +@@ -644,7 +646,9 @@ class OfflineNodes(TestCase): + node_labels=self.online_node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.online_node_list[0]] ++ communication_list=[ ++ [dict(label=node)] for node in self.online_node_list ++ ], + ) + self.config.http.sbd.enable_sbd(node_labels=self.online_node_list) + enable_sbd( +@@ -1228,7 +1232,7 @@ class FailureHandling(TestCase): + node_labels=self.node_list, + ) + self.config.http.pcmk.remove_stonith_watchdog_timeout( +- node_labels=[self.node_list[0]] ++ communication_list=[[dict(label=node)] for node in self.node_list], + ) + + def _remove_calls(self, count): +@@ -1304,7 +1308,8 @@ class FailureHandling(TestCase): + ) + + def test_removing_stonith_wd_timeout_failure(self): +- self._remove_calls(2) ++ self._remove_calls(len(self.node_list) + 1) ++ + self.config.http.pcmk.remove_stonith_watchdog_timeout( + communication_list=[ + self.communication_list_failure[:1], +@@ -1333,7 +1338,7 @@ class FailureHandling(TestCase): + ) + + def test_removing_stonith_wd_timeout_not_connected(self): +- self._remove_calls(2) ++ self._remove_calls(len(self.node_list) + 1) + self.config.http.pcmk.remove_stonith_watchdog_timeout( + communication_list=[ + self.communication_list_not_connected[:1], +@@ -1362,7 +1367,7 @@ class FailureHandling(TestCase): + ) + + def test_removing_stonith_wd_timeout_complete_failure(self): +- self._remove_calls(2) ++ self._remove_calls(len(self.node_list) + 1) + self.config.http.pcmk.remove_stonith_watchdog_timeout( + communication_list=[ + self.communication_list_not_connected[:1], +@@ -1408,7 +1413,7 @@ class FailureHandling(TestCase): + ) + + def test_set_sbd_config_failure(self): +- self._remove_calls(4) ++ self._remove_calls(len(self.node_list) + 1 + 2) + self.config.http.sbd.set_sbd_config( + communication_list=[ + dict( +@@ -1455,7 +1460,7 @@ class FailureHandling(TestCase): + ) + + def test_set_corosync_conf_failed(self): +- self._remove_calls(5) ++ self._remove_calls(len(self.node_list) + 1 + 3) + self.config.env.push_corosync_conf( + corosync_conf_text=_get_corosync_conf_text_with_atb( + self.corosync_conf_name +@@ -1479,7 +1484,7 @@ class FailureHandling(TestCase): + ) + + def test_check_sbd_invalid_data_format(self): +- self._remove_calls(7) ++ self._remove_calls(len(self.node_list) + 1 + 5) + self.config.http.sbd.check_sbd( + communication_list=[ + dict( +@@ -1518,7 +1523,7 @@ class FailureHandling(TestCase): + ) + + def test_check_sbd_failure(self): +- self._remove_calls(7) ++ self._remove_calls(len(self.node_list) + 1 + 5) + self.config.http.sbd.check_sbd( + communication_list=[ + dict( +@@ -1560,7 +1565,7 @@ class FailureHandling(TestCase): + ) + + def test_check_sbd_not_connected(self): +- self._remove_calls(7) ++ self._remove_calls(len(self.node_list) + 1 + 5) + self.config.http.sbd.check_sbd( + communication_list=[ + dict( +@@ -1603,7 +1608,7 @@ class FailureHandling(TestCase): + ) + + def test_get_online_targets_failed(self): +- self._remove_calls(9) ++ self._remove_calls(len(self.node_list) + 1 + 7) + self.config.http.host.check_auth( + communication_list=self.communication_list_failure + ) +@@ -1628,7 +1633,7 @@ class FailureHandling(TestCase): + ) + + def test_get_online_targets_not_connected(self): +- self._remove_calls(9) ++ self._remove_calls(len(self.node_list) + 1 + 7) + self.config.http.host.check_auth( + communication_list=self.communication_list_not_connected + ) +diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb +index 6d8669d1..d79c863b 100644 +--- a/pcsd/pcs.rb ++++ b/pcsd/pcs.rb +@@ -1642,13 +1642,22 @@ end + def set_cluster_prop_force(auth_user, prop, val) + cmd = ['property', 'set', "#{prop}=#{val}"] + flags = ['--force'] ++ sig_file = "#{CIB_PATH}.sig" ++ retcode = 0 ++ + if pacemaker_running? +- user = auth_user ++ _, _, retcode = run_cmd(auth_user, PCS, *flags, "--", *cmd) + else +- user = PCSAuth.getSuperuserAuth() +- flags += ['-f', CIB_PATH] ++ if File.exist?(CIB_PATH) ++ flags += ['-f', CIB_PATH] ++ _, _, retcode = run_cmd(PCSAuth.getSuperuserAuth(), PCS, *flags, "--", *cmd) ++ begin ++ File.delete(sig_file) ++ rescue => e ++ $logger.debug("Cannot delete file '#{sig_file}': #{e.message}") ++ end ++ end + end +- _, _, retcode = run_cmd(user, PCS, *flags, "--", *cmd) + return (retcode == 0) + end + +-- +2.39.0 + diff --git a/SOURCES/bz2167471-01-fix-broken-typeahead-component.patch b/SOURCES/bz2167471-01-fix-broken-typeahead-component.patch new file mode 100644 index 0000000..2574394 --- /dev/null +++ b/SOURCES/bz2167471-01-fix-broken-typeahead-component.patch @@ -0,0 +1,67 @@ +From c51faf31a1abc08e26e5ccb4492c1a46f101a22a Mon Sep 17 00:00:00 2001 +From: Ivan Devat +Date: Tue, 13 Dec 2022 12:58:00 +0100 +Subject: [PATCH] fix agents filter in resource/fence device create + +--- + .../cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx | 4 ++-- + .../view/cluster/resources/task/create/NameTypeTypeSelect.tsx | 4 ++-- + src/app/view/share/form/Select.tsx | 2 +- + 3 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx b/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx +index 80327801..8d623e2b 100644 +--- a/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx ++++ b/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx +@@ -38,13 +38,13 @@ export const NameTypeTypeSelect = ({ + return ( + 0 ? agentName : undefined} + data-test="resource-agent" + > +diff --git a/src/app/view/share/form/Select.tsx b/src/app/view/share/form/Select.tsx +index d73f126c..e2b81ce2 100644 +--- a/src/app/view/share/form/Select.tsx ++++ b/src/app/view/share/form/Select.tsx +@@ -31,7 +31,7 @@ export const Select = ( + const filter = onFilter + ? (_event: React.ChangeEvent | null, value: string) => { + onFilter(value); +- return null as unknown as React.ReactElement[]; ++ return undefined; + } + : null; + +-- +2.39.0 + diff --git a/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch b/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch index ab241a5..12e609a 100644 --- a/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch +++ b/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch @@ -1,4 +1,4 @@ -From 2a578689db8522971840dbb1644a21c341fe7d83 Mon Sep 17 00:00:00 2001 +From c7b8c999f796cee4899df578944b239e1db29cb5 Mon Sep 17 00:00:00 2001 From: Ivan Devat Date: Tue, 20 Nov 2018 15:03:56 +0100 Subject: [PATCH] do not support cluster setup with udp(u) transport in RHEL9 @@ -9,7 +9,7 @@ Subject: [PATCH] do not support cluster setup with udp(u) transport in RHEL9 2 files changed, 3 insertions(+) diff --git a/pcs/pcs.8.in b/pcs/pcs.8.in -index 363515bb..4c2496aa 100644 +index 7bbd1ae2..53bfebb4 100644 --- a/pcs/pcs.8.in +++ b/pcs/pcs.8.in @@ -457,6 +457,8 @@ By default, encryption is enabled with cipher=aes256 and hash=sha256. To disable @@ -22,10 +22,10 @@ index 363515bb..4c2496aa 100644 .br Transport options are: ip_version, netmtu diff --git a/pcs/usage.py b/pcs/usage.py -index cfa2ac9c..519c853e 100644 +index 073e2b1e..91ceb787 100644 --- a/pcs/usage.py +++ b/pcs/usage.py -@@ -1429,6 +1429,7 @@ Commands: +@@ -1431,6 +1431,7 @@ Commands: hash=sha256. To disable encryption, set cipher=none and hash=none. Transports udp and udpu: @@ -34,5 +34,5 @@ index cfa2ac9c..519c853e 100644 support traffic encryption nor compression. Transport options are: -- -2.35.1 +2.38.1 diff --git a/SPECS/pcs.spec b/SPECS/pcs.spec index 39bd67e..f151372 100644 --- a/SPECS/pcs.spec +++ b/SPECS/pcs.spec @@ -1,17 +1,17 @@ Name: pcs -Version: 0.11.3 -Release: 3%{?dist} +Version: 0.11.4 +Release: 6%{?dist} # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses -# GPLv2: pcs -# ASL 2.0: tornado -# MIT: backports, dacite, daemons, ethon, mustermann, rack, rack-protection, -# rack-test, sinatra, tilt -# GPLv2 or Ruby: eventmachne -# (GPLv2 or Ruby) and BSD: thin -# BSD or Ruby: open4, ruby2_keywords, webrick -# BSD and MIT: ffi -License: GPLv2 and ASL 2.0 and MIT and BSD and (GPLv2 or Ruby) and (BSD or Ruby) +# GPL-2.0-only: pcs +# Apache-2.0: tornado +# MIT: backports, childprocess, dacite, daemons, ethon, mustermann, rack, +# rack-protection, rack-test, sinatra, tilt +# GPL-2.0-only or Ruby: eventmachine +# (GPL-2.0-only or Ruby) and BSD-2-Clause: thin +# BSD-2-Clause or Ruby: ruby2_keywords, webrick +# BSD-3-Clause and MIT: ffi +License: GPL-2.0-only AND Apache-2.0 AND MIT AND BSD-3-Clause AND (GPL-2.0-only OR Ruby) AND BSD-2-Clause AND (BSD-2-Clause OR Ruby) URL: https://github.com/ClusterLabs/pcs Group: System Environment/Base Summary: Pacemaker Configuration System @@ -19,34 +19,34 @@ Summary: Pacemaker Configuration System ExclusiveArch: i686 x86_64 s390x ppc64le aarch64 %global version_or_commit %{version} -# %%global version_or_commit %%{version}.44-9da7 +# %%global version_or_commit %%{version}.206-f51f %global pcs_source_name %{name}-%{version_or_commit} # ui_commit can be determined by hash, tag or branch -%global ui_commit 0.1.14 -%global ui_modules_version 0.1.14 +%global ui_commit 0.1.16 +%global ui_modules_version 0.1.16 %global ui_src_name pcs-web-ui-%{ui_commit} %global pcs_snmp_pkg_name pcs-snmp %global pyagentx_version 0.4.pcs.2 -%global tornado_version 6.1.0 +%global tornado_version 6.2.0 %global dacite_version 1.6.0 %global version_rubygem_backports 3.23.0 +%global version_rubygem_childprocess 4.1.0 %global version_rubygem_daemons 1.4.1 -%global version_rubygem_ethon 0.15.0 +%global version_rubygem_ethon 0.16.0 %global version_rubygem_eventmachine 1.2.7 %global version_rubygem_ffi 1.15.5 -%global version_rubygem_mustermann 1.1.1 -%global version_rubygem_open4 1.3.4 -%global version_rubygem_rack 2.2.3.1 -%global version_rubygem_rack_protection 2.2.0 -%global version_rubygem_rack_test 1.1.0 +%global version_rubygem_mustermann 3.0.0 +%global version_rubygem_rack 2.2.5 +%global version_rubygem_rack_protection 3.0.5 +%global version_rubygem_rack_test 2.0.2 %global version_rubygem_ruby2_keywords 0.0.5 -%global version_rubygem_sinatra 2.2.0 +%global version_rubygem_sinatra 3.0.5 %global version_rubygem_thin 1.8.1 -%global version_rubygem_tilt 2.0.10 +%global version_rubygem_tilt 2.0.11 %global version_rubygem_webrick 1.7.0 %global required_pacemaker_version 2.1.0 @@ -81,10 +81,7 @@ Source81: https://rubygems.org/downloads/backports-%{version_rubygem_backports}. Source82: https://rubygems.org/downloads/ethon-%{version_rubygem_ethon}.gem Source83: https://rubygems.org/downloads/ffi-%{version_rubygem_ffi}.gem Source86: https://rubygems.org/downloads/mustermann-%{version_rubygem_mustermann}.gem -# We needed to re-upload open4 rubygem because of issues with sources in gating. -# Unfortunately, there was no newer version available, therefore we had to -# change its 'version' ourselves. -Source87: https://rubygems.org/downloads/open4-%{version_rubygem_open4}.gem#/open4-%{version_rubygem_open4}-1.gem +Source87: https://rubygems.org/downloads/childprocess-%{version_rubygem_childprocess}.gem Source88: https://rubygems.org/downloads/rack-%{version_rubygem_rack}.gem Source89: https://rubygems.org/downloads/rack-protection-%{version_rubygem_rack_protection}.gem Source90: https://rubygems.org/downloads/rack-test-%{version_rubygem_rack_test}.gem @@ -110,13 +107,17 @@ Source101: https://github.com/ClusterLabs/pcs-web-ui/releases/download/%{ui_comm # pcs patches: <= 200 Patch1: do-not-support-cluster-setup-with-udp-u-transport.patch -Patch2: bz2102663-01-fix-pcs-resource-restart-traceback.patch -Patch3: bz2058243-01-code-formatting.patch -Patch4: bz2058243-02-make-booth-ticket-mode-value-case-insensitive.patch -Patch5: bz2026725-01-booth-sync-check-whether-etc-booth-exists.patch +Patch2: bz2148124-01-pcsd-systemd-killmode.patch +Patch3: 01-smoke-test-fix.patch +Patch4: 02-smoke-test-fix.patch +Patch5: bz2151524-01-add-warning-when-updating-a-misconfigured-resource.patch +Patch6: bz2151164-01-fix-displaying-bool-and-integer-values.patch +Patch7: bz2159454-01-add-agent-validation-option.patch +Patch8: bz2158790-01-fix-stonith-watchdog-timeout-validation.patch +Patch9: bz2166249-01-fix-stonith-watchdog-timeout-offline-update.patch # ui patches: >200 -# Patch201: bzNUMBER-01-name.patch +Patch201: bz2167471-01-fix-broken-typeahead-component.patch # git for patches BuildRequires: git-core @@ -153,6 +154,9 @@ BuildRequires: rubygem-test-unit BuildRequires: diffstat # for post, preun and postun macros BuildRequires: systemd +# pam is used for authentication inside daemon (python ctypes) +# needed for tier0 tests during build +BuildRequires: pam BuildRequires: make # Red Hat logo for creating symlink of favicon BuildRequires: redhat-logos @@ -210,7 +214,7 @@ Provides: bundled(ethon) = %{version_rubygem_ethon} Provides: bundled(eventmachine) = %{version_rubygem_eventmachine} Provides: bundled(ffi) = %{version_rubygem_ffi} Provides: bundled(mustermann) = %{version_rubygem_mustermann} -Provides: bundled(open4) = %{version_rubygem_open4} +Provides: bundled(childprocess) = %{version_rubygem_childprocess} Provides: bundled(rack) = %{version_rubygem_rack} Provides: bundled(rack_protection) = %{version_rubygem_rack_protection} Provides: bundled(rack_test) = %{version_rubygem_rack_test} @@ -229,9 +233,9 @@ easily view, modify and create pacemaker based clusters. Group: System Environment/Base Summary: Pacemaker cluster SNMP agent # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses -# GPLv2: pcs +# GPL-2.0-only: pcs # BSD-2-Clause: pyagentx -License: GPLv2 and BSD-2-Clause +License: GPL-2.0-only and BSD-2-Clause URL: https://github.com/ClusterLabs/pcs # tar for unpacking pyagetx source tar ball @@ -292,7 +296,7 @@ update_times_patch(){ # patch web-ui sources %autosetup -D -T -b 100 -a 101 -S git -n %{ui_src_name} -N %autopatch -p1 -m 201 -# update_times_patch %%{PATCH201} +update_times_patch %{PATCH201} # patch pcs sources %autosetup -S git -n %{pcs_source_name} -N @@ -302,6 +306,10 @@ update_times_patch %{PATCH2} update_times_patch %{PATCH3} update_times_patch %{PATCH4} update_times_patch %{PATCH5} +update_times_patch %{PATCH6} +update_times_patch %{PATCH7} +update_times_patch %{PATCH8} +update_times_patch %{PATCH9} # prepare dirs/files necessary for building all bundles # ----------------------------------------------------- @@ -312,9 +320,7 @@ cp -f %SOURCE81 %{rubygem_cache_dir} cp -f %SOURCE82 %{rubygem_cache_dir} cp -f %SOURCE83 %{rubygem_cache_dir} cp -f %SOURCE86 %{rubygem_cache_dir} -# For reason why we are renaming open4 rubygem, see comment of source -# definition above. -cp -f %SOURCE87 %{rubygem_cache_dir}/open4-%{version_rubygem_open4}.gem +cp -f %SOURCE87 %{rubygem_cache_dir} cp -f %SOURCE88 %{rubygem_cache_dir} cp -f %SOURCE89 %{rubygem_cache_dir} cp -f %SOURCE90 %{rubygem_cache_dir} @@ -337,7 +343,7 @@ cp -f %SOURCE44 rpm/ %define debug_package %{nil} ./autogen.sh -%{configure} --enable-local-build --enable-use-local-cache-only --enable-individual-bundling PYTHON=%{__python3} ruby_CFLAGS="%{optflags}" ruby_LIBS="%{build_ldflags}" +%{configure} --enable-local-build --enable-use-local-cache-only --enable-individual-bundling --enable-booth-enable-authfile-set --enable-booth-enable-authfile-unset PYTHON=%{__python3} ruby_CFLAGS="%{optflags}" ruby_LIBS="%{build_ldflags}" make all # build pcs-web-ui @@ -357,7 +363,7 @@ mkdir -p ${RPM_BUILD_ROOT}%{_libdir}/%{pcsd_public_dir}/images/ ln -fs /etc/favicon.png ${RPM_BUILD_ROOT}%{_libdir}/%{pcsd_public_dir}/images/favicon.png # prepare license files -# some rubygems do not have a license file (ruby2_keywords, thin) +# some rubygems do not have a license file (thin) mv %{rubygem_bundle_dir}/gems/backports-%{version_rubygem_backports}/LICENSE.txt backports_LICENSE.txt mv %{rubygem_bundle_dir}/gems/daemons-%{version_rubygem_daemons}/LICENSE daemons_LICENSE mv %{rubygem_bundle_dir}/gems/ethon-%{version_rubygem_ethon}/LICENSE ethon_LICENSE @@ -367,10 +373,11 @@ mv %{rubygem_bundle_dir}/gems/ffi-%{version_rubygem_ffi}/COPYING ffi_COPYING mv %{rubygem_bundle_dir}/gems/ffi-%{version_rubygem_ffi}/LICENSE ffi_LICENSE mv %{rubygem_bundle_dir}/gems/ffi-%{version_rubygem_ffi}/LICENSE.SPECS ffi_LICENSE.SPECS mv %{rubygem_bundle_dir}/gems/mustermann-%{version_rubygem_mustermann}/LICENSE mustermann_LICENSE -mv %{rubygem_bundle_dir}/gems/open4-%{version_rubygem_open4}/LICENSE open4_LICENSE +mv %{rubygem_bundle_dir}/gems/childprocess-%{version_rubygem_childprocess}/LICENSE childprocess_LICENSE mv %{rubygem_bundle_dir}/gems/rack-%{version_rubygem_rack}/MIT-LICENSE rack_MIT-LICENSE mv %{rubygem_bundle_dir}/gems/rack-protection-%{version_rubygem_rack_protection}/License rack-protection_License mv %{rubygem_bundle_dir}/gems/rack-test-%{version_rubygem_rack_test}/MIT-LICENSE.txt rack-test_MIT-LICENSE.txt +mv %{rubygem_bundle_dir}/gems/ruby2_keywords-%{version_rubygem_ruby2_keywords}/LICENSE ruby2_keywords_LICENSE mv %{rubygem_bundle_dir}/gems/sinatra-%{version_rubygem_sinatra}/LICENSE sinatra_LICENSE mv %{rubygem_bundle_dir}/gems/tilt-%{version_rubygem_tilt}/COPYING tilt_COPYING mv %{rubygem_bundle_dir}/gems/webrick-%{version_rubygem_webrick}/LICENSE.txt webrick_LICENSE.txt @@ -418,13 +425,10 @@ run_all_tests(){ # disabled tests: # - # pcs_test.tier0.lib.commands.test_resource_agent.DescribeAgentUtf8.test_describe - # For an unknown reason this test is failing in mock environment and - # passing outside the mock environment. - # TODO: Investigate the issue + # pcs_test.tier0.daemon.app.test_app_remote.SyncConfigMutualExclusive + # disabled due to race conditions on slower machines %{__python3} pcs_test/suite --tier0 -v --vanilla --all-but \ - pcs_test.tier0.lib.commands.test_resource_agent.DescribeAgentUtf8.test_describe \ pcs_test.tier0.daemon.app.test_app_remote.SyncConfigMutualExclusive.test_get_not_locked \ pcs_test.tier0.daemon.app.test_app_remote.SyncConfigMutualExclusive.test_post_not_locked \ @@ -446,7 +450,7 @@ run_all_tests(){ run_all_tests %posttrans -# Make sure the new version of the daemon is runnning. +# Make sure the new version of the daemon is running. # Also, make sure to start pcsd-ruby if it hasn't been started or even # installed before. This is done by restarting pcsd.service. %{_bindir}/systemctl daemon-reload @@ -482,8 +486,9 @@ run_all_tests %license tornado_LICENSE %license dacite_LICENSE %license COPYING -# rugygem licenses +# rubygem licenses %license backports_LICENSE.txt +%license childprocess_LICENSE %license daemons_LICENSE %license ethon_LICENSE %license eventmachine_LICENSE @@ -492,10 +497,10 @@ run_all_tests %license ffi_LICENSE %license ffi_LICENSE.SPECS %license mustermann_LICENSE -%license open4_LICENSE %license rack_MIT-LICENSE %license rack-protection_License %license rack-test_MIT-LICENSE.txt +%license ruby2_keywords_LICENSE %license sinatra_LICENSE %license tilt_COPYING %license webrick_LICENSE.txt @@ -539,6 +544,51 @@ run_all_tests %license pyagentx_LICENSE.txt %changelog +* Mon Feb 13 2023 Michal Pospisil - 0.11.4-6 +- Fixed broken filtering in create resource/fence device wizards in the web interface +- Added BuildRequires: pam - needed for tier0 tests during build +- Resolves: rhbz#2167471 + +* Thu Feb 02 2023 Michal Pospisil - 0.11.4-5 +- Fixed enabling/disabling sbd when cluster is not running +- Resolves: rhbz#2166249 + +* Fri Jan 13 2023 Michal Pospisil - 0.11.4-4 +- Rebuilt with fixed patches +- Resolves: rhbz#2158790 rhbz#2159454 + +* Thu Jan 12 2023 Michal Pospisil - 0.11.4-3 +- Allow time values in stonith-watchdog-time property +- Resource/stonith agent self-validation of instance attributes is now disabled by default, as many agents do not work with it properly. +- Updated bundled rubygems: rack, rack-protection, sinatra +- Added license for ruby2_keywords +- Resolves: rhbz#2158790 rhbz#2159454 + +* Wed Dec 14 2022 Michal Pospisil - 0.11.4-2 +- Fixed stopping of pcsd service using systemctl stop pcsd command +- Fixed smoke test execution during gating +- Added warning when omitting validation of misconfigured resource +- Fixed displaying of bool and integer values in `pcs resource config` command +- Updated bundled rubygems: ethon, rack-protection, sinatra +- Resolves: rhbz#2148124 rhbz#2151164 rhbz#2151524 + +* Tue Nov 22 2022 Michal Pospisil - 0.11.4-1 +- Rebased to latest upstream sources (see CHANGELOG.md) +- Updated pcs-web-ui +- Resolves: rhbz#1620043 rhbz#2019464 rhbz#2099653 rhbz#2109633 rhbz#2112293 rhbz#2116295 rhbz#2117600 rhbz#2117601 + +* Mon Oct 24 2022 Miroslav Lisik - 0.11.3-5 +- Rebased to latest upstream sources (see CHANGELOG.md) +- Updated pcs-web-ui +- Added bundled rubygem: childprocess +- Removed bundled rubygem: open4 +- Updated bundled rubygems: mustermann, rack, rack-protection, rack-test, sinatra, tilt +- Resolves: rhbz#1493416 rhbz#1796827 rhbz#2059147 rhbz#2092950 rhbz#2112079 rhbz#2112270 rhbz#2112293 rhbz#2117599 rhbz#2117601 + +* Mon Sep 05 2022 Miroslav Lisik - 0.11.3-4 +- Fixed ruby socket permissions +- Resolves: rhbz#2116841 + * Thu Jul 28 2022 Miroslav Lisik - 0.11.3-3 - Fixed booth ticket mode value case insensitive - Fixed booth sync check whether /etc/booth exists