From 871b8fbc41794c3838542b277217bebb80aba0bd Mon Sep 17 00:00:00 2001 From: vivi Date: Wed, 24 Nov 2021 17:36:51 +0800 Subject: [PATCH 1/6] booth: Check whether path '/etc/booth' exists --- pcs/common/reports/codes.py | 1 + pcs/common/reports/messages.py | 17 +++++ pcs/lib/commands/booth.py | 8 ++- .../tier0/common/reports/test_messages.py | 6 ++ pcs_test/tier0/lib/commands/test_booth.py | 67 +++++++++++++------ 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/pcs/common/reports/codes.py b/pcs/common/reports/codes.py index 2680f7e2e..09c965994 100644 --- a/pcs/common/reports/codes.py +++ b/pcs/common/reports/codes.py @@ -43,6 +43,7 @@ AGENT_NAME_GUESS_FOUND_NONE = M("AGENT_NAME_GUESS_FOUND_NONE") AGENT_NAME_GUESSED = M("AGENT_NAME_GUESSED") BAD_CLUSTER_STATE_FORMAT = M("BAD_CLUSTER_STATE_FORMAT") +BOOTH_PATH_NOT_EXISTS = M("BOOTH_PATH_NOT_EXISTS") BOOTH_ADDRESS_DUPLICATION = M("BOOTH_ADDRESS_DUPLICATION") BOOTH_ALREADY_IN_CIB = M("BOOTH_ALREADY_IN_CIB") BOOTH_CANNOT_DETERMINE_LOCAL_SITE_IP = M("BOOTH_CANNOT_DETERMINE_LOCAL_SITE_IP") diff --git a/pcs/common/reports/messages.py b/pcs/common/reports/messages.py index b175bb12f..0b17ec85f 100644 --- a/pcs/common/reports/messages.py +++ b/pcs/common/reports/messages.py @@ -6273,6 +6273,23 @@ class NodeInLocalCluster(ReportItemMessage): def message(self) -> str: return f"Node '{self.node}' is part of local cluster" +@dataclass(frozen=True) +class BoothPathNotExists(ReportItemMessage): + """ + Path '/etc/booth' is generated when Booth is installed, so it can be used to + check whether Booth is installed + + path -- The path generated by booth installation + """ + + path: str + _code = codes.BOOTH_PATH_NOT_EXISTS + + @property + def message(self) -> str: + return ( + f"Configuration directory for booth '{self.path}' is missing. Is booth installed?" + ) @dataclass(frozen=True) class BoothLackOfSites(ReportItemMessage): diff --git a/pcs/lib/commands/booth.py b/pcs/lib/commands/booth.py index cb3971437..6867f3dce 100644 --- a/pcs/lib/commands/booth.py +++ b/pcs/lib/commands/booth.py @@ -85,6 +85,8 @@ def config_setup( booth_conf = booth_env.create_facade(site_list, arbitrator_list) booth_conf.set_authfile(booth_env.key_path) + key_dir=os.path.dirname(booth_env.key_path) + try: booth_env.key.write_raw( tools.generate_binary_key( @@ -109,7 +111,11 @@ def config_setup( ) ) except RawFileError as e: - report_processor.report(raw_file_error_report(e)) + if os.path.exists(key_dir) is not True: + report_processor.report(ReportItem.error(reports.messages.BoothPathNotExists(key_dir))) + raise LibraryError() + else: + report_processor.report(raw_file_error_report(e)) if report_processor.has_errors: raise LibraryError() diff --git a/pcs_test/tier0/common/reports/test_messages.py b/pcs_test/tier0/common/reports/test_messages.py index 7474dbfda..125994f52 100644 --- a/pcs_test/tier0/common/reports/test_messages.py +++ b/pcs_test/tier0/common/reports/test_messages.py @@ -4739,6 +4739,12 @@ def test_success(self): reports.BoothAlreadyInCib("name"), ) +class BoothPathNotExists(NameBuildTest): + def test_success(self): + self.assert_message_from_report( + "Configuration directory for booth 'path' is missing. Is booth installed?", + reports.BoothPathNotExists("path"), + ) class BoothNotExistsInCib(NameBuildTest): def test_success(self): diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py index 2e23f93a0..457347457 100644 --- a/pcs_test/tier0/lib/commands/test_booth.py +++ b/pcs_test/tier0/lib/commands/test_booth.py @@ -358,17 +358,29 @@ def test_write_config_error(self): self.arbitrators, ) ) - self.env_assist.assert_reports( - [ - fixture.error( - reports.codes.FILE_IO_ERROR, - file_type_code=file_type_codes.BOOTH_CONFIG, - file_path=self.fixture_cfg_path(), - reason=error, - operation=RawFileError.ACTION_WRITE, - ), - ] - ) + + if os.path.exists(os.path.dirname(self.fixture_key_path())) is not True: + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.BOOTH_PATH_NOT_EXISTS, + path=os.path.dirname(self.fixture_key_path()), + ), + ] + ) + else: + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.FILE_IO_ERROR, + file_type_code=file_type_codes.BOOTH_CONFIG, + file_path=self.fixture_cfg_path(), + reason=error, + operation=RawFileError.ACTION_WRITE, + ), + ] + ) + def test_write_key_error(self): error = "an error occurred" @@ -390,17 +402,28 @@ def test_write_key_error(self): self.arbitrators, ) ) - self.env_assist.assert_reports( - [ - fixture.error( - reports.codes.FILE_IO_ERROR, - file_type_code=file_type_codes.BOOTH_KEY, - file_path=self.fixture_key_path(), - reason=error, - operation=RawFileError.ACTION_WRITE, - ), - ] - ) + + if os.path.exists(os.path.dirname(self.fixture_key_path())) is not True: + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.BOOTH_PATH_NOT_EXISTS, + path=os.path.dirname(self.fixture_key_path()), + ), + ] + ) + else: + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.FILE_IO_ERROR, + file_type_code=file_type_codes.BOOTH_KEY, + file_path=self.fixture_key_path(), + reason=error, + operation=RawFileError.ACTION_WRITE, + ), + ] + ) def test_not_live(self): key_path = "/tmp/pcs_test/booth.key" From 03a5ab7e2058ed26617ff5b11f5b8c9d9d7e8fe5 Mon Sep 17 00:00:00 2001 From: Tomas Jelinek Date: Thu, 25 Nov 2021 15:19:57 +0100 Subject: [PATCH 2/6] fix code formatting and linter issues --- pcs/common/reports/codes.py | 2 +- pcs/common/reports/messages.py | 5 ++++- pcs/lib/commands/booth.py | 9 +++++---- pcs_test/tier0/common/reports/test_messages.py | 7 ++++++- pcs_test/tier0/lib/commands/test_booth.py | 5 ++--- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pcs/common/reports/codes.py b/pcs/common/reports/codes.py index 09c965994..5bae71705 100644 --- a/pcs/common/reports/codes.py +++ b/pcs/common/reports/codes.py @@ -43,7 +43,6 @@ AGENT_NAME_GUESS_FOUND_NONE = M("AGENT_NAME_GUESS_FOUND_NONE") AGENT_NAME_GUESSED = M("AGENT_NAME_GUESSED") BAD_CLUSTER_STATE_FORMAT = M("BAD_CLUSTER_STATE_FORMAT") -BOOTH_PATH_NOT_EXISTS = M("BOOTH_PATH_NOT_EXISTS") BOOTH_ADDRESS_DUPLICATION = M("BOOTH_ADDRESS_DUPLICATION") BOOTH_ALREADY_IN_CIB = M("BOOTH_ALREADY_IN_CIB") BOOTH_CANNOT_DETERMINE_LOCAL_SITE_IP = M("BOOTH_CANNOT_DETERMINE_LOCAL_SITE_IP") @@ -59,6 +58,7 @@ BOOTH_LACK_OF_SITES = M("BOOTH_LACK_OF_SITES") BOOTH_MULTIPLE_TIMES_IN_CIB = M("BOOTH_MULTIPLE_TIMES_IN_CIB") BOOTH_NOT_EXISTS_IN_CIB = M("BOOTH_NOT_EXISTS_IN_CIB") +BOOTH_PATH_NOT_EXISTS = M("BOOTH_PATH_NOT_EXISTS") BOOTH_PEERS_STATUS_ERROR = M("BOOTH_PEERS_STATUS_ERROR") BOOTH_TICKET_DOES_NOT_EXIST = M("BOOTH_TICKET_DOES_NOT_EXIST") BOOTH_TICKET_DUPLICATE = M("BOOTH_TICKET_DUPLICATE") diff --git a/pcs/common/reports/messages.py b/pcs/common/reports/messages.py index 0b17ec85f..43ce38e1b 100644 --- a/pcs/common/reports/messages.py +++ b/pcs/common/reports/messages.py @@ -6273,6 +6273,7 @@ class NodeInLocalCluster(ReportItemMessage): def message(self) -> str: return f"Node '{self.node}' is part of local cluster" + @dataclass(frozen=True) class BoothPathNotExists(ReportItemMessage): """ @@ -6288,9 +6289,11 @@ class BoothPathNotExists(ReportItemMessage): @property def message(self) -> str: return ( - f"Configuration directory for booth '{self.path}' is missing. Is booth installed?" + f"Configuration directory for booth '{self.path}' is missing. " + "Is booth installed?" ) + @dataclass(frozen=True) class BoothLackOfSites(ReportItemMessage): """ diff --git a/pcs/lib/commands/booth.py b/pcs/lib/commands/booth.py index 6867f3dce..8c9ecd936 100644 --- a/pcs/lib/commands/booth.py +++ b/pcs/lib/commands/booth.py @@ -85,7 +85,7 @@ def config_setup( booth_conf = booth_env.create_facade(site_list, arbitrator_list) booth_conf.set_authfile(booth_env.key_path) - key_dir=os.path.dirname(booth_env.key_path) + key_dir = os.path.dirname(booth_env.key_path) try: booth_env.key.write_raw( @@ -111,9 +111,10 @@ def config_setup( ) ) except RawFileError as e: - if os.path.exists(key_dir) is not True: - report_processor.report(ReportItem.error(reports.messages.BoothPathNotExists(key_dir))) - raise LibraryError() + if not os.path.exists(key_dir): + report_processor.report( + ReportItem.error(reports.messages.BoothPathNotExists(key_dir)) + ) else: report_processor.report(raw_file_error_report(e)) if report_processor.has_errors: diff --git a/pcs_test/tier0/common/reports/test_messages.py b/pcs_test/tier0/common/reports/test_messages.py index 125994f52..c85aaa9c7 100644 --- a/pcs_test/tier0/common/reports/test_messages.py +++ b/pcs_test/tier0/common/reports/test_messages.py @@ -4739,13 +4739,18 @@ def test_success(self): reports.BoothAlreadyInCib("name"), ) + class BoothPathNotExists(NameBuildTest): def test_success(self): self.assert_message_from_report( - "Configuration directory for booth 'path' is missing. Is booth installed?", + ( + "Configuration directory for booth 'path' is missing. Is booth " + "installed?" + ), reports.BoothPathNotExists("path"), ) + class BoothNotExistsInCib(NameBuildTest): def test_success(self): self.assert_message_from_report( diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py index 457347457..f3f224f15 100644 --- a/pcs_test/tier0/lib/commands/test_booth.py +++ b/pcs_test/tier0/lib/commands/test_booth.py @@ -359,7 +359,7 @@ def test_write_config_error(self): ) ) - if os.path.exists(os.path.dirname(self.fixture_key_path())) is not True: + if not os.path.exists(os.path.dirname(self.fixture_key_path())): self.env_assist.assert_reports( [ fixture.error( @@ -381,7 +381,6 @@ def test_write_config_error(self): ] ) - def test_write_key_error(self): error = "an error occurred" @@ -403,7 +402,7 @@ def test_write_key_error(self): ) ) - if os.path.exists(os.path.dirname(self.fixture_key_path())) is not True: + if not os.path.exists(os.path.dirname(self.fixture_key_path())): self.env_assist.assert_reports( [ fixture.error( From db0fb1c3e4bc77e67acb51b23bc708d50b7c4304 Mon Sep 17 00:00:00 2001 From: Tomas Jelinek Date: Thu, 25 Nov 2021 15:51:06 +0100 Subject: [PATCH 3/6] fix handling booth ghost files --- pcs/lib/commands/booth.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pcs/lib/commands/booth.py b/pcs/lib/commands/booth.py index 8c9ecd936..830e2ea90 100644 --- a/pcs/lib/commands/booth.py +++ b/pcs/lib/commands/booth.py @@ -85,7 +85,11 @@ def config_setup( booth_conf = booth_env.create_facade(site_list, arbitrator_list) booth_conf.set_authfile(booth_env.key_path) - key_dir = os.path.dirname(booth_env.key_path) + conf_dir = ( + None + if booth_env.ghost_file_codes + else os.path.dirname(booth_env.config_path) + ) try: booth_env.key.write_raw( @@ -111,9 +115,9 @@ def config_setup( ) ) except RawFileError as e: - if not os.path.exists(key_dir): + if conf_dir and not os.path.exists(conf_dir): report_processor.report( - ReportItem.error(reports.messages.BoothPathNotExists(key_dir)) + ReportItem.error(reports.messages.BoothPathNotExists(conf_dir)) ) else: report_processor.report(raw_file_error_report(e)) From 6e130b80ec7fd0c1fe71dc9e842b3fb73a9f76c3 Mon Sep 17 00:00:00 2001 From: Tomas Jelinek Date: Thu, 25 Nov 2021 16:02:37 +0100 Subject: [PATCH 4/6] fix tier0 tests Make tier0 tests independent on the real filesystem (which is a requirement for tier0 tests) and thus allow them testing both situations --- pcs_test/tier0/lib/commands/test_booth.py | 138 +++++++++++----------- 1 file changed, 72 insertions(+), 66 deletions(-) diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py index f3f224f15..e6bb00113 100644 --- a/pcs_test/tier0/lib/commands/test_booth.py +++ b/pcs_test/tier0/lib/commands/test_booth.py @@ -333,23 +333,22 @@ def test_files_exist_forced(self): overwrite_existing=True, ) - def test_write_config_error(self): - error = "an error occurred" + def _assert_write_config_error(self, error, booth_dir_exists): - ( - 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(), - exception_msg=error, - 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(), + exception_msg=error, + name="raw_file.write.cfg", + ) + self.config.fs.exists(self.booth_dir, booth_dir_exists) self.env_assist.assert_raise_library_error( lambda: commands.config_setup( @@ -359,40 +358,42 @@ def test_write_config_error(self): ) ) - if not os.path.exists(os.path.dirname(self.fixture_key_path())): - self.env_assist.assert_reports( - [ - fixture.error( - reports.codes.BOOTH_PATH_NOT_EXISTS, - path=os.path.dirname(self.fixture_key_path()), - ), - ] - ) - else: - self.env_assist.assert_reports( - [ - fixture.error( - reports.codes.FILE_IO_ERROR, - file_type_code=file_type_codes.BOOTH_CONFIG, - file_path=self.fixture_cfg_path(), - reason=error, - operation=RawFileError.ACTION_WRITE, - ), - ] - ) + def test_write_config_error(self): + error = "an error occurred" + self._assert_write_config_error(error, True) + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.FILE_IO_ERROR, + file_type_code=file_type_codes.BOOTH_CONFIG, + file_path=self.fixture_cfg_path(), + reason=error, + operation=RawFileError.ACTION_WRITE, + ), + ] + ) - def test_write_key_error(self): + def test_write_config_error_booth_dir_missing(self): error = "an error occurred" + self._assert_write_config_error(error, False) + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.BOOTH_PATH_NOT_EXISTS, + path=self.booth_dir, + ), + ] + ) - ( - self.config.raw_file.write( - file_type_codes.BOOTH_KEY, - self.fixture_key_path(), - RANDOM_KEY, - exception_msg=error, - name="raw_file.write.key", - ) + def _assert_write_key_error(self, error, booth_dir_exists): + self.config.raw_file.write( + file_type_codes.BOOTH_KEY, + self.fixture_key_path(), + RANDOM_KEY, + exception_msg=error, + name="raw_file.write.key", ) + self.config.fs.exists(self.booth_dir, booth_dir_exists) self.env_assist.assert_raise_library_error( lambda: commands.config_setup( @@ -402,27 +403,32 @@ def test_write_key_error(self): ) ) - if not os.path.exists(os.path.dirname(self.fixture_key_path())): - self.env_assist.assert_reports( - [ - fixture.error( - reports.codes.BOOTH_PATH_NOT_EXISTS, - path=os.path.dirname(self.fixture_key_path()), - ), - ] - ) - else: - self.env_assist.assert_reports( - [ - fixture.error( - reports.codes.FILE_IO_ERROR, - file_type_code=file_type_codes.BOOTH_KEY, - file_path=self.fixture_key_path(), - reason=error, - operation=RawFileError.ACTION_WRITE, - ), - ] - ) + def test_write_key_error(self): + error = "an error occurred" + self._assert_write_key_error(error, True) + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.FILE_IO_ERROR, + file_type_code=file_type_codes.BOOTH_KEY, + file_path=self.fixture_key_path(), + reason=error, + operation=RawFileError.ACTION_WRITE, + ), + ] + ) + + def test_write_key_error_booth_dir_missing(self): + error = "an error occurred" + self._assert_write_key_error(error, False) + self.env_assist.assert_reports( + [ + fixture.error( + reports.codes.BOOTH_PATH_NOT_EXISTS, + path=self.booth_dir, + ), + ] + ) def test_not_live(self): key_path = "/tmp/pcs_test/booth.key" From 4b20897f3591a843a74d4793323ab9070858ab1d Mon Sep 17 00:00:00 2001 From: Tomas Jelinek Date: Thu, 25 Nov 2021 16:17:37 +0100 Subject: [PATCH 5/6] booth pull: check whether /etc/booth exists --- pcs/lib/commands/booth.py | 8 ++- pcs_test/tier0/lib/commands/test_booth.py | 73 ++++++++++++++++------- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/pcs/lib/commands/booth.py b/pcs/lib/commands/booth.py index 830e2ea90..1c4abe23e 100644 --- a/pcs/lib/commands/booth.py +++ b/pcs/lib/commands/booth.py @@ -777,6 +777,7 @@ def pull_config(env: LibraryEnvironment, node_name, instance_name=None): booth_env = env.get_booth_env(instance_name) instance_name = booth_env.instance_name _ensure_live_env(env, booth_env) + conf_dir = os.path.dirname(booth_env.config_path) env.report_processor.report( ReportItem.info( @@ -820,7 +821,12 @@ def pull_config(env: LibraryEnvironment, node_name, instance_name=None): ) ) except RawFileError as e: - report_processor.report(raw_file_error_report(e)) + if not os.path.exists(conf_dir): + report_processor.report( + ReportItem.error(reports.messages.BoothPathNotExists(conf_dir)) + ) + else: + report_processor.report(raw_file_error_report(e)) except KeyError as e: raise LibraryError( ReportItem.error(reports.messages.InvalidResponseFormat(node_name)) diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py index e6bb00113..5631d56f4 100644 --- a/pcs_test/tier0/lib/commands/test_booth.py +++ b/pcs_test/tier0/lib/commands/test_booth.py @@ -3336,26 +3336,29 @@ def test_not_live(self): expected_in_processor=False, ) - def test_write_failure(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, - exception_msg=self.reason, - ) + def _assert_write_failure(self, booth_dir_exists): + 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, + exception_msg=self.reason, ) + self.config.fs.exists(self.booth_dir, booth_dir_exists) self.env_assist.assert_raise_library_error( lambda: commands.pull_config( self.env_assist.get_env(), self.node_name ), ) + + def test_write_failure(self): + self._assert_write_failure(True) self.env_assist.assert_reports( self.report_list[:1] + [ @@ -3369,6 +3372,18 @@ def test_write_failure(self): ] ) + def test_write_failure_booth_dir_missing(self): + self._assert_write_failure(False) + self.env_assist.assert_reports( + self.report_list[:1] + + [ + fixture.error( + reports.codes.BOOTH_PATH_NOT_EXISTS, + path=self.booth_dir, + ), + ] + ) + def test_network_failure(self): self.config.http.booth.get_config( self.name, @@ -3534,22 +3549,24 @@ def setUp(self): super().setUp() self.reason = "reason" - def test_authfile_write_failure(self): - ( - self.config.raw_file.write( - file_type_codes.BOOTH_KEY, - self.authfile_path, - self.authfile_data, - can_overwrite=True, - exception_msg=self.reason, - ) + def _assert_authfile_write_failure(self, booth_dir_exists): + self.config.raw_file.write( + file_type_codes.BOOTH_KEY, + self.authfile_path, + self.authfile_data, + can_overwrite=True, + exception_msg=self.reason, ) + self.config.fs.exists(self.booth_dir, booth_dir_exists) self.env_assist.assert_raise_library_error( lambda: commands.pull_config( self.env_assist.get_env(), self.node_name ), ) + + def test_authfile_write_failure(self): + self._assert_authfile_write_failure(True) self.env_assist.assert_reports( self.report_list[:1] + [ @@ -3563,6 +3580,18 @@ def test_authfile_write_failure(self): ] ) + def test_authfile_write_failure_booth_dir_missing(self): + self._assert_authfile_write_failure(False) + self.env_assist.assert_reports( + self.report_list[:1] + + [ + fixture.error( + reports.codes.BOOTH_PATH_NOT_EXISTS, + path=self.booth_dir, + ), + ] + ) + class GetStatus(TestCase): def setUp(self): From bf5f3e2734fe236fd109ee851fd9d9a5dc779fe9 Mon Sep 17 00:00:00 2001 From: Tomas Jelinek Date: Fri, 26 Nov 2021 10:21:22 +0100 Subject: [PATCH 6/6] changelog update --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa22a2748..b9f25b235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,14 @@ is not false when a stonith device is in a group ([ghpull#370]) - Misleading error message from `pcs quorum unblock` when `wait_for_all=0` ([rhbz#1968088]) +- Misleading error message from `pcs booth setup` and `pcs booth pull` when + booth config directory (`/etc/booth`) is missing ([rhbz#1791670], + [ghpull#411], [ghissue#225]) +[ghissue#225]: https://github.com/ClusterLabs/pcs/issues/225 [ghpull#370]: https://github.com/ClusterLabs/pcs/pull/370 +[ghpull#411]: https://github.com/ClusterLabs/pcs/pull/411 +[rhbz#1791670]: https://bugzilla.redhat.com/show_bug.cgi?id=1791670 [rhbz#1968088]: https://bugzilla.redhat.com/show_bug.cgi?id=1968088 [rhbz#1990784]: https://bugzilla.redhat.com/show_bug.cgi?id=1990784 [rhbz#2018969]: https://bugzilla.redhat.com/show_bug.cgi?id=2018969