Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pcs/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ EXTRA_DIST = \
common/services/drivers/openrc.py \
common/services/drivers/systemd.py \
common/services/drivers/sysvinit_rhel.py \
common/sbd_dto.py \
common/services_dto.py \
common/services/errors.py \
common/services/__init__.py \
Expand Down
8 changes: 1 addition & 7 deletions pcs/cli/routing/stonith.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import pcs.cli.stonith.command as stonith_cli
import pcs.cli.stonith.levels.command as levels_cli
from pcs import (
resource,
stonith,
usage,
)
from pcs import resource, stonith, usage
from pcs.cli.cib.element import command as cib_element_cmd
from pcs.cli.common.routing import create_router

Expand Down Expand Up @@ -79,8 +75,6 @@
{
"list": stonith.sbd_watchdog_list,
"test": stonith.sbd_watchdog_test,
# internal use only
"list_json": stonith.sbd_watchdog_list_json,
},
["stonith", "sbd", "watchdog"],
),
Expand Down
26 changes: 26 additions & 0 deletions pcs/common/sbd_dto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from dataclasses import dataclass
from typing import Optional

from pcs.common.interface.dto import DataTransferObject
from pcs.common.services_dto import ServiceStatusDto


@dataclass(frozen=True)
class SbdWatchdogStatusDto(DataTransferObject):
path: str
exists: bool
is_supported: bool


@dataclass(frozen=True)
class SbdDeviceStatusDto(DataTransferObject):
path: str
exists: bool
is_block_device: bool


