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 @@ -125,6 +125,7 @@ EXTRA_DIST = \
cli/tag/output.py \
cluster.py \
common/auth.py \
common/booth_dto.py \
common/cluster_dto.py \
common/corosync_conf.py \
common/const.py \
Expand Down
16 changes: 16 additions & 0 deletions pcs/common/booth_dto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from dataclasses import dataclass
from typing import Optional

from pcs.common.interface.dto import DataTransferObject


@dataclass(frozen=True)
class BoothConfigFileDto(DataTransferObject):
name: str
data: str


@dataclass(frozen=True)
class BoothConfigAndAuthfileDto(DataTransferObject):
config: BoothConfigFileDto
authfile: Optional[BoothConfigFileDto]
27 changes: 27 additions & 0 deletions pcs/daemon/app/api_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from pcs import settings
from pcs.common import file_type_codes, reports
from pcs.common.async_tasks.dto import CommandDto, CommandOptionsDto
from pcs.common.booth_dto import BoothConfigAndAuthfileDto
from pcs.common.cluster_dto import ClusterDaemonsInfoDto
from pcs.common.interface.dto import to_dict
from pcs.common.pcs_cfgsync_dto import SyncConfigsDto
from pcs.common.str_tools import format_list
from pcs.daemon import log
Expand Down Expand Up @@ -296,6 +298,29 @@ async def _handle_request(self) -> None:
raise self._error(reports_to_str(result.reports))


class BoothGetConfigHandler(_BaseApiV0Handler):
async def _handle_request(self) -> None:
instance_name = self.get_argument("name", None)
result = await self._run_library_command(
"booth.get_config_and_authfile",
dict(instance_name=instance_name),
)
if not result.success:
raise self._error(reports_to_str(result.reports))

# Here we convert warning to an error. The overall goal was to convert
# the ruby behaviour to python (ruby produced error) and at the same
# time we wanted to use function which had exactly the behaviour that
# we wanted (except it produced warning instead of error).
# This is the compromise.
if any(
rep.message.code == reports.codes.BOOTH_UNSUPPORTED_FILE_LOCATION
for rep in result.reports
):
raise self._error(reports_to_str(result.reports))
self.write(to_dict(cast(BoothConfigAndAuthfileDto, result.result)))


