diff --git a/pg_backup_api/pg_backup_api/server_operation.py b/pg_backup_api/pg_backup_api/server_operation.py index 443a077..553328e 100644 --- a/pg_backup_api/pg_backup_api/server_operation.py +++ b/pg_backup_api/pg_backup_api/server_operation.py @@ -71,15 +71,17 @@ class OperationNotExists(LookupError): class OperationServer: """ - Contain metadata about a Barman server and logic to handle operations. + Contain logic to handle operations for a Barman instance or Barman server. - :ivar name: name of the Barman server. - :ivar config: Barman configuration of the Barman server. + :ivar name: name of the Barman server, if it's server operation, otherwise + ``None`` for a "global" (instance) operation. + :ivar config: Barman configuration of the Barman server, if it's a server + operation, otherwise ``None`` for a "global" (instance) operation. :ivar jobs_basedir: directory where to save files of operations that have - been created for this Barman server. + been created for this Barman server or instance. :ivar output_basedir: directory where to save files with output of - operations that have been finished for this Barman server -- both for - failed and successful executions. + operations that have been finished for this Barman server or instance + -- both for failed and successful executions. """ # Name of the pg-backup-api ``jobs`` directory. Files created under this @@ -94,27 +96,33 @@ class OperationServer: # Set of required keys when creating an operation output file. _REQUIRED_OUTPUT_KEYS = ("success", "end_time", "output",) - def __init__(self, name: str) -> None: + def __init__(self, name: Optional[str]) -> None: """ Initialize a new instance of :class:`OperationServer`. - Fill all the metadata required by pg-backup-api of a given Barman - server named *name*, if it exists in Barman. Also prepare the Barman - server to execute pg-backup-api operations. + Fill all the metadata required by pg-backup-api for a given Barman + server named *name*, if a Barman server opartion and the server exists + in Barman. Also prepare the Barman server or instance to execute + pg-backup-api operations. - :param name: name of the Barman server. + :param name: name of the Barman server, if it's a Barman server + operation, ``None`` for a "global" (instance) operation. :raises: :exc:`OperationServerConfigError`: if no Barman configuration could - be found for server *name*. + be found for server *name*, in case of a Barman server + operation. """ self.name = name - self.config = get_server_by_name(name) + self.config = None - if not self.config: - raise OperationServerConfigError( - f"No barman config found for '{name}'." - ) + if name: + self.config = get_server_by_name(name) + + if not self.config: + raise OperationServerConfigError( + f"No barman config found for '{name}'." + ) load_barman_config() @@ -123,10 +131,15 @@ def __init__(self, name: str) -> None: barman_home = barman.__config__.barman_home - self.jobs_basedir = join(barman_home, name, self._JOBS_DIR_NAME) - self._create_jobs_dir() + if name: + self.jobs_basedir = join(barman_home, name, self._JOBS_DIR_NAME) + self.output_basedir = join(barman_home, name, + self._OUTPUT_DIR_NAME) + else: + self.jobs_basedir = join(barman_home, self._JOBS_DIR_NAME) + self.output_basedir = join(barman_home, self._OUTPUT_DIR_NAME) - self.output_basedir = join(barman_home, name, self._OUTPUT_DIR_NAME) + self._create_jobs_dir() self._create_output_dir() @staticmethod @@ -151,11 +164,11 @@ def _create_dir(dir_path: str) -> None: os.makedirs(dir_path) def _create_jobs_dir(self) -> None: - """Create the ``jobs`` directory of this Barman server.""" + """Create the ``jobs`` directory of Barman server or instance.""" self._create_dir(self.jobs_basedir) def _create_output_dir(self) -> None: - """Create the ``outputs`` directory of this Barman server.""" + """Create the ``outputs`` directory of Barman server or instance.""" self._create_dir(self.output_basedir) def get_job_file_path(self, op_id: str) -> str: @@ -318,16 +331,16 @@ def read_output_file(self, op_id: str) -> Dict[str, Any]: def get_operations_list(self, op_type: Optional[OperationType] = None) \ -> List[Dict[str, Any]]: """ - Get the list of operations of this Barman server. + Get the list of operations of this Barman server or instance. Fetch operation from all ``.json`` files found under the - :attr:`jobs_basedir` of this server. + :attr:`jobs_basedir` of this server or instance. :param op_type: if ``None`` retrieve all operations. If something other than ``None``, filter by the given type. - :return: list of operations of this Barman server. Each item has the - following keys: + :return: list of operations of this Barman server or instance. Each + item has the following keys: * ``id``: ID of the operation; * ``type``: type of the operation. @@ -393,12 +406,13 @@ class Operation: :ivar id: ID of this operation. """ - def __init__(self, server_name: str, id: Optional[str] = None) -> None: + def __init__(self, server_name: Optional[str], + id: Optional[str] = None) -> None: """ Initialize a new instance of :class:`Operation`. - :param server_name: name of the Barman server, so we can manage this - operation. + :param server_name: name of the Barman server, in case of a Barman + server operation, ``None`` in case of a Barman instance operation. :param id: ID of the operation. Useful when querying an existing operation. Use ``None`` when creating an operation, so this class generates a new ID. @@ -789,7 +803,7 @@ def main(callback: Callable[..., Any], *args: Tuple[Any, ...]) -> int: "get information about jobs without a running REST API.", ) parser.add_argument( - "--server-name", required=True, + "--server-name", help="Name of the Barman server related to the operation.", ) parser.add_argument( diff --git a/pg_backup_api/pg_backup_api/tests/test_server_operation.py b/pg_backup_api/pg_backup_api/tests/test_server_operation.py index 00a0e86..d0f0898 100644 --- a/pg_backup_api/pg_backup_api/tests/test_server_operation.py +++ b/pg_backup_api/pg_backup_api/tests/test_server_operation.py @@ -41,34 +41,44 @@ class TestOperationServer: """Run tests for :class:`OperationServer`.""" - @pytest.fixture + @pytest.fixture(params=[_BARMAN_SERVER, None]) @patch("pg_backup_api.server_operation.get_server_by_name", Mock()) @patch("pg_backup_api.server_operation.load_barman_config", Mock()) @patch.object(OperationServer, "_create_dir", Mock()) - def op_server(self): + def op_server(self, request): """Create a :class:`OperationServer` instance for testing. :return: :class:`OperationServer` instance for testing. """ with patch("barman.__config__") as mock_config: mock_config.barman_home = _BARMAN_HOME - return OperationServer(_BARMAN_SERVER) + return OperationServer(request.param) def test___init__(self, op_server): """Test :meth:`OperationServer.__init__`. Ensure its attributes are set as expected. """ + # Handle the 2 possible fixtures, one for server operations and another + # for instance operations + expected_name = None + expected_jobs = os.path.join(_BARMAN_HOME, "jobs") + expected_output = os.path.join(_BARMAN_HOME, "output") + + if op_server.name is not None: + expected_name = _BARMAN_SERVER + expected_jobs = os.path.join(_BARMAN_HOME, _BARMAN_SERVER, "jobs") + expected_output = os.path.join(_BARMAN_HOME, _BARMAN_SERVER, + "output") + # Ensure name is as expected. - assert op_server.name == _BARMAN_SERVER + assert op_server.name == expected_name # Ensure "jobs" directory is created in expected path. - expected = os.path.join(_BARMAN_HOME, _BARMAN_SERVER, "jobs") - assert op_server.jobs_basedir == expected + assert op_server.jobs_basedir == expected_jobs # Ensure "output" directory is created in the expected path. - expected = os.path.join(_BARMAN_HOME, _BARMAN_SERVER, "output") - assert op_server.output_basedir == expected + assert op_server.output_basedir == expected_output @patch("os.path.isdir") @patch("os.path.exists") @@ -586,14 +596,14 @@ def test_get_operation_status_exception(self, mock_read_job_file, class TestOperation: """Run tests for :class:`Operation`.""" - @pytest.fixture - @patch("pg_backup_api.server_operation.OperationServer", MagicMock()) - def operation(self): + @pytest.fixture(params=[_BARMAN_SERVER, None]) + @patch("pg_backup_api.server_operation.OperationServer") + def operation(self, mock_op_server, request): """Create an :class:`Operation` instance for testing. :return: a new instance of :class:`Operation` for testing. """ - return Operation(_BARMAN_SERVER) + return Operation(request.param) def test___init___auto_id(self, operation): """Test :meth:`Operation.__init__`. @@ -604,7 +614,7 @@ def test___init___auto_id(self, operation): with patch.object(Operation, "_generate_id") as mock_generate_id: mock_generate_id.return_value = id - operation = Operation(_BARMAN_SERVER) + operation = Operation(operation.server.name) assert operation.id == id mock_generate_id.assert_called_once() @@ -616,7 +626,7 @@ def test___init___custom_id(self, operation): id = "CUSTOM_OP_ID" with patch.object(Operation, "_generate_id") as mock_generate_id: - operation = Operation(_BARMAN_SERVER, id) + operation = Operation(operation.server.name, id) assert operation.id == id mock_generate_id.assert_not_called()