diff --git a/.gitignore b/.gitignore index b4c905b..d44421c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,12 +14,12 @@ SOURCES/pcs-web-ui-0.1.13.tar.gz SOURCES/pcs-web-ui-node-modules-0.1.13.tar.xz SOURCES/pyagentx-0.4.pcs.2.tar.gz SOURCES/python-dateutil-2.8.2.tar.gz -SOURCES/rack-2.2.4.gem -SOURCES/rack-protection-2.2.3.gem +SOURCES/rack-2.2.5.gem +SOURCES/rack-protection-2.2.4.gem SOURCES/rack-test-2.0.2.gem SOURCES/rexml-3.2.5.gem SOURCES/ruby2_keywords-0.0.5.gem -SOURCES/sinatra-2.2.3.gem +SOURCES/sinatra-2.2.4.gem SOURCES/thin-1.8.1.gem SOURCES/tilt-2.0.11.gem SOURCES/tornado-6.1.0.tar.gz diff --git a/.pcs.metadata b/.pcs.metadata index 7add1c5..288d2ee 100644 --- a/.pcs.metadata +++ b/.pcs.metadata @@ -14,12 +14,12 @@ f7455776936492ce7b241f9801d6bbc946b0461a SOURCES/pcs-web-ui-0.1.13.tar.gz bd18d97d611233914828719c97b4d98d079913d2 SOURCES/pcs-web-ui-node-modules-0.1.13.tar.xz 3176b2f2b332c2b6bf79fe882e83feecf3d3f011 SOURCES/pyagentx-0.4.pcs.2.tar.gz c2ba10c775b7a52a4b57cac4d4110a0c0f812a82 SOURCES/python-dateutil-2.8.2.tar.gz -d884e586767313f8fcf8c863b5220831a95fccb8 SOURCES/rack-2.2.4.gem -312014942846897a363990784c3be291e66d38a2 SOURCES/rack-protection-2.2.3.gem +3ad7b27b68d5dd893ce91f216bb2685ae6c9846a SOURCES/rack-2.2.5.gem +5347315a7283f0b04443e924ed4eaa17807432c8 SOURCES/rack-protection-2.2.4.gem 3c669527ecbcb9f915a83983ec89320c356e1fe3 SOURCES/rack-test-2.0.2.gem e7f48fa5fb2d92e6cb21d6b1638fe41a5a7c4287 SOURCES/rexml-3.2.5.gem d017b9e4d1978e0b3ccc3e2a31493809e4693cd3 SOURCES/ruby2_keywords-0.0.5.gem -5bf6cbdabd03404a654e639b4e949705473c1050 SOURCES/sinatra-2.2.3.gem +fa6a6c98f885e93f54c23dd0454cae906e82c31b SOURCES/sinatra-2.2.4.gem 1ac6292a98e17247b7bb847a35ff868605256f7b SOURCES/thin-1.8.1.gem 360d77c80d2851a538fb13d43751093115c34712 SOURCES/tilt-2.0.11.gem c23c617c7a0205e465bebad5b8cdf289ae8402a2 SOURCES/tornado-6.1.0.tar.gz diff --git a/SOURCES/bz2151166-01-fix-displaying-bool-and-integer-values.patch b/SOURCES/bz2151166-01-fix-displaying-bool-and-integer-values.patch index 5794eec..cda72c4 100644 --- a/SOURCES/bz2151166-01-fix-displaying-bool-and-integer-values.patch +++ b/SOURCES/bz2151166-01-fix-displaying-bool-and-integer-values.patch @@ -1,34 +1,16 @@ -From 2a3747fedeae228345c4ee74ce8ad6d72e297525 Mon Sep 17 00:00:00 2001 +From 0da95a7f05ae7600eebe30df78a3d4622cd6b4f8 Mon Sep 17 00:00:00 2001 From: Ondrej Mular Date: Wed, 7 Dec 2022 15:53:25 +0100 -Subject: [PATCH 2/2] fix displaying bool and integer values in `pcs resource +Subject: [PATCH 2/5] fix displaying bool and integer values in `pcs resource config` command --- - CHANGELOG.md | 6 ++++++ 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, 20 insertions(+), 13 deletions(-) + 4 files changed, 14 insertions(+), 13 deletions(-) -diff --git a/CHANGELOG.md b/CHANGELOG.md -index 75d50acd..b4d64578 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -7,6 +7,12 @@ - self-validation feature when the resource is already misconfigured - ([rhbz#2151511]) - -+### Fixed -+- Displaying bool and integer values in `pcs resource config` command -+ ([rhbz#2151166], [ghissue#604]) -+ -+[ghissue#604]: https://github.com/ClusterLabs/pcs/issues/604 -+[rhbz#2151166]: https://bugzilla.redhat.com/show_bug.cgi?id=2151166 - [rhbz#2151511]: https://bugzilla.redhat.com/show_bug.cgi?id=2151511 - - diff --git a/pcs/cli/resource/output.py b/pcs/cli/resource/output.py index 6d1fad16..0705d27b 100644 --- a/pcs/cli/resource/output.py @@ -142,5 +124,5 @@ index 8f46f6dd..a980ec80 100644 on_fail=None, meta_attributes=[], -- -2.38.1 +2.39.0 diff --git a/SOURCES/bz2151511-01-add-warning-when-updating-a-misconfigured-resource.patch b/SOURCES/bz2151511-01-add-warning-when-updating-a-misconfigured-resource.patch index dd73144..52e75f8 100644 --- a/SOURCES/bz2151511-01-add-warning-when-updating-a-misconfigured-resource.patch +++ b/SOURCES/bz2151511-01-add-warning-when-updating-a-misconfigured-resource.patch @@ -1,10 +1,9 @@ -From fc2cc9c30dbe3559b4989acda32f8a8613ee5c16 Mon Sep 17 00:00:00 2001 +From 58589e47f2913276ea1c2164a3ce8ee694fb2b78 Mon Sep 17 00:00:00 2001 From: Ondrej Mular Date: Wed, 7 Dec 2022 11:33:25 +0100 -Subject: [PATCH 1/2] add warning when updating a misconfigured resource +Subject: [PATCH 1/5] add warning when updating a misconfigured resource --- - CHANGELOG.md | 10 +++ pcs/common/reports/codes.py | 3 + pcs/common/reports/messages.py | 19 +++++ pcs/lib/cib/resource/primitive.py | 84 ++++++++++++++----- @@ -13,28 +12,8 @@ Subject: [PATCH 1/2] add warning when updating a misconfigured resource .../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, 171 insertions(+), 138 deletions(-) + 8 files changed, 161 insertions(+), 138 deletions(-) -diff --git a/CHANGELOG.md b/CHANGELOG.md -index 2af6bee1..75d50acd 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -1,5 +1,15 @@ - # Change Log - -+## [Unreleased] -+ -+### Added -+- Warning to `pcs resource|stonith update` commands about not using agent -+ self-validation feature when the resource is already misconfigured -+ ([rhbz#2151511]) -+ -+[rhbz#2151511]: https://bugzilla.redhat.com/show_bug.cgi?id=2151511 -+ -+ - ## [0.10.15] - 2022-11-23 - - ### Security diff --git a/pcs/common/reports/codes.py b/pcs/common/reports/codes.py index deecc626..48048af7 100644 --- a/pcs/common/reports/codes.py @@ -749,5 +728,5 @@ index 9911d604..cf430d75 100644 self.assert_pcs_success( "stonith config test2".split(), -- -2.38.1 +2.39.0 diff --git a/SOURCES/bz2158804-01-fix-stonith-watchdog-timeout-validation.patch b/SOURCES/bz2158804-01-fix-stonith-watchdog-timeout-validation.patch new file mode 100644 index 0000000..897cedb --- /dev/null +++ b/SOURCES/bz2158804-01-fix-stonith-watchdog-timeout-validation.patch @@ -0,0 +1,485 @@ +From 5bed788246ac19c866a60ab3773d94fa4ca28c37 Mon Sep 17 00:00:00 2001 +From: Miroslav Lisik +Date: Thu, 5 Jan 2023 16:21:44 +0100 +Subject: [PATCH 5/5] Fix stonith-watchdog-timeout validation + +--- + 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 ++- + 5 files changed, 157 insertions(+), 45 deletions(-) + +diff --git a/pcs/lib/cluster_property.py b/pcs/lib/cluster_property.py +index 9ccacd74..b622bdaf 100644 +--- a/pcs/lib/cluster_property.py ++++ b/pcs/lib/cluster_property.py +@@ -8,6 +8,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, +@@ -38,8 +39,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( +@@ -124,9 +138,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( +@@ -154,9 +165,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 319d1df6..fd124843 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 ff1f9cfb..51e25efc 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(), + stdout_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(), ++ stdout_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/bz2159455-01-add-agent-validation-option.patch b/SOURCES/bz2159455-01-add-agent-validation-option.patch new file mode 100644 index 0000000..f922c58 --- /dev/null +++ b/SOURCES/bz2159455-01-add-agent-validation-option.patch @@ -0,0 +1,1438 @@ +From 537d18f785edfffb7505510ef309cbd4c31bb914 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular +Date: Thu, 12 Jan 2023 14:14:26 +0100 +Subject: [PATCH 1/2] add '--agent-validation' option for enabling agent + self-validation feature + +--- + 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 | 13 +- + pcs/stonith.py | 3 + + pcs/usage.py | 31 ++-- + .../cib/resource/test_primitive_validate.py | 49 ++++++ + .../commands/resource/test_resource_create.py | 155 ++++++++++++------ + 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 ++- + 18 files changed, 272 insertions(+), 146 deletions(-) + +diff --git a/pcs/cli/common/parse_args.py b/pcs/cli/common/parse_args.py +index 0a43deec..a16d882e 100644 +--- a/pcs/cli/common/parse_args.py ++++ b/pcs/cli/common/parse_args.py +@@ -94,6 +94,8 @@ PCS_LONG_OPTIONS = [ + f"{_OUTPUT_FORMAT_OPTION_STR}=", + # auth token + "token=", ++ # enable agent self validation ++ "agent-validation", + ] + + +@@ -485,6 +487,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 c5df8e58..5a6e1e0d 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 = [] + agent_name = resource_agent.metadata.name +@@ -419,7 +424,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( +@@ -447,6 +453,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 +@@ -521,7 +528,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 28407d48..12b1f0e9 100644 +--- a/pcs/lib/commands/resource.py ++++ b/pcs/lib/commands/resource.py +@@ -348,6 +348,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 + """ +@@ -381,6 +382,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) +@@ -427,6 +430,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() +@@ -451,6 +455,7 @@ def create_as_clone( + 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 + """ +@@ -478,6 +483,8 @@ def create_as_clone( + ensure_disabled -- is flag that keeps resource in target-role "Stopped" + 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) +@@ -531,6 +538,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( +@@ -561,6 +569,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 + """ +@@ -589,6 +598,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) +@@ -664,6 +675,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) +@@ -701,6 +713,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 + """ +@@ -728,6 +741,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) +@@ -775,6 +790,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 15b10dec..fc0264e7 100644 +--- a/pcs/lib/commands/stonith.py ++++ b/pcs/lib/commands/stonith.py +@@ -117,6 +117,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 + """ +@@ -139,6 +140,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) +@@ -174,6 +177,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 + """ +@@ -221,6 +226,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) +@@ -286,6 +293,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 cd00f8ac..6f7fe9cc 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 +@@ -183,11 +183,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] +@@ -670,8 +672,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 +@@ -681,8 +683,10 @@ 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 device options] ++update [stonith device options] [\fB\-\-agent\-validation\fR] + Add, remove or change options of specified stonith id. Unspecified options will be kept unchanged. If you wish to remove an option, set it to empty value, i.e. 'option_name='. ++ ++If \fB\-\-agent\-validation\fR is specified, stonith agent validate\-all action will be used to validate stonith device options. + .TP + update\-scsi\-devices (set [...]) | (add [...] delete|remove [...] ) + Update scsi fencing devices without affecting other resources. You must specify either list of set devices or at least one device for add or delete/remove devices. Stonith resource must be running on one cluster node. Each device will be unfenced on each cluster node running cluster. Supported fence agents: fence_scsi, fence_mpath. +diff --git a/pcs/resource.py b/pcs/resource.py +index bf34e8d7..7500b37e 100644 +--- a/pcs/resource.py ++++ b/pcs/resource.py +@@ -580,6 +580,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 +@@ -593,6 +594,7 @@ def resource_create(lib, argv, modifiers): + * -f - CIB file + """ + modifiers.ensure_only_supported( ++ "--agent-validation", + "--before", + "--after", + "--group", +@@ -655,6 +657,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) +@@ -894,12 +897,15 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True): + """ + 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 + """ + del lib +- 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) +@@ -970,7 +976,10 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True): + dict(params), + res_id, + get_resources(lib_pacemaker.get_cib(cib_xml)), +- force=modifiers.get("--force"), ++ 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 58cd14fc..17ba6aca 100644 +--- a/pcs/stonith.py ++++ b/pcs/stonith.py +@@ -127,6 +127,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 + """ +@@ -137,6 +138,7 @@ def stonith_create(lib, argv, modifiers): + "--force", + "--disabled", + "--no-default-ops", ++ "--agent-validation", + "--wait", + "-f", + ) +@@ -170,6 +172,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 0a6ffcb6..bb5f864d 100644 +--- a/pcs/usage.py ++++ b/pcs/usage.py +@@ -357,7 +357,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 +@@ -365,12 +366,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: +@@ -545,7 +547,8 @@ Commands: + List available agents optionally filtered by standard and provider. + + update [resource options] [op [ +- ]...] [meta ...] [--wait[=n]] ++ ]...] [meta ...] ++ [--agent-validation] [--wait[=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. +@@ -558,6 +561,9 @@ Commands: + if not specified.) If you want to create multiple monitor operations + you should use the 'op add' & 'op remove' commands. + ++ If --agent-validation is specified, resource agent validate-all action ++ will be used to validate resource 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 + 1 otherwise. If 'n' is not specified it defaults to 60 minutes. +@@ -1420,13 +1426,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 +@@ -1436,11 +1444,14 @@ Commands: + Example: Use port p1 for node n1 and ports p2 and p3 for node n2 + pcs stonith create MyFence fence_virt 'pcmk_host_map=n1:p1;n2:p2,p3' + +- update [stonith device options] ++ update [stonith device options] [--agent-validation] + Add, remove or change options of specified stonith id. Unspecified + options will be kept unchanged. If you wish to remove an option, set it + to empty value, i.e. 'option_name='. + ++ If --agent-validation is specified, stonith agent validate-all action ++ will be used to validate stonith device options. ++ + update-scsi-devices (set [...]) + | (add [...] delete|remove + [device-path>...]) +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 1bc3a5a6..0456abcf 100644 +--- a/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py ++++ b/pcs_test/tier0/lib/cib/resource/test_primitive_validate.py +@@ -592,6 +592,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() +@@ -602,6 +618,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -621,6 +638,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=True, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -642,6 +660,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -667,6 +686,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -686,6 +706,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -701,6 +722,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -716,6 +738,7 @@ class ValidateResourceInstanceAttributesCreateSelfValidation(TestCase): + attributes, + etree.Element("resources"), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -1275,6 +1298,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"} +@@ -1287,6 +1328,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1318,6 +1360,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=True, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1354,6 +1397,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -1391,6 +1435,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1422,6 +1467,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1439,6 +1485,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [], + ) +@@ -1456,6 +1503,7 @@ class ValidateResourceInstanceAttributesUpdateSelfValidation(TestCase): + self._NAME, + self._fixture_resources(old_attributes), + force=False, ++ enable_agent_self_validation=True, + ), + [ + fixture.error( +@@ -1482,6 +1530,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 3384a674..225bb57b 100644 +--- a/pcs_test/tier0/lib/commands/resource/test_resource_create.py ++++ b/pcs_test/tier0/lib/commands/resource/test_resource_create.py +@@ -29,7 +29,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", +@@ -41,6 +43,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, + ) + + +@@ -50,6 +53,7 @@ def create_group( + disabled=False, + meta_attributes=None, + operation_list=None, ++ enable_agent_self_validation=False, + ): + return resource.create_in_group( + env, +@@ -61,6 +65,7 @@ def create_group( + instance_attributes={}, + wait=wait, + ensure_disabled=disabled, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + + +@@ -72,6 +77,7 @@ def create_clone( + clone_options=None, + operation_list=None, + clone_id=None, ++ enable_agent_self_validation=False, + ): + return resource.create_as_clone( + env, +@@ -84,6 +90,7 @@ def create_clone( + clone_id=clone_id, + wait=wait, + ensure_disabled=disabled, ++ enable_agent_self_validation=enable_agent_self_validation, + ) + + +@@ -94,6 +101,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, +@@ -106,6 +114,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, + ) + + +@@ -390,13 +399,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( +@@ -564,7 +566,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 + ) +@@ -586,7 +587,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( + [ +@@ -617,7 +620,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( + [ +@@ -637,7 +642,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( + [ +@@ -682,7 +690,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 + ) +@@ -845,7 +852,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=""" + +@@ -891,14 +897,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(), +@@ -1080,9 +1084,6 @@ class Create(TestCase): + + """, + ) +- self.config.runner.pcmk.resource_agent_self_validation( +- dict(state=1), output="" +- ) + self.config.env.push_cib( + resources=""" + +@@ -1168,7 +1169,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=""" + +@@ -1224,7 +1224,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, +@@ -1354,15 +1353,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): + ( +@@ -1405,7 +1398,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", +@@ -1413,7 +1405,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=""" + +@@ -1465,6 +1456,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, +@@ -1608,15 +1627,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): + ( +@@ -1626,6 +1639,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( +@@ -1637,7 +1678,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" +@@ -1649,7 +1689,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=""" + +@@ -1672,7 +1711,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", +@@ -1680,7 +1718,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=""" + +@@ -2237,7 +2274,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=""" +@@ -2267,13 +2303,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), + [ +@@ -2296,7 +2330,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), +@@ -2322,7 +2355,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), + [ +@@ -2337,7 +2369,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, +@@ -2362,7 +2393,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 + ) +@@ -2383,7 +2413,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 + ) +@@ -2408,7 +2437,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 + ) +@@ -2427,7 +2455,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 + ) +@@ -2455,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) + ) +@@ -2479,7 +2505,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( +@@ -2512,7 +2537,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( +@@ -2529,3 +2553,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 16c20116..29db0ffd 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 6d6841fe..d8801871 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 65ad1090..f4424a58 100644 +--- a/pcs_test/tier1/legacy/test_resource.py ++++ b/pcs_test/tier1/legacy/test_resource.py +@@ -5809,7 +5809,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 cf430d75..c528c921 100644 +--- a/pcs_test/tier1/legacy/test_stonith.py ++++ b/pcs_test/tier1/legacy/test_stonith.py +@@ -1286,6 +1286,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=( +@@ -1295,8 +1316,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/pcsd-rubygem-json-error-message-change.patch b/SOURCES/pcsd-rubygem-json-error-message-change.patch index cd5b357..60e31f8 100644 --- a/SOURCES/pcsd-rubygem-json-error-message-change.patch +++ b/SOURCES/pcsd-rubygem-json-error-message-change.patch @@ -1,7 +1,7 @@ -From 38333342cb0e931b11bbf57ba469b47172221a32 Mon Sep 17 00:00:00 2001 +From 91d13a82a0803f2a4653a2ec9379a27f4555dcb5 Mon Sep 17 00:00:00 2001 From: Mamoru TASAKA Date: Thu, 8 Dec 2022 22:47:59 +0900 -Subject: [PATCH] pcsd ruby: adjust to json 2.6.3 error message change +Subject: [PATCH 3/5] pcsd ruby: adjust to json 2.6.3 error message change json 2.6.3 now removes line number information from parser error message. @@ -36,5 +36,5 @@ index 7aaf4349..a580b24f 100644 ) assert_empty_data(cfg) -- -2.38.1 +2.39.0 diff --git a/SPECS/pcs.spec b/SPECS/pcs.spec index e8710c6..96dcd42 100644 --- a/SPECS/pcs.spec +++ b/SPECS/pcs.spec @@ -1,6 +1,6 @@ Name: pcs Version: 0.10.15 -Release: 2%{?dist} +Release: 3%{?dist} # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses # GPL-2.0-only: pcs @@ -43,12 +43,12 @@ ExclusiveArch: i686 x86_64 s390x ppc64le aarch64 %global version_rubygem_json 2.6.3 %global version_rubygem_mustermann 2.0.2 %global version_rubygem_open4 1.3.4 -%global version_rubygem_rack 2.2.4 -%global version_rubygem_rack_protection 2.2.3 +%global version_rubygem_rack 2.2.5 +%global version_rubygem_rack_protection 2.2.4 %global version_rubygem_rack_test 2.0.2 %global version_rubygem_rexml 3.2.5 %global version_rubygem_ruby2_keywords 0.0.5 -%global version_rubygem_sinatra 2.2.3 +%global version_rubygem_sinatra 2.2.4 %global version_rubygem_thin 1.8.1 %global version_rubygem_tilt 2.0.11 @@ -125,6 +125,8 @@ Patch1: do-not-support-cluster-setup-with-udp-u-transport.patch Patch2: bz2151511-01-add-warning-when-updating-a-misconfigured-resource.patch Patch3: bz2151166-01-fix-displaying-bool-and-integer-values.patch Patch4: pcsd-rubygem-json-error-message-change.patch +Patch5: bz2159455-01-add-agent-validation-option.patch +Patch6: bz2158804-01-fix-stonith-watchdog-timeout-validation.patch # Downstream patches do not come from upstream. They adapt pcs for specific # RHEL needs. @@ -327,6 +329,8 @@ update_times_patch %{PATCH1} update_times_patch %{PATCH2} update_times_patch %{PATCH3} update_times_patch %{PATCH4} +update_times_patch %{PATCH5} +update_times_patch %{PATCH6} # update_times_patch %{PATCH101} @@ -385,7 +389,7 @@ pwd cp -r %{_builddir}/%{ui_src_name}/build ${RPM_BUILD_ROOT}%{_libdir}/%{pcsd_public_dir}/ui # 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 @@ -400,6 +404,7 @@ mv %{rubygem_bundle_dir}/gems/open4-%{version_rubygem_open4}/LICENSE open4_LICEN 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 @@ -535,6 +540,7 @@ remove_all_tests %license rack_MIT-LICENSE %license rack-protection_License %license rack-test_MIT-LICENSE.txt +%license ruby2_keywords_LICENSE %license sinatra_LICENSE %license tilt_COPYING %{python3_sitelib}/* @@ -577,6 +583,13 @@ remove_all_tests %license pyagentx_LICENSE.txt %changelog +* Mon Jan 16 2023 Michal Pospisil - 0.10.15-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#2158804 rhbz#2159455 + * Fri Dec 16 2022 Michal Pospisil - 0.10.15-2 - Added warning when omitting validation of misconfigured resource - Fixed displaying of bool and integer values in `pcs resource config` command