Skip to content

Commit

Permalink
Implement pg-backup-api config-model operation
Browse files Browse the repository at this point in the history
This commit adds the class `ConfigModelOperation` and the command
`pg-backup-api config-model`.

We still need to define the final implementation of the command
in Barman (`barman config-model`), so we can back here and update
this branch with changes in the code and unit tests.

References: BAR-126.

Signed-off-by: Israel Barth Rubio <[email protected]>
  • Loading branch information
barthisrael committed Jan 10, 2024
1 parent 3b5af28 commit 1cfc093
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 4 deletions.
12 changes: 11 additions & 1 deletion pg_backup_api/pg_backup_api/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import sys

from pg_backup_api.run import (serve, status, recovery_operation,
config_switch_operation)
config_switch_operation, config_model_operation)


def main() -> None:
Expand Down Expand Up @@ -82,6 +82,16 @@ def main() -> None:
help="ID of the operation in the 'pg-backup-api'.")
p_ops.set_defaults(func=config_switch_operation)

p_ops = subparsers.add_parser(
"config-model",
description="Perform a 'barman config-model' through the "
"'pg-backup-api'. Can only be run if a config model "
"operation has been previously registered."
)
p_ops.add_argument("--operation-id", required=True,
help="ID of the operation in the 'pg-backup-api'.")
p_ops.set_defaults(func=config_model_operation)

args = p.parse_args()
if hasattr(args, "func") is False:
p.print_help()
Expand Down
32 changes: 30 additions & 2 deletions pg_backup_api/pg_backup_api/logic/utility_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
DEFAULT_OP_TYPE,
RecoveryOperation,
ConfigSwitchOperation,
ConfigModelOperation,
MalformedContent)

if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -322,7 +323,13 @@ def instance_operations_post(request: 'Request') -> Dict[str, str]:
Should contain a JSON body with a key ``type``, which identifies the
type of the operation. The rest of the content depends on the type of
operation being requested.
operation being requested:
* ``config_model``:
* ``todo_to``: to value;
* ``todo_be``: be value;
* ``todo_defined``: defined value.
:return: if the JSON body informed through the ``POST`` request is valid,
return a JSON response containing a key ``operation_id`` with the ID of
Expand All @@ -340,7 +347,28 @@ def instance_operations_post(request: 'Request') -> Dict[str, str]:
msg_400 = "Minimum barman options not met for instance operation"
abort(400, description=msg_400)

return {"operation_id": "DUMMY"}
operation = None
cmd = None
op_type = OperationType(request_body.get("type"))

if op_type == OperationType.CONFIG_MODEL:
operation = ConfigModelOperation()
cmd = "pg-backup-api config-model"

if TYPE_CHECKING: # pragma: no cover
assert isinstance(operation, Operation)
assert isinstance(cmd, str)

try:
operation.write_job_file(request_body)
except MalformedContent:
msg_400 = "Make sure all options/arguments are met and try again"
abort(400, description=msg_400)

cmd += f" --operation-id {operation.id}"
subprocess.Popen(cmd.split())

return {"operation_id": operation.id}


@app.route("/operations", methods=("GET", "POST"))
Expand Down
21 changes: 20 additions & 1 deletion pg_backup_api/pg_backup_api/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@

from pg_backup_api.utils import create_app, load_barman_config
from pg_backup_api.server_operation import (RecoveryOperation,
ConfigSwitchOperation)
ConfigSwitchOperation,
ConfigModelOperation)


if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -153,3 +154,21 @@ def config_switch_operation(args: 'argparse.Namespace') -> Tuple[None, bool]:
"""
return _run_operation(ConfigSwitchOperation(args.server_name,
args.operation_id))


def config_model_operation(args: 'argparse.Namespace') -> Tuple[None, bool]:
"""
Perform a ``barman config-model`` through the pg-backup-api.
.. note::
See :func:`_run_operation` for more details.
:param args: command-line arguments for ``pg-backup-api config-model``
command. Contains the operation ID to be run.
:return: a tuple consisting of two items:
* ``None`` -- output of :meth:`ConfigModelOperation.write_output_file`
* ``True`` if ``barman config-model`` was successful, ``False``
otherwise.
"""
return _run_operation(ConfigModelOperation(args.operation_id))
98 changes: 98 additions & 0 deletions pg_backup_api/pg_backup_api/server_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class OperationType(Enum):
"""Describe operations that can be performed through pg-backup-api."""
RECOVERY = "recovery"
CONFIG_SWITCH = "config_switch"
CONFIG_MODEL = "config_model"


DEFAULT_OP_TYPE = OperationType.RECOVERY
Expand Down Expand Up @@ -773,6 +774,103 @@ def _run_logic(self) -> \
return self._run_subprocess(cmd)


class ConfigModelOperation(Operation):
"""
Contain information and logic to process a config model operation.
:cvar POSSIBLE_ARGUMENTS: possible arguments when creating a config model
operation.
:cvar TYPE: enum type of this operation.
"""

POSSIBLE_ARGUMENTS = ("todo_to", "todo_be", "todo_defined",)
TYPE = OperationType.CONFIG_MODEL

@classmethod
def _validate_job_content(cls, content: Dict[str, Any]) -> None:
"""
Validate the content of the job file before creating it.
:param content: Python dictionary representing the JSON content of the
job file.
:raises:
:exc:`MalformedContent`: if the set of options in *content* is not
compliant with the supported options and how to use them.
"""
for key, type_ in [
("todo_to", str,),
("todo_be", str,),
("todo_defined", str),
]:
if key in content and not isinstance(content[key], type_):
msg = (
f"`{key}` is expected to be a `{type_}`, but a "
f"`{type(content[key])}` was found instead: "
f"`{content[key]}`."
)
raise MalformedContent(msg)

def write_job_file(self, content: Dict[str, Any]) -> None:
"""
Write the job file with *content*.
.. note::
See :meth:`Operation.write_job_file` for more details.
:param content: Python dictionary representing the JSON content of the
job file. Besides what is contained in *content*, this method adds
the following keys:
* ``operation_type``: ``config_model``;
* ``start_time``: current timestamp.
"""
content["operation_type"] = self.TYPE.value
content["start_time"] = self.time_event_now()
self._validate_job_content(content)
super().write_job_file(content)

def _get_args(self) -> List[str]:
"""
Get arguments for running ``barman config-model`` command.
:return: list of arguments for ``barman config-model`` command.
"""
job_content = self.read_job_file()

todo_to = job_content.get("todo_to")
todo_be = job_content.get("todo_be")
todo_defined = job_content.get("todo_defined")

if TYPE_CHECKING: # pragma: no cover
assert isinstance(todo_to, str)
assert isinstance(todo_be, str)
assert isinstance(todo_defined, str)

return [
todo_to,
todo_be,
todo_defined,
]

def _run_logic(self) -> \
Tuple[Union[str, bytearray, memoryview], Union[int, Any]]:
"""
Logic to be ran when executing the config model operation.
Run ``barman config-model`` command with the configured arguments.
Will be called when running :meth:`Operation.run`.
:return: a tuple consisting of:
* ``stdout``/``stderr`` of ``barman config-model``;
* exit code of ``barman config-model``.
"""
cmd = ["barman", "config-model"] + self._get_args()
return self._run_subprocess(cmd)


def main(callback: Callable[..., Any], *args: Tuple[Any, ...]) -> int:
"""
Execute *callback* with *args* and log its output as an ``INFO`` message.
Expand Down

0 comments on commit 1cfc093

Please sign in to comment.