1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-04-28 06:28:56 +00:00

pacemaker: fix race condition on resource creation (#11750)

* remove pacemaker wait arg and fix race condition

* fix up pacemaker resource and stonith polling

* add changelog for pacemaker timeout bug

* remove env from test case and fix changelog file name

* Update changelogs/fragments/11750-pacemaker-wait-race-condition.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
munchtoast 2026-04-18 16:45:58 -04:00 committed by GitHub
parent afe9de7562
commit 6c809dd9db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 382 additions and 17 deletions

View file

@ -5,6 +5,7 @@
from __future__ import annotations
import re
import time
import typing as t
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
@ -59,6 +60,22 @@ def get_pacemaker_maintenance_mode(runner: CmdRunner) -> bool:
return bool(maintenance_mode_output)
def wait_for_resource(runner: CmdRunner, cli_noun: str, name: str, wait: int, sleep_interval: int = 5) -> None:
"""Poll ``pcs <cli_noun> status <name>`` until the resource reports Started or the wait budget expires.
Raises an exception if the resource does not reach the Started state within *wait* seconds.
"""
deadline = time.monotonic() + wait
while True:
with runner("cli_action state name") as ctx:
rc, out, err = ctx.run(cli_action=cli_noun, state="status")
if out and "Started" in out:
return
if time.monotonic() >= deadline:
raise Exception(f"Timed out waiting {wait}s for {cli_noun} resource '{name}' to start")
time.sleep(sleep_interval)
def pacemaker_runner(module: AnsibleModule, **kwargs) -> CmdRunner:
runner_command = ["pcs"]
runner = CmdRunner(

View file

@ -149,6 +149,7 @@ from ansible_collections.community.general.plugins.module_utils.module_helper im
from ansible_collections.community.general.plugins.module_utils.pacemaker import (
get_pacemaker_maintenance_mode,
pacemaker_runner,
wait_for_resource,
)
@ -237,7 +238,7 @@ class PacemakerResource(StateModuleHelper):
def state_present(self):
with self.runner(
"cli_action state name resource_type resource_option resource_operation resource_meta resource_argument "
"resource_clone_ids resource_clone_meta wait",
"resource_clone_ids resource_clone_meta",
output_process=self._process_command_output(
not get_pacemaker_maintenance_mode(self.runner), "already exists"
),
@ -247,10 +248,12 @@ class PacemakerResource(StateModuleHelper):
cli_action="resource",
resource_clone_ids=self.fmt_as_stack_argument(self.module.params["resource_clone_ids"], "clone"),
)
if not self.module.check_mode and self.vars.wait and not get_pacemaker_maintenance_mode(self.runner):
wait_for_resource(self.runner, "resource", self.vars.name, self.vars.wait)
def state_cloned(self):
with self.runner(
"cli_action state name resource_clone_ids resource_clone_meta wait",
"cli_action state name resource_clone_ids resource_clone_meta",
output_process=self._process_command_output(
not get_pacemaker_maintenance_mode(self.runner), "already a clone resource"
),
@ -260,6 +263,8 @@ class PacemakerResource(StateModuleHelper):
cli_action="resource",
resource_clone_meta=self.fmt_as_stack_argument(self.module.params["resource_clone_meta"], "meta"),
)
if not self.module.check_mode and self.vars.wait and not get_pacemaker_maintenance_mode(self.runner):
wait_for_resource(self.runner, "resource", self.vars.name, self.vars.wait)
def state_enabled(self):
with self.runner(

View file

@ -125,7 +125,7 @@ value:
"""
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
from ansible_collections.community.general.plugins.module_utils.pacemaker import pacemaker_runner
from ansible_collections.community.general.plugins.module_utils.pacemaker import pacemaker_runner, wait_for_resource
class PacemakerStonith(StateModuleHelper):
@ -206,7 +206,7 @@ class PacemakerStonith(StateModuleHelper):
def state_present(self):
with self.runner(
"cli_action state name resource_type resource_option resource_operation resource_meta resource_argument agent_validation wait",
"cli_action state name resource_type resource_option resource_operation resource_meta resource_argument agent_validation",
output_process=self._process_command_output(True, "already exists"),
check_mode_skip=True,
) as ctx:
@ -218,6 +218,8 @@ class PacemakerStonith(StateModuleHelper):
resource_meta=self.vars.stonith_metas,
resource_argument=self.vars.stonith_argument,
)
if not self.module.check_mode and self.vars.wait:
wait_for_resource(self.runner, "stonith", self.vars.name, self.vars.wait)
def state_enabled(self):
with self.runner(