@dataclass(frozen=True)
class SbdCheckResultDto(DataTransferObject):
sbd_service: ServiceStatusDto
watchdog: Optional[SbdWatchdogStatusDto] = None
device_list: Optional[list[SbdDeviceStatusDto]] = None
52 changes: 52 additions & 0 deletions pcs/daemon/app/api_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
PermissionMetadataDependenciesDto,
PermissionMetadataDto,
)
from pcs.common.sbd_dto import SbdCheckResultDto
from pcs.common.str_tools import format_list
from pcs.daemon import log
from pcs.daemon.app.api_v0_tools import (
Expand Down Expand Up @@ -699,6 +700,56 @@ async def _handle_request(self) -> None:
raise self._error(reports_to_str(result.reports))


class CheckSbdHandler(_BaseApiV0Handler):
async def _handle_request(self) -> None:
watchdog = self.get_argument("watchdog", "")
device_list_json = self.get_argument("device_list", "[]")
try:
device_list = json.loads(device_list_json)
except json.JSONDecodeError as e:
raise self._error("Invalid input data format") from e
if not isinstance(device_list, list) or not all(
isinstance(device, str) for device in device_list
):
raise self._error("Invalid input data format")

result = await self._run_library_command(
"sbd.check_sbd",
dict(watchdog=watchdog, device_list=device_list),
)
if not result.success:
raise self._error(reports_to_str(result.reports))
# Not using to_dict to keep backward compatibility with older
# pcs versions which don't expect watchdog and device_list keys
# to be present in the response when they have no values. Also the
# response was different than our DTOs so we make them into legacy
# format by hand.
sbd_status = cast(SbdCheckResultDto, result.result)
response: dict[str, Any] = dict(
sbd=dict(
installed=sbd_status.sbd_service.installed,
enabled=sbd_status.sbd_service.enabled,
running=sbd_status.sbd_service.running,
),
)
if sbd_status.watchdog is not None:
response["watchdog"] = dict(
path=sbd_status.watchdog.path,
exist=sbd_status.watchdog.exists,
is_supported=sbd_status.watchdog.is_supported,
)
if sbd_status.device_list is not None:
response["device_list"] = [
dict(
path=device.path,
exist=device.exists,
block_device=device.is_block_device,
)
for device in sbd_status.device_list
]
self.write(json.dumps(response))


class SetCorosyncConf(_BaseApiV0Handler):
async def _handle_request(self) -> None:
self._check_required_params({"corosync_conf"})
Expand Down Expand Up @@ -806,6 +857,7 @@ def r(url: str) -> str:
# cluster config
(r("set_corosync_conf"), SetCorosyncConf, params),
# sbd
(r("check_sbd"), CheckSbdHandler, params),
(r("get_sbd_config"), GetSbdConfigHandler, params),
(r("set_sbd_config"), SetSbdConfigHandler, params),
]
5 changes: 5 additions & 0 deletions pcs/daemon/async_tasks/worker/command_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,10 @@ class _Cmd:
cmd=services.pacemaker_remote_off_local,
required_permission=p.WRITE,
),
"sbd.check_sbd": _Cmd(
cmd=sbd.check_sbd,
required_permission=p.READ,
),
"sbd.disable_sbd": _Cmd(
cmd=sbd.disable_sbd,
required_permission=p.WRITE,
Expand Down Expand Up @@ -570,6 +574,7 @@ class _Cmd:
"resource_agent.list_standards",
# The sbd URLs are ready to be exposed in APIv2, just waiting for all the
# other URLs to get moved to APIv2
"sbd.check_sbd",
"sbd.get_node_sbd_config_text",
"sbd.set_node_sbd_config_text",
# There is a lot of url handlers managing cluster services and they should
Expand Down
62 changes: 58 additions & 4 deletions pcs/lib/commands/sbd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
from pcs import settings
from pcs.common import reports
from pcs.common.node_communicator import RequestTarget
from pcs.common.sbd_dto import (
SbdCheckResultDto,
SbdDeviceStatusDto,
SbdWatchdogStatusDto,
)
from pcs.common.services_dto import ServiceStatusDto
from pcs.common.types import StringSequence
from pcs.common.validate import is_integer
from pcs.lib import (
sbd,
validate,
)
from pcs.lib import sbd, validate
from pcs.lib.cib.tools import get_resources
from pcs.lib.communication.nodes import GetOnlineTargets
from pcs.lib.communication.sbd import (
Expand Down Expand Up @@ -153,6 +156,57 @@ def _get_full_target_dict[T](
}


def check_sbd(
lib_env: LibraryEnvironment,
watchdog: Optional[str] = None,
device_list: Optional[StringSequence] = None,
) -> SbdCheckResultDto:
"""
Check whether sbd is installed, enabled and running.
- If watchdog is specified, also check whether the watchdog exists on the
local node.
- If device_list is specified, also check whether paths specified in the
list exist on the local node and if they are block devices.

watchdog -- watchdog path to check
device_list -- list of paths to check
"""
sbd_status = ServiceStatusDto(
service=settings.sbd_service_name,
installed=lib_env.service_manager.is_installed(
settings.sbd_service_name
),
enabled=lib_env.service_manager.is_enabled(settings.sbd_service_name),
running=lib_env.service_manager.is_running(settings.sbd_service_name),
)

watchdog_dto: Optional[SbdWatchdogStatusDto] = None
if watchdog:
available_watchdogs = sbd.get_available_watchdogs(lib_env.cmd_runner())
exists = watchdog in available_watchdogs
# The support status provided by sbd is unreliable, so we are reporting
# every device as supported for now
# https://bugzilla.redhat.com/show_bug.cgi?id=1578891
# https://github.com/ClusterLabs/pcs/commit/1ca412e151bf531533a525640899fbd109aa
watchdog_dto = SbdWatchdogStatusDto(
path=watchdog,
exists=exists,
is_supported=exists,
)

device_list_dto: Optional[list[SbdDeviceStatusDto]] = None
if device_list:
device_list_dto = [
sbd.check_sbd_device_exists(device) for device in device_list
]

return SbdCheckResultDto(
sbd_service=sbd_status,
watchdog=watchdog_dto,
device_list=device_list_dto,
)


def enable_sbd( # noqa: PLR0913
lib_env: LibraryEnvironment,
default_watchdog: Optional[str],
Expand Down
44 changes: 31 additions & 13 deletions pcs/lib/sbd.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import fcntl
import os
import re
from os import path
from typing import (
Mapping,
Optional,
Union,
)
import stat
from typing import Mapping, Optional, Union

from pcs import settings
from pcs.common import reports
from pcs.common.sbd_dto import SbdDeviceStatusDto
from pcs.common.services.interfaces import ServiceManagerInterface
from pcs.common.types import StringSequence
from pcs.common.validate import is_integer
from pcs.lib import validate
from pcs.lib.corosync.config_facade import ConfigFacade as CorosyncConfFacade
from pcs.lib.errors import LibraryError
from pcs.lib.external import CommandRunner
from pcs.lib.tools import (
dict_to_environment_file,
environment_file_to_dict,
)
from pcs.lib.tools import dict_to_environment_file, environment_file_to_dict

DEVICE_INITIALIZATION_OPTIONS_MAPPING = {
"watchdog-timeout": "-1",
Expand Down Expand Up @@ -212,7 +207,7 @@ def validate_nodes_devices(
reports.messages.SbdDevicePathNotAbsolute(device, node_label)
)
for device in device_list
if not device or not path.isabs(device)
if not device or not os.path.isabs(device)
)
return report_item_list

Expand Down Expand Up @@ -341,11 +336,34 @@ def initialize_block_devices(
)


def check_sbd_device_exists(
device: str,
) -> SbdDeviceStatusDto:
"""
Check whether a path exists on the local node and if it is a block device.

device -- device path to be checked
"""
try:
mode = os.stat(device).st_mode
return SbdDeviceStatusDto(
path=device,
exists=True,
is_block_device=stat.S_ISBLK(mode),
)
except OSError:
return SbdDeviceStatusDto(
path=device,
exists=False,
is_block_device=False,
)


def get_local_sbd_device_list() -> list[str]:
"""
Returns list of devices specified in local SBD config
"""
if not path.exists(settings.sbd_config):
if not os.path.exists(settings.sbd_config):
return []

cfg = environment_file_to_dict(get_local_sbd_config())
Expand Down Expand Up @@ -403,7 +421,7 @@ def _get_local_sbd_watchdog_timeout() -> int:
"""
Return the value of SBD_WATCHDOG_TIMEOUT used in local SBD config
"""
if not path.exists(settings.sbd_config):
if not os.path.exists(settings.sbd_config):
return _DEFAULT_SBD_WATCHDOG_TIMEOUT

cfg = environment_file_to_dict(get_local_sbd_config())
Expand Down
12 changes: 0 additions & 12 deletions pcs/stonith.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,6 @@ def sbd_watchdog_list(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
print_to_stderr("No available watchdog")


def sbd_watchdog_list_json(
lib: Any, argv: Argv, modifiers: InputModifiers
) -> None:
"""
Options: no options
"""
modifiers.ensure_only_supported()
if argv:
raise CmdLineInputError()
print(json.dumps(lib.sbd.get_local_available_watchdogs()))


def sbd_watchdog_test(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
"""
Options:
Expand Down
1 change: 1 addition & 0 deletions pcs_test/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ EXTRA_DIST = \
tier0/lib/commands/resource/test_restart.py \
tier0/lib/commands/resource/test_stop.py \
tier0/lib/commands/sbd/__init__.py \
tier0/lib/commands/sbd/test_check_sbd.py \
tier0/lib/commands/sbd/test_disable_sbd.py \
tier0/lib/commands/sbd/test_enable_sbd.py \
tier0/lib/commands/sbd/test_get_cluster_sbd_config.py \
Expand Down
Loading
Loading