class CheckHostHandler(_BaseApiV0Handler):
async def _handle_request(self) -> None:
result = await self._run_library_command(
Expand Down Expand Up @@ -587,6 +612,8 @@ def r(url: str) -> str:
QdeviceNetSignNodeCertificateHandler,
params,
),
# booth
(r("booth_get_config"), BoothGetConfigHandler, params),
# cfgsync
(r("get_configs"), GetConfigsHandler, params),
(
Expand Down
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 @@ -121,6 +121,10 @@ class _Cmd:
cmd=auth.known_hosts_change,
required_permission=p.FULL,
),
"booth.get_config_and_authfile": _Cmd(
cmd=booth.get_config_and_authfile,
required_permission=p.READ,
),
"booth.ticket_cleanup": _Cmd(
cmd=booth.ticket_cleanup,
required_permission=p.WRITE,
Expand Down Expand Up @@ -525,6 +529,7 @@ class _Cmd:

LEGACY_API_COMMANDS = (
"auth.known_hosts_change",
"booth.get_config",
"cluster.set_permissions",
"manage_clusters.add_cluster",
"manage_clusters.remove_clusters",
Expand Down
101 changes: 64 additions & 37 deletions pcs/lib/commands/booth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,19 @@
from lxml.etree import _Element

from pcs import settings
from pcs.common import (
file_type_codes,
reports,
)
from pcs.common.file import (
FileAlreadyExists,
RawFileError,
from pcs.common import file_type_codes, reports
from pcs.common.booth_dto import (
BoothConfigAndAuthfileDto,
BoothConfigFileDto,
)
from pcs.common.file import FileAlreadyExists, RawFileError
from pcs.common.reports import ReportProcessor
from pcs.common.reports import codes as report_codes
from pcs.common.reports.item import (
ReportItem,
get_severity,
)
from pcs.common.reports.item import ReportItem, get_severity
from pcs.common.services.errors import ManageServiceError
from pcs.common.str_tools import join_multilines
from pcs.common.types import StringSequence
from pcs.lib import (
tools,
validate,
)
from pcs.lib import tools, validate
from pcs.lib.booth import (
config_files,
config_validators,
Expand All @@ -44,35 +36,18 @@
ensure_resources_stopped,
remove_specified_elements,
)
from pcs.lib.cib.resource import (
group,
hierarchy,
primitive,
)
from pcs.lib.cib.tools import (
IdProvider,
get_resources,
)
from pcs.lib.communication.booth import (
BoothGetConfig,
BoothSendConfig,
)
from pcs.lib.cib.resource import group, hierarchy, primitive
from pcs.lib.cib.tools import IdProvider, get_resources
from pcs.lib.communication.booth import BoothGetConfig, BoothSendConfig
from pcs.lib.communication.tools import run_and_raise
from pcs.lib.env import LibraryEnvironment
from pcs.lib.errors import LibraryError
from pcs.lib.external import CommandRunner
from pcs.lib.file.instance import FileInstance
from pcs.lib.file.raw_file import (
GhostFile,
RealFile,
raw_file_error_report,
)
from pcs.lib.file.raw_file import GhostFile, RealFile, raw_file_error_report
from pcs.lib.interface.config import ParserErrorException
from pcs.lib.node import get_existing_nodes_names
from pcs.lib.pacemaker.live import (
has_cib_xml,
resource_restart,
)
from pcs.lib.pacemaker.live import has_cib_xml, resource_restart
from pcs.lib.pacemaker.live import ticket_cleanup as live_ticket_cleanup
from pcs.lib.pacemaker.live import ticket_standby as live_ticket_standby
from pcs.lib.pacemaker.live import ticket_unstandby as live_ticket_unstandby
Expand Down Expand Up @@ -378,6 +353,58 @@ def config_text(
) from e


def get_config_and_authfile(
env: LibraryEnvironment,
instance_name: Optional[str] = None,
) -> BoothConfigAndAuthfileDto:
"""
Read booth config and its authfile and return their content.

instance_name -- booth instance name
"""
report_processor = env.report_processor
booth_env = env.get_booth_env(instance_name)
_ensure_live_booth_env(booth_env)

try:
config_raw_data = booth_env.config.read_raw()
booth_conf = booth_env.config.raw_to_facade(config_raw_data)
except RawFileError as e:
report_processor.report(raw_file_error_report(e))
raise LibraryError() from e
except ParserErrorException as e:
report_processor.report_list(
booth_env.config.parser_exception_to_report_list(e)
)
raise LibraryError() from e

try:
(
authfile_name,
authfile_data,
authfile_report_list,
) = config_files.get_authfile_name_and_data(booth_conf)
except RawFileError as e:
report_processor.report(raw_file_error_report(e))
raise LibraryError() from e
report_processor.report_list(authfile_report_list)

authfile: Optional[BoothConfigFileDto] = None
if authfile_name and authfile_data is not None:
authfile = BoothConfigFileDto(
name=authfile_name,
data=base64.b64encode(authfile_data).decode("utf-8"),
)

return BoothConfigAndAuthfileDto(
config=BoothConfigFileDto(
name=f"{booth_env.instance_name}.conf",
data=config_raw_data.decode("utf-8"),
),
authfile=authfile,
)


def config_ticket_add(
env: LibraryEnvironment,
ticket_name: str,
Expand Down
92 changes: 92 additions & 0 deletions pcs_test/tier0/daemon/app/test_api_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from tornado.util import TimeoutError as TornadoTimeoutError
from tornado.web import Application

from pcs import settings
from pcs.common import file_type_codes, reports
from pcs.common.async_tasks.dto import (
CommandDto,
Expand All @@ -21,6 +22,10 @@
TaskKillReason,
TaskState,
)
from pcs.common.booth_dto import (
BoothConfigAndAuthfileDto,
BoothConfigFileDto,
)
from pcs.common.cluster_dto import (
ClusterComponentVersionDto,
ClusterDaemonsInfoDto,
Expand Down Expand Up @@ -863,6 +868,93 @@ def test_failure(self):
)


class BoothGetConfigHandler(ApiV0HandlerTest):
url = "/remote/booth_get_config"

def test_success(self):
result_dto = BoothConfigAndAuthfileDto(
config=BoothConfigFileDto(name="booth.conf", data="some config"),
authfile=BoothConfigFileDto(name="booth.key", data="base64data"),
)
self.mock_run_library_command.return_value = self.result_success(
result_dto
)
response = self.fetch(self.url)
self.assertEqual(response.code, 200)
self.assert_body(
response.body,
json.dumps(
{
"config": {"name": "booth.conf", "data": "some config"},
"authfile": {"name": "booth.key", "data": "base64data"},
}
),
)
self.mock_run_library_command.assert_called_once_with(
"booth.get_config_and_authfile", dict(instance_name=None)
)

def test_success_with_name(self):
result_dto = BoothConfigAndAuthfileDto(
config=BoothConfigFileDto(name="my_booth.conf", data="some config"),
authfile=None,
)
self.mock_run_library_command.return_value = self.result_success(
result_dto
)
response = self.fetch(self.url, body=urlencode({"name": "my_booth"}))
self.assertEqual(response.code, 200)
self.assert_body(
response.body,
json.dumps(
{
"config": {
"name": "my_booth.conf",
"data": "some config",
},
"authfile": None,
}
),
)
self.mock_run_library_command.assert_called_once_with(
"booth.get_config_and_authfile", dict(instance_name="my_booth")
)

def test_failure(self):
self.assert_error_with_report(self.url)
self.mock_run_library_command.assert_called_once_with(
"booth.get_config_and_authfile", dict(instance_name=None)
)

def test_authfile_not_in_booth_dir(self):
result_dto = BoothConfigAndAuthfileDto(
config=BoothConfigFileDto(name="booth.conf", data="some config"),
authfile=None,
)
self.mock_run_library_command.return_value = self.result_success(
result_dto,
reports=[
reports.ReportItem.warning(
reports.messages.BoothUnsupportedFileLocation(
"/etc/my_booth.key",
settings.booth_config_dir,
file_type_codes.BOOTH_KEY,
)
).to_dto()
],
)
response = self.fetch(self.url)
self.assertEqual(response.code, 400)
self.assert_body(
response.body,
"Warning: Booth key '/etc/my_booth.key' is outside of supported "
f"booth config directory '{settings.booth_config_dir}', ignoring the file",
)
self.mock_run_library_command.assert_called_once_with(
"booth.get_config_and_authfile", dict(instance_name=None)
)


class CheckHostHandler(ApiV0HandlerTest):
url = "/remote/check_host"

Expand Down
Loading
Loading