Files
patroni/tests/test_barman.py
Alexander Kukushkin b470ade20e Change master->primary, take two (#3127)
This commit is a breaking change:
1. `role` in DCS is written as "primary" instead of "master".
2. `role` in REST API responses is also written as "primary".
3. REST API no longer accepts role=master in requests (for example switchover/failover/restart endpoints).
4. `/metrics` REST API endpoint will no longer report `patroni_master`.
5. `patronictl` no longer accepts `--master` argument.
6. `no_master` option in declarative configuration of custom replica creation methods is no longer treated as a special option, please use `no_leader` instead.
7. `patroni_wale_restore` doesn't accept `--no_master` anymore.
8. `patroni_barman` doesn't accept `--role=master` anymore.
9. callback scripts will be executed with role=primary instead of role=master
10. On Kubernetes Patroni by default will set role label to primary. In case if you want to keep old behavior and avoid downtime or lengthy complex migrations you can configure `kubernetes.leader_label_value` and `kubernetes.standby_leader_label_value` to `master`.

However, a few exceptions regarding master are still in place:
1. `GET /master` REST API endpoint will continue to work.
2. `master_start_timeout` and `master_stop_timeout` in global configuration are still accepted.
3. `master` tag is still preserved in Consul services in addition to `primary`.

Rationale for these exceptions: DBA doesn't always 100% control the infrastructure and can't adjust the configuration.
2024-08-28 17:19:00 +02:00

763 lines
30 KiB
Python

import logging
import unittest
from unittest import mock
from unittest.mock import MagicMock, Mock, patch
from urllib3.exceptions import MaxRetryError
from patroni.scripts.barman.cli import main
from patroni.scripts.barman.config_switch import _should_skip_switch, _switch_config, \
ExitCode as BarmanConfigSwitchExitCode, run_barman_config_switch
from patroni.scripts.barman.recover import _restore_backup, ExitCode as BarmanRecoverExitCode, run_barman_recover
from patroni.scripts.barman.utils import ApiNotOk, OperationStatus, PgBackupApi, RetriesExceeded, set_up_logging
API_URL = "http://localhost:7480"
BARMAN_SERVER = "my_server"
BARMAN_MODEL = "my_model"
BACKUP_ID = "backup_id"
SSH_COMMAND = "ssh postgres@localhost"
DATA_DIRECTORY = "/path/to/pgdata"
LOOP_WAIT = 10
RETRY_WAIT = 2
MAX_RETRIES = 5
# stuff from patroni.scripts.barman.utils
@patch("logging.basicConfig")
def test_set_up_logging(mock_log_config):
log_file = "/path/to/some/file.log"
set_up_logging(log_file)
mock_log_config.assert_called_once_with(filename=log_file, level=logging.INFO,
format="%(asctime)s %(levelname)s: %(message)s")
class TestPgBackupApi(unittest.TestCase):
@patch.object(PgBackupApi, "_ensure_api_ok", Mock())
@patch("patroni.scripts.barman.utils.PoolManager", MagicMock())
def setUp(self):
self.api = PgBackupApi(API_URL, None, None, RETRY_WAIT, MAX_RETRIES)
# Reset the mock as the same instance is used across tests
self.api._http.request.reset_mock()
self.api._http.request.side_effect = None
def test__build_full_url(self):
self.assertEqual(self.api._build_full_url("/some/path"), f"{API_URL}/some/path")
@patch("json.loads")
def test__deserialize_response(self, mock_json_loads):
mock_response = MagicMock()
self.assertIsNotNone(self.api._deserialize_response(mock_response))
mock_json_loads.assert_called_once_with(mock_response.data.decode("utf-8"))
@patch("json.dumps")
def test__serialize_request(self, mock_json_dumps):
body = "some_body"
ret = self.api._serialize_request(body)
self.assertIsNotNone(ret)
mock_json_dumps.assert_called_once_with(body)
mock_json_dumps.return_value.encode.assert_called_once_with("utf-8")
@patch.object(PgBackupApi, "_deserialize_response", Mock(return_value="test"))
def test__get_request(self):
mock_request = self.api._http.request
# with no error
self.assertEqual(self.api._get_request("/some/path"), "test")
mock_request.assert_called_once_with("GET", f"{API_URL}/some/path")
# with MaxRetryError
http_error = MaxRetryError(self.api._http, f"{API_URL}/some/path")
mock_request.side_effect = http_error
with self.assertRaises(RetriesExceeded) as exc:
self.assertIsNone(self.api._get_request("/some/path"))
self.assertEqual(
str(exc.exception),
"Failed to perform a GET request to http://localhost:7480/some/path"
)
@patch.object(PgBackupApi, "_deserialize_response", Mock(return_value="test"))
@patch.object(PgBackupApi, "_serialize_request")
def test__post_request(self, mock_serialize):
mock_request = self.api._http.request
# with no error
self.assertEqual(self.api._post_request("/some/path", "some body"), "test")
mock_serialize.assert_called_once_with("some body")
mock_request.assert_called_once_with("POST", f"{API_URL}/some/path", body=mock_serialize.return_value,
headers={"Content-Type": "application/json"})
# with HTTPError
http_error = MaxRetryError(self.api._http, f"{API_URL}/some/path")
mock_request.side_effect = http_error
with self.assertRaises(RetriesExceeded) as exc:
self.assertIsNone(self.api._post_request("/some/path", "some body"))
self.assertEqual(
str(exc.exception),
f"Failed to perform a POST request to http://localhost:7480/some/path with {mock_serialize.return_value}"
)
@patch.object(PgBackupApi, "_get_request")
def test__ensure_api_ok(self, mock_get_request):
# API ok
mock_get_request.return_value = "OK"
self.assertIsNone(self.api._ensure_api_ok())
# API not ok
mock_get_request.return_value = "random"
with self.assertRaises(ApiNotOk) as exc:
self.assertIsNone(self.api._ensure_api_ok())
self.assertEqual(
str(exc.exception),
"pg-backup-api is currently not up and running at http://localhost:7480: random",
)
@patch("patroni.scripts.barman.utils.OperationStatus")
@patch("logging.warning")
@patch("time.sleep")
@patch.object(PgBackupApi, "_get_request")
def test_get_operation_status(self, mock_get_request, mock_sleep, mock_logging, mock_op_status):
# well formed response
mock_get_request.return_value = {"status": "some status"}
mock_op_status.__getitem__.return_value = "SOME_STATUS"
self.assertEqual(self.api.get_operation_status(BARMAN_SERVER, "some_id"), "SOME_STATUS")
mock_get_request.assert_called_once_with(f"servers/{BARMAN_SERVER}/operations/some_id")
mock_sleep.assert_not_called()
mock_logging.assert_not_called()
mock_op_status.__getitem__.assert_called_once_with("some status")
# malformed response
mock_get_request.return_value = {"statuss": "some status"}
with self.assertRaises(RetriesExceeded) as exc:
self.api.get_operation_status(BARMAN_SERVER, "some_id")
self.assertEqual(str(exc.exception),
"Maximum number of retries exceeded for method PgBackupApi.get_operation_status.")
self.assertEqual(mock_sleep.call_count, self.api.max_retries)
mock_sleep.assert_has_calls([mock.call(self.api.retry_wait)] * self.api.max_retries)
self.assertEqual(mock_logging.call_count, self.api.max_retries)
for i in range(mock_logging.call_count):
call_args = mock_logging.call_args_list[i][0]
self.assertEqual(len(call_args), 5)
self.assertEqual(call_args[0], "Attempt %d of %d on method %s failed with %r.")
self.assertEqual(call_args[1], i + 1)
self.assertEqual(call_args[2], self.api.max_retries)
self.assertEqual(call_args[3], "PgBackupApi.get_operation_status")
self.assertIsInstance(call_args[4], KeyError)
self.assertEqual(call_args[4].args, ('status',))
@patch("logging.warning")
@patch("time.sleep")
@patch.object(PgBackupApi, "_post_request")
def test_create_recovery_operation(self, mock_post_request, mock_sleep, mock_logging):
# well formed response
mock_post_request.return_value = {"operation_id": "some_id"}
self.assertEqual(
self.api.create_recovery_operation(BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY),
"some_id",
)
mock_sleep.assert_not_called()
mock_logging.assert_not_called()
mock_post_request.assert_called_once_with(
f"servers/{BARMAN_SERVER}/operations",
{
"type": "recovery",
"backup_id": BACKUP_ID,
"remote_ssh_command": SSH_COMMAND,
"destination_directory": DATA_DIRECTORY,
}
)
# malformed response
mock_post_request.return_value = {"operation_idd": "some_id"}
with self.assertRaises(RetriesExceeded) as exc:
self.api.create_recovery_operation(BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY)
self.assertEqual(str(exc.exception),
"Maximum number of retries exceeded for method PgBackupApi.create_recovery_operation.")
self.assertEqual(mock_sleep.call_count, self.api.max_retries)
mock_sleep.assert_has_calls([mock.call(self.api.retry_wait)] * self.api.max_retries)
self.assertEqual(mock_logging.call_count, self.api.max_retries)
for i in range(mock_logging.call_count):
call_args = mock_logging.call_args_list[i][0]
self.assertEqual(len(call_args), 5)
self.assertEqual(call_args[0], "Attempt %d of %d on method %s failed with %r.")
self.assertEqual(call_args[1], i + 1)
self.assertEqual(call_args[2], self.api.max_retries)
self.assertEqual(call_args[3], "PgBackupApi.create_recovery_operation")
self.assertIsInstance(call_args[4], KeyError)
self.assertEqual(call_args[4].args, ('operation_id',))
@patch("logging.warning")
@patch("time.sleep")
@patch.object(PgBackupApi, "_post_request")
def test_create_config_switch_operation(self, mock_post_request, mock_sleep, mock_logging):
# well formed response -- sample 1
mock_post_request.return_value = {"operation_id": "some_id"}
self.assertEqual(
self.api.create_config_switch_operation(BARMAN_SERVER, BARMAN_MODEL, None),
"some_id",
)
mock_sleep.assert_not_called()
mock_logging.assert_not_called()
mock_post_request.assert_called_once_with(
f"servers/{BARMAN_SERVER}/operations",
{
"type": "config_switch",
"model_name": BARMAN_MODEL,
}
)
# well formed response -- sample 2
mock_post_request.reset_mock()
self.assertEqual(
self.api.create_config_switch_operation(BARMAN_SERVER, None, True),
"some_id",
)
mock_sleep.assert_not_called()
mock_logging.assert_not_called()
mock_post_request.assert_called_once_with(
f"servers/{BARMAN_SERVER}/operations",
{
"type": "config_switch",
"reset": True,
}
)
# malformed response
mock_post_request.return_value = {"operation_idd": "some_id"}
with self.assertRaises(RetriesExceeded) as exc:
self.api.create_config_switch_operation(BARMAN_SERVER, BARMAN_MODEL, None)
self.assertEqual(str(exc.exception),
"Maximum number of retries exceeded for method PgBackupApi.create_config_switch_operation.")
self.assertEqual(mock_sleep.call_count, self.api.max_retries)
mock_sleep.assert_has_calls([mock.call(self.api.retry_wait)] * self.api.max_retries)
self.assertEqual(mock_logging.call_count, self.api.max_retries)
for i in range(mock_logging.call_count):
call_args = mock_logging.call_args_list[i][0]
self.assertEqual(len(call_args), 5)
self.assertEqual(call_args[0], "Attempt %d of %d on method %s failed with %r.")
self.assertEqual(call_args[1], i + 1)
self.assertEqual(call_args[2], self.api.max_retries)
self.assertEqual(call_args[3], "PgBackupApi.create_config_switch_operation")
self.assertIsInstance(call_args[4], KeyError)
self.assertEqual(call_args[4].args, ('operation_id',))
# stuff from patroni.scripts.barman.recover
class TestBarmanRecover(unittest.TestCase):
def setUp(self):
self.api = MagicMock()
# Reset the mock as the same instance is used across tests
self.api._http.request.reset_mock()
self.api._http.request.side_effect = None
@patch("time.sleep")
@patch("logging.info")
@patch("logging.error")
def test__restore_backup(self, mock_log_error, mock_log_info, mock_sleep):
mock_create_op = self.api.create_recovery_operation
mock_get_status = self.api.get_operation_status
# successful fast restore
mock_create_op.return_value = "some_id"
mock_get_status.return_value = OperationStatus.DONE
self.assertEqual(
_restore_backup(self.api, BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY, LOOP_WAIT),
BarmanRecoverExitCode.RECOVERY_DONE,
)
mock_create_op.assert_called_once_with(BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY)
mock_get_status.assert_called_once_with(BARMAN_SERVER, "some_id")
mock_log_info.assert_has_calls([
mock.call("Created the recovery operation with ID %s", "some_id"),
mock.call("Recovery operation finished successfully."),
])
mock_log_error.assert_not_called()
mock_sleep.assert_not_called()
# successful slow restore
mock_create_op.reset_mock()
mock_get_status.reset_mock()
mock_log_info.reset_mock()
mock_get_status.side_effect = [OperationStatus.IN_PROGRESS] * 20 + [OperationStatus.DONE]
self.assertEqual(
_restore_backup(self.api, BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY, LOOP_WAIT),
BarmanRecoverExitCode.RECOVERY_DONE,
)
mock_create_op.assert_called_once()
self.assertEqual(mock_get_status.call_count, 21)
mock_get_status.assert_has_calls([mock.call(BARMAN_SERVER, "some_id")] * 21)
self.assertEqual(mock_log_info.call_count, 22)
mock_log_info.assert_has_calls([mock.call("Created the recovery operation with ID %s", "some_id")]
+ [mock.call("Recovery operation %s is still in progress", "some_id")] * 20
+ [mock.call("Recovery operation finished successfully.")])
mock_log_error.assert_not_called()
self.assertEqual(mock_sleep.call_count, 20)
mock_sleep.assert_has_calls([mock.call(LOOP_WAIT)] * 20)
# failed fast restore
mock_create_op.reset_mock()
mock_get_status.reset_mock()
mock_log_info.reset_mock()
mock_sleep.reset_mock()
mock_get_status.side_effect = None
mock_get_status.return_value = OperationStatus.FAILED
self.assertEqual(
_restore_backup(self.api, BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY, LOOP_WAIT),
BarmanRecoverExitCode.RECOVERY_FAILED,
)
mock_create_op.assert_called_once()
mock_get_status.assert_called_once_with(BARMAN_SERVER, "some_id")
mock_log_info.assert_has_calls([
mock.call("Created the recovery operation with ID %s", "some_id"),
])
mock_log_error.assert_has_calls([
mock.call("Recovery operation failed."),
])
mock_sleep.assert_not_called()
# failed slow restore
mock_create_op.reset_mock()
mock_get_status.reset_mock()
mock_log_info.reset_mock()
mock_log_error.reset_mock()
mock_sleep.reset_mock()
mock_get_status.side_effect = [OperationStatus.IN_PROGRESS] * 20 + [OperationStatus.FAILED]
self.assertEqual(
_restore_backup(self.api, BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY, LOOP_WAIT),
BarmanRecoverExitCode.RECOVERY_FAILED,
)
mock_create_op.assert_called_once()
self.assertEqual(mock_get_status.call_count, 21)
mock_get_status.assert_has_calls([mock.call(BARMAN_SERVER, "some_id")] * 21)
self.assertEqual(mock_log_info.call_count, 21)
mock_log_info.assert_has_calls([mock.call("Created the recovery operation with ID %s", "some_id")]
+ [mock.call("Recovery operation %s is still in progress", "some_id")] * 20)
mock_log_error.assert_has_calls([
mock.call("Recovery operation failed."),
])
self.assertEqual(mock_sleep.call_count, 20)
mock_sleep.assert_has_calls([mock.call(LOOP_WAIT)] * 20)
# create retries exceeded
mock_log_info.reset_mock()
mock_log_error.reset_mock()
mock_sleep.reset_mock()
mock_create_op.side_effect = RetriesExceeded()
mock_get_status.side_effect = None
self.assertEqual(
_restore_backup(self.api, BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY, LOOP_WAIT),
BarmanRecoverExitCode.HTTP_ERROR,
)
mock_log_info.assert_not_called()
mock_log_error.assert_called_once_with("An issue was faced while trying to create a recovery operation: %r",
mock_create_op.side_effect)
mock_sleep.assert_not_called()
# get status retries exceeded
mock_create_op.reset_mock()
mock_create_op.side_effect = None
mock_log_error.reset_mock()
mock_log_info.reset_mock()
mock_get_status.side_effect = RetriesExceeded
self.assertEqual(
_restore_backup(self.api, BARMAN_SERVER, BACKUP_ID, SSH_COMMAND, DATA_DIRECTORY, LOOP_WAIT),
BarmanRecoverExitCode.HTTP_ERROR,
)
mock_log_info.assert_called_once_with("Created the recovery operation with ID %s", "some_id")
mock_log_error.assert_called_once_with("Maximum number of retries exceeded, exiting.")
mock_sleep.assert_not_called()
class TestBarmanRecoverCli(unittest.TestCase):
@patch("patroni.scripts.barman.recover._restore_backup")
def test_run_barman_recover(self, mock_rb):
api = MagicMock()
args = MagicMock()
# successful execution
mock_rb.return_value = BarmanRecoverExitCode.RECOVERY_DONE
self.assertEqual(
run_barman_recover(api, args),
BarmanRecoverExitCode.RECOVERY_DONE,
)
mock_rb.assert_called_once_with(api, args.barman_server, args.backup_id,
args.ssh_command, args.data_directory,
args.loop_wait)
# failed execution
mock_rb.reset_mock()
mock_rb.return_value = BarmanRecoverExitCode.RECOVERY_FAILED
self.assertEqual(
run_barman_recover(api, args),
BarmanRecoverExitCode.RECOVERY_FAILED,
)
mock_rb.assert_called_once_with(api, args.barman_server, args.backup_id,
args.ssh_command, args.data_directory,
args.loop_wait)
# stuff from patroni.scripts.barman.config_switch
class TestBarmanConfigSwitch(unittest.TestCase):
def setUp(self):
self.api = MagicMock()
# Reset the mock as the same instance is used across tests
self.api._http.request.reset_mock()
self.api._http.request.side_effect = None
@patch("time.sleep")
@patch("logging.info")
@patch("logging.error")
def test__switch_config(self, mock_log_error, mock_log_info, mock_sleep):
mock_create_op = self.api.create_config_switch_operation
mock_get_status = self.api.get_operation_status
# successful fast config-switch
mock_create_op.return_value = "some_id"
mock_get_status.return_value = OperationStatus.DONE
self.assertEqual(
_switch_config(self.api, BARMAN_SERVER, BARMAN_MODEL, None),
BarmanConfigSwitchExitCode.CONFIG_SWITCH_DONE,
)
mock_create_op.assert_called_once_with(BARMAN_SERVER, BARMAN_MODEL, None)
mock_get_status.assert_called_once_with(BARMAN_SERVER, "some_id")
mock_log_info.assert_has_calls([
mock.call("Created the config switch operation with ID %s", "some_id"),
mock.call("Config switch operation finished successfully."),
])
mock_log_error.assert_not_called()
mock_sleep.assert_not_called()
# successful slow config-switch
mock_create_op.reset_mock()
mock_get_status.reset_mock()
mock_log_info.reset_mock()
mock_get_status.side_effect = [OperationStatus.IN_PROGRESS] * 20 + [OperationStatus.DONE]
self.assertEqual(
_switch_config(self.api, BARMAN_SERVER, BARMAN_MODEL, None),
BarmanConfigSwitchExitCode.CONFIG_SWITCH_DONE,
)
mock_create_op.assert_called_once_with(BARMAN_SERVER, BARMAN_MODEL, None)
self.assertEqual(mock_get_status.call_count, 21)
mock_get_status.assert_has_calls([mock.call(BARMAN_SERVER, "some_id")] * 21)
self.assertEqual(mock_log_info.call_count, 22)
mock_log_info.assert_has_calls([mock.call("Created the config switch operation with ID %s", "some_id")]
+ [mock.call("Config switch operation %s is still in progress", "some_id")] * 20
+ [mock.call("Config switch operation finished successfully.")])
mock_log_error.assert_not_called()
self.assertEqual(mock_sleep.call_count, 20)
mock_sleep.assert_has_calls([mock.call(5)] * 20)
# failed fast config-switch
mock_create_op.reset_mock()
mock_get_status.reset_mock()
mock_log_info.reset_mock()
mock_sleep.reset_mock()
mock_get_status.side_effect = None
mock_get_status.return_value = OperationStatus.FAILED
self.assertEqual(
_switch_config(self.api, BARMAN_SERVER, BARMAN_MODEL, None),
BarmanConfigSwitchExitCode.CONFIG_SWITCH_FAILED,
)
mock_create_op.assert_called_once()
mock_get_status.assert_called_once_with(BARMAN_SERVER, "some_id")
mock_log_info.assert_called_once_with("Created the config switch operation with ID %s", "some_id")
mock_log_error.assert_called_once_with("Config switch operation failed.")
mock_sleep.assert_not_called()
# failed slow config-switch
mock_create_op.reset_mock()
mock_get_status.reset_mock()
mock_log_info.reset_mock()
mock_log_error.reset_mock()
mock_sleep.reset_mock()
mock_get_status.side_effect = [OperationStatus.IN_PROGRESS] * 20 + [OperationStatus.FAILED]
self.assertEqual(
_switch_config(self.api, BARMAN_SERVER, BARMAN_MODEL, None),
BarmanConfigSwitchExitCode.CONFIG_SWITCH_FAILED,
)
mock_create_op.assert_called_once()
self.assertEqual(mock_get_status.call_count, 21)
mock_get_status.assert_has_calls([mock.call(BARMAN_SERVER, "some_id")] * 21)
self.assertEqual(mock_log_info.call_count, 21)
mock_log_info.assert_has_calls([mock.call("Created the config switch operation with ID %s", "some_id")]
+ [mock.call("Config switch operation %s is still in progress", "some_id")] * 20)
mock_log_error.assert_called_once_with("Config switch operation failed.")
self.assertEqual(mock_sleep.call_count, 20)
mock_sleep.assert_has_calls([mock.call(5)] * 20)
# create retries exceeded
mock_log_info.reset_mock()
mock_log_error.reset_mock()
mock_sleep.reset_mock()
mock_create_op.side_effect = RetriesExceeded()
mock_get_status.side_effect = None
self.assertEqual(
_switch_config(self.api, BARMAN_SERVER, BARMAN_MODEL, None),
BarmanConfigSwitchExitCode.HTTP_ERROR,
)
mock_log_info.assert_not_called()
mock_log_error.assert_called_once_with("An issue was faced while trying to create a config switch operation: "
"%r",
mock_create_op.side_effect)
mock_sleep.assert_not_called()
# get status retries exceeded
mock_create_op.reset_mock()
mock_create_op.side_effect = None
mock_log_error.reset_mock()
mock_get_status.side_effect = RetriesExceeded
self.assertEqual(
_switch_config(self.api, BARMAN_SERVER, BARMAN_MODEL, None),
BarmanConfigSwitchExitCode.HTTP_ERROR,
)
mock_log_info.assert_called_once_with("Created the config switch operation with ID %s", "some_id")
mock_log_error.assert_called_once_with("Maximum number of retries exceeded, exiting.")
mock_sleep.assert_not_called()
class TestBarmanConfigSwitchCli(unittest.TestCase):
def test__should_skip_switch(self):
args = MagicMock()
for role, switch_when, expected in [
("primary", "promoted", False),
("primary", "demoted", True),
("primary", "always", False),
("promoted", "promoted", False),
("promoted", "demoted", True),
("promoted", "always", False),
("standby_leader", "promoted", True),
("standby_leader", "demoted", True),
("standby_leader", "always", False),
("replica", "promoted", True),
("replica", "demoted", False),
("replica", "always", False),
("demoted", "promoted", True),
("demoted", "demoted", False),
("demoted", "always", False),
]:
args.role = role
args.switch_when = switch_when
self.assertEqual(_should_skip_switch(args), expected)
@patch("patroni.scripts.barman.config_switch._should_skip_switch")
@patch("patroni.scripts.barman.config_switch._switch_config")
@patch("logging.error")
@patch("logging.info")
def test_run_barman_config_switch(self, mock_log_info, mock_log_error, mock_sc, mock_skip):
api = MagicMock()
args = MagicMock()
args.reset = None
# successful execution
mock_skip.return_value = False
mock_sc.return_value = BarmanConfigSwitchExitCode.CONFIG_SWITCH_DONE
self.assertEqual(
run_barman_config_switch(api, args),
BarmanConfigSwitchExitCode.CONFIG_SWITCH_DONE,
)
mock_sc.assert_called_once_with(api, args.barman_server, args.barman_model,
args.reset)
# failed execution
mock_sc.reset_mock()
mock_sc.return_value = BarmanConfigSwitchExitCode.CONFIG_SWITCH_FAILED
self.assertEqual(
run_barman_config_switch(api, args),
BarmanConfigSwitchExitCode.CONFIG_SWITCH_FAILED,
)
mock_sc.assert_called_once_with(api, args.barman_server, args.barman_model,
args.reset)
# skipped execution
mock_sc.reset_mock()
mock_skip.return_value = True
self.assertEqual(
run_barman_config_switch(api, args),
BarmanConfigSwitchExitCode.CONFIG_SWITCH_SKIPPED
)
mock_sc.assert_not_called()
mock_log_info.assert_called_once_with("Config switch operation was skipped (role=%s, "
"switch_when=%s).", args.role, args.switch_when)
mock_log_error.assert_not_called()
# invalid args -- sample 1
mock_skip.return_value = False
args = MagicMock()
args.barman_server = BARMAN_SERVER
args.barman_model = BARMAN_MODEL
args.reset = True
self.assertEqual(
run_barman_config_switch(api, args),
BarmanConfigSwitchExitCode.INVALID_ARGS,
)
mock_log_error.assert_called_once_with("One, and only one among 'barman_model' ('%s') and 'reset' "
"('%s') should be given", BARMAN_MODEL, True)
api.assert_not_called()
# invalid args -- sample 2
args = MagicMock()
args.barman_server = BARMAN_SERVER
args.barman_model = None
args.reset = None
mock_log_error.reset_mock()
api.reset_mock()
self.assertEqual(
run_barman_config_switch(api, args),
BarmanConfigSwitchExitCode.INVALID_ARGS,
)
mock_log_error.assert_called_once_with("One, and only one among 'barman_model' ('%s') and 'reset' "
"('%s') should be given", None, None)
api.assert_not_called()
# stuff from patroni.scripts.barman.cli
class TestMain(unittest.TestCase):
@patch("patroni.scripts.barman.cli.PgBackupApi")
@patch("patroni.scripts.barman.cli.set_up_logging")
@patch("patroni.scripts.barman.cli.ArgumentParser")
def test_main(self, mock_arg_parse, mock_set_up_log, mock_api):
# sub-command specified
args = MagicMock()
args.func.return_value = 0
mock_arg_parse.return_value.parse_known_args.return_value = (args, None)
with self.assertRaises(SystemExit) as exc:
main()
mock_arg_parse.assert_called_once()
mock_set_up_log.assert_called_once_with(args.log_file)
mock_api.assert_called_once_with(args.api_url, args.cert_file,
args.key_file, args.retry_wait,
args.max_retries)
mock_arg_parse.return_value.print_help.assert_not_called()
args.func.assert_called_once_with(mock_api.return_value, args)
self.assertEqual(exc.exception.code, 0)
# Issue in the API
mock_arg_parse.reset_mock()
mock_set_up_log.reset_mock()
mock_api.reset_mock()
mock_api.side_effect = ApiNotOk()
with self.assertRaises(SystemExit) as exc:
main()
mock_arg_parse.assert_called_once()
mock_set_up_log.assert_called_once_with(args.log_file)
mock_api.assert_called_once_with(args.api_url, args.cert_file,
args.key_file, args.retry_wait,
args.max_retries)
mock_arg_parse.return_value.print_help.assert_not_called()
self.assertEqual(exc.exception.code, -2)
# sub-command not specified
mock_arg_parse.reset_mock()
mock_set_up_log.reset_mock()
mock_api.reset_mock()
delattr(args, "func")
mock_api.side_effect = None
with self.assertRaises(SystemExit) as exc:
main()
mock_arg_parse.assert_called_once()
mock_set_up_log.assert_called_once_with(args.log_file)
mock_api.assert_not_called()
mock_arg_parse.return_value.print_help.assert_called_once_with()
self.assertEqual(exc.exception.code, -1)