Skip to content

Commit a7194ee

Browse files
committed
Move check_sbd to python from ruby
Assisted-by: ClaudeCode
1 parent 724f8eb commit a7194ee

12 files changed

Lines changed: 540 additions & 94 deletions

File tree

pcs/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ EXTRA_DIST = \
198198
common/services/drivers/openrc.py \
199199
common/services/drivers/systemd.py \
200200
common/services/drivers/sysvinit_rhel.py \
201+
common/sbd_dto.py \
201202
common/services_dto.py \
202203
common/services/errors.py \
203204
common/services/__init__.py \

pcs/cli/routing/stonith.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import pcs.cli.stonith.command as stonith_cli
22
import pcs.cli.stonith.levels.command as levels_cli
3-
from pcs import (
4-
resource,
5-
stonith,
6-
usage,
7-
)
3+
from pcs import resource, stonith, usage
84
from pcs.cli.cib.element import command as cib_element_cmd
95
from pcs.cli.common.routing import create_router
106

@@ -79,8 +75,6 @@
7975
{
8076
"list": stonith.sbd_watchdog_list,
8177
"test": stonith.sbd_watchdog_test,
82-
# internal use only
83-
"list_json": stonith.sbd_watchdog_list_json,
8478
},
8579
["stonith", "sbd", "watchdog"],
8680
),

pcs/common/sbd_dto.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from dataclasses import dataclass
2+
from typing import Optional
3+
4+
from pcs.common.interface.dto import DataTransferObject
5+
from pcs.common.services_dto import ServiceStatusDto
6+
7+
8+
@dataclass(frozen=True)
9+
class SbdWatchdogStatusDto(DataTransferObject):
10+
path: str
11+
exists: bool
12+
is_supported: bool
13+
14+
15+
@dataclass(frozen=True)
16+
class SbdDeviceStatusDto(DataTransferObject):
17+
path: str
18+
exists: bool
19+
is_block_device: bool
20+
21+
22+
@dataclass(frozen=True)
23+
class SbdCheckResultDto(DataTransferObject):
24+
sbd_service: ServiceStatusDto
25+
watchdog: Optional[SbdWatchdogStatusDto] = None
26+
device_list: Optional[list[SbdDeviceStatusDto]] = None

pcs/daemon/app/api_v0.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
PermissionMetadataDependenciesDto,
1717
PermissionMetadataDto,
1818
)
19+
from pcs.common.sbd_dto import SbdCheckResultDto
1920
from pcs.common.str_tools import format_list
2021
from pcs.daemon import log
2122
from pcs.daemon.app.api_v0_tools import (
@@ -699,6 +700,56 @@ async def _handle_request(self) -> None:
699700
raise self._error(reports_to_str(result.reports))
700701

701702

703+
class CheckSbdHandler(_BaseApiV0Handler):
704+
async def _handle_request(self) -> None:
705+
watchdog = self.get_argument("watchdog", "")
706+
device_list_json = self.get_argument("device_list", "[]")
707+
try:
708+
device_list = json.loads(device_list_json)
709+
except json.JSONDecodeError as e:
710+
raise self._error("Invalid input data format") from e
711+
if not isinstance(device_list, list) or not all(
712+
isinstance(device, str) for device in device_list
713+
):
714+
raise self._error("Invalid input data format")
715+
716+
result = await self._run_library_command(
717+
"sbd.check_sbd",
718+
dict(watchdog=watchdog, device_list=device_list),
719+
)
720+
if not result.success:
721+
raise self._error(reports_to_str(result.reports))
722+
# Not using to_dict to keep backward compatibility with older
723+
# pcs versions which don't expect watchdog and device_list keys
724+
# to be present in the response when they have no values. Also the
725+
# response was different than our DTOs so we make them into legacy
726+
# format by hand.
727+
sbd_status = cast(SbdCheckResultDto, result.result)
728+
response: dict[str, Any] = dict(
729+
sbd=dict(
730+
installed=sbd_status.sbd_service.installed,
731+
enabled=sbd_status.sbd_service.enabled,
732+
running=sbd_status.sbd_service.running,
733+
),
734+
)
735+
if sbd_status.watchdog is not None:
736+
response["watchdog"] = dict(
737+
path=sbd_status.watchdog.path,
738+
exist=sbd_status.watchdog.exists,
739+
is_supported=sbd_status.watchdog.is_supported,
740+
)
741+
if sbd_status.device_list is not None:
742+
response["device_list"] = [
743+
dict(
744+
path=device.path,
745+
exist=device.exists,
746+
block_device=device.is_block_device,
747+
)
748+
for device in sbd_status.device_list
749+
]
750+
self.write(json.dumps(response))
751+
752+
702753
class SetCorosyncConf(_BaseApiV0Handler):
703754
async def _handle_request(self) -> None:
704755
self._check_required_params({"corosync_conf"})
@@ -806,6 +857,7 @@ def r(url: str) -> str:
806857
# cluster config
807858
(r("set_corosync_conf"), SetCorosyncConf, params),
808859
# sbd
860+
(r("check_sbd"), CheckSbdHandler, params),
809861
(r("get_sbd_config"), GetSbdConfigHandler, params),
810862
(r("set_sbd_config"), SetSbdConfigHandler, params),
811863
]

pcs/daemon/async_tasks/worker/command_mapping.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,10 @@ class _Cmd:
491491
cmd=services.pacemaker_remote_off_local,
492492
required_permission=p.WRITE,
493493
),
494+
"sbd.check_sbd": _Cmd(
495+
cmd=sbd.check_sbd,
496+
required_permission=p.READ,
497+
),
494498
"sbd.disable_sbd": _Cmd(
495499
cmd=sbd.disable_sbd,
496500
required_permission=p.WRITE,
@@ -570,6 +574,7 @@ class _Cmd:
570574
"resource_agent.list_standards",
571575
# The sbd URLs are ready to be exposed in APIv2, just waiting for all the
572576
# other URLs to get moved to APIv2
577+
"sbd.check_sbd",
573578
"sbd.get_node_sbd_config_text",
574579
"sbd.set_node_sbd_config_text",
575580
# There is a lot of url handlers managing cluster services and they should

pcs/lib/commands/sbd.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
from pcs import settings
44
from pcs.common import reports
55
from pcs.common.node_communicator import RequestTarget
6+
from pcs.common.sbd_dto import (
7+
SbdCheckResultDto,
8+
SbdDeviceStatusDto,
9+
SbdWatchdogStatusDto,
10+
)
11+
from pcs.common.services_dto import ServiceStatusDto
612
from pcs.common.types import StringSequence
713
from pcs.common.validate import is_integer
8-
from pcs.lib import (
9-
sbd,
10-
validate,
11-
)
14+
from pcs.lib import sbd, validate
1215
from pcs.lib.cib.tools import get_resources
1316
from pcs.lib.communication.nodes import GetOnlineTargets
1417
from pcs.lib.communication.sbd import (
@@ -153,6 +156,57 @@ def _get_full_target_dict[T](
153156
}
154157

155158

159+
def check_sbd(
160+
lib_env: LibraryEnvironment,
161+
watchdog: Optional[str] = None,
162+
device_list: Optional[StringSequence] = None,
163+
) -> SbdCheckResultDto:
164+
"""
165+
Check whether sbd is installed, enabled and running.
166+
- If watchdog is specified, also check whether the watchdog exists on the
167+
local node.
168+
- If device_list is specified, also check whether paths specified in the
169+
list exist on the local node and if they are block devices.
170+
171+
watchdog -- watchdog path to check
172+
device_list -- list of paths to check
173+
"""
174+
sbd_status = ServiceStatusDto(
175+
service=settings.sbd_service_name,
176+
installed=lib_env.service_manager.is_installed(
177+
settings.sbd_service_name
178+
),
179+
enabled=lib_env.service_manager.is_enabled(settings.sbd_service_name),
180+
running=lib_env.service_manager.is_running(settings.sbd_service_name),
181+
)
182+
183+
watchdog_dto: Optional[SbdWatchdogStatusDto] = None
184+
if watchdog:
185+
available_watchdogs = sbd.get_available_watchdogs(lib_env.cmd_runner())
186+
exists = watchdog in available_watchdogs
187+
# The support status provided by sbd is unreliable, so we are reporting
188+
# every device as supported for now
189+
# https://bugzilla.redhat.com/show_bug.cgi?id=1578891
190+
# https://github.com/ClusterLabs/pcs/commit/1ca412e151bf531533a525640899fbd109aa
191+
watchdog_dto = SbdWatchdogStatusDto(
192+
path=watchdog,
193+
exists=exists,
194+
is_supported=exists,
195+
)
196+
197+
device_list_dto: Optional[list[SbdDeviceStatusDto]] = None
198+
if device_list:
199+
device_list_dto = [
200+
sbd.check_sbd_device_exists(device) for device in device_list
201+
]
202+
203+
return SbdCheckResultDto(
204+
sbd_service=sbd_status,
205+
watchdog=watchdog_dto,
206+
device_list=device_list_dto,
207+
)
208+
209+
156210
def enable_sbd( # noqa: PLR0913
157211
lib_env: LibraryEnvironment,
158212
default_watchdog: Optional[str],

pcs/lib/sbd.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
import fcntl
2+
import os
23
import re
3-
from os import path
4-
from typing import (
5-
Mapping,
6-
Optional,
7-
Union,
8-
)
4+
import stat
5+
from typing import Mapping, Optional, Union
96

107
from pcs import settings
118
from pcs.common import reports
9+
from pcs.common.sbd_dto import SbdDeviceStatusDto
1210
from pcs.common.services.interfaces import ServiceManagerInterface
1311
from pcs.common.types import StringSequence
1412
from pcs.common.validate import is_integer
1513
from pcs.lib import validate
1614
from pcs.lib.corosync.config_facade import ConfigFacade as CorosyncConfFacade
1715
from pcs.lib.errors import LibraryError
1816
from pcs.lib.external import CommandRunner
19-
from pcs.lib.tools import (
20-
dict_to_environment_file,
21-
environment_file_to_dict,
22-
)
17+
from pcs.lib.tools import dict_to_environment_file, environment_file_to_dict
2318

2419
DEVICE_INITIALIZATION_OPTIONS_MAPPING = {
2520
"watchdog-timeout": "-1",
@@ -212,7 +207,7 @@ def validate_nodes_devices(
212207
reports.messages.SbdDevicePathNotAbsolute(device, node_label)
213208
)
214209
for device in device_list
215-
if not device or not path.isabs(device)
210+
if not device or not os.path.isabs(device)
216211
)
217212
return report_item_list
218213

@@ -341,11 +336,34 @@ def initialize_block_devices(
341336
)
342337

343338

339+
def check_sbd_device_exists(
340+
device: str,
341+
) -> SbdDeviceStatusDto:
342+
"""
343+
Check whether a path exists on the local node and if it is a block device.
344+
345+
device -- device path to be checked
346+
"""
347+
try:
348+
mode = os.stat(device).st_mode
349+
return SbdDeviceStatusDto(
350+
path=device,
351+
exists=True,
352+
is_block_device=stat.S_ISBLK(mode),
353+
)
354+
except OSError:
355+
return SbdDeviceStatusDto(
356+
path=device,
357+
exists=False,
358+
is_block_device=False,
359+
)
360+
361+
344362
def get_local_sbd_device_list() -> list[str]:
345363
"""
346364
Returns list of devices specified in local SBD config
347365
"""
348-
if not path.exists(settings.sbd_config):
366+
if not os.path.exists(settings.sbd_config):
349367
return []
350368

351369
cfg = environment_file_to_dict(get_local_sbd_config())
@@ -403,7 +421,7 @@ def _get_local_sbd_watchdog_timeout() -> int:
403421
"""
404422
Return the value of SBD_WATCHDOG_TIMEOUT used in local SBD config
405423
"""
406-
if not path.exists(settings.sbd_config):
424+
if not os.path.exists(settings.sbd_config):
407425
return _DEFAULT_SBD_WATCHDOG_TIMEOUT
408426

409427
cfg = environment_file_to_dict(get_local_sbd_config())

pcs/stonith.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -380,18 +380,6 @@ def sbd_watchdog_list(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
380380
print_to_stderr("No available watchdog")
381381

382382

383-
def sbd_watchdog_list_json(
384-
lib: Any, argv: Argv, modifiers: InputModifiers
385-
) -> None:
386-
"""
387-
Options: no options
388-
"""
389-
modifiers.ensure_only_supported()
390-
if argv:
391-
raise CmdLineInputError()
392-
print(json.dumps(lib.sbd.get_local_available_watchdogs()))
393-
394-
395383
def sbd_watchdog_test(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
396384
"""
397385
Options:

pcs_test/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ EXTRA_DIST = \
343343
tier0/lib/commands/resource/test_restart.py \
344344
tier0/lib/commands/resource/test_stop.py \
345345
tier0/lib/commands/sbd/__init__.py \
346+
tier0/lib/commands/sbd/test_check_sbd.py \
346347
tier0/lib/commands/sbd/test_disable_sbd.py \
347348
tier0/lib/commands/sbd/test_enable_sbd.py \
348349
tier0/lib/commands/sbd/test_get_cluster_sbd_config.py \

0 commit comments

Comments
 (0)