From 5be976971d5da9541c907d0f64c447739a7ca1ba Mon Sep 17 00:00:00 2001 From: Claudiu Belu Date: Thu, 27 Jun 2024 08:04:12 +0000 Subject: [PATCH 01/51] tests: Adds mock patch autospec fixture When mocking something, there's the possibility to reference a property that does not exist, or call a method with invalid arguments, but the unit tests to keep passing, resulting in false positives. We've seen this issue several times in OpenStack, and we have created and started using a mock patch fixture (can be seen in OpenStack Nova's test.py) which enables autospec by default. This guarantees 2 things: that the thing we're patching exists, and if it's callable, that the calls respect the callable's signature (e.g.: no unknown arguments can be used). --- coriolisclient/tests/cli/test_diagnostics.py | 3 ++- coriolisclient/tests/test_base.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/coriolisclient/tests/cli/test_diagnostics.py b/coriolisclient/tests/cli/test_diagnostics.py index 46d26c1..ba79c12 100644 --- a/coriolisclient/tests/cli/test_diagnostics.py +++ b/coriolisclient/tests/cli/test_diagnostics.py @@ -60,7 +60,8 @@ class GetCoriolisDiagnosticsTestCase(test_base.CoriolisBaseTestCase): def setUp(self, mock__init__): mock__init__.return_value = None super(GetCoriolisDiagnosticsTestCase, self).setUp() - self.diag = diagnostics.GetCoriolisDiagnostics() + self.diag = diagnostics.GetCoriolisDiagnostics( + mock.sentinel.app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') def test_get_parser(self, mock_get_parser): diff --git a/coriolisclient/tests/test_base.py b/coriolisclient/tests/test_base.py index af448cc..7c8c9b1 100644 --- a/coriolisclient/tests/test_base.py +++ b/coriolisclient/tests/test_base.py @@ -4,6 +4,13 @@ """Defines base class for all tests.""" from oslotest import base +from oslotest import mock_fixture + +# NOTE(claudiub): this needs to be called before any mock.patch calls are +# being done, and especially before any other test classes load. This fixes +# the mock.patch autospec issue: +# https://github.com/testing-cabal/mock/issues/396 +mock_fixture.patch_mock_module() class CoriolisBaseTestCase(base.BaseTestCase): From 6cfe1b06f56c266f5e150282a358ab6de917c12c Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 21 Nov 2024 17:16:42 +0200 Subject: [PATCH 02/51] Add unit tests for `coriolisclient.cli.formatter` module --- coriolisclient/tests/cli/test_formatter.py | 158 +++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 coriolisclient/tests/cli/test_formatter.py diff --git a/coriolisclient/tests/cli/test_formatter.py b/coriolisclient/tests/cli/test_formatter.py new file mode 100644 index 0000000..6b9830d --- /dev/null +++ b/coriolisclient/tests/cli/test_formatter.py @@ -0,0 +1,158 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +from unittest import mock + +from coriolisclient.cli import formatter +from coriolisclient.tests import test_base + + +class TestEntityFormattter(formatter.EntityFormatter): + + def __init__(self): + self.columns = [ + "column_1", + "column_2" + ] + + def _get_formatted_data(self, obj): + return obj + + +@ddt.ddt +class TestEntityFormattterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Entity Formatter.""" + + def setUp(self): + super(TestEntityFormattterTestCase, self).setUp() + self.format = TestEntityFormattter() + + def test_get_sorted_list(self): + obj_list = ["obj2", "obj1"] + + result = self.format._get_sorted_list(obj_list) + + self.assertEqual( + obj_list, + result + ) + + def test_list_objects(self): + obj_list = ["obj2", "obj1"] + + (columns, data) = self.format.list_objects(obj_list) + + self.assertEqual( + ( + self.format.columns, + ["obj2", "obj1"] + ), + ( + columns, + list(data) + ) + ) + + def test_get_generic_data(self): + obj = mock.Mock() + + result = self.format._get_generic_data(obj) + + self.assertEqual( + obj, + result + ) + + def test_get_generic_columns(self): + result = self.format._get_generic_columns() + + self.assertEqual( + self.format.columns, + result + ) + + def test_get_formatted_entity(self): + obj = mock.Mock() + + (columns, data) = self.format.get_formatted_entity(obj) + + self.assertEqual( + (self.format.columns, obj), + (columns, data) + ) + + @ddt.data( + { + "current_value": 3, + "max_value": 9, + "percent_format": "{:.0f}%", + "expected_result": "33%" + }, + { + "current_value": 3, + "max_value": 9, + "percent_format": "{:.2f}%", + "expected_result": "33.33%" + }, + { + "current_value": 0, + "max_value": 9, + "percent_format": "{:.0f}%", + "expected_result": None + }, + { + "current_value": 3, + "max_value": None, + "percent_format": "{:.0f}%", + "expected_result": None + } + ) + def test_get_percent_string(self, data): + result = self.format._get_percent_string( + data["current_value"], + data["max_value"], + percent_format=data["percent_format"] + ) + + self.assertEqual( + data["expected_result"], + result + ) + + @mock.patch.object(formatter.EntityFormatter, '_get_percent_string') + def test_format_progress_update(self, mock_get_percent_string): + progress_update = { + "current_step": "mock_current_step", + "total_steps": "mock_total_steps", + "created_at": "mock_created_at", + "message": "mock_message", + } + mock_get_percent_string.return_value = "mock_percent_string" + + result = self.format._format_progress_update(progress_update) + + self.assertEqual( + "mock_created_at [mock_percent_string] mock_message", + result + ) + + @mock.patch.object(formatter.EntityFormatter, '_get_percent_string') + def test_format_progress_update_no_percent_string( + self, + mock_get_percent_string + ): + progress_update = { + "current_step": "mock_current_step", + "total_steps": "mock_total_steps", + "created_at": "mock_created_at", + "message": "mock_message", + } + mock_get_percent_string.return_value = None + + result = self.format._format_progress_update(progress_update) + + self.assertEqual( + "mock_created_at mock_message", + result + ) From 573527ec844b9cdba189aa13b6582dd5780678cc Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 21 Nov 2024 17:17:19 +0200 Subject: [PATCH 03/51] Add unit tests for `coriolisclient.cli.shell` module --- .../cli/data/shell_check_auth_arguments.yml | 129 ++++++ .../cli/data/shell_create_keystone_auth.yml | 133 ++++++ coriolisclient/tests/cli/test_shell.py | 438 ++++++++++++++++++ 3 files changed, 700 insertions(+) create mode 100644 coriolisclient/tests/cli/data/shell_check_auth_arguments.yml create mode 100644 coriolisclient/tests/cli/data/shell_create_keystone_auth.yml create mode 100644 coriolisclient/tests/cli/test_shell.py diff --git a/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml b/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml new file mode 100644 index 0000000..79118cf --- /dev/null +++ b/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml @@ -0,0 +1,129 @@ + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '3' + raise_exc: False + expected_result: True + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: + raise_exc: False + expected_result: True + +- config: + args: + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_id: "mock_os_project_id" + api_version: '3' + raise_exc: False + expected_result: True + +- config: + args: + os_project_name: "mock_os_project_name" + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: '3' + raise_exc: False + expected_result: True + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: + raise_exc: False + expected_result: True + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: + raise_exc: False + expected_result: False + +- config: + args: + api_version: + raise_exc: False + expected_result: False + +- config: + args: + api_version: + raise_exc: True + expected_result: + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + api_version: '2' + raise_exc: False + expected_result: False + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: '2' + raise_exc: False + expected_result: False diff --git a/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml new file mode 100644 index 0000000..c36fcf3 --- /dev/null +++ b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml @@ -0,0 +1,133 @@ + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + insecure: True + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + project_id: "mock_os_project_id" + project_name: "mock_os_project_name" + project_domain_name: "mock_os_project_domain_name" + project_domain_id: "mock_os_project_domain_id" + verify: False + api_version: '3' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v3.Token' + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + project_id: "mock_os_project_id" + api_version: '3' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v3.Token' + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + project_id: "mock_os_project_id" + api_version: '3' + auth_type: 'other' + auth_fun: 'keystoneauth1.identity.v3.Password' + + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + project_id: "mock_os_project_id" + api_version: '3' + auth_type: + auth_fun: 'keystoneauth1.identity.v3.Password' + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v2.Token' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v2.Token' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'other' + auth_fun: 'keystoneauth1.identity.v2.Password' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: + auth_fun: 'keystoneauth1.identity.v2.Password' diff --git a/coriolisclient/tests/cli/test_shell.py b/coriolisclient/tests/cli/test_shell.py new file mode 100644 index 0000000..196808e --- /dev/null +++ b/coriolisclient/tests/cli/test_shell.py @@ -0,0 +1,438 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +import logging +import os +from unittest import mock + +from cliff import app +from keystoneauth1 import loading +from keystoneauth1 import session + +from coriolisclient.cli import shell +from coriolisclient import client +from coriolisclient import exceptions +from coriolisclient.tests import test_base + + +class CustomMock(mock.MagicMock): + def __getattr__(self, name): + return None + + +@ddt.ddt +class CoriolisTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Shell App.""" + + def setUp(self): + super(CoriolisTestCase, self).setUp() + self.coriolis = shell.Coriolis() + + @ddt.file_data('data/shell_check_auth_arguments.yml') + @ddt.unpack + def test_check_auth_arguments(self, config): + args = CustomMock() + if config.get("args"): + for key, value in config.get("args").items(): + setattr(args, key, value) + expected_result = config.get("expected_result") + + if expected_result is not None: + result = self.coriolis.check_auth_arguments( + args, + api_version=config.get("api_version", None), + raise_exc=config.get("raise_exc", False) + ) + + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + exceptions.CoriolisException, + self.coriolis.check_auth_arguments, + args, + api_version=config.get("api_version", None), + raise_exc=config.get("raise_exc", False) + ) + + @ddt.file_data('data/shell_create_keystone_auth.yml') + @ddt.unpack + @mock.patch.object(session, 'Session') + def test_create_keystone_session( + self, + mock_Session, + config + ): + args = CustomMock() + if config.get("args"): + for key, value in config.get("args").items(): + setattr(args, key, value) + kwargs = config.get("kwargs", {}) + expected_kwargs = config.get("expected_kwargs", {}) + mock_stderr = mock.Mock() + self.coriolis.stderr = mock_stderr + + auth_fun = config['auth_fun'] + with mock.patch(auth_fun) as mock_auth: + result = self.coriolis.create_keystone_session( + args, + api_version=config.get("api_version", None), + kwargs_dict=kwargs, + auth_type=config.get("auth_type", None), + verify=config.get("verify", True) + ) + + self.assertEqual( + mock_Session.return_value, + result + ) + mock_Session.assert_called_once_with( + auth=mock_auth.return_value, verify=config.get("verify", True)) + mock_auth.assert_called_once_with(**expected_kwargs) + + @ddt.data( + { + "args": { + "no_auth": "mock_no_auth", + "os_auth_url": "mock_os_auth_url" + } + }, + { + "args": { + "no_auth": "mock_no_auth", + "endpoint": "mock_endpoint" + } + }, + { + "args": { + "no_auth": "mock_no_auth", + "endpoint": "mock_endpoint", + "os_tenant_id": "mock_os_tenant_id" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "project_id": "mock_os_tenant_id", + } + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token" + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name", + "insecure": True + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token", + "verify": False + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name", + "insecure": True, + "os_cacert": "mock_os_cacert" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token", + "verify": "mock_os_cacert" + }, + { + "args": { + "os_auth_token": "mock_os_auth_token" + } + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token" + }, + { + "args": { + "os_auth_url": "mock_os_auth_url", + "os_password": "mock_os_password", + "os_user_id": "mock_os_user_id", + "os_username": "mock_os_username", + "endpoint": "mock_endpoint" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "password": "mock_os_password", + "user_id": "mock_os_user_id", + "username": "mock_os_username", + }, + "auth_type": "password" + }, + {} + ) + @mock.patch.object(shell.Coriolis, 'create_keystone_session') + @mock.patch.object(client, 'Client') + def test_create_client( + self, + data, + mock_Client, + mock_create_ks_session + ): + args = CustomMock() + args.os_identity_api_version = mock.sentinel.os_identity_api_version + for key, value in data.get("args", {}).items(): + setattr(args, key, value) + expected_client_kwargs = data.get("expected_client_kwargs") + expected_ks_session_kwargs = data.get("expected_ks_session_kwargs", {}) + + if expected_client_kwargs is not None: + result = self.coriolis.create_client(args) + self.assertEqual( + result, mock_Client.return_value) + if expected_ks_session_kwargs: + mock_Client.assert_called_once_with( + session=mock_create_ks_session.return_value, + **expected_client_kwargs, + verify=data.get("verify", True)) + mock_create_ks_session.assert_called_once_with( + args, + mock.sentinel.os_identity_api_version, + expected_ks_session_kwargs, + auth_type=data["auth_type"], + verify=data.get("verify", True)) + else: + mock_Client.assert_called_once_with( + **expected_client_kwargs, + verify=data.get("verify", True)) + else: + self.assertRaises( + exceptions.CoriolisException, + self.coriolis.create_client, + args + ) + + @ddt.data( + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": 3, + "expected_kwargs": { + "tenant_name": "mock_tenant_name" + } + }, + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": 2, + "expected_kwargs": { + "tenant_name": "mock_tenant_name" + } + }, + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": None, + "expected_kwargs": { + "project_id": "mock_project_id" + } + }, + { + "args": {}, + "api_version": None, + "expected_kwargs": {} + } + ) + def test_build_kwargs_based_on_version(self, data): + args = CustomMock() + for key, value in data.get("args", {}).items(): + setattr(args, key, value) + api_version = data["api_version"] + + result = self.coriolis.build_kwargs_based_on_version( + args, api_version=api_version) + + self.assertEqual( + data["expected_kwargs"], + result + ) + + def test_get_endpoint_filter_kwargs( + self + ): + args = mock.Mock() + args.interface = mock.sentinel.interface + args.service_type = mock.sentinel.service_type + args.service_name = mock.sentinel.service_name + args.coriolis_api_version = mock.sentinel.coriolis_api_version + args.region_name = mock.sentinel.region_name + expected_result = { + "interface": mock.sentinel.interface, + "service_type": mock.sentinel.service_type, + "service_name": mock.sentinel.service_name, + "region_name": mock.sentinel.region_name, + "version": mock.sentinel.coriolis_api_version, + } + + result = self.coriolis._get_endpoint_filter_kwargs(args) + + self.assertEqual( + expected_result, + result + ) + + @mock.patch.object(shell.Coriolis, '_env') + @mock.patch.object(loading, 'register_session_argparse_arguments') + @mock.patch.object(app.App, 'build_option_parser') + def test_build_option_parser( + self, + mock_build_option_parser, + mock_register_session_argparse_arguments, + *_ + ): + result = self.coriolis.build_option_parser( + mock.sentinel.description, mock.sentinel.version) + + self.assertEqual( + mock_build_option_parser.return_value, + result + ) + mock_build_option_parser.assert_called_once_with( + mock.sentinel.description, mock.sentinel.version, None) + mock_register_session_argparse_arguments.assert_called_once_with( + mock_build_option_parser.return_value) + + def test_env(self): + os.environ["TEST_ENV"] = "test_value" + result = self.coriolis._env( + "TEST_ENV", mock.sentinel.default) + + self.assertEqual( + "test_value", + result + ) + + @mock.patch.object(shell.Coriolis, 'create_client') + def test_prepare_to_run_command(self, mock_create_client): + cmd = mock.Mock() + cmd.auth_required = True + self.coriolis.options = mock.sentinel.options + + self.coriolis.prepare_to_run_command(cmd) + + self.assertEqual( + mock_create_client.return_value, + self.coriolis.client_manager.coriolis + ) + mock_create_client.assert_called_once_with(mock.sentinel.options) + + @mock.patch.object(app.App, 'run') + def test_run(self, mock_run): + mock_stderr = mock.Mock() + mock_parser = mock.Mock() + self.coriolis.stderr = mock_stderr + self.coriolis.parser = mock_parser + + result = self.coriolis.run(None) + + self.assertEqual( + 1, + result + ) + mock_stderr.write.assert_called_once_with( + mock_parser.format_usage.return_value) + mock_run.assert_not_called() + + mock_stderr.reset_mock() + mock_parser.reset_mock() + + result = self.coriolis.run(mock.sentinel.argv) + + self.assertEqual( + mock_run.return_value, + result + ) + mock_parser.format_usage.assert_not_called() + + +class ShellTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Shell.""" + + def test_setup_logging(self): + shell._setup_logging() + self.assertEqual( + ( + logging.WARNING, + logging.ERROR + ), + ( + logging.getLogger("requests").level, + logging.getLogger("keystoneclient").level + ) + ) + + @mock.patch.object(shell.Coriolis, 'run') + def test_main( + self, + mock_run + ): + result = shell.main(mock.sentinel.argv) + + self.assertEqual( + mock_run.return_value, + result + ) + mock_run.assert_called_once_with(mock.sentinel.argv) From 289c34f190e59ce8ebe350f9fe697588f40c123a Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 21 Nov 2024 17:17:43 +0200 Subject: [PATCH 04/51] Add unit tests for `coriolisclient.cli.utils` module --- .../tests/cli/data/user_scripts.yml | 2 + coriolisclient/tests/cli/test_utils.py | 325 ++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 coriolisclient/tests/cli/data/user_scripts.yml create mode 100644 coriolisclient/tests/cli/test_utils.py diff --git a/coriolisclient/tests/cli/data/user_scripts.yml b/coriolisclient/tests/cli/data/user_scripts.yml new file mode 100644 index 0000000..7510465 --- /dev/null +++ b/coriolisclient/tests/cli/data/user_scripts.yml @@ -0,0 +1,2 @@ +"mock_script1" +"mock_script2" diff --git a/coriolisclient/tests/cli/test_utils.py b/coriolisclient/tests/cli/test_utils.py new file mode 100644 index 0000000..61461fd --- /dev/null +++ b/coriolisclient/tests/cli/test_utils.py @@ -0,0 +1,325 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import argparse +import ddt +import os +from unittest import mock + +from coriolisclient.cli import utils +from coriolisclient.tests import test_base + + +@ddt.ddt +class UtilsTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Utils.""" + + def test_add_storage_mappings_arguments_to_parser(self): + parser = argparse.ArgumentParser() + + utils.add_storage_mappings_arguments_to_parser(parser) + args = parser.parse_args( + ['--storage-backend-mapping', 'mock_source=mock_destination', + '--disk-storage-mapping', 'mock_disk_id=mock_destination', + '--default-storage-backend', 'mock_default_storage_backend']) + + self.assertEqual( + [{'disk_id': 'mock_disk_id', 'destination': 'mock_destination'}], + args.disk_storage_mappings + ) + self.assertEqual( + [{'source': 'mock_source', 'destination': 'mock_destination'}], + args.storage_backend_mappings + ) + self.assertEqual( + 'mock_default_storage_backend', + args.default_storage_backend + ) + + def test_get_storage_mappings_dict_from_args(self): + args = mock.Mock() + args.default_storage_backend = mock.sentinel.default_storage_backend + args.disk_storage_mappings = mock.sentinel.disk_storage_mappings + args.storage_backend_mappings = mock.sentinel.storage_backend_mappings + + result = utils.get_storage_mappings_dict_from_args(args) + + self.assertEqual( + { + "backend_mappings": mock.sentinel.storage_backend_mappings, + "default": mock.sentinel.default_storage_backend, + "disk_mappings": mock.sentinel.disk_storage_mappings + }, + result + ) + + def test_format_mapping(self): + mapping = { + "mapping1": "mock_mapping1", + "mapping2": "mock_mapping2", + "mapping3": "mock_mapping3" + } + + result = utils.format_mapping(mapping) + + self.assertEqual( + ( + "'mapping1'='mock_mapping1', " + "'mapping2'='mock_mapping2', " + "'mapping3'='mock_mapping3'" + ), + result + ) + + def test_parse_storage_mappings(self): + storage_mappings = { + "default": "mock_default", + "backend_mappings": [ + { + "source": "mock_source", + "destination": "mock_destination" + } + ], + "disk_mappings": [ + { + "disk_id": "mock_disk_id", + "destination": "mock_destination" + } + ] + } + + result = utils.parse_storage_mappings(storage_mappings) + + self.assertEqual( + ( + 'mock_default', + {'mock_source': 'mock_destination'}, + {'mock_disk_id': 'mock_destination'} + ), + result + ) + + def test_parse_storage_mappings_none(self): + result = utils.parse_storage_mappings(None) + + self.assertEqual( + (None, {}, {}), + result + ) + + def test_format_json_for_object_property(self): + obj = mock.Mock() + obj.prop_name = {"key1": "value1", "key2": "value2"} + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{\n "key1": "value1",\n "key2": "value2"\n}', + result + ) + + def test_format_json_for_object_property_to_dict(self): + obj = mock.Mock() + obj.prop_name.to_dict.return_value = \ + {"key1": "value1", "key2": "value2"} + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{\n "key1": "value1",\n "key2": "value2"\n}', + result + ) + + def test_format_json_for_object_property_none(self): + obj = mock.Mock() + obj.prop_name = None + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{}', + result + ) + + def test_validate_uuid_string(self): + result = utils.validate_uuid_string( + "12345678-9ABC-DEF1-2345-6789abcdef12") + + self.assertEqual( + True, + result + ) + + result = utils.validate_uuid_string( + "123456789ABCDEF") + + self.assertEqual( + False, + result + ) + + @mock.patch.object(argparse, 'FileType') + def test_add_args_for_json_option_to_parser(self, mock_file_type): + parser = argparse.ArgumentParser() + + utils.add_args_for_json_option_to_parser( + parser, "option_name") + + args = parser.parse_args( + ['--option-name', 'mock_option']) + + args_file = parser.parse_args( + ['--option-name-file', 'mock_option_file']) + + self.assertEqual( + ('mock_option', mock_file_type.return_value.return_value), + (args.option_name, args_file.option_name_file) + ) + + def test_get_option_value_from_args(self): + args = mock.MagicMock() + args.option_name = '{"option": "raw_value"}' + args.option_name_file.__enter__.return_value.read.return_value = \ + '{"option": "file_value"}' + + result = utils.get_option_value_from_args(args, "option-name") + + self.assertEqual( + {'option': 'raw_value'}, + result + ) + + args.option_name = None + + result = utils.get_option_value_from_args(args, "option-name") + + self.assertEqual( + {'option': 'file_value'}, + result + ) + + def test_get_option_value_from_args_no_value(self): + args = mock.Mock() + args.option_name = None + args.option_name_file = None + + result = utils.get_option_value_from_args( + args, "option-name", error_on_no_value=False) + + self.assertEqual( + None, + result + ) + + self.assertRaises( + ValueError, + utils.get_option_value_from_args, + args, + "option-name" + ) + + def test_get_option_value_from_args_json_value_error(self): + args = mock.Mock() + args.option_name = "invalid" + args.option_name_file = None + + self.assertRaises( + ValueError, + utils.get_option_value_from_args, + args, + "option-name" + ) + + @ddt.data( + { + "global_scripts": None, + "instance_scripts": None, + "expected_result": + { + "global": {}, + "instances": {} + } + }, + { + "global_scripts": ["linux="], + "instance_scripts": ["instance_1="], + "expected_result": + { + "global": {"linux": None}, + "instances": {"instance_1": None} + } + }, + { + "global_scripts": ["linux script"], + "instance_scripts": ["linux script"], + "expected_result": + { + "global": {}, + "instances": {} + } + }, + { + "global_scripts": ["invalid_os=scrips"], + "instance_scripts": None, + "expected_result": None + }, + { + "global_scripts": ["linux='invalid/file/path'"], + "instance_scripts": None, + "expected_result": None + }, + { + "global_scripts": None, + "instance_scripts": ["linux='invalid/file/path'"], + "expected_result": None + }, + ) + def test_compose_user_scripts(self, data): + global_scripts = data["global_scripts"] + instance_scripts = data["instance_scripts"] + expected_result = data["expected_result"] + + if expected_result: + result = utils.compose_user_scripts( + global_scripts, instance_scripts) + + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + ValueError, + utils.compose_user_scripts, + global_scripts, + instance_scripts + ) + + def test_compose_user_scripts_from_file(self): + script_path = os.path.dirname(os.path.realpath(__file__)) + script_path = os.path.join(script_path, 'data/user_scripts.yml') + global_scripts = ["linux=%s" % script_path] + instance_scripts = ["linux=%s" % script_path] + + result = utils.compose_user_scripts(global_scripts, instance_scripts) + + self.assertEqual( + { + 'global': {'linux': '"mock_script1"\n"mock_script2"\n'}, + 'instances': {'linux': '"mock_script1"\n"mock_script2"\n'} + }, + result + ) + + def test_add_minion_pool_args_to_parser(self): + parser = argparse.ArgumentParser() + + utils.add_minion_pool_args_to_parser(parser) + args = parser.parse_args( + ['--osmorphing-minion-pool-mapping', + 'mock_instance_id=mock_pool_id']) + + self.assertEqual( + [{'instance_id': 'mock_instance_id', 'pool_id': 'mock_pool_id'}], + args.instance_osmorphing_minion_pool_mappings + ) From cd6f3f9b2dcfc69e4864b99b8bece4efe66ef3e5 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 21 Nov 2024 17:18:11 +0200 Subject: [PATCH 05/51] Change generic Exception to CoriolisException in `coriolisclient.cli.shell.py` --- coriolisclient/cli/shell.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/coriolisclient/cli/shell.py b/coriolisclient/cli/shell.py index 2b1f37c..c30c1ef 100644 --- a/coriolisclient/cli/shell.py +++ b/coriolisclient/cli/shell.py @@ -35,6 +35,7 @@ import six from coriolisclient import client +from coriolisclient import exceptions from coriolisclient import version @@ -100,7 +101,7 @@ def check_auth_arguments(self, args, api_version=None, raise_exc=False): successful = False if not successful and raise_exc: - raise Exception(msg) + raise exceptions.CoriolisException(msg) return successful @@ -157,7 +158,7 @@ def create_client(self, args): api_version = args.os_identity_api_version verify = args.os_cacert or not args.insecure if args.no_auth and args.os_auth_url: - raise Exception( + raise exceptions.CoriolisException( 'ERROR: argument --os-auth-url/-A: not allowed ' 'with argument --no-auth/-N' ) @@ -165,7 +166,7 @@ def create_client(self, args): if args.no_auth: if not all([args.endpoint, args.os_tenant_id or args.os_project_id]): - raise Exception( + raise exceptions.CoriolisException( 'ERROR: please specify --endpoint and ' '--os-project-id (or --os-tenant-id)') created_client = client.Client( @@ -177,7 +178,8 @@ def create_client(self, args): # Token-based authentication elif args.os_auth_token: if not args.os_auth_url: - raise Exception('ERROR: please specify --os-auth-url') + raise exceptions.CoriolisException( + 'ERROR: please specify --os-auth-url') token_kwargs = { 'auth_url': args.os_auth_url, 'token': args.os_auth_token @@ -210,7 +212,8 @@ def create_client(self, args): **endpoint_filter_kwargs ) else: - raise Exception('ERROR: please specify authentication credentials') + raise exceptions.CoriolisException( + 'ERROR: please specify authentication credentials') return created_client From 2f17b44d9885a04ba35c403b5bb8030fcf40dd1d Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 21 Nov 2024 17:18:36 +0200 Subject: [PATCH 06/51] Remove idempotent condition in method: `get_option_value_from_args` from `coriolisclient/cli/utils.py` --- coriolisclient/cli/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coriolisclient/cli/utils.py b/coriolisclient/cli/utils.py index b61ee28..730363f 100644 --- a/coriolisclient/cli/utils.py +++ b/coriolisclient/cli/utils.py @@ -175,7 +175,7 @@ def get_option_value_from_args(args, option_name, error_on_no_value=True): with file_arg as fin: raw_value = fin.read() - if not value and raw_value: + if raw_value: try: value = json.loads(raw_value) except ValueError as ex: From 95f519f4d03876e8f8b2d36794992ccd8394fac3 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 17 May 2024 19:07:06 +0300 Subject: [PATCH 07/51] Add unit tests for `coriolisclient.v1.common` module --- coriolisclient/tests/v1/__init__.py | 0 coriolisclient/tests/v1/test_common.py | 65 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 coriolisclient/tests/v1/__init__.py create mode 100644 coriolisclient/tests/v1/test_common.py diff --git a/coriolisclient/tests/v1/__init__.py b/coriolisclient/tests/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/coriolisclient/tests/v1/test_common.py b/coriolisclient/tests/v1/test_common.py new file mode 100644 index 0000000..0f0086c --- /dev/null +++ b/coriolisclient/tests/v1/test_common.py @@ -0,0 +1,65 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import common + + +class TaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Task.""" + + def setUp(self): + super(TaskTestCase, self).setUp() + self.task = common.Task( + None, + { + "progress_updates": [ + {"progress_update1": "mock_update1"}, + {"progress_update2": "mock_update2"} + ] + }, + loaded=False + ) + + def test_progress_updates(self): + result = self.task.progress_updates + + self.assertEqual( + ("mock_update1", "mock_update2"), + (result[0].progress_update1, result[1].progress_update2) + ) + + +@ddt.ddt +class CommonTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Common.""" + + @ddt.data( + ("value", False, "dmFsdWU="), + ({"key": "value"}, True, "eyJrZXkiOiAidmFsdWUifQ==") + ) + @ddt.unpack + def test_encode_base64_param(self, param, is_json, expected_result): + result = common.encode_base64_param(param, is_json=is_json) + + self.assertEqual( + result, + expected_result + ) + + @ddt.data( + (12345, False), + (None, False), + ({"key value"}, True) + ) + @ddt.unpack + def test_encode_base64_param_raises(self, param, is_json): + self.assertRaises( + exceptions.CoriolisException, + common.encode_base64_param, + param, + is_json=is_json + ) From 103eb8f7ae6cccbf35766baf0536946321ae247f Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:27:15 +0300 Subject: [PATCH 08/51] Add unit tests for `coriolisclient.v1.diagnostics` module --- coriolisclient/tests/v1/test_diagnostics.py | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 coriolisclient/tests/v1/test_diagnostics.py diff --git a/coriolisclient/tests/v1/test_diagnostics.py b/coriolisclient/tests/v1/test_diagnostics.py new file mode 100644 index 0000000..fdb7337 --- /dev/null +++ b/coriolisclient/tests/v1/test_diagnostics.py @@ -0,0 +1,26 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import diagnostics + + +class DiagnosticsManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Diagnostics Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(DiagnosticsManagerTestCase, self).setUp() + self.diag = diagnostics.DiagnosticsManager(mock_client) + + @mock.patch.object(diagnostics.DiagnosticsManager, '_list') + def test_get(self, mock_list): + result = self.diag.get() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with('/diagnostics', 'diagnostics') From fd39bf646d014b2e4c50af215cf806beff78f353 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:27:47 +0300 Subject: [PATCH 09/51] Add unit tests for `coriolisclient.v1.endpoint_destination_minion_pool_options` module --- ...ndpoint_destination_minion_pool_options.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoint_destination_minion_pool_options.py diff --git a/coriolisclient/tests/v1/test_endpoint_destination_minion_pool_options.py b/coriolisclient/tests/v1/test_endpoint_destination_minion_pool_options.py new file mode 100644 index 0000000..773a50d --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_destination_minion_pool_options.py @@ -0,0 +1,49 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_destination_minion_pool_options + + +class EndpointDestinationMinionPoolOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """ + Test suite for the Coriolis v1 Endpoint Destination Minion Pool Options + Manager. + """ + + def setUp(self): + mock_client = mock.Mock() + super(EndpointDestinationMinionPoolOptionsManagerTestCase, self + ).setUp() + self.endpoint = ( + endpoint_destination_minion_pool_options. + EndpointDestinationMinionPoolOptionsManager)(mock_client) + + @mock.patch.object(endpoint_destination_minion_pool_options. + EndpointDestinationMinionPoolOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/' + 'destination-minion-pool-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'destination_minion_pool_options') From ad1b3230e66b5dffcc761edf9dec5164eebde746 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:27:59 +0300 Subject: [PATCH 10/51] Add unit tests for `coriolisclient.v1.endpoint_destination_options` module --- .../v1/test_endpoint_destination_options.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoint_destination_options.py diff --git a/coriolisclient/tests/v1/test_endpoint_destination_options.py b/coriolisclient/tests/v1/test_endpoint_destination_options.py new file mode 100644 index 0000000..4cceb1c --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_destination_options.py @@ -0,0 +1,45 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_destination_options + + +class EndpointDestinationOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Destination Options Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointDestinationOptionsManagerTestCase, self).setUp() + self.endpoint = ( + endpoint_destination_options. + EndpointDestinationOptionsManager)(mock_client) + + @mock.patch.object(endpoint_destination_options. + EndpointDestinationOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/' + 'destination-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'destination_options') From 58d91075a583b670ecf74657c6c20329201388aa Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:28:12 +0300 Subject: [PATCH 11/51] Add unit tests for `coriolisclient.v1.endpoint_destination_instances` module --- .../tests/v1/test_endpoint_instances.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoint_instances.py diff --git a/coriolisclient/tests/v1/test_endpoint_instances.py b/coriolisclient/tests/v1/test_endpoint_instances.py new file mode 100644 index 0000000..0b7b3c6 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_instances.py @@ -0,0 +1,115 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import common +from coriolisclient.v1 import endpoint_instances + + +class EndpointInstanceTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Instance.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointInstanceTestCase, self).setUp() + self.endpoint = endpoint_instances.EndpointInstance( + mock_client, + {"flavor_name": mock.sentinel.flavor_name}) + + def test_flavor_name(self): + result = self.endpoint.flavor_name + + self.assertEqual( + mock.sentinel.flavor_name, + result + ) + + +class EndpointInstanceManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Instance Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointInstanceManagerTestCase, self).setUp() + self.endpoint = endpoint_instances.EndpointInstanceManager(mock_client) + + @mock.patch.object(endpoint_instances.EndpointInstanceManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + env={"env": "mock_env"}, + marker="mock_marker", + limit="mock_limit", + name="mock_name" + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/instances' + '?marker=mock_marker&limit=mock_limit&name=mock_name&' + 'env=eyJlbnYiOiAibW9ja19lbnYifQ%3D%3D'), + 'instances') + + @mock.patch.object(common, 'encode_base64_param') + def test_list_value_error( + self, + mock_encode_base64_param + ): + mock_endpoint = mock.Mock() + mock_encode_base64_param.return_value = mock.sentinel.encoded_env + + self.assertRaises( + ValueError, + self.endpoint.list, + mock_endpoint, + env=mock.sentinel.env + ) + + mock_encode_base64_param.assert_not_called() + + @mock.patch.object(endpoint_instances.EndpointInstanceManager, '_get') + def test_get( + self, + mock_get + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.get( + mock_endpoint, + "mock_id", + env={"env": "mock_env"}, + ) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/instances/' + 'bW9ja19pZA==?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'instance') + + def test_get_value_error(self): + mock_endpoint = mock.Mock() + + self.assertRaises( + ValueError, + self.endpoint.get, + mock_endpoint, + "mock_instance_id", + env=mock.sentinel.env + ) From a0fbc59b9cb2e2dda8b637f88a62ece981be6489 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:28:27 +0300 Subject: [PATCH 12/51] Add unit tests for `coriolisclient.v1.endpoint_destination_networks` module --- .../tests/v1/test_endpoint_networks.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoint_networks.py diff --git a/coriolisclient/tests/v1/test_endpoint_networks.py b/coriolisclient/tests/v1/test_endpoint_networks.py new file mode 100644 index 0000000..febe4b4 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_networks.py @@ -0,0 +1,39 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_networks + + +class EndpointNetworkManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Network Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointNetworkManagerTestCase, self).setUp() + self.endpoint = endpoint_networks.EndpointNetworkManager(mock_client) + + @mock.patch.object(endpoint_networks.EndpointNetworkManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/networks' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'networks') From decf0c1ae76cc73368603bfa03baa5138e0eefec Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:28:40 +0300 Subject: [PATCH 13/51] Add unit tests for `coriolisclient.v1.endpoint_source_minion_pool_options` module --- ...est_endpoint_source_minion_pool_options.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoint_source_minion_pool_options.py diff --git a/coriolisclient/tests/v1/test_endpoint_source_minion_pool_options.py b/coriolisclient/tests/v1/test_endpoint_source_minion_pool_options.py new file mode 100644 index 0000000..6fc7e9e --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_source_minion_pool_options.py @@ -0,0 +1,49 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_source_minion_pool_options + + +class EndpointSourceMinionPoolOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """ + Test suite for the Coriolis v1 Endpoint Source Minion Pool Options + Manager. + """ + + def setUp(self): + mock_client = mock.Mock() + super(EndpointSourceMinionPoolOptionsManagerTestCase, self + ).setUp() + self.endpoint = ( + endpoint_source_minion_pool_options. + EndpointSourceMinionPoolOptionsManager)(mock_client) + + @mock.patch.object(endpoint_source_minion_pool_options. + EndpointSourceMinionPoolOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/' + 'source-minion-pool-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'source_minion_pool_options') From a1f1b5db61bdcc7f6b3a4f2e57272dfafdfa0dd5 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:28:48 +0300 Subject: [PATCH 14/51] Add unit tests for `coriolisclient.v1.endpoint_source_options` module --- .../tests/v1/test_endpoint_source_options.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoint_source_options.py diff --git a/coriolisclient/tests/v1/test_endpoint_source_options.py b/coriolisclient/tests/v1/test_endpoint_source_options.py new file mode 100644 index 0000000..d2939a4 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_source_options.py @@ -0,0 +1,44 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_source_options + + +class EndpointSourceOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Source Options Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointSourceOptionsManagerTestCase, self).setUp() + self.endpoint = ( + endpoint_source_options. + EndpointSourceOptionsManager)(mock_client) + + @mock.patch.object(endpoint_source_options. + EndpointSourceOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/source-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'source_options') From 60d5576b20399b3dcecccbdc7ac2f584aa5a6b9d Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:29:04 +0300 Subject: [PATCH 15/51] Add unit tests for `coriolisclient.v1.endpoint_storage` module --- .../tests/v1/test_endpoint_storage.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoint_storage.py diff --git a/coriolisclient/tests/v1/test_endpoint_storage.py b/coriolisclient/tests/v1/test_endpoint_storage.py new file mode 100644 index 0000000..23d7acc --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_storage.py @@ -0,0 +1,63 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_storage + + +class EndpointStorageManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Storage Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointStorageManagerTestCase, self).setUp() + self.endpoint = endpoint_storage.EndpointStorageManager(mock_client) + + @mock.patch.object(endpoint_storage.EndpointStorageManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"} + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/storage' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'storage', + values_key='storage_backends') + + @mock.patch.object(endpoint_storage.EndpointStorageManager, '_get') + def test_get_default( + self, + mock_get + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + mock_get.return_value = {"config_default": "mock_default"} + + result = self.endpoint.get_default( + mock_endpoint, + environment={"env": "mock_env"}, + ) + + self.assertEqual( + "mock_default", + result + ) + mock_get.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/storage' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'storage') From f9687ba092ae2bf9d47657026a4642880088b723 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 22 May 2024 14:29:12 +0300 Subject: [PATCH 16/51] Add unit tests for `coriolisclient.v1.endpoints` module --- coriolisclient/tests/v1/test_endpoints.py | 262 ++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 coriolisclient/tests/v1/test_endpoints.py diff --git a/coriolisclient/tests/v1/test_endpoints.py b/coriolisclient/tests/v1/test_endpoints.py new file mode 100644 index 0000000..1e3e058 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoints.py @@ -0,0 +1,262 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoints + + +class EndpointTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint.""" + + def setUp(self): + super(EndpointTestCase, self).setUp() + self.endpoint = endpoints.Endpoint( + None, + { + "connection_info": { + "connection_info1": mock.sentinel.connection_info} + } + ) + + def test_connection_info(self): + result = self.endpoint.connection_info + + self.assertEqual( + mock.sentinel.connection_info, + result.connection_info1 + ) + + +class EndpointManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Manager.""" + + def setUp(self): + self.mock_client = mock.Mock() + super(EndpointManagerTestCase, self).setUp() + self.endpoint = endpoints.EndpointManager(self.mock_client) + + @mock.patch.object(endpoints.EndpointManager, '_list') + def test_list( + self, + mock_list + ): + result = self.endpoint.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with('/endpoints', 'endpoints') + + @mock.patch.object(endpoints.EndpointManager, '_get') + def test_get( + self, + mock_get + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.get(mock_endpoint) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb', 'endpoint') + + @mock.patch.object(endpoints.EndpointManager, '_post') + def test_create( + self, + mock_post + ): + result = self.endpoint.create( + mock.sentinel.name, + mock.sentinel.endpoint_type, + mock.sentinel.connection_info, + mock.sentinel.description, + mock.sentinel.regions + ) + expected_data = { + "endpoint": { + "name": mock.sentinel.name, + "type": mock.sentinel.endpoint_type, + "description": mock.sentinel.description, + "connection_info": mock.sentinel.connection_info, + "mapped_regions": mock.sentinel.regions + } + } + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + '/endpoints', expected_data, 'endpoint') + + @mock.patch.object(endpoints.EndpointManager, '_put') + def test_update( + self, + mock_put + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.update( + mock_endpoint, + mock.sentinel.updated_values + ) + expected_data = { + "endpoint": mock.sentinel.updated_values + } + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb', + expected_data, 'endpoint') + + @mock.patch.object(endpoints.EndpointManager, '_delete') + def test_delete( + self, + mock_delete + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.delete(mock_endpoint) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb') + + def test_validate_connection(self): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + self.mock_client.post.return_value.json.return_value = { + "validate-connection": { + "valid": mock.sentinel.valid, + "message": mock.sentinel.message + } + } + + result = self.endpoint.validate_connection(mock_endpoint) + + self.assertEqual( + (mock.sentinel.valid, mock.sentinel.message), + result + ) + self.mock_client.post.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/actions', + json={'validate-connection': None}) + + @mock.patch.object(endpoints.EndpointManager, '_get_endpoint_id_for_name') + def test_get_endpoint_id_for_name_uuid( + self, + mock_get_endpoint_id_for_name + ): + mock_endpoint = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + result = self.endpoint.get_endpoint_id_for_name(mock_endpoint) + + self.assertEqual( + '53773ab8-1474-4cf7-bf0c-a496a6595ecb', + result + ) + mock_get_endpoint_id_for_name.assert_not_called() + + @mock.patch.object(endpoints.EndpointManager, '_get_endpoint_id_for_name') + def test_get_endpoint_id_for_name( + self, + mock_get_endpoint_id_for_name + ): + mock_endpoint = mock.Mock() + + result = self.endpoint.get_endpoint_id_for_name(mock_endpoint) + + self.assertEqual( + mock_get_endpoint_id_for_name.return_value, + result + ) + + @mock.patch.object(endpoints.EndpointManager, 'list') + def test__get_endpoint_id_for_name( + self, + mock_list + ): + obj1 = mock.Mock() + obj2 = mock.Mock() + obj3 = mock.Mock() + obj1.id = '1' + obj2.id = '2' + obj3.id = '3' + obj1.name = 'mock_name1' + obj2.name = 'mock_name2' + obj3.name = 'mock_name3' + obj_list = [obj1, obj2, obj3] + mock_list.return_value = obj_list + endpoint_name = "mock_name2" + + result = self.endpoint._get_endpoint_id_for_name(endpoint_name) + + self.assertEqual( + '2', + result + ) + + @mock.patch.object(endpoints.EndpointManager, 'list') + def test__get_endpoint_id_for_name_not_found( + self, + mock_list + ): + obj1 = mock.Mock() + obj2 = mock.Mock() + obj3 = mock.Mock() + obj1.id = '1' + obj2.id = '2' + obj3.id = '3' + obj1.name = 'mock_name1' + obj2.name = 'mock_name2' + obj3.name = 'mock_name3' + obj_list = [obj1, obj2, obj3] + mock_list.return_value = obj_list + endpoint_name = "mock_name4" + + self.assertRaises( + exceptions.EndpointIDNotFound, + self.endpoint._get_endpoint_id_for_name, + endpoint_name + ) + + @mock.patch.object(endpoints.EndpointManager, 'list') + def test__get_endpoint_id_for_name_not_unique( + self, + mock_list + ): + obj1 = mock.Mock() + obj2 = mock.Mock() + obj3 = mock.Mock() + obj1.id = '1' + obj2.id = '2' + obj3.id = '3' + obj1.name = 'mock_name1' + obj2.name = 'mock_name2' + obj3.name = 'mock_name2' + obj_list = [obj1, obj2, obj3] + mock_list.return_value = obj_list + endpoint_name = "mock_name2" + + self.assertRaises( + exceptions.NoUniqueEndpointNameMatch, + self.endpoint._get_endpoint_id_for_name, + endpoint_name + ) From a0cf0be74db12d75905ab464588c986e8de748e2 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 29 May 2024 14:47:37 +0300 Subject: [PATCH 17/51] Fix typo in `coriolisclient.v1.licensing_appliances.py` --- coriolisclient/v1/licensing_appliances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coriolisclient/v1/licensing_appliances.py b/coriolisclient/v1/licensing_appliances.py index 0121107..7cfc208 100644 --- a/coriolisclient/v1/licensing_appliances.py +++ b/coriolisclient/v1/licensing_appliances.py @@ -41,5 +41,5 @@ def show(self, appliance_id): def create(self): url = '/appliances' - data = self._licensing_cli.post(url, response_key='apppliance') + data = self._licensing_cli.post(url, response_key='appliance') return self.resource_class(self, data, loaded=True) From 3010f7820b69f8a36774cffdb8aa6c5a5dc8b4ba Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 29 May 2024 15:02:43 +0300 Subject: [PATCH 18/51] `coriolisclient.v1.logging.py`: Convert count to int and fix logs when period is string --- coriolisclient/v1/logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coriolisclient/v1/logging.py b/coriolisclient/v1/logging.py index e69fe52..2e841c2 100644 --- a/coriolisclient/v1/logging.py +++ b/coriolisclient/v1/logging.py @@ -137,7 +137,7 @@ def _convert_period_to_timestamp(self, period): _ = datetime.datetime.fromtimestamp(int(period)) return int(period) except (OverflowError, OSError): - LOG.warning("Failed to initialize timestamp from period value: %d", + LOG.warning("Failed to initialize timestamp from period value: %s", period) except ValueError: LOG.warning("Invalid value type for period: %s", period) @@ -157,7 +157,7 @@ def _convert_period_to_timestamp(self, period): if conv_unit is None: raise exceptions.CoriolisException("invalid period %s" % period) - args = {conv_unit: count} + args = {conv_unit: int(count)} tm = datetime.datetime.utcnow() - datetime.timedelta(**args) return int(tm.timestamp()) From 27fc89ae2481c8df39799164fcbc6692b9990bd5 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 29 May 2024 15:03:32 +0300 Subject: [PATCH 19/51] Add unit tests for `coriolisclient.v1.licensing_appliances.py` module --- .../tests/v1/test_licensing_appliances.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 coriolisclient/tests/v1/test_licensing_appliances.py diff --git a/coriolisclient/tests/v1/test_licensing_appliances.py b/coriolisclient/tests/v1/test_licensing_appliances.py new file mode 100644 index 0000000..394defe --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing_appliances.py @@ -0,0 +1,79 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing +from coriolisclient.v1 import licensing_appliances + + +class LicensingAppliancesManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Appliances Manager.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + self.mock_licencing_client = mock.Mock() + mock_LicensingClient.return_value = self.mock_licencing_client + super(LicensingAppliancesManagerTestCase, self).setUp() + self.licence = licensing_appliances.LicensingAppliancesManager( + mock_client) + + def test_list(self): + self.licence._licensing_cli.get.return_value = { + "appliance1": "mock_appliance1", + "appliance2": "mock_appliance2" + } + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.list() + + self.assertEqual( + [mock_resource_class.return_value, + mock_resource_class.return_value], + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances', response_key='appliances') + mock_resource_class.assert_has_calls([ + mock.call(self.licence, "appliance1", loaded=True), + mock.call(self.licence, "appliance2", loaded=True) + ]) + + def test_show(self): + self.licence._licensing_cli.get.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.show(mock.sentinel.appliance_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances/%s' % mock.sentinel.appliance_id, + response_key='appliance') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) + + def test_create(self): + self.licence._licensing_cli.post.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.create() + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.post.assert_called_once_with( + '/appliances', response_key='appliance') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) From 23041f5be75708d10faf01aea3cfe1fbb11cdd73 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 29 May 2024 15:03:47 +0300 Subject: [PATCH 20/51] Add unit tests for `coriolisclient.v1.licensing_reservations.py` module --- .../tests/v1/test_licensing_reservations.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 coriolisclient/tests/v1/test_licensing_reservations.py diff --git a/coriolisclient/tests/v1/test_licensing_reservations.py b/coriolisclient/tests/v1/test_licensing_reservations.py new file mode 100644 index 0000000..bbf8cb1 --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing_reservations.py @@ -0,0 +1,85 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing +from coriolisclient.v1 import licensing_reservations + + +class LicensingReservationsManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Reservations Manager.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + self.mock_licencing_client = mock.Mock() + mock_LicensingClient.return_value = self.mock_licencing_client + super(LicensingReservationsManagerTestCase, self).setUp() + self.licence = licensing_reservations.LicensingReservationsManager( + mock_client) + + def test_list(self): + self.licence._licensing_cli.get.return_value = { + "reservation1": "mock_reservation1", + "reservation2": "mock_reservation2" + } + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.list(mock.sentinel.appliance_id) + + self.assertEqual( + [mock_resource_class.return_value, + mock_resource_class.return_value], + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances/%s/reservations' % mock.sentinel.appliance_id, + response_key='reservations') + mock_resource_class.assert_has_calls([ + mock.call(self.licence, "reservation1", loaded=True), + mock.call(self.licence, "reservation2", loaded=True) + ]) + + def test_show(self): + self.licence._licensing_cli.get.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.show( + mock.sentinel.appliance_id, mock.sentinel.reservation_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances/%s/reservations/%s' % (mock.sentinel.appliance_id, + mock.sentinel.reservation_id), + response_key='reservation') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) + + def test_refresh(self): + self.licence._licensing_cli.post.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.refresh( + mock.sentinel.appliance_id, mock.sentinel.reservation_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.post.assert_called_once_with( + '/appliances/%s/reservations/%s/refresh' % + (mock.sentinel.appliance_id, mock.sentinel.reservation_id), + response_key='reservation') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) From 22cad4fdd99dc461603e7b61ad26c2f4db8e914a Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 29 May 2024 15:03:58 +0300 Subject: [PATCH 21/51] Add unit tests for `coriolisclient.v1.licensing_server.py` module --- .../tests/v1/test_licensing_server.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 coriolisclient/tests/v1/test_licensing_server.py diff --git a/coriolisclient/tests/v1/test_licensing_server.py b/coriolisclient/tests/v1/test_licensing_server.py new file mode 100644 index 0000000..721f49f --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing_server.py @@ -0,0 +1,39 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing +from coriolisclient.v1 import licensing_server + + +class LicensingServerManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Server Manager.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + self.mock_licencing_client = mock.Mock() + mock_LicensingClient.return_value = self.mock_licencing_client + super(LicensingServerManagerTestCase, self).setUp() + self.licence = licensing_server.LicensingServerManager( + mock_client) + + def test_status(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.status() + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/status', + response_key='status') + mock_resource_class.assert_called_once_with( + self.licence, self.mock_licencing_client.get.return_value, + loaded=True) From 42d680a94f15c0b5ea03285555e1b59bdecd8016 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 29 May 2024 15:04:05 +0300 Subject: [PATCH 22/51] Add unit tests for `coriolisclient.v1.licensing.py` module --- coriolisclient/tests/v1/test_licensing.py | 371 ++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 coriolisclient/tests/v1/test_licensing.py diff --git a/coriolisclient/tests/v1/test_licensing.py b/coriolisclient/tests/v1/test_licensing.py new file mode 100644 index 0000000..c8b4de8 --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing.py @@ -0,0 +1,371 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +import requests + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing + + +class LicensingClientTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Client.""" + + def setUp(self): + mock_client = mock.Mock() + super(LicensingClientTestCase, self).setUp() + self.licence = licensing.LicensingClient( + mock_client, "endpoint_name") + mock_client.verify = True + self.licence._cli = mock_client + + def test_get_licensing_endpoint_url(self): + self.licence._cli.get_endpoint.return_value = "url/endpoint_url/" + + result = self.licence._get_licensing_endpoint_url() + + self.assertEqual( + "url/endpoint_url", + result + ) + self.licence._cli.get_endpoint.assert_called_once_with( + service_type="endpoint_name") + + def test_get_licensing_endpoint_url_raises(self): + self.licence._cli.get_endpoint.side_effect = Exception() + + with self.assertLogs(level="WARN"): + self.assertRaises( + exceptions.LicensingEndpointNotFound, + self.licence._get_licensing_endpoint_url + ) + self.licence._cli.get_endpoint.assert_called_once_with( + service_type="endpoint_name") + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_raw( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = True + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + result = self.licence._do_req( + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key=None, + raw_response=True + ) + + self.assertEqual( + mock_resp, + result + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_json( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = True + mock_resp.json.return_value = {"response_key": mock.sentinel.data} + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + result = self.licence._do_req( + method_name="mock_method", + resource='url/resource_url/', + body={"mock_body": "value"}, + response_key='response_key', + raw_response=False + ) + + self.assertEqual( + mock.sentinel.data, + result + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify, + data='{"mock_body": "value"}' + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_error( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = False + mock_resp.json.side_effect = Exception + mock_resp.raise_for_status.side_effect = exceptions.CoriolisException + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + + with self.assertLogs(level="DEBUG"): + self.assertRaises( + exceptions.CoriolisException, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='response_key', + raw_response=False + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_http_error( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = False + mock_resp.json.return_value = {"error": {"code": 123, "message": ""}} + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + + self.assertRaises( + exceptions.HTTPError, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='response_key', + raw_response=False + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_response_key_error( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = False + mock_resp.json.return_value = {"response_key": mock.sentinel.data} + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + + self.assertRaises( + ValueError, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='invalid', + raw_response=False + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + def test_do_req_method_error(self): + setattr(requests, "mock_method", None) + + self.assertRaises( + ValueError, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='invalid', + raw_response=False + ) + + @mock.patch.object(licensing.LicensingClient, '_do_req') + def test_get(self, mock_do_req): + result = self.licence.get( + resource=mock.sentinel.resource, + body=mock.sentinel.body, + response_key=mock.sentinel.response_key, + raw_response=False + ) + self.assertEqual( + mock_do_req.return_value, + result + ) + mock_do_req.assert_called_once_with( + 'GET', + mock.sentinel.resource, + response_key=mock.sentinel.response_key, + body=mock.sentinel.body, + raw_response=False + ) + + @mock.patch.object(licensing.LicensingClient, '_do_req') + def test_post(self, mock_do_req): + result = self.licence.post( + resource=mock.sentinel.resource, + body=mock.sentinel.body, + response_key=mock.sentinel.response_key, + raw_response=False + ) + self.assertEqual( + mock_do_req.return_value, + result + ) + mock_do_req.assert_called_once_with( + 'POST', + mock.sentinel.resource, + response_key=mock.sentinel.response_key, + body=mock.sentinel.body, + raw_response=False + ) + + @mock.patch.object(licensing.LicensingClient, '_do_req') + def test_delete(self, mock_do_req): + result = self.licence.delete( + resource=mock.sentinel.resource, + body=mock.sentinel.body, + response_key=mock.sentinel.response_key, + raw_response=False + ) + self.assertEqual( + mock_do_req.return_value, + result + ) + mock_do_req.assert_called_once_with( + 'DELETE', + mock.sentinel.resource, + response_key=mock.sentinel.response_key, + body=mock.sentinel.body, + raw_response=False + ) + + +class LicensingManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Client.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + super(LicensingManagerTestCase, self).setUp() + self.licence = licensing.LicensingManager(mock_client) + self.licence._licensing_cli = mock_LicensingClient + + def test_status(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.status(mock.sentinel.appliance_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.licence._licensing_cli.get.assert_called_once_with( + '/appliances/%s/status' % mock.sentinel.appliance_id, + response_key='appliance_licence_status') + mock_resource_class.assert_called_once_with( + self.licence, self.licence._licensing_cli.get.return_value, + loaded=True) + + def test_list(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + self.licence._licensing_cli.get.return_value = { + "licence1": "mock_licence1", + "licence2": "mock_licence2" + } + + result = self.licence.list(mock.sentinel.appliance_id) + + self.assertEqual( + [mock_resource_class.return_value, + mock_resource_class.return_value], + result + ) + self.licence._licensing_cli.get.assert_called_once_with( + '/appliances/%s/licences' % mock.sentinel.appliance_id, + response_key='licences') + mock_resource_class.assert_has_calls([ + mock.call(self.licence, "licence1", loaded=True), + mock.call(self.licence, "licence2", loaded=True) + ]) + + def test_register(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.register( + mock.sentinel.appliance_id, mock.sentinel.licence) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.licence._licensing_cli.post.assert_called_once_with( + '/appliances/%s/licences' % mock.sentinel.appliance_id, + body=mock.sentinel.licence, + response_key='licence') + mock_resource_class.assert_called_once_with( + self.licence, self.licence._licensing_cli.post.return_value, + loaded=True) + + def test_show(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.show( + mock.sentinel.appliance_id, mock.sentinel.licence_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.licence._licensing_cli.get.assert_called_once_with( + '/appliances/%s/licences/%s' % (mock.sentinel.appliance_id, + mock.sentinel.licence_id), + response_key='licence') + mock_resource_class.assert_called_once_with( + self.licence, self.licence._licensing_cli.get.return_value, + loaded=True) + + def test_delete(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.delete( + mock.sentinel.appliance_id, mock.sentinel.licence_id) + + self.assertEqual( + self.licence._licensing_cli.delete.return_value, + result + ) + self.licence._licensing_cli.delete.assert_called_once_with( + '/appliances/%s/licences/%s' % (mock.sentinel.appliance_id, + mock.sentinel.licence_id), + raw_response=True) From 034300dfbfdc25e3dc04c4fa49c3f5f3b51bef42 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 29 May 2024 15:04:21 +0300 Subject: [PATCH 23/51] Add unit tests for `coriolisclient.v1.logging.py` module --- coriolisclient/tests/v1/test_logging.py | 282 ++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 coriolisclient/tests/v1/test_logging.py diff --git a/coriolisclient/tests/v1/test_logging.py b/coriolisclient/tests/v1/test_logging.py new file mode 100644 index 0000000..a2b449e --- /dev/null +++ b/coriolisclient/tests/v1/test_logging.py @@ -0,0 +1,282 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import copy +import datetime +import ddt +import requests +import tempfile +from unittest import mock + +from keystoneauth1.exceptions import http + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import logging + + +@ddt.ddt +class LoggingClientTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Logging Client.""" + + @mock.patch.object(logging.LoggingClient, '_get_endpoint_url') + def setUp(self, mock_get_endpoint_url): + mock_get_endpoint_url.return_value = mock.sentinel.ep_url + mock_client = mock.Mock() + mock_client.verify = True + super(LoggingClientTestCase, self).setUp() + self.logger = logging.LoggingClient(mock_client) + self.datetime = copy.deepcopy(datetime.datetime) + + @mock.patch.object(logging.LoggingClient, '_get_endpoint_url') + def test__init__(self, mock_get_endpoint_url): + mock_get_endpoint_url.side_effect = Exception + + with self.assertLogs(logger=logging.LOG, level="WARNING"): + logger = logging.LoggingClient(None) + + self.assertEqual( + None, + logger._ep_url + ) + + def test_get_endpoint_url(self): + self.logger._cli.get_endpoint.return_value = "url/endpoint_url/" + + result = self.logger._get_endpoint_url(mock.sentinel.name) + + self.assertEqual( + "url/endpoint_url", + result + ) + self.logger._cli.get_endpoint.assert_called_once_with( + service_type=mock.sentinel.name) + + def test_get_endpoint_url_not_found(self): + self.logger._cli.get_endpoint.return_value = None + + self.assertRaises( + exceptions.LoggingEndpointNotFound, + self.logger._get_endpoint_url, + mock.sentinel.name + ) + self.logger._cli.get_endpoint.assert_called_once_with( + service_type=mock.sentinel.name) + + def test_get_endpoint_url_http_unauthorized(self): + self.logger._cli.get_endpoint.side_effect = http.Unauthorized + + with self.assertLogs(logger=logging.LOG, level="ERROR"): + self.assertRaises( + exceptions.HTTPAuthError, + self.logger._get_endpoint_url, + mock.sentinel.name + ) + self.logger._cli.get_endpoint.assert_called_once_with( + service_type=mock.sentinel.name) + + @ddt.data( + { + "query_args": { + "arg1": None, + "arg2": None + }, + "is_websocket": True, + "expected_result": + "ws:///None/sentinel.resource" + }, + { + "query_args": { + "arg1": None, + "arg2": "mock_arg2" + }, + "_ep_url": "https:///ep_url", + "is_websocket": True, + "expected_result": + "wss:///ep_url/sentinel.resource?arg2=mock_arg2" + }, + { + "query_args": { + "arg1": "mock_arg1", + "arg2": "mock_arg2" + }, + "_ep_url": "https:///ep_url", + "is_websocket": False, + "expected_result": "https:///ep_url/sentinel.resource" + "?arg1=mock_arg1&arg2=mock_arg2" + } + ) + @mock.patch.object(logging.LoggingClient, '_get_endpoint_url') + def test_construct_url(self, data, mock_get_endpoint_url): + self.logger._ep_url = None + mock_get_endpoint_url.return_value = data.get("_ep_url", None) + + result = self.logger._construct_url( + mock.sentinel.resource, + data.get("query_args"), + is_websocket=data.get("is_websocket", False), + ) + + self.assertEqual( + data.get("expected_result"), + result + ) + + @ddt.data( + (None, None, False), + ("1", 1, False), + ("1234567890123456789", None, True), + ("abc", None, True), + ("", None, True), + ) + @ddt.unpack + def test_convert_period_to_timestamp( + self, + period, + expected_result, + raises + ): + if raises is False: + result = self.logger._convert_period_to_timestamp(period) + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + exceptions.CoriolisException, + self.logger._convert_period_to_timestamp, + period + ) + + @mock.patch.object(datetime, 'datetime') + def test_convert_period_to_timestamp_period( + self, + mock_datetime + ): + mock_datetime.utcnow.return_value = self.datetime.fromtimestamp(100000) + result = self.logger._convert_period_to_timestamp("1d") + self.assertEqual( + 13600, + result + ) + + @mock.patch.object(requests, "get") + @mock.patch.object(logging.LoggingClient, "_construct_url") + @mock.patch.object(logging.LoggingClient, "_convert_period_to_timestamp") + def test_download_logs( + self, + mock_convert_period_to_timestamp, + mock_construct_url, + mock_get + ): + mock_r = mock.Mock() + mock_r.iter_content.return_value = [b'test_chunk1', b'test_chunk2'] + mock_get.return_value.__enter__.return_value = mock_r + with tempfile.NamedTemporaryFile() as fd: + self.logger.download_logs( + mock.sentinel.app, + fd.name, + start_time=mock.sentinel.start_time, + end_time=mock.sentinel.end_time + ) + + result = fd.read() + self.assertEqual( + result, + b'test_chunk1test_chunk2' + ) + mock_get.assert_called_once_with( + mock_construct_url.return_value, + headers=self.logger._auth_headers, + stream=True, + verify=True + ) + mock_construct_url.assert_called_once_with( + "logs/sentinel.app/", + { + "start_date": mock_convert_period_to_timestamp.return_value, + "end_date": mock_convert_period_to_timestamp.return_value, + } + ) + + def test_download_logs_no_app(self): + self.assertRaises( + exceptions.CoriolisException, + self.logger.download_logs, + "", + None + ) + + @mock.patch.object(requests, "get") + def test_list_logs(self, mock_get): + mock_get.return_value.raise_for_status.return_value = None + mock_get.return_value.json.return_value = { + "logs": ["mock_log1", "mock_log2"] + } + + result = self.logger.list_logs() + + self.assertEqual( + ['mock_log1', 'mock_log2'], + result + ) + + +class CoriolisLogDownloadManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Coriolis Log Download Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(CoriolisLogDownloadManagerTestCase, self).setUp() + self.logger = logging.CoriolisLogDownloadManager(mock_client) + self.logger._coriolis_cli = mock.Mock() + self.logger.resource_class = mock.Mock() + + def test_list(self): + self.logger._coriolis_cli.list_logs.return_value = [ + "mock_log1", "mock_log2"] + + result = self.logger.list() + + self.assertEqual( + [self.logger.resource_class.return_value, + self.logger.resource_class.return_value], + result + ) + self.logger.resource_class.assert_has_calls([ + mock.call(self.logger, "mock_log1", loaded=True), + mock.call(self.logger, "mock_log2", loaded=True) + ]) + + def test_get(self): + result = self.logger.get( + mock.sentinel.app, + mock.sentinel.to, + start_time=mock.sentinel.start_time, + end_time=mock.sentinel.end_time, + ) + + self.assertEqual( + self.logger._coriolis_cli.download_logs.return_value, + result + ) + self.logger._coriolis_cli.download_logs.assert_called_once_with( + mock.sentinel.app, + mock.sentinel.to, + start_time=mock.sentinel.start_time, + end_time=mock.sentinel.end_time, + ) + + def test_stream(self): + self.logger.stream( + app_name=mock.sentinel.app_name, + severity=mock.sentinel.severity, + ) + + self.logger._coriolis_cli.stream_logs.assert_called_once_with( + app_name=mock.sentinel.app_name, + severity=mock.sentinel.severity, + ) From 0b0d4c772df8b590b34b44fa78d2738e8ac2d52b Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:30:31 +0300 Subject: [PATCH 24/51] Add unit tests for `coriolisclient.v1.migrations.py` module --- coriolisclient/tests/v1/test_migrations.py | 206 +++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 coriolisclient/tests/v1/test_migrations.py diff --git a/coriolisclient/tests/v1/test_migrations.py b/coriolisclient/tests/v1/test_migrations.py new file mode 100644 index 0000000..e139530 --- /dev/null +++ b/coriolisclient/tests/v1/test_migrations.py @@ -0,0 +1,206 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import migrations + + +class MigrationTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Migration.""" + + def test_properties(self): + mock_client = mock.Mock() + self.migration = migrations.Migration( + mock_client, + { + "source_environment": { + "source_environment1": mock.sentinel.source_environment}, + "destination_environment": { + "destination_environment1": + mock.sentinel.destination_environment}, + "transfer_result": { + "transfer_result1": mock.sentinel.transfer_result}, + "tasks": [{"task1": mock.sentinel.task1}, + {"task2": mock.sentinel.task2}] + } + ) + self.assertEqual( + ( + mock.sentinel.source_environment, + mock.sentinel.destination_environment, + mock.sentinel.transfer_result, + mock.sentinel.task1, + mock.sentinel.task2 + ), + ( + self.migration.source_environment.source_environment1, + (self.migration.destination_environment. + destination_environment1), + self.migration.transfer_result.transfer_result1, + self.migration.tasks[0].task1, + self.migration.tasks[1].task2 + ) + ) + + @mock.patch.object(migrations.Migration, "get") + def test_properties_none(self, mock_get): + mock_client = mock.Mock() + self.migration = migrations.Migration( + mock_client, + {} + ) + self.assertEqual( + ( + None, + None, + None, + [] + ), + ( + self.migration.source_environment, + self.migration.destination_environment, + self.migration.transfer_result, + self.migration.tasks, + ) + ) + mock_get.assert_called_once() + + +class MigrationManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Migration Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(MigrationManagerTestCase, self).setUp() + self.migration = migrations.MigrationManager(mock_client) + + @mock.patch.object(migrations.MigrationManager, "_list") + def test_list(self, mock_list): + result = self.migration.list(detail=True) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with("/migrations/detail", "migrations") + + @mock.patch.object(migrations.MigrationManager, "_get") + def test_get(self, mock_get): + result = self.migration.get(mock.sentinel.migration) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/migrations/sentinel.migration", "migration") + + @mock.patch.object(migrations.MigrationManager, "_post") + def test_create(self, mock_post): + expected_data = { + "origin_endpoint_id": mock.sentinel.origin_endpoint_id, + "destination_endpoint_id": mock.sentinel.destination_endpoint_id, + "source_environment": mock.sentinel.source_environment, + "destination_environment": { + "network_map": mock.sentinel.network_map, + "storage_mappings": mock.sentinel.storage_mappings + }, + "instances": mock.sentinel.instances, + "network_map": mock.sentinel.network_map, + "notes": mock.sentinel.notes, + "storage_mappings": mock.sentinel.storage_mappings, + "skip_os_morphing": False, + "replication_count": mock.sentinel.replication_count, + "shutdown_instances": mock.sentinel.shutdown_instances, + "user_scripts": mock.sentinel.user_scripts, + "origin_minion_pool_id": mock.sentinel.origin_minion_pool_id, + "destination_minion_pool_id": + mock.sentinel.destination_minion_pool_id, + "instance_osmorphing_minion_pool_mappings": + mock.sentinel.instance_osmorphing_minion_pool_mappings, + } + expected_data = {"migration": expected_data} + + result = self.migration.create( + mock.sentinel.origin_endpoint_id, + mock.sentinel.destination_endpoint_id, + mock.sentinel.source_environment, + { + "network_map": mock.sentinel.network_map, + "storage_mappings": mock.sentinel.storage_mappings + }, + mock.sentinel.instances, + network_map=None, + notes=mock.sentinel.notes, + storage_mappings=None, + skip_os_morphing=False, + replication_count=mock.sentinel.replication_count, + shutdown_instances=mock.sentinel.shutdown_instances, + user_scripts=mock.sentinel.user_scripts, + origin_minion_pool_id=mock.sentinel.origin_minion_pool_id, + destination_minion_pool_id= + mock.sentinel.destination_minion_pool_id, + instance_osmorphing_minion_pool_mappings= + mock.sentinel.instance_osmorphing_minion_pool_mappings, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/migrations", expected_data, "migration") + + @mock.patch.object(migrations.MigrationManager, "_post") + def test_create_from_replica(self, mock_post): + expected_data = { + "replica_id": mock.sentinel.replica_id, + "clone_disks": False, + "force": False, + "skip_os_morphing": False, + "user_scripts": mock.sentinel.user_scripts, + "instance_osmorphing_minion_pool_mappings": + mock.sentinel.instance_osmorphing_minion_pool_mappings, + } + expected_data = {"migration": expected_data} + + result = self.migration.create_from_replica( + mock.sentinel.replica_id, + clone_disks=False, + force=False, + skip_os_morphing=False, + user_scripts=mock.sentinel.user_scripts, + instance_osmorphing_minion_pool_mappings= + mock.sentinel.instance_osmorphing_minion_pool_mappings, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/migrations", expected_data, "migration") + + @mock.patch.object(migrations.MigrationManager, "_delete") + def test_delete(self, mock_delete): + result = self.migration.delete(mock.sentinel.migration) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/migrations/%s" % mock.sentinel.migration) + + def test_cancel(self): + result = self.migration.cancel(mock.sentinel.migration, force=False) + + self.assertEqual( + self.migration.client.post.return_value, + result + ) + self.migration.client.post.assert_called_once_with( + "/migrations/%s/actions" % mock.sentinel.migration, + json={'cancel': {'force': False}}) From c58d2598ba40f45bcda1c4517f3eb6a2727e2d94 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:30:53 +0300 Subject: [PATCH 25/51] Add unit tests for `coriolisclient.v1.minion_pools.py` module --- coriolisclient/tests/v1/test_minion_pools.py | 141 +++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 coriolisclient/tests/v1/test_minion_pools.py diff --git a/coriolisclient/tests/v1/test_minion_pools.py b/coriolisclient/tests/v1/test_minion_pools.py new file mode 100644 index 0000000..d1d950c --- /dev/null +++ b/coriolisclient/tests/v1/test_minion_pools.py @@ -0,0 +1,141 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import minion_pools + + +class MinionPoolManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Migration Pool Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(MinionPoolManagerTestCase, self).setUp() + self.minion_pool = minion_pools.MinionPoolManager(mock_client) + + @mock.patch.object(minion_pools.MinionPoolManager, "_list") + def test_list(self, mock_list): + result = self.minion_pool.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + "/minion_pools", response_key="minion_pools") + + @mock.patch.object(minion_pools.MinionPoolManager, "_get") + def test_get(self, mock_get): + result = self.minion_pool.get(mock.sentinel.minion_pool) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/minion_pools/sentinel.minion_pool", response_key="minion_pool") + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_create(self, mock_post): + expected_data = { + "name": mock.sentinel.name, + "endpoint_id": mock.sentinel.endpoint, + "platform": mock.sentinel.platform, + "os_type": mock.sentinel.os_type, + "environment_options": mock.sentinel.environment_options, + "minimum_minions": mock.sentinel.minimum_minions, + "maximum_minions": mock.sentinel.maximum_minions, + "minion_max_idle_time": mock.sentinel.minion_max_idle_time, + "minion_retention_strategy": + mock.sentinel.minion_retention_strategy, + "notes": mock.sentinel.notes, + "skip_allocation": False, + } + expected_data = {"minion_pool": expected_data} + + result = self.minion_pool.create( + mock.sentinel.name, + mock.sentinel.endpoint, + mock.sentinel.platform, + mock.sentinel.os_type, + environment_options=mock.sentinel.environment_options, + minimum_minions=mock.sentinel.minimum_minions, + maximum_minions=mock.sentinel.maximum_minions, + minion_max_idle_time=mock.sentinel.minion_max_idle_time, + minion_retention_strategy=mock.sentinel.minion_retention_strategy, + notes=mock.sentinel.notes, + skip_allocation=False, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools", expected_data, response_key="minion_pool") + + @mock.patch.object(minion_pools.MinionPoolManager, "_put") + def test_update(self, mock_put): + result = self.minion_pool.update( + mock.sentinel.minion_pool, mock.sentinel.updated_values) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/minion_pools/%s" % mock.sentinel.minion_pool, + {"minion_pool": mock.sentinel.updated_values}, 'minion_pool') + + @mock.patch.object(minion_pools.MinionPoolManager, "_delete") + def test_delete(self, mock_delete): + result = self.minion_pool.delete(mock.sentinel.minion_pool) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/minion_pools/%s" % mock.sentinel.minion_pool) + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_allocate_minion_pool(self, mock_post): + result = self.minion_pool.allocate_minion_pool( + mock.sentinel.minion_pool) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools/%s/actions" % mock.sentinel.minion_pool, + {'allocate': None}, response_key='minion_pool') + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_refresh_minion_pool(self, mock_post): + result = self.minion_pool.refresh_minion_pool( + mock.sentinel.minion_pool) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools/%s/actions" % mock.sentinel.minion_pool, + {'refresh': None}, response_key='minion_pool') + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_deallocate_minion_pool(self, mock_post): + result = self.minion_pool.deallocate_minion_pool( + mock.sentinel.minion_pool, force=False) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools/%s/actions" % mock.sentinel.minion_pool, + {'deallocate': {'force': False}}, + response_key='minion_pool') From ba9b0d2c67d8579d02959030ea178556aeecafa6 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:31:16 +0300 Subject: [PATCH 26/51] Add unit tests for `coriolisclient.v1.providers.py` module --- coriolisclient/tests/v1/test_providers.py | 85 +++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 coriolisclient/tests/v1/test_providers.py diff --git a/coriolisclient/tests/v1/test_providers.py b/coriolisclient/tests/v1/test_providers.py new file mode 100644 index 0000000..0d14d39 --- /dev/null +++ b/coriolisclient/tests/v1/test_providers.py @@ -0,0 +1,85 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import providers + + +class ProvidersTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Providers.""" + + def test_properties(self): + mock_client = mock.Mock() + self.provider = providers.Providers( + mock_client, + { + "provider1": { + "types": [mock.sentinel.type1, mock.sentinel.type2], + }, + "provider2": { + "types": [mock.sentinel.type3, mock.sentinel.type4], + } + } + ) + expected_provider_list = [ + { + 'name': 'provider1', + 'types': [mock.sentinel.type1, mock.sentinel.type2] + }, + { + 'name': 'provider2', + 'types': [mock.sentinel.type3, mock.sentinel.type4] + } + ] + expected_provider_schemas = [ + { + 'schema': { + 'types': [mock.sentinel.type1, mock.sentinel.type2]}, + 'type': 'provider1' + }, + { + 'schema': { + 'types': [mock.sentinel.type3, mock.sentinel.type4]}, + 'type': 'provider2' + } + ] + + self.assertEqual( + (expected_provider_list, expected_provider_schemas), + (self.provider.providers_list, self.provider.provider_schemas) + ) + + +class ProvidersManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Providers Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(ProvidersManagerTestCase, self).setUp() + self.provider = providers.ProvidersManager(mock_client) + + @mock.patch.object(providers.ProvidersManager, "_get") + def test_list(self, mock_get): + result = self.provider.list() + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with("/providers", "providers") + + @mock.patch.object(providers.ProvidersManager, "_get") + def test_schemas_list(self, mock_get): + result = self.provider.schemas_list( + mock.sentinel.provider_name, mock.sentinel.provider_type) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + '/providers/%s/schemas/%s' % (mock.sentinel.provider_name, + mock.sentinel.provider_type), + "schemas") From 12d3c39a3a49a6ef3eb281c80218d947cca3eb6b Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:31:29 +0300 Subject: [PATCH 27/51] Add unit tests for `coriolisclient.v1.regions.py` module --- coriolisclient/tests/v1/test_regions.py | 196 ++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 coriolisclient/tests/v1/test_regions.py diff --git a/coriolisclient/tests/v1/test_regions.py b/coriolisclient/tests/v1/test_regions.py new file mode 100644 index 0000000..f413821 --- /dev/null +++ b/coriolisclient/tests/v1/test_regions.py @@ -0,0 +1,196 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import regions + + +@ddt.ddt +class RegionManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Region Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(RegionManagerTestCase, self).setUp() + self.region = regions.RegionManager(mock_client) + + @mock.patch.object(regions.RegionManager, "_list") + def test_list(self, mock_list): + result = self.region.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + "/regions", "regions") + + @mock.patch.object(regions.RegionManager, "_get") + def test_get(self, mock_get): + result = self.region.get(mock.sentinel.region) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/regions/sentinel.region", "region") + + @mock.patch.object(regions.RegionManager, "_post") + def test_create(self, mock_post): + expected_data = { + "name": mock.sentinel.name, + "description": mock.sentinel.description, + "enabled": False, + } + expected_data = {"region": expected_data} + + result = self.region.create( + mock.sentinel.name, + description=mock.sentinel.description, + enabled=False, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/regions", expected_data, "region") + + @mock.patch.object(regions.RegionManager, "_put") + def test_update(self, mock_put): + result = self.region.update( + mock.sentinel.region, mock.sentinel.updated_values) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/regions/%s" % mock.sentinel.region, + {"region": mock.sentinel.updated_values}, 'region') + + @mock.patch.object(regions.RegionManager, "_delete") + def test_delete(self, mock_delete): + result = self.region.delete(mock.sentinel.region) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/regions/%s" % mock.sentinel.region) + + @mock.patch.object(regions.RegionManager, "list") + @ddt.data( + { + "region_name_or_id": "mock_name", + "has_regions_cache": True, + "expected_region": "region1", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_name_duplicate", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_name4", + "has_regions_cache": False, + "expected_region": "region4", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_name_not_found", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_name_not_found", + "has_regions_cache": True, + "expected_region": None, + "raise_on_not_found": False, + "raises": False + }, + { + "region_name_or_id": "mock_id", + "has_regions_cache": True, + "expected_region": "region1", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_id_duplicate", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_id4", + "has_regions_cache": False, + "expected_region": "region4", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_id_not_found", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_id_not_found", + "has_regions_cache": True, + "expected_region": None, + "raise_on_not_found": False, + "raises": False + } + ) + def test_get_region_by_name_or_id( + self, + data, + mock_list + ): + self.region1 = mock.Mock() + self.region2 = mock.Mock() + self.region3 = mock.Mock() + self.region4 = mock.Mock() + self.region1.id = "mock_id" + self.region1.name = "mock_name" + self.region2.id = "mock_id_duplicate" + self.region2.name = "mock_name_duplicate" + self.region3.id = "mock_id_duplicate" + self.region3.name = "mock_name_duplicate" + self.region4.id = "mock_id4" + self.region4.name = "mock_name4" + regions_cache = [self.region1, self.region2, self.region3] + if data.get("has_regions_cache") is False: + regions_cache = None + mock_list.return_value = [self.region4] + + if data.get("raises", False): + self.assertRaises( + ValueError, + self.region.get_region_by_name_or_id, + data.get("region_name_or_id"), + regions_cache=regions_cache, + raise_on_not_found=data.get("raise_on_not_found") + ) + else: + result = self.region.get_region_by_name_or_id( + data.get("region_name_or_id"), + regions_cache=regions_cache, + raise_on_not_found=data.get("raise_on_not_found") + ) + self.assertEqual( + getattr(self, str(data.get("expected_region")), None), + result + ) From 9f8c463e1e0196c67aef394335fa0335bd93fe86 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:31:51 +0300 Subject: [PATCH 28/51] Add unit tests for `coriolisclient.v1.replica_executions.py` module --- .../tests/v1/test_replica_executions.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 coriolisclient/tests/v1/test_replica_executions.py diff --git a/coriolisclient/tests/v1/test_replica_executions.py b/coriolisclient/tests/v1/test_replica_executions.py new file mode 100644 index 0000000..53e3d95 --- /dev/null +++ b/coriolisclient/tests/v1/test_replica_executions.py @@ -0,0 +1,112 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import replica_executions + + +class ReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Replica Execution.""" + + def test_tasks(self): + mock_client = mock.Mock() + self.replica_execution = replica_executions.ReplicaExecution( + mock_client, + { + "tasks": [{"task1": mock.sentinel.task1}, + {"task2": mock.sentinel.task2}], + } + ) + self.assertEqual( + ( + mock.sentinel.task1, + mock.sentinel.task2 + ), + ( + self.replica_execution.tasks[0].task1, + self.replica_execution.tasks[1].task2 + ) + ) + + +class ReplicaExecutionManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Replica Execution Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(ReplicaExecutionManagerTestCase, self).setUp() + self.replica_execution = replica_executions.ReplicaExecutionManager( + mock_client) + + @mock.patch.object(replica_executions.ReplicaExecutionManager, "_list") + def test_list(self, mock_list): + result = self.replica_execution.list(mock.sentinel.replica) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + '/replicas/%s/executions' % mock.sentinel.replica, "executions") + + @mock.patch.object(replica_executions.ReplicaExecutionManager, "_get") + def test_get(self, mock_get): + result = self.replica_execution.get( + mock.sentinel.replica, mock.sentinel.execution) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/replicas/%s/executions/%s" % (mock.sentinel.replica, + mock.sentinel.execution), + "execution") + + @mock.patch.object(replica_executions.ReplicaExecutionManager, "_post") + def test_create(self, mock_post): + expected_data = { + "shutdown_instances": mock.sentinel.shutdown_instances + } + expected_data = {"execution": expected_data} + + result = self.replica_execution.create( + mock.sentinel.replica, + shutdown_instances=mock.sentinel.shutdown_instances + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + '/replicas/%s/executions' % mock.sentinel.replica, + expected_data, "execution") + + @mock.patch.object(replica_executions.ReplicaExecutionManager, "_delete") + def test_delete(self, mock_delete): + result = self.replica_execution.delete( + mock.sentinel.replica, mock.sentinel.execution) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/replicas/%s/executions/%s" % (mock.sentinel.replica, + mock.sentinel.execution),) + + def test_cancel(self): + result = self.replica_execution.cancel( + mock.sentinel.replica, mock.sentinel.execution, force=False) + + self.assertEqual( + self.replica_execution.client.post.return_value, + result + ) + self.replica_execution.client.post.assert_called_once_with( + "/replicas/%s/executions/%s/actions" % (mock.sentinel.replica, + mock.sentinel.execution), + json={'cancel': {'force': False}}) From 04d39a00d64f65fe1abb60414022a96094e6513a Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 16:23:11 +0300 Subject: [PATCH 29/51] `coriolisclient.v1.replica_schedules.py`: Remove unnecessary check --- coriolisclient/v1/replica_schedules.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/coriolisclient/v1/replica_schedules.py b/coriolisclient/v1/replica_schedules.py index c439196..5b83f82 100644 --- a/coriolisclient/v1/replica_schedules.py +++ b/coriolisclient/v1/replica_schedules.py @@ -30,13 +30,11 @@ def __init__(self, api): def list(self, replica, hide_expired=False): query = {} + url = '/replicas/%s/schedules' % base.getid(replica) if hide_expired: query["show_expired"] = hide_expired is False - url = '/replicas/%s/schedules' % base.getid(replica) - if query: url += "?" + urlparse.urlencode(query) - return self._list( - url, 'schedules') + return self._list(url, 'schedules') def get(self, replica, schedule): return self._get( From 752122d16a04f18ce8472e27c2b824bb4cca15fb Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 16:23:29 +0300 Subject: [PATCH 30/51] Add unit tests for `coriolisclient.v1.replica_schedules.py` module --- .../tests/v1/test_replica_schedules.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 coriolisclient/tests/v1/test_replica_schedules.py diff --git a/coriolisclient/tests/v1/test_replica_schedules.py b/coriolisclient/tests/v1/test_replica_schedules.py new file mode 100644 index 0000000..6275b6d --- /dev/null +++ b/coriolisclient/tests/v1/test_replica_schedules.py @@ -0,0 +1,126 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import datetime +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import replica_schedules + + +class ReplicaScheduleManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Replica Schedule Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(ReplicaScheduleManagerTestCase, self).setUp() + self.replica_schedule = replica_schedules.ReplicaScheduleManager( + mock_client) + + @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_list") + def test_list(self, mock_list): + result = self.replica_schedule.list( + mock.sentinel.replica, hide_expired=False) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + '/replicas/%s/schedules' % mock.sentinel.replica, "schedules") + + @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_list") + def test_list_hide_expired(self, mock_list): + result = self.replica_schedule.list( + mock.sentinel.replica, hide_expired=True) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + '/replicas/%s/schedules?show_expired=False' + % mock.sentinel.replica, "schedules") + + @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_get") + def test_get(self, mock_get): + result = self.replica_schedule.get( + mock.sentinel.replica, mock.sentinel.schedule) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/replicas/%s/schedules/%s" % (mock.sentinel.replica, + mock.sentinel.schedule), + "schedule") + + @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_post") + def test_create(self, mock_post): + expiration_date = datetime.datetime.fromisoformat("2034-11-26") + expected_data = { + "schedule": mock.sentinel.schedule, + "enabled": True, + "expiration_date": '2034-11-26T00:00:00Z', + "shutdown_instance": mock.sentinel.shutdown_instance + } + + result = self.replica_schedule.create( + mock.sentinel.replica, + mock.sentinel.schedule, + True, + expiration_date, + mock.sentinel.shutdown_instance + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + '/replicas/%s/schedules' % mock.sentinel.replica, + expected_data, "schedule") + + @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_put") + def test_update(self, mock_put): + expiration_date = datetime.datetime.fromisoformat("2034-11-26") + updated_values = {"expiration_date": expiration_date} + result = self.replica_schedule.update( + mock.sentinel.replica_id, + mock.sentinel.schedule_id, + updated_values + ) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/replicas/%s/schedules/%s" % (mock.sentinel.replica_id, + mock.sentinel.schedule_id), + {"expiration_date": '2034-11-26T00:00:00Z'}, + "schedule") + + @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_delete") + def test_delete(self, mock_delete): + result = self.replica_schedule.delete( + mock.sentinel.replica, mock.sentinel.schedule) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/replicas/%s/schedules/%s" % (mock.sentinel.replica, + mock.sentinel.schedule),) + + def test_format_rfc3339_datetime(self): + dt = datetime.date(2024, 1, 1) + + result = self.replica_schedule._format_rfc3339_datetime(dt) + + self.assertEqual( + "2024-01-01Z", + result + ) From cb288543908be2aa564d1e5b90a35bc73147d0cb Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 17:05:59 +0300 Subject: [PATCH 31/51] Add unit tests for `coriolisclient.v1.replicas.py` module --- coriolisclient/tests/v1/test_replicas.py | 207 +++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 coriolisclient/tests/v1/test_replicas.py diff --git a/coriolisclient/tests/v1/test_replicas.py b/coriolisclient/tests/v1/test_replicas.py new file mode 100644 index 0000000..d8f92c0 --- /dev/null +++ b/coriolisclient/tests/v1/test_replicas.py @@ -0,0 +1,207 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import replica_executions +from coriolisclient.v1 import replicas + + +class ReplicaTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Replica.""" + + @mock.patch.object(replicas.Replica, "get") + def test_properties(self, mock_get): + mock_client = mock.Mock() + self.replica = replicas.Replica( + mock_client, + { + "source_environment": { + "source_environment1": + mock.sentinel.source_environment1, + }, + "destination_environment": { + "destination_environment1": + mock.sentinel.destination_environment1, + }, + "executions": [{"execution1": mock.sentinel.execution1}, + {"execution2": mock.sentinel.execution2}], + } + ) + + self.assertEqual( + ( + mock.sentinel.source_environment1, + mock.sentinel.destination_environment1, + mock.sentinel.execution1, + mock.sentinel.execution2 + ), + ( + self.replica.source_environment.source_environment1, + self.replica.destination_environment.destination_environment1, + self.replica.executions[0].execution1, + self.replica.executions[1].execution2, + ) + ) + mock_get.assert_not_called() + + @mock.patch.object(replicas.Replica, "get") + def test_properties_none(self, mock_get): + mock_client = mock.Mock() + self.replica = replicas.Replica( + mock_client, + {} + ) + + self.assertEqual( + ( + None, + None, + [] + ), + ( + self.replica.source_environment, + self.replica.destination_environment, + self.replica.executions + ) + ) + mock_get.assert_called_once() + + +class ReplicaManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Replica Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(ReplicaManagerTestCase, self).setUp() + self.replica = replicas.ReplicaManager(mock_client) + + @mock.patch.object(replicas.ReplicaManager, "_list") + def test_list(self, mock_list): + result = self.replica.list(detail=False) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with("/replicas", "replicas") + + @mock.patch.object(replicas.ReplicaManager, "_list") + def test_list_details(self, mock_list): + result = self.replica.list(detail=True) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with("/replicas/detail", "replicas") + + @mock.patch.object(replicas.ReplicaManager, "_get") + def test_get(self, mock_get): + result = self.replica.get(mock.sentinel.replica) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/replicas/%s" % (mock.sentinel.replica), "replica") + + @mock.patch.object(replicas.ReplicaManager, "_post") + def test_create(self, mock_post): + expected_data = { + "origin_endpoint_id": mock.sentinel.origin_endpoint_id, + "destination_endpoint_id": mock.sentinel.destination_endpoint_id, + "source_environment": mock.sentinel.source_environment, + "destination_environment": { + "network_map": mock.sentinel.network_map, + "storage_mappings": mock.sentinel.storage_mappings + }, + "instances": mock.sentinel.instances, + "network_map": mock.sentinel.network_map, + "notes": mock.sentinel.notes, + "storage_mappings": mock.sentinel.storage_mappings, + "user_scripts": mock.sentinel.user_scripts, + "origin_minion_pool_id": mock.sentinel.origin_minion_pool_id, + "destination_minion_pool_id": + mock.sentinel.destination_minion_pool_id, + "instance_osmorphing_minion_pool_mappings": + mock.sentinel.instance_osmorphing_minion_pool_mappings, + } + expected_data = {"replica": expected_data} + + result = self.replica.create( + mock.sentinel.origin_endpoint_id, + mock.sentinel.destination_endpoint_id, + mock.sentinel.source_environment, + { + "network_map": mock.sentinel.network_map, + "storage_mappings": mock.sentinel.storage_mappings + }, + mock.sentinel.instances, + network_map=None, + notes=mock.sentinel.notes, + storage_mappings=None, + user_scripts=mock.sentinel.user_scripts, + origin_minion_pool_id=mock.sentinel.origin_minion_pool_id, + destination_minion_pool_id= + mock.sentinel.destination_minion_pool_id, + instance_osmorphing_minion_pool_mappings= + mock.sentinel.instance_osmorphing_minion_pool_mappings, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/replicas", expected_data, "replica") + + @mock.patch.object(replicas.ReplicaManager, "_delete") + def test_delete(self, mock_delete): + result = self.replica.delete(mock.sentinel.replica) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/replicas/%s" % mock.sentinel.replica) + + @mock.patch.object(replica_executions, "ReplicaExecution") + def test_delete_disks(self, mock_ReplicaExecution): + result = self.replica.delete_disks(mock.sentinel.replica) + + self.assertEqual( + mock_ReplicaExecution.return_value, + result + ) + self.replica.client.post.assert_called_once_with( + "/replicas/%s/actions" % mock.sentinel.replica, + json={'delete-disks': None}) + mock_ReplicaExecution.assert_called_once_with( + self.replica, + (self.replica.client.post.return_value.json.return_value. + get("execution")), + loaded=True + ) + + @mock.patch.object(replica_executions, "ReplicaExecution") + def test_update(self, mock_ReplicaExecution): + updated_values = {"network_map": mock.sentinel.network_map} + result = self.replica.update(mock.sentinel.replica, updated_values) + + self.assertEqual( + mock_ReplicaExecution.return_value, + result + ) + self.replica.client.put.assert_called_once_with( + "/replicas/%s" % mock.sentinel.replica, + json={"replica": updated_values}) + mock_ReplicaExecution.assert_called_once_with( + self.replica, + (self.replica.client.put.return_value.json.return_value. + get("execution")), + loaded=True + ) From 9359432633ea5ccc22fa7253cb5ef635fe91e1e4 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 17:48:11 +0300 Subject: [PATCH 32/51] Add unit tests for `coriolisclient.v1.services.py` module --- coriolisclient/tests/v1/test_services.py | 148 +++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 coriolisclient/tests/v1/test_services.py diff --git a/coriolisclient/tests/v1/test_services.py b/coriolisclient/tests/v1/test_services.py new file mode 100644 index 0000000..98cf133 --- /dev/null +++ b/coriolisclient/tests/v1/test_services.py @@ -0,0 +1,148 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import services + + +@ddt.ddt +class ServiceManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Service Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(ServiceManagerTestCase, self).setUp() + self.service = services.ServiceManager(mock_client) + + @mock.patch.object(services.ServiceManager, "_list") + def test_list(self, mock_list): + result = self.service.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + "/services", "services") + + @mock.patch.object(services.ServiceManager, "_get") + def test_get(self, mock_get): + result = self.service.get(mock.sentinel.service) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/services/sentinel.service", "service") + + @mock.patch.object(services.ServiceManager, "_post") + def test_create(self, mock_post): + expected_data = { + "host": mock.sentinel.host, + "binary": mock.sentinel.binary, + "topic": mock.sentinel.topic, + "mapped_regions": mock.sentinel.regions, + "enabled": True + } + expected_data = {"service": expected_data} + + result = self.service.create( + mock.sentinel.host, + mock.sentinel.binary, + mock.sentinel.topic, + mock.sentinel.regions, + enabled=True, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/services", expected_data, "service") + + @mock.patch.object(services.ServiceManager, "_put") + def test_update(self, mock_put): + result = self.service.update( + mock.sentinel.service, mock.sentinel.updated_values) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/services/%s" % mock.sentinel.service, + {"service": mock.sentinel.updated_values}, 'service') + + @mock.patch.object(services.ServiceManager, "_delete") + def test_delete(self, mock_delete): + result = self.service.delete(mock.sentinel.service) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/services/%s" % mock.sentinel.service) + + @mock.patch.object(services.ServiceManager, "list") + @ddt.data( + { + "host": "mock_host", + "topic": "mock_topic", + "expected_service": "service1", + "raises": False + }, + { + "host": "mock_host", + "topic": "mock_topic_not_found", + "raises": True + }, + { + "host": "mock_host_duplicate", + "topic": "mock_topic_duplicate", + "raises": True + }, + { + "host": "mock_host_not_found", + "topic": "mock_topic", + "raises": True + }, + ) + def test_find_service_by_host_and_topic( + self, + data, + mock_list + ): + self.service1 = mock.Mock() + self.service2 = mock.Mock() + self.service3 = mock.Mock() + self.service1.host = "mock_host" + self.service1.topic = "mock_topic" + self.service2.host = "mock_host_duplicate" + self.service2.topic = "mock_topic_duplicate" + self.service3.host = "mock_host_duplicate" + self.service3.topic = "mock_topic_duplicate" + services = [self.service1, self.service2, self.service3] + mock_list.return_value = services + + if data.get("raises", False): + self.assertRaises( + ValueError, + self.service.find_service_by_host_and_topic, + data.get("host"), + data.get("topic") + ) + else: + result = self.service.find_service_by_host_and_topic( + data.get("host"), + data.get("topic") + ) + self.assertEqual( + getattr(self, str(data.get("expected_service")), None), + result + ) From e0a77f06e83dab39fed965ce1ec30484fae76e82 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 5 Jun 2024 15:07:01 +0300 Subject: [PATCH 33/51] Add unit tests for `coriolisclient.base.py` module --- coriolisclient/tests/test_base.py | 473 +++++++++++++++++++++++++++++- coriolisclient/tests/testutils.py | 24 ++ 2 files changed, 495 insertions(+), 2 deletions(-) create mode 100644 coriolisclient/tests/testutils.py diff --git a/coriolisclient/tests/test_base.py b/coriolisclient/tests/test_base.py index 7c8c9b1..dd6dd2f 100644 --- a/coriolisclient/tests/test_base.py +++ b/coriolisclient/tests/test_base.py @@ -3,8 +3,14 @@ """Defines base class for all tests.""" -from oslotest import base +from keystoneauth1 import exceptions as keystoneauth_exceptions +from oslotest import base as test_base from oslotest import mock_fixture +from unittest import mock + +from coriolisclient import base +from coriolisclient import exceptions +from coriolisclient.tests import testutils # NOTE(claudiub): this needs to be called before any mock.patch calls are # being done, and especially before any other test classes load. This fixes @@ -13,7 +19,470 @@ mock_fixture.patch_mock_module() -class CoriolisBaseTestCase(base.BaseTestCase): +class CoriolisBaseTestCase(test_base.BaseTestCase): def setUp(self): super(CoriolisBaseTestCase, self).setUp() + + +class BaseTestCase(CoriolisBaseTestCase): + """Test suite for the Coriolis Client Base.""" + + def test_getid_uuid(self): + obj = mock.Mock() + + result = base.getid(obj) + + self.assertEqual( + obj.uuid, + result + ) + + def test_getid_id(self): + obj = mock.Mock() + + result = base.getid(obj, possible_fields=["id"]) + + self.assertEqual( + obj.id, + result + ) + + def test_getid_id_no_fields(self): + obj = mock.Mock() + + result = base.getid(obj, possible_fields=None) + + self.assertEqual( + obj.id, + result + ) + + def test_getid_none(self): + obj = "mock_obj" + + result = base.getid(obj, possible_fields=None) + + self.assertEqual( + obj, + result + ) + + def test_wrap_unauthorized_exception(self): + + @base.wrap_unauthorized_exception + def mock_fun(func): + return func() + + func = mock.Mock() + result = mock_fun(func) + + self.assertEqual( + func.return_value, + result + ) + + def test_wrap_unauthorized_exception_http_unauthorized(self): + + @base.wrap_unauthorized_exception + def mock_fun(func): + return func() + + func = mock.Mock() + func.side_effect = keystoneauth_exceptions.http.Unauthorized() + + with self.assertLogs(logger=base.LOG, level="ERROR"): + self.assertRaises( + exceptions.HTTPAuthError, + mock_fun, + func + ) + + +class ResourceTestCase(CoriolisBaseTestCase): + """Test suite for the Coriolis Client Resource.""" + + def setUp(self): + mock_manager = mock.Mock() + super(ResourceTestCase, self).setUp() + self.resource = base.Resource( + mock_manager, + {"info": mock.sentinel.info} + ) + + def test__repr__(self): + result = repr(self.resource) + self.assertEqual( + "" % mock.sentinel.info, + result + ) + + def test_human_id(self): + self.assertEqual( + None, + self.resource.human_id + ) + + class HumanResource(base.Resource): + HUMAN_ID = True + + mock_manager = mock.Mock() + resource = HumanResource( + mock_manager, + {"name": "mock_name"} + ) + + self.assertEqual( + "mock_name", + resource.human_id + ) + + def test_add_details(self): + new_info = {"new_info": mock.sentinel.new_info} + + self.resource._add_details(new_info) + + self.assertEqual( + mock.sentinel.new_info, + self.resource.new_info + ) + + def test_add_details_attribute_already_defined(self): + info = {"human_id": mock.sentinel.human_id} + + self.resource._add_details(info) + + self.assertEqual( + None, + self.resource._info.get("human_id") + ) + + @mock.patch.object(base.Resource, "is_loaded") + @mock.patch.object(base.Resource, "get") + def test__getattr__(self, mock_get, mock_is_loaded): + mock_is_loaded.side_effect = [False, True] + mock_get.side_effect = lambda: setattr(self.resource, "attr", "value") + + self.assertEqual( + "value", + self.resource.attr + ) + + @mock.patch.object(base.Resource, "is_loaded") + @mock.patch.object(base.Resource, "get") + def test__getattr__error(self, mock_get, mock_is_loaded): + mock_is_loaded.side_effect = [False, True] + + self.assertRaises( + AttributeError, + self.resource.__getattr__, + "attr" + ) + mock_get.assert_called_once() + + @mock.patch.object(base.Resource, "_add_details") + def test_get(self, mock_add_details): + self.resource._loaded = False + self.resource.id = mock.sentinel.id + + self.resource.get() + + mock_add_details.assert_called_once_with( + self.resource.manager.get.return_value._info) + self.assertEqual(True, self.resource._loaded) + + @mock.patch.object(base.Resource, "_add_details") + def test_get_none(self, mock_add_details): + self.resource._loaded = False + self.resource.id = mock.sentinel.id + delattr(self.resource.manager, "get") + + self.resource.get() + + mock_add_details.assert_not_called() + + def test__eq__(self): + mock_manager = mock.Mock() + resource2 = base.Resource( + mock_manager, + {"info": mock.sentinel.info} + ) + + self.assertEqual( + ( + True, + NotImplemented, + ), + ( + self.resource.__eq__(resource2), + self.resource.__eq__("string"), + ) + ) + + resource2._info = {"info": mock.sentinel.info2} + + self.assertEqual( + False, + self.resource.__eq__(resource2) + ) + + def test__eq__different_subclasses(self): + mock_manager = mock.Mock() + + class SubResource1(base.Resource): + pass + + class SubResource2(base.Resource): + pass + + resource1 = SubResource1( + mock_manager, + {"info": mock.sentinel.info} + ) + resource2 = SubResource2( + mock_manager, + {"info": mock.sentinel.info} + ) + + self.assertEqual( + False, + resource1.__eq__(resource2) + ) + + def test_is_loaded(self): + self.resource._loaded = True + self.assertEqual(True, self.resource.is_loaded()) + self.resource._loaded = False + self.assertEqual(False, self.resource.is_loaded()) + + def test_set_loaded(self): + self.resource.set_loaded(True) + self.assertEqual(True, self.resource._loaded) + self.resource.set_loaded(False) + self.assertEqual(False, self.resource._loaded) + + def test_to_dict(self): + result = self.resource.to_dict() + + self.assertEqual( + {'info': mock.sentinel.info}, + result + ) + + +class BaseManagerTestCase(CoriolisBaseTestCase): + """Test suite for the Coriolis Client Base Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(BaseManagerTestCase, self).setUp() + self.manager = base.BaseManager(mock_client) + + def test_list(self): + self.manager.client.get(mock.sentinel.url).json.return_value = { + "mock_response_key": { + "data": [mock.sentinel.data1, mock.sentinel.data2]} + } + obj_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._list)( + self.manager, + url=mock.sentinel.url, + response_key="mock_response_key", + obj_class=obj_class, + json=None, + values_key="data" + ) + + self.assertEqual( + [obj_class.return_value] * 2, + result + ) + obj_class.assert_has_calls([ + mock.call(self.manager, mock.sentinel.data1, loaded=True), + mock.call(self.manager, mock.sentinel.data2, loaded=True) + ]) + + def test_list_json(self): + (self.manager.client.post(mock.sentinel.url, json=True).json. + return_value) = [mock.sentinel.data] + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._list)( + self.manager, + url=mock.sentinel.url, + response_key=None, + obj_class=None, + json=True, + values_key=None + ) + + self.assertEqual( + [self.manager.resource_class.return_value], + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data, loaded=True) + + def test_get(self): + self.manager.client.get().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._get)( + self.manager, + url=mock.sentinel.url, + response_key="mock_response_key" + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data, loaded=True) + + def test_get_no_response_key(self): + self.manager.client.get().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._get)( + self.manager, + url=mock.sentinel.url, + response_key=None + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data, loaded=True) + + def test_post(self): + self.manager.client.post().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._post)( + self.manager, + url=mock.sentinel.url, + json=True, + response_key="mock_response_key", + return_raw=False + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_post_no_response_key(self): + self.manager.client.post().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._post)( + self.manager, + url=mock.sentinel.url, + json=False, + response_key=None, + return_raw=True + ) + + self.assertEqual( + mock.sentinel.data, + result + ) + self.manager.resource_class.assert_not_called() + + def test_put(self): + self.manager.client.put().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._put)( + self.manager, + url=mock.sentinel.url, + json=True, + response_key="mock_response_key" + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_put_no_response_key(self): + self.manager.client.put().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._put)( + self.manager, + url=mock.sentinel.url, + json=False, + response_key=None + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_patch(self): + self.manager.client.patch().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._patch)( + self.manager, + url=mock.sentinel.url, + json=True, + response_key="mock_response_key" + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_patch_no_response_key(self): + self.manager.client.patch().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._patch)( + self.manager, + url=mock.sentinel.url, + json=False, + response_key=None + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_delete(self): + result = testutils.get_wrapped_function(self.manager._delete)( + self.manager, + url=mock.sentinel.url + ) + + self.assertEqual( + self.manager.client.delete.return_value, + result + ) + self.manager.client.delete.assert_called_once_with(mock.sentinel.url) diff --git a/coriolisclient/tests/testutils.py b/coriolisclient/tests/testutils.py new file mode 100644 index 0000000..418a7b9 --- /dev/null +++ b/coriolisclient/tests/testutils.py @@ -0,0 +1,24 @@ +"""Defines general utilities for all tests.""" + + +def get_wrapped_function(function): + """Get the method at the bottom of a stack of decorators.""" + if not hasattr(function, '__closure__') or not function.__closure__: + return function + + def _get_wrapped_function(function): + if not hasattr(function, '__closure__') or not function.__closure__: + return None + + for closure in function.__closure__: + func = closure.cell_contents + + deeper_func = _get_wrapped_function(func) + if deeper_func: + return deeper_func + elif hasattr(closure.cell_contents, '__call__'): + return closure.cell_contents + + return function + + return _get_wrapped_function(function) From dd1ea31c07b75e12590710f7fe0e2f8ec5614a49 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 7 Jun 2024 11:14:05 +0300 Subject: [PATCH 34/51] Add unit tests for `coriolisclient.client.py` module --- coriolisclient/tests/test_client.py | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 coriolisclient/tests/test_client.py diff --git a/coriolisclient/tests/test_client.py b/coriolisclient/tests/test_client.py new file mode 100644 index 0000000..f4fdf41 --- /dev/null +++ b/coriolisclient/tests/test_client.py @@ -0,0 +1,40 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient import client as coriolis_client +from coriolisclient.tests import test_base + + +class _HTTPClientTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis HTTP Client.""" + + def test__init__(self): + session = mock.Mock() + self.client = coriolis_client._HTTPClient( + session, endpoint=mock.sentinel.endpoint, version='v0') + self.assertEqual( + "%s/%s" % (mock.sentinel.endpoint, 'v0'), + self.client.endpoint_override + ) + + def test__init__no_endpoint(self): + session = mock.Mock() + self.client = coriolis_client._HTTPClient(session) + self.assertEqual( + None, + self.client.endpoint_override + ) + + +class ClientTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client.""" + + @mock.patch.object(coriolis_client, "_HTTPClient") + def test__init__(self, mock_HTTPClient): + try: + self.client = coriolis_client.Client(session=mock.sentinel.session) + except Exception: + self.fail("Failed to initialize Client") + mock_HTTPClient.assert_called_once_with(session=mock.sentinel.session) From 2239e6750f8ee780ebef341e0d57ea51975b4274 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Thu, 11 Apr 2024 13:00:41 +0300 Subject: [PATCH 35/51] Add '/deployments' resource operations. Signed-off-by: Nashwan Azhari --- coriolisclient/cli/deployments.py | 279 ++++++++++++++++++++++++++++++ coriolisclient/client.py | 2 + coriolisclient/v1/deployments.py | 85 +++++++++ setup.cfg | 6 + 4 files changed, 372 insertions(+) create mode 100644 coriolisclient/cli/deployments.py create mode 100644 coriolisclient/v1/deployments.py diff --git a/coriolisclient/cli/deployments.py b/coriolisclient/cli/deployments.py new file mode 100644 index 0000000..0337cd5 --- /dev/null +++ b/coriolisclient/cli/deployments.py @@ -0,0 +1,279 @@ +# Copyright (c) 2024 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Command-line interface sub-commands related to deployments. +""" + +import os + +from cliff import command +from cliff import lister +from cliff import show + +from coriolisclient.cli import formatter +from coriolisclient.cli import utils as cli_utils + + +class DeploymentFormatter(formatter.EntityFormatter): + + columns = ("ID", + "Status", + "Instances", + "Notes", + "Created", + ) + + def _get_sorted_list(self, obj_list): + return sorted(obj_list, key=lambda o: o.created_at) + + def _get_formatted_data(self, obj): + data = (obj.id, + obj.last_execution_status, + "\n".join(obj.instances), + obj.notes, + obj.created_at, + ) + return data + + +class DeploymentDetailFormatter(formatter.EntityFormatter): + + def __init__(self, show_instances_data=False): + self.columns = [ + "id", + "status", + "created", + "last_updated", + "replica_id", + "replica_scenario_type", + "reservation_id", + "instances", + "notes", + "origin_endpoint_id", + "origin_minion_pool_id", + "destination_endpoint_id", + "destination_minion_pool_id", + "instance_osmorphing_minion_pool_mappings", + "replication_count", + "shutdown_instances", + "destination_environment", + "source_environment", + "network_map", + "disk_storage_mappings", + "storage_backend_mappings", + "default_storage_backend", + "user_scripts", + "tasks", + "transfer_result" + ] + + if show_instances_data: + self.columns.append("instances_data") + + def _format_instances(self, obj): + return os.linesep.join(sorted(obj.instances)) + + def _format_progress_updates(self, task_dict): + return ("%(ls)s" % {"ls": os.linesep}).join( + [self._format_progress_update(p) for p in + sorted(task_dict.get("progress_updates", []), + key=lambda p: (p.get('index', 0), p["created_at"]))]) + + def _format_task(self, task): + d = task.to_dict() + d["depends_on"] = ", ".join(d.get("depends_on") or []) + + progress_updates_format = "progress_updates:" + progress_updates = self._format_progress_updates(d) + if progress_updates: + progress_updates_format += os.linesep + progress_updates_format += progress_updates + + return os.linesep.join( + ["%s: %s" % (k, d.get(k) or "") for k in + ['id', + 'task_type', + 'instance', + 'status', + 'depends_on', + 'exception_details']] + + [progress_updates_format]) + + def _format_tasks(self, obj): + return ("%(ls)s%(ls)s" % {"ls": os.linesep}).join( + [self._format_task(t) for t in obj.tasks]) + + def _get_formatted_data(self, obj): + storage_mappings = obj.to_dict().get("storage_mappings", {}) + default_storage, backend_mappings, disk_mappings = ( + cli_utils.parse_storage_mappings(storage_mappings)) + data = [obj.id, + obj.last_execution_status, + obj.created_at, + obj.updated_at, + getattr(obj, 'replica_id', ""), + getattr(obj, 'replica_scenario_type', ""), + obj.reservation_id, + self._format_instances(obj), + obj.notes, + obj.origin_endpoint_id, + obj.origin_minion_pool_id, + obj.destination_endpoint_id, + obj.destination_minion_pool_id, + cli_utils.format_json_for_object_property( + obj, 'instance_osmorphing_minion_pool_mappings'), + getattr(obj, 'replication_count', None), + getattr(obj, 'shutdown_instances', False), + cli_utils.format_json_for_object_property( + obj, prop_name="destination_environment"), + cli_utils.format_json_for_object_property( + obj, 'source_environment'), + cli_utils.format_json_for_object_property(obj, 'network_map'), + cli_utils.format_mapping(disk_mappings), + cli_utils.format_mapping(backend_mappings), + default_storage, + cli_utils.format_json_for_object_property(obj, 'user_scripts'), + self._format_tasks(obj), + cli_utils.format_json_for_object_property( + obj, 'transfer_result') + ] + + if "instances_data" in self.columns: + data.append(obj.info) + + return data + + +class CreateDeployment(show.ShowOne): + """Start a new deployment from an existing replica""" + def get_parser(self, prog_name): + parser = super(CreateDeployment, self).get_parser(prog_name) + parser.add_argument('replica', + help='The ID of the replica to migrate') + parser.add_argument('--force', + help='Force the deployment in case of a replica ' + 'with failed executions', action='store_true', + default=False) + parser.add_argument('--dont-clone-disks', + help='Retain the replica disks by cloning them', + action='store_false', dest="clone_disks", + default=True) + parser.add_argument('--skip-os-morphing', + help='Skip the OS morphing process', + action='store_true', + default=False) + parser.add_argument('--user-script-global', action='append', + required=False, + dest="global_scripts", + help='A script that will run for a particular ' + 'os_type. This option can be used multiple ' + 'times. Use: linux=/path/to/script.sh or ' + 'windows=/path/to/script.ps1') + parser.add_argument('--user-script-instance', action='append', + required=False, + dest="instance_scripts", + help='A script that will run for a particular ' + 'instance specified by the --instance option. ' + 'This option can be used multiple times. ' + 'Use: "instance_name"=/path/to/script.sh.' + ' This option overwrites any OS specific script ' + 'specified in --user-script-global for this ' + 'instance') + cli_utils.add_minion_pool_args_to_parser( + parser, include_origin_pool_arg=False, + include_destination_pool_arg=False, + include_osmorphing_pool_mappings_arg=True) + + return parser + + def take_action(self, args): + m = self.app.client_manager.coriolis.deployments + user_scripts = cli_utils.compose_user_scripts( + args.global_scripts, args.instance_scripts) + instance_osmorphing_minion_pool_mappings = None + if args.instance_osmorphing_minion_pool_mappings: + instance_osmorphing_minion_pool_mappings = { + mp['instance_id']: mp['pool_id'] + for mp in args.instance_osmorphing_minion_pool_mappings} + + deployment = m.create_from_replica( + args.replica, + args.clone_disks, + args.force, + args.skip_os_morphing, + user_scripts=user_scripts, + instance_osmorphing_minion_pool_mappings=( + instance_osmorphing_minion_pool_mappings)) + + return DeploymentDetailFormatter().get_formatted_entity(deployment) + + +class ShowDeployment(show.ShowOne): + """Show a deployment""" + + def get_parser(self, prog_name): + parser = super(ShowDeployment, self).get_parser(prog_name) + parser.add_argument('id', help='The deployment\'s id') + parser.add_argument('--show-instances-data', action='store_true', + help='Includes the instances data used for tasks ' + 'execution, this is useful for troubleshooting', + default=False) + return parser + + def take_action(self, args): + deployment = self.app.client_manager.coriolis.deployments.get(args.id) + return DeploymentDetailFormatter( + args.show_instances_data).get_formatted_entity(deployment) + + +class CancelDeployment(command.Command): + """Cancel a deployment""" + + def get_parser(self, prog_name): + parser = super(CancelDeployment, self).get_parser(prog_name) + parser.add_argument('id', help='The deployment\'s id') + parser.add_argument('--force', + help='Perform a forced termination of running ' + 'tasks', action='store_true', + default=False) + return parser + + def take_action(self, args): + self.app.client_manager.coriolis.deployments.cancel(args.id, args.force) + + +class DeleteDeployment(command.Command): + """Delete a deployment""" + + def get_parser(self, prog_name): + parser = super(DeleteDeployment, self).get_parser(prog_name) + parser.add_argument('id', help='The deployment\'s id') + return parser + + def take_action(self, args): + self.app.client_manager.coriolis.deployments.delete(args.id) + + +class ListDeployment(lister.Lister): + """List deployments""" + + def get_parser(self, prog_name): + parser = super(ListDeployment, self).get_parser(prog_name) + return parser + + def take_action(self, args): + obj_list = self.app.client_manager.coriolis.deployments.list() + return DeploymentFormatter().list_objects(obj_list) diff --git a/coriolisclient/client.py b/coriolisclient/client.py index d032d3d..50594b0 100644 --- a/coriolisclient/client.py +++ b/coriolisclient/client.py @@ -17,6 +17,7 @@ from keystoneauth1 import adapter +from coriolisclient.v1 import deployments from coriolisclient.v1 import diagnostics from coriolisclient.v1 import endpoint_destination_minion_pool_options from coriolisclient.v1 import endpoint_destination_options @@ -84,6 +85,7 @@ def __init__(self, session=None, *args, **kwargs): endpoint_source_options.EndpointSourceOptionsManager(httpclient)) self.endpoint_storage = endpoint_storage.EndpointStorageManager( httpclient) + self.deployments = deployments.DeploymentManager(httpclient) self.migrations = migrations.MigrationManager(httpclient) self.minion_pools = minion_pools.MinionPoolManager(httpclient) self.providers = providers.ProvidersManager(httpclient) diff --git a/coriolisclient/v1/deployments.py b/coriolisclient/v1/deployments.py new file mode 100644 index 0000000..99990b2 --- /dev/null +++ b/coriolisclient/v1/deployments.py @@ -0,0 +1,85 @@ +# Copyright (c) 2024 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from coriolisclient import base +from coriolisclient.v1 import common + + +class Deployment(base.Resource): + _tasks = None + + @property + def source_environment(self): + source_env = self._info.get("source_environment") + if source_env is not None: + return common.SourceEnvironment(None, source_env, loaded=True) + + @property + def destination_environment(self): + dest_env = self._info.get("destination_environment") + if dest_env is not None: + return common.DestinationEnvironment(None, dest_env, loaded=True) + + @property + def transfer_result(self): + res = self._info.get("transfer_result") + if res is not None: + return common.TransferResult(None, res, loaded=True) + + @property + def tasks(self): + if self._info.get('tasks') is None: + self.get() + return [common.Task(None, d, loaded=True) for d in + self._info.get('tasks', [])] + + +class DeploymentManager(base.BaseManager): + resource_class = Deployment + + def __init__(self, api): + super(DeploymentManager, self).__init__(api) + + def list(self, detail=False): + path = "/deployments" + if detail: + path = "%s/detail" % path + return self._list(path, 'deployments') + + def get(self, deployment): + return self._get('/deployments/%s' % base.getid(deployment), 'deployment') + + def create_from_replica(self, replica_id, clone_disks=True, force=False, + skip_os_morphing=False, user_scripts=None, + instance_osmorphing_minion_pool_mappings=None): + data = { + "deployment": { + "replica_id": replica_id, + "clone_disks": clone_disks, + "force": force, + "skip_os_morphing": skip_os_morphing, + "user_scripts": user_scripts}} + if instance_osmorphing_minion_pool_mappings is not None: + data['deployment']['instance_osmorphing_minion_pool_mappings'] = ( + instance_osmorphing_minion_pool_mappings) + return self._post('/deployments', data, 'deployment') + + def delete(self, deployment): + return self._delete('/deployment/%s' % base.getid(deployment)) + + def cancel(self, deployment, force=False): + return self.client.post( + '/deployments/%s/actions' % base.getid(deployment), + json={'cancel': {'force': force}}) diff --git a/setup.cfg b/setup.cfg index eeaabb9..e39bbbe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,12 @@ coriolis.v1 = migration_list = coriolisclient.cli.migrations:ListMigration migration_show = coriolisclient.cli.migrations:ShowMigration + deployment_cancel = coriolisclient.cli.deployments:CancelDeployment + deployment_create = coriolisclient.cli.deployments:CreateDeployment + deployment_delete = coriolisclient.cli.deployments:DeleteDeployment + deployment_list = coriolisclient.cli.deployments:ListDeployment + deployment_show = coriolisclient.cli.deployments:ShowDeployment + provider_list = coriolisclient.cli.providers:ListProvider provider_schema_list = coriolisclient.cli.providers:ListProviderSchemas From ba6127a27a78637a02e1a0c81cb85efbf5973f6d Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Thu, 11 Apr 2024 13:14:08 +0300 Subject: [PATCH 36/51] Add '--scenario' argument to Replica creation. Signed-off-by: Nashwan Azhari --- coriolisclient/cli/deployments.py | 4 ++-- coriolisclient/cli/replicas.py | 21 +++++++++++++++++++++ coriolisclient/v1/replicas.py | 2 ++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/coriolisclient/cli/deployments.py b/coriolisclient/cli/deployments.py index 0337cd5..eba108d 100644 --- a/coriolisclient/cli/deployments.py +++ b/coriolisclient/cli/deployments.py @@ -124,8 +124,8 @@ def _get_formatted_data(self, obj): obj.last_execution_status, obj.created_at, obj.updated_at, - getattr(obj, 'replica_id', ""), - getattr(obj, 'replica_scenario_type', ""), + obj.replica_id, + obj.replica_scenario_type, obj.reservation_id, self._format_instances(obj), obj.notes, diff --git a/coriolisclient/cli/replicas.py b/coriolisclient/cli/replicas.py index b4ab7be..29a3cbf 100644 --- a/coriolisclient/cli/replicas.py +++ b/coriolisclient/cli/replicas.py @@ -28,6 +28,10 @@ from coriolisclient.cli import utils as cli_utils +REPLICA_SCENARIO_REPLICA = "replica" +REPLICA_SCENARIO_LIVE_MIGRATION = "live_migration" + + class ReplicaFormatter(formatter.EntityFormatter): columns = ("ID", @@ -63,6 +67,7 @@ def __init__(self, show_instances_data=False): "id", "created", "last_updated", + "scenario_type", "reservation_id", "instances", "notes", @@ -102,6 +107,7 @@ def _get_formatted_data(self, obj): data = [obj.id, obj.created_at, obj.updated_at, + getattr(obj, "scenario", ""), obj.reservation_id, self._format_instances(obj), obj.notes, @@ -141,6 +147,20 @@ def get_parser(self, prog_name): dest="instances", metavar="INSTANCE_IDENTIFIER", help='The identifier of a source instance to be ' 'replicated. Can be specified multiple times') + parser.add_argument('--scenario', + dest="scenario", metavar="SCENARIO", + choices=[ + REPLICA_SCENARIO_REPLICA, + REPLICA_SCENARIO_LIVE_MIGRATION], + default=REPLICA_SCENARIO_REPLICA, + help='The type of scenario to use when creating ' + 'the Replica. "replica" will create a ' + 'monthly-billed Replica which can be ' + 'executed and deployed as many times as ' + 'desired, while "live_migration" will ' + 'create a Replica which can be synced ' + 'as many times as needed but only ' + 'deployed once.') parser.add_argument('--notes', dest='notes', help='Notes about the replica') parser.add_argument('--user-script-global', action='append', @@ -202,6 +222,7 @@ def take_action(self, args): source_environment, destination_environment, args.instances, + args.scenario, network_map=network_map, notes=args.notes, storage_mappings=storage_mappings, diff --git a/coriolisclient/v1/replicas.py b/coriolisclient/v1/replicas.py index e82c023..48a3b5c 100644 --- a/coriolisclient/v1/replicas.py +++ b/coriolisclient/v1/replicas.py @@ -58,6 +58,7 @@ def get(self, replica): def create(self, origin_endpoint_id, destination_endpoint_id, source_environment, destination_environment, instances, + replica_scenario, network_map=None, notes=None, storage_mappings=None, origin_minion_pool_id=None, destination_minion_pool_id=None, instance_osmorphing_minion_pool_mappings=None, @@ -73,6 +74,7 @@ def create(self, origin_endpoint_id, destination_endpoint_id, "destination_endpoint_id": destination_endpoint_id, "destination_environment": destination_environment, "instances": instances, + "scenario": replica_scenario, "network_map": network_map, "notes": notes, "storage_mappings": storage_mappings, From 38a81e0c7bb12ebc8afe3c0343a69eed3433ea61 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Fri, 12 Apr 2024 16:27:24 +0300 Subject: [PATCH 37/51] Add 'replica_id' field to `deployment list` subcommand. Signed-off-by: Nashwan Azhari --- coriolisclient/cli/deployments.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coriolisclient/cli/deployments.py b/coriolisclient/cli/deployments.py index eba108d..0ef00f1 100644 --- a/coriolisclient/cli/deployments.py +++ b/coriolisclient/cli/deployments.py @@ -30,6 +30,7 @@ class DeploymentFormatter(formatter.EntityFormatter): columns = ("ID", + "Replica ID", "Status", "Instances", "Notes", @@ -41,6 +42,7 @@ def _get_sorted_list(self, obj_list): def _get_formatted_data(self, obj): data = (obj.id, + obj.replica_id, obj.last_execution_status, "\n".join(obj.instances), obj.notes, From a25c233d663a1d7bba00ce6a75ef65afb0e31398 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Fri, 12 Apr 2024 16:27:39 +0300 Subject: [PATCH 38/51] Add 'scenario' field to replica list` subcommand. Signed-off-by: Nashwan Azhari --- coriolisclient/cli/replicas.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coriolisclient/cli/replicas.py b/coriolisclient/cli/replicas.py index 29a3cbf..a99d195 100644 --- a/coriolisclient/cli/replicas.py +++ b/coriolisclient/cli/replicas.py @@ -35,6 +35,7 @@ class ReplicaFormatter(formatter.EntityFormatter): columns = ("ID", + "Scenario", "Instances", "Notes", "Last Execution Status", @@ -52,6 +53,7 @@ def _format_last_execution(self, obj): def _get_formatted_data(self, obj): data = (obj.id, + getattr(obj, "scenario", "replica"), "\n".join(obj.instances), obj.notes, obj.last_execution_status, From a8618ac39f8cde8185ea382366cb8cf12b2351f8 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Thu, 8 Aug 2024 17:00:00 +0300 Subject: [PATCH 39/51] Remove `migration` subcommands --- coriolisclient/cli/migrations.py | 375 ------------- coriolisclient/client.py | 2 - coriolisclient/tests/cli/test_migrations.py | 568 -------------------- coriolisclient/v1/migrations.py | 125 ----- 4 files changed, 1070 deletions(-) delete mode 100644 coriolisclient/cli/migrations.py delete mode 100644 coriolisclient/tests/cli/test_migrations.py delete mode 100644 coriolisclient/v1/migrations.py diff --git a/coriolisclient/cli/migrations.py b/coriolisclient/cli/migrations.py deleted file mode 100644 index 7c10b56..0000000 --- a/coriolisclient/cli/migrations.py +++ /dev/null @@ -1,375 +0,0 @@ -# Copyright (c) 2016 Cloudbase Solutions Srl -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Command-line interface sub-commands related to migrations. -""" - -import os - -from cliff import command -from cliff import lister -from cliff import show - -from coriolisclient.cli import formatter -from coriolisclient.cli import utils as cli_utils - - -class MigrationFormatter(formatter.EntityFormatter): - - columns = ("ID", - "Status", - "Instances", - "Notes", - "Created", - ) - - def _get_sorted_list(self, obj_list): - return sorted(obj_list, key=lambda o: o.created_at) - - def _get_formatted_data(self, obj): - data = (obj.id, - obj.last_execution_status, - "\n".join(obj.instances), - obj.notes, - obj.created_at, - ) - return data - - -class MigrationDetailFormatter(formatter.EntityFormatter): - - def __init__(self, show_instances_data=False): - self.columns = [ - "id", - "status", - "created", - "last_updated", - "reservation_id", - "instances", - "notes", - "origin_endpoint_id", - "origin_minion_pool_id", - "destination_endpoint_id", - "destination_minion_pool_id", - "instance_osmorphing_minion_pool_mappings", - "replication_count", - "shutdown_instances", - "destination_environment", - "source_environment", - "network_map", - "disk_storage_mappings", - "storage_backend_mappings", - "default_storage_backend", - "user_scripts", - "tasks", - "transfer_result" - ] - - if show_instances_data: - self.columns.append("instances_data") - - def _format_instances(self, obj): - return os.linesep.join(sorted(obj.instances)) - - def _format_progress_updates(self, task_dict): - return ("%(ls)s" % {"ls": os.linesep}).join( - [self._format_progress_update(p) for p in - sorted(task_dict.get("progress_updates", []), - key=lambda p: (p.get('index', 0), p["created_at"]))]) - - def _format_task(self, task): - d = task.to_dict() - d["depends_on"] = ", ".join(d.get("depends_on") or []) - - progress_updates_format = "progress_updates:" - progress_updates = self._format_progress_updates(d) - if progress_updates: - progress_updates_format += os.linesep - progress_updates_format += progress_updates - - return os.linesep.join( - ["%s: %s" % (k, d.get(k) or "") for k in - ['id', - 'task_type', - 'instance', - 'status', - 'depends_on', - 'exception_details']] + - [progress_updates_format]) - - def _format_tasks(self, obj): - return ("%(ls)s%(ls)s" % {"ls": os.linesep}).join( - [self._format_task(t) for t in obj.tasks]) - - def _get_formatted_data(self, obj): - storage_mappings = obj.to_dict().get("storage_mappings", {}) - default_storage, backend_mappings, disk_mappings = ( - cli_utils.parse_storage_mappings(storage_mappings)) - data = [obj.id, - obj.last_execution_status, - obj.created_at, - obj.updated_at, - obj.reservation_id, - self._format_instances(obj), - obj.notes, - obj.origin_endpoint_id, - obj.origin_minion_pool_id, - obj.destination_endpoint_id, - obj.destination_minion_pool_id, - cli_utils.format_json_for_object_property( - obj, 'instance_osmorphing_minion_pool_mappings'), - getattr(obj, 'replication_count', None), - getattr(obj, 'shutdown_instances', False), - cli_utils.format_json_for_object_property( - obj, prop_name="destination_environment"), - cli_utils.format_json_for_object_property( - obj, 'source_environment'), - cli_utils.format_json_for_object_property(obj, 'network_map'), - cli_utils.format_mapping(disk_mappings), - cli_utils.format_mapping(backend_mappings), - default_storage, - cli_utils.format_json_for_object_property(obj, 'user_scripts'), - self._format_tasks(obj), - cli_utils.format_json_for_object_property( - obj, 'transfer_result') - ] - - if "instances_data" in self.columns: - data.append(obj.info) - - return data - - -class CreateMigration(show.ShowOne): - """Start a new migration""" - def get_parser(self, prog_name): - parser = super(CreateMigration, self).get_parser(prog_name) - parser.add_argument('--origin-endpoint', required=True, - help='The origin endpoint id') - parser.add_argument('--destination-endpoint', required=True, - help='The destination endpoint id') - parser.add_argument('--instance', action='append', required=True, - dest="instances", metavar="INSTANCE_IDENTIFIER", - help='The identifier of a source instance to be ' - 'migrated. Can be specified multiple times') - parser.add_argument('--notes', dest='notes', - help='Notes about the migration') - parser.add_argument('--user-script-global', action='append', - required=False, - dest="global_scripts", - help='A script that will run for a particular ' - 'os_type. This option can be used multiple ' - 'times. Use: linux=/path/to/script.sh or ' - 'windows=/path/to/script.ps1') - parser.add_argument('--user-script-instance', action='append', - required=False, - dest="instance_scripts", - help='A script that will run for a particular ' - 'instance specified by the --instance option. ' - 'This option can be used multiple times. ' - 'Use: "instance_name"=/path/to/script.sh.' - ' This option overwrites any OS specific script ' - 'specified in --user-script-global for this ' - 'instance') - parser.add_argument('--skip-os-morphing', - help='Skip the OS morphing process', - action='store_true', - default=False) - parser.add_argument('--replication-count', - type=int, - help='Number of times to perform a replica sync ' - 'before deploying the migrated instance.') - parser.add_argument('--shutdown-instances', - action='store_true', - help='Whether or not to shut down the instance on ' - 'the source platform before performing the ' - 'final Replica sync') - - cli_utils.add_args_for_json_option_to_parser( - parser, 'destination-environment') - cli_utils.add_args_for_json_option_to_parser(parser, 'network-map') - cli_utils.add_args_for_json_option_to_parser( - parser, 'source-environment') - cli_utils.add_storage_mappings_arguments_to_parser(parser) - cli_utils.add_minion_pool_args_to_parser( - parser, include_origin_pool_arg=True, - include_destination_pool_arg=True, - include_osmorphing_pool_mappings_arg=True) - - return parser - - def take_action(self, args): - destination_environment = cli_utils.get_option_value_from_args( - args, 'destination-environment') - source_environment = cli_utils.get_option_value_from_args( - args, 'source-environment') - network_map = cli_utils.get_option_value_from_args( - args, 'network-map') - storage_mappings = cli_utils.get_storage_mappings_dict_from_args(args) - endpoints = self.app.client_manager.coriolis.endpoints - origin_endpoint_id = endpoints.get_endpoint_id_for_name( - args.origin_endpoint) - destination_endpoint_id = endpoints.get_endpoint_id_for_name( - args.destination_endpoint) - user_scripts = cli_utils.compose_user_scripts( - args.global_scripts, args.instance_scripts) - instance_osmorphing_minion_pool_mappings = None - if args.instance_osmorphing_minion_pool_mappings: - instance_osmorphing_minion_pool_mappings = { - mp['instance_id']: mp['pool_id'] - for mp in args.instance_osmorphing_minion_pool_mappings} - - migration = self.app.client_manager.coriolis.migrations.create( - origin_endpoint_id, - destination_endpoint_id, - source_environment, - destination_environment, - args.instances, - network_map=network_map, - notes=args.notes, - storage_mappings=storage_mappings, - skip_os_morphing=args.skip_os_morphing, - replication_count=args.replication_count, - shutdown_instances=args.shutdown_instances, - origin_minion_pool_id=args.origin_minion_pool_id, - destination_minion_pool_id=args.destination_minion_pool_id, - instance_osmorphing_minion_pool_mappings=( - instance_osmorphing_minion_pool_mappings), - user_scripts=user_scripts) - - return MigrationDetailFormatter().get_formatted_entity(migration) - - -class CreateMigrationFromReplica(show.ShowOne): - """Start a new migration from an existing replica""" - def get_parser(self, prog_name): - parser = super(CreateMigrationFromReplica, self).get_parser(prog_name) - parser.add_argument('replica', - help='The ID of the replica to migrate') - parser.add_argument('--force', - help='Force the migration in case of a replica ' - 'with failed executions', action='store_true', - default=False) - parser.add_argument('--dont-clone-disks', - help='Retain the replica disks by cloning them', - action='store_false', dest="clone_disks", - default=True) - parser.add_argument('--skip-os-morphing', - help='Skip the OS morphing process', - action='store_true', - default=False) - parser.add_argument('--user-script-global', action='append', - required=False, - dest="global_scripts", - help='A script that will run for a particular ' - 'os_type. This option can be used multiple ' - 'times. Use: linux=/path/to/script.sh or ' - 'windows=/path/to/script.ps1') - parser.add_argument('--user-script-instance', action='append', - required=False, - dest="instance_scripts", - help='A script that will run for a particular ' - 'instance specified by the --instance option. ' - 'This option can be used multiple times. ' - 'Use: "instance_name"=/path/to/script.sh.' - ' This option overwrites any OS specific script ' - 'specified in --user-script-global for this ' - 'instance') - cli_utils.add_minion_pool_args_to_parser( - parser, include_origin_pool_arg=False, - include_destination_pool_arg=False, - include_osmorphing_pool_mappings_arg=True) - - return parser - - def take_action(self, args): - m = self.app.client_manager.coriolis.migrations - user_scripts = cli_utils.compose_user_scripts( - args.global_scripts, args.instance_scripts) - instance_osmorphing_minion_pool_mappings = None - if args.instance_osmorphing_minion_pool_mappings: - instance_osmorphing_minion_pool_mappings = { - mp['instance_id']: mp['pool_id'] - for mp in args.instance_osmorphing_minion_pool_mappings} - - migration = m.create_from_replica( - args.replica, - args.clone_disks, - args.force, - args.skip_os_morphing, - user_scripts=user_scripts, - instance_osmorphing_minion_pool_mappings=( - instance_osmorphing_minion_pool_mappings)) - - return MigrationDetailFormatter().get_formatted_entity(migration) - - -class ShowMigration(show.ShowOne): - """Show a migration""" - - def get_parser(self, prog_name): - parser = super(ShowMigration, self).get_parser(prog_name) - parser.add_argument('id', help='The migration\'s id') - parser.add_argument('--show-instances-data', action='store_true', - help='Includes the instances data used for tasks ' - 'execution, this is useful for troubleshooting', - default=False) - return parser - - def take_action(self, args): - migration = self.app.client_manager.coriolis.migrations.get(args.id) - return MigrationDetailFormatter( - args.show_instances_data).get_formatted_entity(migration) - - -class CancelMigration(command.Command): - """Cancel a migration""" - - def get_parser(self, prog_name): - parser = super(CancelMigration, self).get_parser(prog_name) - parser.add_argument('id', help='The migration\'s id') - parser.add_argument('--force', - help='Perform a forced termination of running ' - 'tasks', action='store_true', - default=False) - return parser - - def take_action(self, args): - self.app.client_manager.coriolis.migrations.cancel(args.id, args.force) - - -class DeleteMigration(command.Command): - """Delete a migration""" - - def get_parser(self, prog_name): - parser = super(DeleteMigration, self).get_parser(prog_name) - parser.add_argument('id', help='The migration\'s id') - return parser - - def take_action(self, args): - self.app.client_manager.coriolis.migrations.delete(args.id) - - -class ListMigration(lister.Lister): - """List migrations""" - - def get_parser(self, prog_name): - parser = super(ListMigration, self).get_parser(prog_name) - return parser - - def take_action(self, args): - obj_list = self.app.client_manager.coriolis.migrations.list() - return MigrationFormatter().list_objects(obj_list) diff --git a/coriolisclient/client.py b/coriolisclient/client.py index 50594b0..95a1dfa 100644 --- a/coriolisclient/client.py +++ b/coriolisclient/client.py @@ -32,7 +32,6 @@ from coriolisclient.v1 import licensing_reservations from coriolisclient.v1 import licensing_server from coriolisclient.v1 import logging as coriolis_logging -from coriolisclient.v1 import migrations from coriolisclient.v1 import minion_pools from coriolisclient.v1 import providers from coriolisclient.v1 import regions @@ -86,7 +85,6 @@ def __init__(self, session=None, *args, **kwargs): self.endpoint_storage = endpoint_storage.EndpointStorageManager( httpclient) self.deployments = deployments.DeploymentManager(httpclient) - self.migrations = migrations.MigrationManager(httpclient) self.minion_pools = minion_pools.MinionPoolManager(httpclient) self.providers = providers.ProvidersManager(httpclient) self.replicas = replicas.ReplicaManager(httpclient) diff --git a/coriolisclient/tests/cli/test_migrations.py b/coriolisclient/tests/cli/test_migrations.py deleted file mode 100644 index 85d9720..0000000 --- a/coriolisclient/tests/cli/test_migrations.py +++ /dev/null @@ -1,568 +0,0 @@ -# Copyright 2024 Cloudbase Solutions Srl -# All Rights Reserved. - -import os -from unittest import mock - -from cliff import command -from cliff import lister -from cliff import show - -from coriolisclient.cli import formatter -from coriolisclient.cli import migrations -from coriolisclient.cli import utils as cli_utils -from coriolisclient.tests import test_base - - -class MigrationFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Migration Formatter.""" - - def setUp(self): - super(MigrationFormatterTestCase, self).setUp() - self.migration = migrations.MigrationFormatter() - - def test_get_sorted_list(self): - obj1 = mock.Mock() - obj2 = mock.Mock() - obj3 = mock.Mock() - obj1.created_at = "date1" - obj2.created_at = "date2" - obj3.created_at = "date3" - obj_list = [obj2, obj1, obj3] - - result = self.migration._get_sorted_list(obj_list) - - self.assertEqual( - [obj1, obj2, obj3], - result - ) - - def test_get_formatted_data(self): - obj = mock.Mock() - obj.id = mock.sentinel.id - obj.last_execution_status = mock.sentinel.last_execution_status - obj.instances = ["mock_instance3", "mock_instance1", "mock_instance2"] - obj.notes = mock.sentinel.notes - obj.created_at = mock.sentinel.created_at - - result = self.migration._get_formatted_data(obj) - - self.assertEqual( - ( - mock.sentinel.id, - mock.sentinel.last_execution_status, - ('mock_instance3%(ls)smock_instance1%(ls)smock_instance2' - % {"ls": os.linesep}), - mock.sentinel.notes, - mock.sentinel.created_at - ), - result - ) - - -class MigrationDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Migration Detail Formatter.""" - - def setUp(self): - super(MigrationDetailFormatterTestCase, self).setUp() - self.migration = migrations.MigrationDetailFormatter( - show_instances_data=True) - - def test_format_instances(self): - obj = mock.Mock() - obj.instances = ["mock_instance3", "mock_instance1", "mock_instance2"] - expected_result = ( - 'mock_instance1%(ls)smock_instance2%(ls)smock_instance3' - % {"ls": os.linesep} - ) - - result = self.migration._format_instances(obj) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(formatter.EntityFormatter, '_format_progress_update') - def test_format_progress_updates(self, mock_format_progress_update): - update1 = {"created_at": "date2", "message2": "message2"} - update2 = {"created_at": "date3", "message3": "message3"} - update3 = {"index": 2, "created_at": "date1"} - ret_update1 = "date2 [10] message2" - ret_update2 = "date3 [20] message3" - ret_update3 = "date1 [30] None" - task_dict = {"progress_updates": [update3, update1, update2]} - mock_format_progress_update.side_effect = [ - ret_update1, ret_update2, ret_update3] - expected_result = ( - 'date2 [10] message2%(ls)sdate3 [20] message3%(ls)s' - 'date1 [30] None' % {"ls": os.linesep} - ) - - result = self.migration._format_progress_updates(task_dict) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, - '_format_progress_updates') - def test_format_task(self, mock_format_progress_updates): - mock_task = mock.Mock() - task = { - "depends_on": ["mock_dep1", "mock_dep2"], - "id": mock.sentinel.id, - "task_type": mock.sentinel.task_type, - "instance": mock.sentinel.instance, - "status": mock.sentinel.status, - "exception_details": mock.sentinel.exception_details - } - mock_task.to_dict.return_value = task - mock_format_progress_updates.return_value = ( - 'date2 [10] message2%(ls)sdate3 [20] message3%(ls)s' - 'date1 [30] None' % {"ls": os.linesep} - ) - expected_result = ( - 'id: sentinel.id%(ls)s' - 'task_type: sentinel.task_type%(ls)s' - 'instance: sentinel.instance%(ls)s' - 'status: sentinel.status%(ls)s' - 'depends_on: mock_dep1, mock_dep2%(ls)s' - 'exception_details: sentinel.exception_details%(ls)s' - 'progress_updates:%(ls)s' - 'date2 [10] message2%(ls)s' - 'date3 [20] message3%(ls)s' - 'date1 [30] None' % {"ls": os.linesep}) - - result = self.migration._format_task(mock_task) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, - '_format_progress_updates') - def test_format_task_no_progress_updates( - self, mock_format_progress_updates): - mock_task = mock.Mock() - task = { - "depends_on": ["mock_dep1", "mock_dep2"], - "id": mock.sentinel.id, - "task_type": mock.sentinel.task_type, - "instance": mock.sentinel.instance, - "status": mock.sentinel.status, - "exception_details": mock.sentinel.exception_details - } - mock_task.to_dict.return_value = task - mock_format_progress_updates.return_value = None - expected_result = ( - 'id: sentinel.id%(ls)s' - 'task_type: sentinel.task_type%(ls)s' - 'instance: sentinel.instance%(ls)s' - 'status: sentinel.status%(ls)s' - 'depends_on: mock_dep1, mock_dep2%(ls)s' - 'exception_details: sentinel.exception_details%(ls)s' - 'progress_updates:' % {"ls": os.linesep} - ) - - result = self.migration._format_task(mock_task) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, '_format_task') - def test_format_tasks(self, mock_format_task): - obj = mock.Mock() - obj.tasks = [mock.sentinel.task1, mock.sentinel.task2] - mock_format_task.side_effect = ["task1", "task2"] - expected_result = 'task1%(ls)s%(ls)stask2' % {"ls": os.linesep} - - result = self.migration._format_tasks(obj) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, '_format_tasks') - @mock.patch.object(cli_utils, 'format_mapping') - @mock.patch.object(cli_utils, 'format_json_for_object_property') - @mock.patch.object(migrations.MigrationDetailFormatter, - '_format_instances') - @mock.patch.object(cli_utils, 'parse_storage_mappings') - def test_get_formatted_data( - self, - mock_parse_storage_mappings, - mock_format_instances, - mock_format_json_for_object_property, - mock_format_mapping, - mock_format_tasks - ): - mock_obj = mock.Mock() - obj = { - "storage_mappings": {'default_storage': mock.sentinel.storage} - } - mock_obj.to_dict.return_value = obj - mock_obj.id = mock.sentinel.id - mock_obj.last_execution_status = mock.sentinel.last_execution_status - mock_obj.created_at = mock.sentinel.created_at - mock_obj.updated_at = mock.sentinel.updated_at - mock_obj.reservation_id = mock.sentinel.reservation_id - mock_format_instances.return_value = mock.sentinel.formatted_instances - mock_obj.notes = mock.sentinel.notes - mock_obj.origin_endpoint_id = mock.sentinel.origin_endpoint_id - mock_obj.origin_minion_pool_id = mock.sentinel.origin_minion_pool_id - mock_obj.destination_endpoint_id = \ - mock.sentinel.destination_endpoint_id - mock_obj.destination_minion_pool_id = \ - mock.sentinel.destination_minion_pool_id - mock_obj.replication_count = mock.sentinel.replication_count - mock_obj.shutdown_instances = mock.sentinel.shutdown_instances - mock_parse_storage_mappings.return_value = ( - mock.sentinel.default_storage, - mock.sentinel.backend_mappings, - mock.sentinel.disk_mappings - ) - mock_format_json_for_object_property.side_effect = [ - mock.sentinel.instance_osmorphing_minion_pool_mappings, - mock.sentinel.destination_environment, - mock.sentinel.source_environment, - mock.sentinel.network_map, - mock.sentinel.user_scripts, - mock.sentinel.transfer_result, - ] - mock_format_mapping.side_effect = [ - mock.sentinel.disk_mapping, mock.sentinel.backend_mappings] - mock_format_tasks.return_value = mock.sentinel.formatted_tasks - mock_obj.info = mock.sentinel.info - expected_result = [ - mock.sentinel.id, - mock.sentinel.last_execution_status, - mock.sentinel.created_at, - mock.sentinel.updated_at, - mock.sentinel.reservation_id, - mock.sentinel.formatted_instances, - mock.sentinel.notes, - mock.sentinel.origin_endpoint_id, - mock.sentinel.origin_minion_pool_id, - mock.sentinel.destination_endpoint_id, - mock.sentinel.destination_minion_pool_id, - mock.sentinel.instance_osmorphing_minion_pool_mappings, - mock.sentinel.replication_count, - mock.sentinel.shutdown_instances, - mock.sentinel.destination_environment, - mock.sentinel.source_environment, - mock.sentinel.network_map, - mock.sentinel.disk_mapping, - mock.sentinel.backend_mappings, - mock.sentinel.default_storage, - mock.sentinel.user_scripts, - mock.sentinel.formatted_tasks, - mock.sentinel.transfer_result, - mock.sentinel.info - ] - - result = self.migration._get_formatted_data(mock_obj) - - self.assertEqual( - expected_result, - result - ) - - -class CreateMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Create Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(CreateMigrationTestCase, self).setUp() - self.migration = migrations.CreateMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(cli_utils, 'add_args_for_json_option_to_parser') - @mock.patch.object(show.ShowOne, 'get_parser') - def test_get_parser( - self, - mock_get_parser, - *_ - ): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(cli_utils, 'compose_user_scripts') - @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') - @mock.patch.object(cli_utils, 'get_option_value_from_args') - @mock.patch.object(migrations.MigrationDetailFormatter, - 'get_formatted_entity') - def test_take_action( - self, - mock_get_formatted_entity, - mock_get_option_value_from_args, - mock_get_storage_mappings_dict_from_args, - mock_compose_user_scripts - ): - args = mock.Mock() - args.instances = mock.sentinel.instances - args.notes = mock.sentinel.notes - args.skip_os_morphing = mock.sentinel.skip_os_morphing - args.replication_count = mock.sentinel.replication_count - args.shutdown_instances = mock.sentinel.shutdown_instances - args.origin_minion_pool_id = mock.sentinel.origin_minion_pool_id - args.destination_minion_pool_id = \ - mock.sentinel.destination_minion_pool_id - args.instance_osmorphing_minion_pool_mappings = [ - {'instance_id': "instance_id1", 'pool_id': "pool_id1"}, - {'instance_id': "instance_id2", 'pool_id': "pool_id2"} - ] - mock_endpoints = mock.Mock() - mock_migrations = mock.Mock() - self.mock_app.client_manager.coriolis.endpoints = mock_endpoints - mock_endpoints.get_endpoint_id_for_name.side_effect = [ - mock.sentinel.origin_endpoint_id, - mock.sentinel.destination_endpoint_id - ] - self.mock_app.client_manager.coriolis.migrations.create = \ - mock_migrations - mock_get_option_value_from_args.side_effect = [ - mock.sentinel.destination_environment, - mock.sentinel.source_environment, - mock.sentinel.network_map, - ] - - result = self.migration.take_action(args) - - self.assertEqual( - mock_get_formatted_entity.return_value, - result - ) - mock_migrations.assert_called_once_with( - mock.sentinel.origin_endpoint_id, - mock.sentinel.destination_endpoint_id, - mock.sentinel.source_environment, - mock.sentinel.destination_environment, - mock.sentinel.instances, - network_map=mock.sentinel.network_map, - notes=mock.sentinel.notes, - storage_mappings=(mock_get_storage_mappings_dict_from_args. - return_value), - skip_os_morphing=mock.sentinel.skip_os_morphing, - replication_count=mock.sentinel.replication_count, - shutdown_instances=mock.sentinel.shutdown_instances, - origin_minion_pool_id=mock.sentinel.origin_minion_pool_id, - destination_minion_pool_id= - mock.sentinel.destination_minion_pool_id, - instance_osmorphing_minion_pool_mappings={ - 'instance_id1': 'pool_id1', 'instance_id2': 'pool_id2'}, - user_scripts=mock_compose_user_scripts.return_value - ) - mock_get_formatted_entity.assert_called_once_with( - mock_migrations.return_value) - - -class CreateMigrationFromReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Create Migration From Replica.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(CreateMigrationFromReplicaTestCase, self).setUp() - self.migration = migrations.CreateMigrationFromReplica( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(cli_utils, 'add_args_for_json_option_to_parser') - @mock.patch.object(show.ShowOne, 'get_parser') - def test_get_parser( - self, - mock_get_parser, - *_ - ): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(cli_utils, 'compose_user_scripts') - @mock.patch.object(migrations.MigrationDetailFormatter, - 'get_formatted_entity') - def test_take_action( - self, - mock_get_formatted_entity, - mock_compose_user_scripts - ): - args = mock.Mock() - args.replica = mock.sentinel.replica - args.clone_disks = mock.sentinel.clone_disks - args.force = mock.sentinel.force - args.skip_os_morphing = mock.sentinel.skip_os_morphing - args.instance_osmorphing_minion_pool_mappings = [ - {'instance_id': "instance_id1", 'pool_id': "pool_id1"}, - {'instance_id': "instance_id2", 'pool_id': "pool_id2"} - ] - mock_migrations = mock.Mock() - self.mock_app.client_manager.coriolis.migrations = mock_migrations - - result = self.migration.take_action(args) - - self.assertEqual( - mock_get_formatted_entity.return_value, - result - ) - mock_migrations.create_from_replica.assert_called_once_with( - mock.sentinel.replica, - mock.sentinel.clone_disks, - mock.sentinel.force, - mock.sentinel.skip_os_morphing, - user_scripts=mock_compose_user_scripts.return_value, - instance_osmorphing_minion_pool_mappings={ - 'instance_id1': 'pool_id1', 'instance_id2': 'pool_id2'} - ) - mock_get_formatted_entity.assert_called_once_with( - mock_migrations.create_from_replica.return_value) - - -class ShowMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(ShowMigrationTestCase, self).setUp() - self.migration = migrations.ShowMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(show.ShowOne, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(migrations.MigrationDetailFormatter, - 'get_formatted_entity') - def test_take_action(self, mock_get_formatted_entity): - args = mock.Mock() - args.id = mock.sentinel.id - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.get = mock_migration - - result = self.migration.take_action(args) - - self.assertEqual( - mock_get_formatted_entity.return_value, - result - ) - mock_migration.assert_called_once_with(mock.sentinel.id) - mock_get_formatted_entity.assert_called_once_with( - mock_migration.return_value) - - -class CancelMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Cancel Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(CancelMigrationTestCase, self).setUp() - self.migration = migrations.CancelMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(command.Command, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - def test_take_action(self): - args = mock.Mock() - args.id = mock.sentinel.id - args.force = mock.sentinel.force - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.cancel = \ - mock_migration - - self.migration.take_action(args) - - mock_migration.assert_called_once_with( - mock.sentinel.id, mock.sentinel.force) - - -class DeleteMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(DeleteMigrationTestCase, self).setUp() - self.migration = migrations.DeleteMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(command.Command, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - def test_take_action(self): - args = mock.Mock() - args.id = mock.sentinel.id - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.delete = \ - mock_migration - - self.migration.take_action(args) - - mock_migration.assert_called_once_with(mock.sentinel.id) - - -class ListMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(ListMigrationTestCase, self).setUp() - self.migration = migrations.ListMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(lister.Lister, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(migrations.MigrationFormatter, 'list_objects') - def test_take_action(self, mock_list_objects): - args = mock.Mock() - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.list = \ - mock_migration - - result = self.migration.take_action(args) - - self.assertEqual( - mock_list_objects.return_value, - result - ) - mock_list_objects.assert_called_once_with(mock_migration.return_value) diff --git a/coriolisclient/v1/migrations.py b/coriolisclient/v1/migrations.py deleted file mode 100644 index e625abd..0000000 --- a/coriolisclient/v1/migrations.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright (c) 2016 Cloudbase Solutions Srl -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from coriolisclient import base -from coriolisclient.v1 import common - - -class Migration(base.Resource): - _tasks = None - - @property - def source_environment(self): - source_env = self._info.get("source_environment") - if source_env is not None: - return common.SourceEnvironment(None, source_env, loaded=True) - - @property - def destination_environment(self): - dest_env = self._info.get("destination_environment") - if dest_env is not None: - return common.DestinationEnvironment(None, dest_env, loaded=True) - - @property - def transfer_result(self): - res = self._info.get("transfer_result") - if res is not None: - return common.TransferResult(None, res, loaded=True) - - @property - def tasks(self): - if self._info.get('tasks') is None: - self.get() - return [common.Task(None, d, loaded=True) for d in - self._info.get('tasks', [])] - - -class MigrationManager(base.BaseManager): - resource_class = Migration - - def __init__(self, api): - super(MigrationManager, self).__init__(api) - - def list(self, detail=False): - path = "/migrations" - if detail: - path = "%s/detail" % path - return self._list(path, 'migrations') - - def get(self, migration): - return self._get('/migrations/%s' % base.getid(migration), 'migration') - - def create(self, origin_endpoint_id, destination_endpoint_id, - source_environment, destination_environment, instances, - network_map=None, notes=None, storage_mappings=None, - skip_os_morphing=False, replication_count=None, - shutdown_instances=None, user_scripts=None, - origin_minion_pool_id=None, destination_minion_pool_id=None, - instance_osmorphing_minion_pool_mappings=None): - if not network_map: - network_map = destination_environment.get('network_map', {}) - if not storage_mappings: - storage_mappings = destination_environment.get( - 'storage_mappings', {}) - - data = { - "migration": { - "origin_endpoint_id": origin_endpoint_id, - "destination_endpoint_id": destination_endpoint_id, - "destination_environment": destination_environment, - "instances": instances, - "skip_os_morphing": skip_os_morphing, - "network_map": network_map, - "notes": notes, - "storage_mappings": storage_mappings, - "user_scripts": user_scripts}} - if source_environment is not None: - data['migration']['source_environment'] = source_environment - if shutdown_instances is not None: - data['migration']['shutdown_instances'] = shutdown_instances - if replication_count is not None: - data['migration']['replication_count'] = replication_count - if origin_minion_pool_id is not None: - data['migration']['origin_minion_pool_id'] = origin_minion_pool_id - if destination_minion_pool_id is not None: - data['migration']['destination_minion_pool_id'] = ( - destination_minion_pool_id) - if instance_osmorphing_minion_pool_mappings: - data['migration']['instance_osmorphing_minion_pool_mappings'] = ( - instance_osmorphing_minion_pool_mappings) - - return self._post('/migrations', data, 'migration') - - def create_from_replica(self, replica_id, clone_disks=True, force=False, - skip_os_morphing=False, user_scripts=None, - instance_osmorphing_minion_pool_mappings=None): - data = {"migration": { - "replica_id": replica_id, - "clone_disks": clone_disks, - "force": force, - "skip_os_morphing": skip_os_morphing, - "user_scripts": user_scripts}} - if instance_osmorphing_minion_pool_mappings is not None: - data['migration']['instance_osmorphing_minion_pool_mappings'] = ( - instance_osmorphing_minion_pool_mappings) - return self._post('/migrations', data, 'migration') - - def delete(self, migration): - return self._delete('/migrations/%s' % base.getid(migration)) - - def cancel(self, migration, force=False): - return self.client.post( - '/migrations/%s/actions' % base.getid(migration), - json={'cancel': {'force': force}}) From 9c5ad3fccc73912c787b6a9c3e073c36ffe00be4 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Thu, 8 Aug 2024 17:07:44 +0300 Subject: [PATCH 40/51] Fix existing unit tests --- coriolisclient/cli/deployments.py | 3 ++- coriolisclient/tests/cli/test_replicas.py | 6 ++++++ coriolisclient/v1/deployments.py | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/coriolisclient/cli/deployments.py b/coriolisclient/cli/deployments.py index 0ef00f1..ac33550 100644 --- a/coriolisclient/cli/deployments.py +++ b/coriolisclient/cli/deployments.py @@ -254,7 +254,8 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - self.app.client_manager.coriolis.deployments.cancel(args.id, args.force) + self.app.client_manager.coriolis.deployments.cancel( + args.id, args.force) class DeleteDeployment(command.Command): diff --git a/coriolisclient/tests/cli/test_replicas.py b/coriolisclient/tests/cli/test_replicas.py index 834da82..293d982 100644 --- a/coriolisclient/tests/cli/test_replicas.py +++ b/coriolisclient/tests/cli/test_replicas.py @@ -80,6 +80,7 @@ def test_get_formatted_data(self): obj.last_execution_status = mock.sentinel.last_execution_status obj.instances = ["mock_instance3", "mock_instance1", "mock_instance2"] obj.notes = mock.sentinel.notes + obj.scenario = mock.sentinel.scenario obj.created_at = mock.sentinel.created_at result = self.replica._get_formatted_data(obj) @@ -87,6 +88,7 @@ def test_get_formatted_data(self): self.assertEqual( ( mock.sentinel.id, + mock.sentinel.scenario, ('mock_instance3%(ls)smock_instance1%(ls)smock_instance2' % {"ls": "\n"}), mock.sentinel.notes, @@ -211,10 +213,12 @@ def test_get_formatted_data( mock_format_executions.return_value = \ mock.sentinel.formatted_executions mock_obj.info = mock.sentinel.info + mock_obj.scenario = mock.sentinel.scenario expected_result = [ mock.sentinel.id, mock.sentinel.created_at, mock.sentinel.updated_at, + mock.sentinel.scenario, mock.sentinel.reservation_id, mock.sentinel.formatted_instances, mock.sentinel.notes, @@ -281,6 +285,7 @@ def test_take_action( args = mock.Mock() args.instances = mock.sentinel.instances args.notes = mock.sentinel.notes + args.scenario = mock.sentinel.scenario args.origin_minion_pool_id = mock.sentinel.origin_minion_pool_id args.destination_minion_pool_id = \ mock.sentinel.destination_minion_pool_id @@ -315,6 +320,7 @@ def test_take_action( mock.sentinel.source_environment, mock.sentinel.destination_environment, mock.sentinel.instances, + mock.sentinel.scenario, network_map=mock.sentinel.network_map, notes=mock.sentinel.notes, storage_mappings=(mock_get_storage_mappings_dict_from_args. diff --git a/coriolisclient/v1/deployments.py b/coriolisclient/v1/deployments.py index 99990b2..8887921 100644 --- a/coriolisclient/v1/deployments.py +++ b/coriolisclient/v1/deployments.py @@ -59,7 +59,8 @@ def list(self, detail=False): return self._list(path, 'deployments') def get(self, deployment): - return self._get('/deployments/%s' % base.getid(deployment), 'deployment') + return self._get( + '/deployments/%s' % base.getid(deployment), 'deployment') def create_from_replica(self, replica_id, clone_disks=True, force=False, skip_os_morphing=False, user_scripts=None, From a831998666eef71eb052e6573410704b3d970d77 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 27 Nov 2024 19:08:47 +0200 Subject: [PATCH 41/51] Update transfer nomenclature --- coriolisclient/cli/deployments.py | 28 ++- ...a_executions.py => transfer_executions.py} | 87 ++++---- ...ica_schedules.py => transfer_schedules.py} | 90 ++++---- .../cli/{replicas.py => transfers.py} | 99 ++++----- coriolisclient/client.py | 14 +- ...cutions.py => test_transfer_executions.py} | 156 ++++++------- ...chedules.py => test_transfer_schedules.py} | 202 ++++++++--------- .../{test_replicas.py => test_transfers.py} | 180 +++++++-------- coriolisclient/tests/v1/test_migrations.py | 206 ------------------ .../tests/v1/test_replica_executions.py | 112 ---------- .../tests/v1/test_transfer_executions.py | 112 ++++++++++ ...chedules.py => test_transfer_schedules.py} | 68 +++--- .../{test_replicas.py => test_transfers.py} | 110 +++++----- coriolisclient/v1/deployments.py | 10 +- ...a_executions.py => transfer_executions.py} | 35 +-- ...ica_schedules.py => transfer_schedules.py} | 35 +-- .../v1/{replicas.py => transfers.py} | 52 ++--- samples/replica_samples.py | 22 +- setup.cfg | 43 ++-- 19 files changed, 726 insertions(+), 935 deletions(-) rename coriolisclient/cli/{replica_executions.py => transfer_executions.py} (60%) rename coriolisclient/cli/{replica_schedules.py => transfer_schedules.py} (76%) rename coriolisclient/cli/{replicas.py => transfers.py} (82%) rename coriolisclient/tests/cli/{test_replica_executions.py => test_transfer_executions.py} (67%) rename coriolisclient/tests/cli/{test_replica_schedules.py => test_transfer_schedules.py} (61%) rename coriolisclient/tests/cli/{test_replicas.py => test_transfers.py} (76%) delete mode 100644 coriolisclient/tests/v1/test_migrations.py delete mode 100644 coriolisclient/tests/v1/test_replica_executions.py create mode 100644 coriolisclient/tests/v1/test_transfer_executions.py rename coriolisclient/tests/v1/{test_replica_schedules.py => test_transfer_schedules.py} (51%) rename coriolisclient/tests/v1/{test_replicas.py => test_transfers.py} (58%) rename coriolisclient/v1/{replica_executions.py => transfer_executions.py} (59%) rename coriolisclient/v1/{replica_schedules.py => transfer_schedules.py} (68%) rename coriolisclient/v1/{replicas.py => transfers.py} (70%) diff --git a/coriolisclient/cli/deployments.py b/coriolisclient/cli/deployments.py index ac33550..31eb7ee 100644 --- a/coriolisclient/cli/deployments.py +++ b/coriolisclient/cli/deployments.py @@ -30,7 +30,7 @@ class DeploymentFormatter(formatter.EntityFormatter): columns = ("ID", - "Replica ID", + "Transfer ID", "Status", "Instances", "Notes", @@ -42,7 +42,7 @@ def _get_sorted_list(self, obj_list): def _get_formatted_data(self, obj): data = (obj.id, - obj.replica_id, + obj.transfer_id, obj.last_execution_status, "\n".join(obj.instances), obj.notes, @@ -59,8 +59,8 @@ def __init__(self, show_instances_data=False): "status", "created", "last_updated", - "replica_id", - "replica_scenario_type", + "transfer_id", + "transfer_scenario_type", "reservation_id", "instances", "notes", @@ -69,7 +69,6 @@ def __init__(self, show_instances_data=False): "destination_endpoint_id", "destination_minion_pool_id", "instance_osmorphing_minion_pool_mappings", - "replication_count", "shutdown_instances", "destination_environment", "source_environment", @@ -126,8 +125,8 @@ def _get_formatted_data(self, obj): obj.last_execution_status, obj.created_at, obj.updated_at, - obj.replica_id, - obj.replica_scenario_type, + obj.transfer_id, + obj.transfer_scenario_type, obj.reservation_id, self._format_instances(obj), obj.notes, @@ -137,7 +136,6 @@ def _get_formatted_data(self, obj): obj.destination_minion_pool_id, cli_utils.format_json_for_object_property( obj, 'instance_osmorphing_minion_pool_mappings'), - getattr(obj, 'replication_count', None), getattr(obj, 'shutdown_instances', False), cli_utils.format_json_for_object_property( obj, prop_name="destination_environment"), @@ -160,17 +158,17 @@ def _get_formatted_data(self, obj): class CreateDeployment(show.ShowOne): - """Start a new deployment from an existing replica""" + """Start a new deployment from an existing transfer""" def get_parser(self, prog_name): parser = super(CreateDeployment, self).get_parser(prog_name) - parser.add_argument('replica', - help='The ID of the replica to migrate') + parser.add_argument('transfer', + help='The ID of the transfer to migrate') parser.add_argument('--force', - help='Force the deployment in case of a replica ' + help='Force the deployment in case of a transfer ' 'with failed executions', action='store_true', default=False) parser.add_argument('--dont-clone-disks', - help='Retain the replica disks by cloning them', + help='Retain the transfer disks by cloning them', action='store_false', dest="clone_disks", default=True) parser.add_argument('--skip-os-morphing', @@ -211,8 +209,8 @@ def take_action(self, args): mp['instance_id']: mp['pool_id'] for mp in args.instance_osmorphing_minion_pool_mappings} - deployment = m.create_from_replica( - args.replica, + deployment = m.create_from_transfer( + args.transfer, args.clone_disks, args.force, args.skip_os_morphing, diff --git a/coriolisclient/cli/replica_executions.py b/coriolisclient/cli/transfer_executions.py similarity index 60% rename from coriolisclient/cli/replica_executions.py rename to coriolisclient/cli/transfer_executions.py index f102284..a585f84 100644 --- a/coriolisclient/cli/replica_executions.py +++ b/coriolisclient/cli/transfer_executions.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Command-line interface sub-commands related to replicas. +Command-line interface sub-commands related to transfers. """ import os @@ -24,9 +24,9 @@ from coriolisclient.cli import formatter -class ReplicaExecutionFormatter(formatter.EntityFormatter): +class TransferExecutionFormatter(formatter.EntityFormatter): - columns = ("Replica ID", + columns = ("Transfer ID", "ID", "Status", "Created", @@ -44,10 +44,10 @@ def _get_formatted_data(self, obj): return data -class ReplicaExecutionDetailFormatter(formatter.EntityFormatter): +class TransferExecutionDetailFormatter(formatter.EntityFormatter): columns = ("id", - "replica_id", + "transfer_id", "status", "created", "last_updated", @@ -100,49 +100,50 @@ def _get_formatted_data(self, obj): return data -class CreateReplicaExecution(show.ShowOne): - """Start a replica execution""" +class CreateTransferExecution(show.ShowOne): + """Start a transfer execution""" def get_parser(self, prog_name): - parser = super(CreateReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', - help='The ID of the replica to execute') + parser = super(CreateTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', + help='The ID of the transfer to execute') parser.add_argument('--shutdown-instances', help='Shutdown instances before executing the ' - 'replica', action='store_true', + 'transfer', action='store_true', default=False) return parser def take_action(self, args): - execution = self.app.client_manager.coriolis.replica_executions.create( - args.replica, args.shutdown_instances) + execution = ( + self.app.client_manager.coriolis.transfer_executions.create( + args.transfer, args.shutdown_instances)) - return ReplicaExecutionDetailFormatter().get_formatted_entity( + return TransferExecutionDetailFormatter().get_formatted_entity( execution) -class ShowReplicaExecution(show.ShowOne): - """Show a replica execution""" +class ShowTransferExecution(show.ShowOne): + """Show a transfer execution""" def get_parser(self, prog_name): - parser = super(ShowReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica execution\'s id') + parser = super(ShowTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer execution\'s id') return parser def take_action(self, args): - execution = self.app.client_manager.coriolis.replica_executions.get( - args.replica, args.id) - return ReplicaExecutionDetailFormatter().get_formatted_entity( + execution = self.app.client_manager.coriolis.transfer_executions.get( + args.transfer, args.id) + return TransferExecutionDetailFormatter().get_formatted_entity( execution) -class CancelReplicaExecution(command.Command): - """Cancel a replica execution""" +class CancelTransferExecution(command.Command): + """Cancel a transfer execution""" def get_parser(self, prog_name): - parser = super(CancelReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica execution\'s id') + parser = super(CancelTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer execution\'s id') parser.add_argument('--force', help='Perform a forced termination of running ' 'tasks', action='store_true', @@ -150,33 +151,33 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - self.app.client_manager.coriolis.replica_executions.cancel( - args.replica, args.id, args.force) + self.app.client_manager.coriolis.transfer_executions.cancel( + args.transfer, args.id, args.force) -class DeleteReplicaExecution(command.Command): - """Delete a replica execution""" +class DeleteTransferExecution(command.Command): + """Delete a transfer execution""" def get_parser(self, prog_name): - parser = super(DeleteReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica execution\'s id') + parser = super(DeleteTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer execution\'s id') return parser def take_action(self, args): - self.app.client_manager.coriolis.replica_executions.delete( - args.replica, args.id) + self.app.client_manager.coriolis.transfer_executions.delete( + args.transfer, args.id) -class ListReplicaExecution(lister.Lister): - """List replica executions""" +class ListTransferExecution(lister.Lister): + """List transfer executions""" def get_parser(self, prog_name): - parser = super(ListReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') + parser = super(ListTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') return parser def take_action(self, args): - obj_list = self.app.client_manager.coriolis.replica_executions.list( - args.replica) - return ReplicaExecutionFormatter().list_objects(obj_list) + obj_list = self.app.client_manager.coriolis.transfer_executions.list( + args.transfer) + return TransferExecutionFormatter().list_objects(obj_list) diff --git a/coriolisclient/cli/replica_schedules.py b/coriolisclient/cli/transfer_schedules.py similarity index 76% rename from coriolisclient/cli/replica_schedules.py rename to coriolisclient/cli/transfer_schedules.py index 7287071..90f8a67 100644 --- a/coriolisclient/cli/replica_schedules.py +++ b/coriolisclient/cli/transfer_schedules.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Command-line interface sub-commands related to replicas. +Command-line interface sub-commands related to transfers. """ import argparse @@ -42,9 +42,9 @@ def __call__(self, parser, namespace, value, option_string=None): setattr(namespace, self.dest, value) -class ReplicaScheduleFormatter(formatter.EntityFormatter): +class TransferScheduleFormatter(formatter.EntityFormatter): - columns = ("Replica ID", + columns = ("Transfer ID", "ID", "Schedule", "Created", @@ -55,7 +55,7 @@ def _get_sorted_list(self, obj_list): return sorted(obj_list, key=lambda o: o.created_at) def _get_formatted_data(self, obj): - data = (obj.replica_id, + data = (obj.transfer_id, obj.id, obj.schedule, obj.created_at, @@ -63,10 +63,10 @@ def _get_formatted_data(self, obj): return data -class ReplicaScheduleDetailFormatter(formatter.EntityFormatter): +class TransferScheduleDetailFormatter(formatter.EntityFormatter): columns = ("id", - "replica_id", + "transfer_id", "schedule", "created", "last_updated", @@ -76,7 +76,7 @@ class ReplicaScheduleDetailFormatter(formatter.EntityFormatter): def _get_formatted_data(self, obj): data = (obj.id, - obj.replica_id, + obj.transfer_id, obj.schedule, obj.created_at, obj.updated_at, @@ -86,12 +86,12 @@ def _get_formatted_data(self, obj): return data -class CreateReplicaSchedule(show.ShowOne): - """Start a replica schedule""" +class CreateTransferSchedule(show.ShowOne): + """Start a transfer schedule""" def get_parser(self, prog_name): - parser = super(CreateReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', - help='The ID of the replica') + parser = super(CreateTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', + help='The ID of the transfer') _add_schedule_group(parser) parser.add_argument('--expires-at', help='ISO8601 formatted date', @@ -113,35 +113,35 @@ def take_action(self, args): "Please provide at least one value in the Schedule group") exp = _parse_expiration_date(args.expires_at) - schedule = self.app.client_manager.coriolis.replica_schedules.create( - args.replica, parsed_schedule, + schedule = self.app.client_manager.coriolis.transfer_schedules.create( + args.transfer, parsed_schedule, args.disabled is False, exp, args.shutdown_instance) - return ReplicaScheduleDetailFormatter().get_formatted_entity( + return TransferScheduleDetailFormatter().get_formatted_entity( schedule) -class ShowReplicaSchedule(show.ShowOne): - """Show a replica schedule""" +class ShowTransferSchedule(show.ShowOne): + """Show a transfer schedule""" def get_parser(self, prog_name): - parser = super(ShowReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica schedule\'s id') + parser = super(ShowTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer schedule\'s id') return parser def take_action(self, args): - schedule = self.app.client_manager.coriolis.replica_schedules.get( - args.replica, args.id) - return ReplicaScheduleDetailFormatter().get_formatted_entity( + schedule = self.app.client_manager.coriolis.transfer_schedules.get( + args.transfer, args.id) + return TransferScheduleDetailFormatter().get_formatted_entity( schedule) -class UpdateReplicaSchedule(show.ShowOne): - """Updates a replica schedule""" +class UpdateTransferSchedule(show.ShowOne): + """Updates a transfer schedule""" def get_parser(self, prog_name): - parser = super(UpdateReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica schedule\'s id') + parser = super(UpdateTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer schedule\'s id') _add_schedule_group(parser) expires_parser = parser.add_mutually_exclusive_group(required=False) expires_parser.add_argument( @@ -199,33 +199,33 @@ def take_action(self, args): if args.enabled is not None: updated_values["enabled"] = args.enabled - schedule = self.app.client_manager.coriolis.replica_schedules.update( - args.replica, args.id, updated_values) + schedule = self.app.client_manager.coriolis.transfer_schedules.update( + args.transfer, args.id, updated_values) - return ReplicaScheduleDetailFormatter().get_formatted_entity( + return TransferScheduleDetailFormatter().get_formatted_entity( schedule) -class DeleteReplicaSchedule(command.Command): - """Delete a replica schedule""" +class DeleteTransferSchedule(command.Command): + """Delete a transfer schedule""" def get_parser(self, prog_name): - parser = super(DeleteReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica schedule\'s id') + parser = super(DeleteTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer schedule\'s id') return parser def take_action(self, args): - self.app.client_manager.coriolis.replica_schedules.delete( - args.replica, args.id) + self.app.client_manager.coriolis.transfer_schedules.delete( + args.transfer, args.id) -class ListReplicaSchedule(lister.Lister): - """List replica schedules""" +class ListTransferSchedule(lister.Lister): + """List transfer schedules""" def get_parser(self, prog_name): - parser = super(ListReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') + parser = super(ListTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') parser.add_argument('--hide-expired', help='Hide expired schedules', action='store_true', @@ -233,9 +233,9 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - obj_list = self.app.client_manager.coriolis.replica_schedules.list( - args.replica, hide_expired=args.hide_expired) - return ReplicaScheduleFormatter().list_objects(obj_list) + obj_list = self.app.client_manager.coriolis.transfer_schedules.list( + args.transfer, hide_expired=args.hide_expired) + return TransferScheduleFormatter().list_objects(obj_list) def _add_schedule_group(parser): diff --git a/coriolisclient/cli/replicas.py b/coriolisclient/cli/transfers.py similarity index 82% rename from coriolisclient/cli/replicas.py rename to coriolisclient/cli/transfers.py index a99d195..c8ede4c 100644 --- a/coriolisclient/cli/replicas.py +++ b/coriolisclient/cli/transfers.py @@ -14,7 +14,7 @@ # limitations under the License. """ -Command-line interface sub-commands related to replicas. +Command-line interface sub-commands related to transfers. """ import os @@ -24,15 +24,15 @@ from cliff import show from coriolisclient.cli import formatter -from coriolisclient.cli import replica_executions +from coriolisclient.cli import transfer_executions from coriolisclient.cli import utils as cli_utils -REPLICA_SCENARIO_REPLICA = "replica" -REPLICA_SCENARIO_LIVE_MIGRATION = "live_migration" +TRANSFER_SCENARIO_REPLICA = "replica" +TRANSFER_SCENARIO_LIVE_MIGRATION = "live_migration" -class ReplicaFormatter(formatter.EntityFormatter): +class TransferFormatter(formatter.EntityFormatter): columns = ("ID", "Scenario", @@ -62,7 +62,7 @@ def _get_formatted_data(self, obj): return data -class ReplicaDetailFormatter(formatter.EntityFormatter): +class TransferDetailFormatter(formatter.EntityFormatter): def __init__(self, show_instances_data=False): self.columns = [ @@ -137,10 +137,10 @@ def _get_formatted_data(self, obj): return data -class CreateReplica(show.ShowOne): - """Create a new replica""" +class CreateTransfer(show.ShowOne): + """Create a new transfer""" def get_parser(self, prog_name): - parser = super(CreateReplica, self).get_parser(prog_name) + parser = super(CreateTransfer, self).get_parser(prog_name) parser.add_argument('--origin-endpoint', required=True, help='The origin endpoint id') parser.add_argument('--destination-endpoint', required=True, @@ -148,23 +148,24 @@ def get_parser(self, prog_name): parser.add_argument('--instance', action='append', required=True, dest="instances", metavar="INSTANCE_IDENTIFIER", help='The identifier of a source instance to be ' - 'replicated. Can be specified multiple times') + 'transferred. Can be specified multiple ' + 'times') parser.add_argument('--scenario', dest="scenario", metavar="SCENARIO", choices=[ - REPLICA_SCENARIO_REPLICA, - REPLICA_SCENARIO_LIVE_MIGRATION], - default=REPLICA_SCENARIO_REPLICA, + TRANSFER_SCENARIO_REPLICA, + TRANSFER_SCENARIO_LIVE_MIGRATION], + default=TRANSFER_SCENARIO_REPLICA, help='The type of scenario to use when creating ' - 'the Replica. "replica" will create a ' + 'the Transfer. "replica" will create a ' 'monthly-billed Replica which can be ' 'executed and deployed as many times as ' 'desired, while "live_migration" will ' - 'create a Replica which can be synced ' + 'create a Transfer which can be synced ' 'as many times as needed but only ' 'deployed once.') parser.add_argument('--notes', dest='notes', - help='Notes about the replica') + help='Notes about the transfer') parser.add_argument('--user-script-global', action='append', required=False, dest="global_scripts", @@ -218,7 +219,7 @@ def take_action(self, args): user_scripts = cli_utils.compose_user_scripts( args.global_scripts, args.instance_scripts) - replica = self.app.client_manager.coriolis.replicas.create( + transfer = self.app.client_manager.coriolis.transfers.create( origin_endpoint_id, destination_endpoint_id, source_environment, @@ -234,15 +235,15 @@ def take_action(self, args): instance_osmorphing_minion_pool_mappings), user_scripts=user_scripts) - return ReplicaDetailFormatter().get_formatted_entity(replica) + return TransferDetailFormatter().get_formatted_entity(transfer) -class ShowReplica(show.ShowOne): - """Show a replica""" +class ShowTransfer(show.ShowOne): + """Show a transfer""" def get_parser(self, prog_name): - parser = super(ShowReplica, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(ShowTransfer, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') parser.add_argument('--show-instances-data', action='store_true', help='Includes the instances data used for tasks ' 'execution, this is useful for troubleshooting', @@ -250,57 +251,57 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - replica = self.app.client_manager.coriolis.replicas.get(args.id) - return ReplicaDetailFormatter( - args.show_instances_data).get_formatted_entity(replica) + transfer = self.app.client_manager.coriolis.transfers.get(args.id) + return TransferDetailFormatter( + args.show_instances_data).get_formatted_entity(transfer) -class DeleteReplica(command.Command): - """Delete a replica""" +class DeleteTransfer(command.Command): + """Delete a transfer""" def get_parser(self, prog_name): - parser = super(DeleteReplica, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(DeleteTransfer, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') return parser def take_action(self, args): - self.app.client_manager.coriolis.replicas.delete(args.id) + self.app.client_manager.coriolis.transfers.delete(args.id) -class DeleteReplicaDisks(show.ShowOne): - """Delete replica target disks""" +class DeleteTransferDisks(show.ShowOne): + """Delete transfer target disks""" def get_parser(self, prog_name): - parser = super(DeleteReplicaDisks, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(DeleteTransferDisks, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') return parser def take_action(self, args): - execution = self.app.client_manager.coriolis.replicas.delete_disks( + execution = self.app.client_manager.coriolis.transfers.delete_disks( args.id) - return replica_executions.ReplicaExecutionDetailFormatter( + return transfer_executions.TransferExecutionDetailFormatter( ).get_formatted_entity(execution) -class ListReplica(lister.Lister): - """List replicas""" +class ListTransfer(lister.Lister): + """List transfers""" def get_parser(self, prog_name): - parser = super(ListReplica, self).get_parser(prog_name) + parser = super(ListTransfer, self).get_parser(prog_name) return parser def take_action(self, args): - obj_list = self.app.client_manager.coriolis.replicas.list() - return ReplicaFormatter().list_objects(obj_list) + obj_list = self.app.client_manager.coriolis.transfers.list() + return TransferFormatter().list_objects(obj_list) -class UpdateReplica(show.ShowOne): - """Create a new replica""" +class UpdateTransfer(show.ShowOne): + """Create a new transfer""" def get_parser(self, prog_name): - parser = super(UpdateReplica, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(UpdateTransfer, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') parser.add_argument('--notes', dest='notes', - help='Notes about the replica.') + help='Notes about the transfer.') parser.add_argument('--user-script-global', action='append', required=False, dest="global_scripts", @@ -375,10 +376,10 @@ def take_action(self, args): if not updated_properties: raise ValueError( "No options provided for update. Please run `coriolis help " - "replica update` for details on accepted parameters.") + "transfer update` for details on accepted parameters.") - execution = self.app.client_manager.coriolis.replicas.update( + execution = self.app.client_manager.coriolis.transfers.update( args.id, updated_properties) - return replica_executions.ReplicaExecutionDetailFormatter( + return transfer_executions.TransferExecutionDetailFormatter( ).get_formatted_entity(execution) diff --git a/coriolisclient/client.py b/coriolisclient/client.py index 95a1dfa..60ef840 100644 --- a/coriolisclient/client.py +++ b/coriolisclient/client.py @@ -35,10 +35,10 @@ from coriolisclient.v1 import minion_pools from coriolisclient.v1 import providers from coriolisclient.v1 import regions -from coriolisclient.v1 import replica_executions -from coriolisclient.v1 import replica_schedules -from coriolisclient.v1 import replicas from coriolisclient.v1 import services +from coriolisclient.v1 import transfer_executions +from coriolisclient.v1 import transfer_schedules +from coriolisclient.v1 import transfers LOG = logging.getLogger(__name__) @@ -87,11 +87,11 @@ def __init__(self, session=None, *args, **kwargs): self.deployments = deployments.DeploymentManager(httpclient) self.minion_pools = minion_pools.MinionPoolManager(httpclient) self.providers = providers.ProvidersManager(httpclient) - self.replicas = replicas.ReplicaManager(httpclient) - self.replica_schedules = replica_schedules.ReplicaScheduleManager( - httpclient) - self.replica_executions = replica_executions.ReplicaExecutionManager( + self.transfers = transfers.TransferManager(httpclient) + self.transfer_schedules = transfer_schedules.TransferScheduleManager( httpclient) + self.transfer_executions = ( + transfer_executions.TransferExecutionManager(httpclient)) self.regions = regions.RegionManager(httpclient) self.services = services.ServiceManager(httpclient) self.logging = coriolis_logging.CoriolisLogDownloadManager(httpclient) diff --git a/coriolisclient/tests/cli/test_replica_executions.py b/coriolisclient/tests/cli/test_transfer_executions.py similarity index 67% rename from coriolisclient/tests/cli/test_replica_executions.py rename to coriolisclient/tests/cli/test_transfer_executions.py index 1d85a27..3366846 100644 --- a/coriolisclient/tests/cli/test_replica_executions.py +++ b/coriolisclient/tests/cli/test_transfer_executions.py @@ -9,16 +9,16 @@ from cliff import show from coriolisclient.cli import formatter -from coriolisclient.cli import replica_executions +from coriolisclient.cli import transfer_executions from coriolisclient.tests import test_base -class ReplicaExecutionFormatterTestCase(test_base.CoriolisBaseTestCase): +class TransferExecutionFormatterTestCase(test_base.CoriolisBaseTestCase): """Test suite for the Coriolis Replic Execution Formatter.""" def setUp(self): - super(ReplicaExecutionFormatterTestCase, self).setUp() - self.replica = replica_executions.ReplicaExecutionFormatter() + super(TransferExecutionFormatterTestCase, self).setUp() + self.transfer = transfer_executions.TransferExecutionFormatter() def test_get_sorted_list(self): obj1 = mock.Mock() @@ -29,7 +29,7 @@ def test_get_sorted_list(self): obj3.created_at = "date3" obj_list = [obj2, obj1, obj3] - result = self.replica._get_sorted_list(obj_list) + result = self.transfer._get_sorted_list(obj_list) self.assertEqual( [obj1, obj2, obj3], @@ -43,7 +43,7 @@ def test_get_formatted_data(self): obj.status = mock.sentinel.status obj.created_at = mock.sentinel.created_at - result = self.replica._get_formatted_data(obj) + result = self.transfer._get_formatted_data(obj) self.assertEqual( ( @@ -56,12 +56,12 @@ def test_get_formatted_data(self): ) -class ReplicaExecutionDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Execution Detail Formatter.""" +class TransferExecutionDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Execution Detail Formatter.""" def setUp(self): - super(ReplicaExecutionDetailFormatterTestCase, self).setUp() - self.replica = replica_executions.ReplicaExecutionDetailFormatter() + super(TransferExecutionDetailFormatterTestCase, self).setUp() + self.transfer = transfer_executions.TransferExecutionDetailFormatter() def test_format_instances(self): obj = mock.Mock() @@ -75,7 +75,7 @@ def test_format_instances(self): % {"ls": os.linesep} ) - result = self.replica._format_instances(obj) + result = self.transfer._format_instances(obj) self.assertEqual( expected_result, @@ -98,14 +98,14 @@ def test_format_progress_updates(self, mock_format_progress_update): 'date1 [30] None' % {"ls": os.linesep} ) - result = self.replica._format_progress_updates(task_dict) + result = self.transfer._format_progress_updates(task_dict) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_progress_updates') def test_format_task(self, mock_format_progress_updates): mock_task = mock.Mock() @@ -134,14 +134,14 @@ def test_format_task(self, mock_format_progress_updates): 'date3 [20] message3%(ls)s' 'date1 [30] None' % {"ls": os.linesep}) - result = self.replica._format_task(mock_task) + result = self.transfer._format_task(mock_task) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_progress_updates') def test_format_task_no_progress_updates( self, mock_format_progress_updates): @@ -166,14 +166,14 @@ def test_format_task_no_progress_updates( 'progress_updates:' % {"ls": os.linesep} ) - result = self.replica._format_task(mock_task) + result = self.transfer._format_task(mock_task) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_task') def test_format_tasks(self, mock_format_task): obj = mock.Mock() @@ -181,16 +181,16 @@ def test_format_tasks(self, mock_format_task): mock_format_task.side_effect = ["task1", "task2"] expected_result = 'task1%(ls)s%(ls)stask2' % {"ls": os.linesep} - result = self.replica._format_tasks(obj) + result = self.transfer._format_tasks(obj) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_tasks') - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_instances') def test_get_formatted_data( self, @@ -219,7 +219,7 @@ def test_get_formatted_data( mock.sentinel.formatted_tasks, ) - result = self.replica._get_formatted_data(mock_obj) + result = self.transfer._get_formatted_data(mock_obj) self.assertEqual( expected_result, @@ -227,13 +227,13 @@ def test_get_formatted_data( ) -class CreateReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Create Replica Execution.""" +class CreateTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Create Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(CreateReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.CreateReplicaExecution( + super(CreateTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.CreateTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -241,7 +241,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -249,38 +249,38 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') def test_take_action( self, mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.shutdown_instances = mock.sentinel.shutdown_instances mock_execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions.create = \ + self.mock_app.client_manager.coriolis.transfer_executions.create = \ mock_execution - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) mock_execution.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.shutdown_instances) + mock.sentinel.transfer, mock.sentinel.shutdown_instances) mock_get_formatted_entity.assert_called_once_with( mock_execution.return_value) -class ShowReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Replica Execution.""" +class ShowTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Show Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(ShowReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.ShowReplicaExecution( + super(ShowTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.ShowTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -288,7 +288,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -296,37 +296,37 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') def test_take_action( self, mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions.get = \ + self.mock_app.client_manager.coriolis.transfer_executions.get = \ execution - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - execution.assert_called_once_with(args.replica, args.id) + execution.assert_called_once_with(args.transfer, args.id) mock_get_formatted_entity.assert_called_once_with( execution.return_value) -class CancelReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Cancel Replica Execution.""" +class CancelTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Cancel Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(CancelReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.CancelReplicaExecution( + super(CancelTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.CancelTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') @@ -334,7 +334,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -344,26 +344,26 @@ def test_get_parser( def test_take_action(self): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id args.force = False - replica_execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions = \ - replica_execution + transfer_execution = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_executions = \ + transfer_execution - self.replica.take_action(args) + self.transfer.take_action(args) - replica_execution.cancel.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.id, False) + transfer_execution.cancel.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id, False) -class DeleteReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Execution.""" +class DeleteTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.DeleteReplicaExecution( + super(DeleteTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.DeleteTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') @@ -371,7 +371,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -381,25 +381,25 @@ def test_get_parser( def test_take_action(self): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id - replica_execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions = \ - replica_execution + transfer_execution = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_executions = \ + transfer_execution - self.replica.take_action(args) + self.transfer.take_action(args) - replica_execution.delete.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.id) + transfer_execution.delete.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id) -class ListReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Replica Execution.""" +class ListTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client List Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(ListReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.ListReplicaExecution( + super(ListTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.ListTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') @@ -407,7 +407,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -415,24 +415,24 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionFormatter, + @mock.patch.object(transfer_executions.TransferExecutionFormatter, 'list_objects') def test_take_action( self, mock_list_objects ): args = mock.Mock() - args.replica = mock.sentinel.replica - mock_replica_list = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions.list = \ - mock_replica_list + args.transfer = mock.sentinel.transfer + mock_transfer_list = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_executions.list = \ + mock_transfer_list - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_list_objects.return_value, result ) - mock_replica_list.assert_called_once_with(mock.sentinel.replica) + mock_transfer_list.assert_called_once_with(mock.sentinel.transfer) mock_list_objects.assert_called_once_with( - mock_replica_list.return_value) + mock_transfer_list.return_value) diff --git a/coriolisclient/tests/cli/test_replica_schedules.py b/coriolisclient/tests/cli/test_transfer_schedules.py similarity index 61% rename from coriolisclient/tests/cli/test_replica_schedules.py rename to coriolisclient/tests/cli/test_transfer_schedules.py index 926293c..25bb2b9 100644 --- a/coriolisclient/tests/cli/test_replica_schedules.py +++ b/coriolisclient/tests/cli/test_transfer_schedules.py @@ -9,17 +9,17 @@ from cliff import lister from cliff import show -from coriolisclient.cli import replica_schedules +from coriolisclient.cli import transfer_schedules from coriolisclient import exceptions from coriolisclient.tests import test_base class RangeActionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Range Action.""" + """Test suite for the Coriolis Transfer Range Action.""" def setUp(self): super(RangeActionTestCase, self).setUp() - self.range_action = replica_schedules.RangeAction( + self.range_action = transfer_schedules.RangeAction( 1, 10, ["mock_option"], "dest") def test__call__(self): @@ -48,12 +48,12 @@ def test__call__argument_error(self): ) -class ReplicaScheduleFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Schedule Formatter.""" +class TransferScheduleFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Schedule Formatter.""" def setUp(self): - super(ReplicaScheduleFormatterTestCase, self).setUp() - self.replica = replica_schedules.ReplicaScheduleFormatter() + super(TransferScheduleFormatterTestCase, self).setUp() + self.transfer = transfer_schedules.TransferScheduleFormatter() def test_get_sorted_list(self): obj1 = mock.Mock() @@ -64,7 +64,7 @@ def test_get_sorted_list(self): obj3.created_at = "date3" obj_list = [obj2, obj1, obj3] - result = self.replica._get_sorted_list(obj_list) + result = self.transfer._get_sorted_list(obj_list) self.assertEqual( [obj1, obj2, obj3], @@ -73,17 +73,17 @@ def test_get_sorted_list(self): def test_get_formatted_data(self): obj = mock.Mock() - obj.replica_id = mock.sentinel.replica_id + obj.transfer_id = mock.sentinel.transfer_id obj.id = mock.sentinel.id obj.schedule = mock.sentinel.schedule obj.created_at = mock.sentinel.created_at obj.expiration_date = mock.sentinel.expiration_date - result = self.replica._get_formatted_data(obj) + result = self.transfer._get_formatted_data(obj) self.assertEqual( ( - mock.sentinel.replica_id, + mock.sentinel.transfer_id, mock.sentinel.id, mock.sentinel.schedule, mock.sentinel.created_at, @@ -93,17 +93,17 @@ def test_get_formatted_data(self): ) -class ReplicaScheduleDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Schedule Detail Formatter.""" +class TransferScheduleDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Schedule Detail Formatter.""" def setUp(self): - super(ReplicaScheduleDetailFormatterTestCase, self).setUp() - self.replica = replica_schedules.ReplicaScheduleDetailFormatter() + super(TransferScheduleDetailFormatterTestCase, self).setUp() + self.transfer = transfer_schedules.TransferScheduleDetailFormatter() def test_get_formatted_data(self): obj = mock.Mock() obj.id = mock.sentinel.id - obj.replica_id = mock.sentinel.replica_id + obj.transfer_id = mock.sentinel.transfer_id obj.schedule = mock.sentinel.schedule obj.created_at = mock.sentinel.created_at obj.updated_at = mock.sentinel.updated_at @@ -111,12 +111,12 @@ def test_get_formatted_data(self): obj.expiration_date = mock.sentinel.expiration_date obj.shutdown_instance = mock.sentinel.shutdown_instance - result = self.replica._get_formatted_data(obj) + result = self.transfer._get_formatted_data(obj) self.assertEqual( ( mock.sentinel.id, - mock.sentinel.replica_id, + mock.sentinel.transfer_id, mock.sentinel.schedule, mock.sentinel.created_at, mock.sentinel.updated_at, @@ -128,13 +128,13 @@ def test_get_formatted_data(self): ) -class CreateReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Create Replica Schedule.""" +class CreateTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Create Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(CreateReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.CreateReplicaSchedule( + super(CreateTransferScheduleTestCase, self).setUp() + self.transfer = transfer_schedules.CreateTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -142,7 +142,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -150,10 +150,10 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action( self, mock_parse_schedule_group_args, @@ -161,23 +161,23 @@ def test_take_action( mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.disabled = False args.shutdown_instance = mock.sentinel.shutdown_instance mock_schedule_group_args = {"minute": mock.sentinel.minute} mock_parse_schedule_group_args.return_value = mock_schedule_group_args mock_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules.create = \ + self.mock_app.client_manager.coriolis.transfer_schedules.create = \ mock_schedule - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) mock_schedule.assert_called_once_with( - mock.sentinel.replica, + mock.sentinel.transfer, mock_schedule_group_args, True, mock_parse_expiration_date.return_value, @@ -186,8 +186,8 @@ def test_take_action( mock_get_formatted_entity.assert_called_once_with( mock_schedule.return_value) - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action_no_parsed_schedule( self, mock_parse_schedule_group_args, @@ -198,19 +198,19 @@ def test_take_action_no_parsed_schedule( self.assertRaises( exceptions.CoriolisException, - self.replica.take_action, + self.transfer.take_action, args ) mock_parse_expiration_date.assert_not_called() -class ShowReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Replica Schedule.""" +class ShowTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Show Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(ShowReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.ShowReplicaSchedule( + super(ShowTransferScheduleTestCase, self).setUp() + self.transfer = transfer_schedules.ShowTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -218,7 +218,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -226,37 +226,37 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') def test_take_action( self, mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules.get = \ + self.mock_app.client_manager.coriolis.transfer_schedules.get = \ schedule - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - schedule.assert_called_once_with(args.replica, args.id) + schedule.assert_called_once_with(args.transfer, args.id) mock_get_formatted_entity.assert_called_once_with( schedule.return_value) -class UpdateReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Update Replica Schedule.""" +class UpdateTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Update Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(UpdateReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.UpdateReplicaSchedule( + super(UpdateTransferScheduleTestCase, self).setUp() + self.transfer = transfer_schedules.UpdateTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -264,7 +264,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -272,10 +272,10 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action( self, mock_parse_schedule_group_args, @@ -286,13 +286,13 @@ def test_take_action( args.expires = True args.shutdown = True args.enabled = True - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id mock_parse_schedule_group_args.return_value = \ {"minute": mock.sentinel.minute} - replica_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules = \ - replica_schedule + transfer_schedule = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules = \ + transfer_schedule expected_updated_values = { "schedule": {"minute": mock.sentinel.minute}, "expiration_date": mock_parse_expiration_date.return_value, @@ -300,23 +300,23 @@ def test_take_action( "enabled": True, } - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - replica_schedule.update.assert_called_once_with( - mock.sentinel.replica, + transfer_schedule.update.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id, expected_updated_values ) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action_no_updated_values( self, mock_parse_schedule_group_args, @@ -324,18 +324,18 @@ def test_take_action_no_updated_values( mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id args.expires = False args.shutdown = None args.enabled = None mock_parse_schedule_group_args.return_value = {} - replica_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules = \ - replica_schedule + transfer_schedule = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules = \ + transfer_schedule expected_updated_values = {"expiration_date": None} - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -343,20 +343,20 @@ def test_take_action_no_updated_values( ) mock_parse_expiration_date.assert_not_called() - replica_schedule.update.assert_called_once_with( - mock.sentinel.replica, + transfer_schedule.update.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id, expected_updated_values ) -class DeleteReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Schedule.""" +class DeleteTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.DeleteReplicaSchedule( + super(DeleteTransferScheduleTestCase, self).setUp() + self.transfer = transfer_schedules.DeleteTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') @@ -364,7 +364,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -374,25 +374,25 @@ def test_get_parser( def test_take_action(self): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id - replica_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules = \ - replica_schedule + transfer_schedule = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules = \ + transfer_schedule - self.replica.take_action(args) + self.transfer.take_action(args) - replica_schedule.delete.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.id) + transfer_schedule.delete.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id) -class ListReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Replica Schedule.""" +class ListTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client List Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(ListReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.ListReplicaSchedule( + super(ListTransferScheduleTestCase, self).setUp() + self.transfer = transfer_schedules.ListTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') @@ -400,7 +400,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -408,38 +408,38 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleFormatter, 'list_objects') def test_take_action( self, mock_list_objects ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.hide_expired = mock.sentinel.hide_expired - mock_replica_list = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules.list = \ - mock_replica_list + mock_transfer_list = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules.list = \ + mock_transfer_list - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_list_objects.return_value, result ) - mock_replica_list.assert_called_once_with( - mock.sentinel.replica, hide_expired=mock.sentinel.hide_expired) + mock_transfer_list.assert_called_once_with( + mock.sentinel.transfer, hide_expired=mock.sentinel.hide_expired) mock_list_objects.assert_called_once_with( - mock_replica_list.return_value) + mock_transfer_list.return_value) -class ReplicaSchedulesTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Schedules.""" +class TransferSchedulesTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Schedules.""" def test_add_schedule_group(self): parser = argparse.ArgumentParser() - replica_schedules._add_schedule_group(parser) + transfer_schedules._add_schedule_group(parser) self.assertIn( "Schedule", @@ -458,7 +458,7 @@ def __getattr__(self, name): "hour": mock.sentinel.hour } - result = replica_schedules._parse_schedule_group_args(args) + result = transfer_schedules._parse_schedule_group_args(args) self.assertEqual( expected_result, @@ -468,7 +468,7 @@ def __getattr__(self, name): def test_parse_expiration_date(self): value = None - result = replica_schedules._parse_expiration_date(value) + result = transfer_schedules._parse_expiration_date(value) self.assertEqual( None, @@ -477,7 +477,7 @@ def test_parse_expiration_date(self): value = "2099-12-31" - result = replica_schedules._parse_expiration_date(value) + result = transfer_schedules._parse_expiration_date(value) self.assertEqual( datetime.datetime(2099, 12, 31, 0, 0), @@ -488,6 +488,6 @@ def test_parse_expiration_date(self): self.assertRaises( exceptions.CoriolisException, - replica_schedules._parse_expiration_date, + transfer_schedules._parse_expiration_date, value ) diff --git a/coriolisclient/tests/cli/test_replicas.py b/coriolisclient/tests/cli/test_transfers.py similarity index 76% rename from coriolisclient/tests/cli/test_replicas.py rename to coriolisclient/tests/cli/test_transfers.py index 293d982..4e6b8d6 100644 --- a/coriolisclient/tests/cli/test_replicas.py +++ b/coriolisclient/tests/cli/test_transfers.py @@ -7,18 +7,18 @@ from cliff import lister from cliff import show -from coriolisclient.cli import replica_executions -from coriolisclient.cli import replicas +from coriolisclient.cli import transfer_executions +from coriolisclient.cli import transfers from coriolisclient.cli import utils as cli_utils from coriolisclient.tests import test_base -class ReplicaFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Replica Formatter.""" +class TransferFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Transfer Formatter.""" def setUp(self): - super(ReplicaFormatterTestCase, self).setUp() - self.replica = replicas.ReplicaFormatter() + super(TransferFormatterTestCase, self).setUp() + self.transfer = transfers.TransferFormatter() def test_get_sorted_list(self): obj1 = mock.Mock() @@ -29,7 +29,7 @@ def test_get_sorted_list(self): obj3.created_at = "date3" obj_list = [obj2, obj1, obj3] - result = self.replica._get_sorted_list(obj_list) + result = self.transfer._get_sorted_list(obj_list) self.assertEqual( [obj1, obj2, obj3], @@ -40,7 +40,7 @@ def test_format_last_execution(self): obj = mock.Mock() obj.executions = None - result = self.replica._format_last_execution(obj) + result = self.transfer._format_last_execution(obj) self.assertEqual( "", @@ -67,7 +67,7 @@ def test_format_last_execution(self): } obj.executions = [execution1, execution3, execution2] - result = self.replica._format_last_execution(obj) + result = self.transfer._format_last_execution(obj) self.assertEqual( "mock_id3 mock_status3", @@ -83,7 +83,7 @@ def test_get_formatted_data(self): obj.scenario = mock.sentinel.scenario obj.created_at = mock.sentinel.created_at - result = self.replica._get_formatted_data(obj) + result = self.transfer._get_formatted_data(obj) self.assertEqual( ( @@ -99,12 +99,12 @@ def test_get_formatted_data(self): ) -class ReplicaDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Replica Detail Formatter.""" +class TransferDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Transfer Detail Formatter.""" def setUp(self): - super(ReplicaDetailFormatterTestCase, self).setUp() - self.replica = replicas.ReplicaDetailFormatter( + super(TransferDetailFormatterTestCase, self).setUp() + self.transfer = transfers.TransferDetailFormatter( show_instances_data=True) def test_format_instances(self): @@ -115,7 +115,7 @@ def test_format_instances(self): % {"ls": "\n"} ) - result = self.replica._format_instances(obj) + result = self.transfer._format_instances(obj) self.assertEqual( expected_result, @@ -130,14 +130,14 @@ def test_format_execution(self): } expected_result = "mock_id mock_status" - result = self.replica._format_execution(execution) + result = self.transfer._format_execution(execution) self.assertEqual( expected_result, result ) - @mock.patch.object(replicas.ReplicaDetailFormatter, '_format_execution') + @mock.patch.object(transfers.TransferDetailFormatter, '_format_execution') def test_format_executions(self, mock_format_execution): executions = mock.Mock() execution1 = mock.Mock() @@ -158,17 +158,17 @@ def test_format_executions(self, mock_format_execution): "mock_id3 mock_status3" % {"ls": "\n"} ) - result = self.replica._format_executions(executions) + result = self.transfer._format_executions(executions) self.assertEqual( expected_result, result ) - @mock.patch.object(replicas.ReplicaDetailFormatter, '_format_executions') + @mock.patch.object(transfers.TransferDetailFormatter, '_format_executions') @mock.patch.object(cli_utils, 'format_mapping') @mock.patch.object(cli_utils, 'format_json_for_object_property') - @mock.patch.object(replicas.ReplicaDetailFormatter, + @mock.patch.object(transfers.TransferDetailFormatter, '_format_instances') @mock.patch.object(cli_utils, 'parse_storage_mappings') def test_get_formatted_data( @@ -238,7 +238,7 @@ def test_get_formatted_data( mock.sentinel.info ] - result = self.replica._get_formatted_data(mock_obj) + result = self.transfer._get_formatted_data(mock_obj) self.assertEqual( expected_result, @@ -246,13 +246,13 @@ def test_get_formatted_data( ) -class CreateReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Create Replica.""" +class CreateTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Create Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(CreateReplicaTestCase, self).setUp() - self.replica = replicas.CreateReplica( + super(CreateTransferTestCase, self).setUp() + self.transfer = transfers.CreateTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(cli_utils, 'add_args_for_json_option_to_parser') @@ -262,7 +262,7 @@ def test_get_parser( mock_get_parser, *_ ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -273,7 +273,7 @@ def test_get_parser( @mock.patch.object(cli_utils, 'compose_user_scripts') @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') @mock.patch.object(cli_utils, 'get_option_value_from_args') - @mock.patch.object(replicas.ReplicaDetailFormatter, + @mock.patch.object(transfers.TransferDetailFormatter, 'get_formatted_entity') def test_take_action( self, @@ -294,27 +294,27 @@ def test_take_action( {'instance_id': "instance_id2", 'pool_id': "pool_id2"} ] mock_endpoints = mock.Mock() - mock_replicas = mock.Mock() + mock_transfers = mock.Mock() self.mock_app.client_manager.coriolis.endpoints = mock_endpoints mock_endpoints.get_endpoint_id_for_name.side_effect = [ mock.sentinel.origin_endpoint_id, mock.sentinel.destination_endpoint_id ] - self.mock_app.client_manager.coriolis.replicas.create = \ - mock_replicas + self.mock_app.client_manager.coriolis.transfers.create = \ + mock_transfers mock_get_option_value_from_args.side_effect = [ mock.sentinel.destination_environment, mock.sentinel.source_environment, mock.sentinel.network_map, ] - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - mock_replicas.assert_called_once_with( + mock_transfers.assert_called_once_with( mock.sentinel.origin_endpoint_id, mock.sentinel.destination_endpoint_id, mock.sentinel.source_environment, @@ -333,21 +333,21 @@ def test_take_action( user_scripts=mock_compose_user_scripts.return_value ) mock_get_formatted_entity.assert_called_once_with( - mock_replicas.return_value) + mock_transfers.return_value) -class ShowReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Replica.""" +class ShowTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Show Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(ShowReplicaTestCase, self).setUp() - self.replica = replicas.ShowReplica( + super(ShowTransferTestCase, self).setUp() + self.transfer = transfers.ShowTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -355,37 +355,37 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replicas.ReplicaDetailFormatter, + @mock.patch.object(transfers.TransferDetailFormatter, 'get_formatted_entity') def test_take_action(self, mock_get_formatted_entity): args = mock.Mock() args.id = mock.sentinel.id - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.get = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.get = mock_transfer - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - mock_replica.assert_called_once_with(mock.sentinel.id) + mock_transfer.assert_called_once_with(mock.sentinel.id) mock_get_formatted_entity.assert_called_once_with( - mock_replica.return_value) + mock_transfer.return_value) -class DeleteReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica.""" +class DeleteTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaTestCase, self).setUp() - self.replica = replicas.DeleteReplica( + super(DeleteTransferTestCase, self).setUp() + self.transfer = transfers.DeleteTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -396,26 +396,26 @@ def test_get_parser(self, mock_get_parser): def test_take_action(self): args = mock.Mock() args.id = mock.sentinel.id - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.delete = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.delete = mock_transfer - self.replica.take_action(args) + self.transfer.take_action(args) - mock_replica.assert_called_once_with(mock.sentinel.id) + mock_transfer.assert_called_once_with(mock.sentinel.id) -class DeleteReplicaDisksTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Disks.""" +class DeleteTransferDisksTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Disks.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaDisksTestCase, self).setUp() - self.replica = replicas.DeleteReplicaDisks( + super(DeleteTransferDisksTestCase, self).setUp() + self.transfer = transfers.DeleteTransferDisks( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -423,38 +423,38 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') def test_take_action(self, mock_get_formatted_entity): args = mock.Mock() args.id = mock.sentinel.id - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.delete_disks = \ - mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.delete_disks = \ + mock_transfer - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - mock_replica.assert_called_once_with(mock.sentinel.id) + mock_transfer.assert_called_once_with(mock.sentinel.id) mock_get_formatted_entity.assert_called_once_with( - mock_replica.return_value) + mock_transfer.return_value) -class ListReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Replica.""" +class ListTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client List Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(ListReplicaTestCase, self).setUp() - self.replica = replicas.ListReplica( + super(ListTransferTestCase, self).setUp() + self.transfer = transfers.ListTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -462,33 +462,33 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replicas.ReplicaFormatter, 'list_objects') + @mock.patch.object(transfers.TransferFormatter, 'list_objects') def test_take_action(self, mock_list_objects): args = mock.Mock() - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.list = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.list = mock_transfer - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_list_objects.return_value, result ) - mock_list_objects.assert_called_once_with(mock_replica.return_value) + mock_list_objects.assert_called_once_with(mock_transfer.return_value) -class UpdateReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Disks.""" +class UpdateTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Disks.""" def setUp(self): self.mock_app = mock.Mock() - super(UpdateReplicaTestCase, self).setUp() - self.replica = replicas.UpdateReplica( + super(UpdateTransferTestCase, self).setUp() + self.transfer = transfers.UpdateTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -496,7 +496,7 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') @mock.patch.object(cli_utils, 'compose_user_scripts') @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') @@ -520,8 +520,8 @@ def test_take_action( {"instance_id": "mock_instance1", "pool_id": "mock_pool1"}, {"instance_id": "mock_instance2", "pool_id": "mock_pool2"} ] - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers = mock_transfer mock_get_option_value_from_args.side_effect = [ mock.sentinel.destination_environment, mock.sentinel.source_environment, @@ -544,7 +544,7 @@ def test_take_action( "user_scripts": mock.sentinel.user_scripts } - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -552,12 +552,12 @@ def test_take_action( ) mock_compose_user_scripts.assert_called_once_with( mock.sentinel.global_scripts, mock.sentinel.instance_scripts) - mock_replica.update.assert_called_once_with( + mock_transfer.update.assert_called_once_with( mock.sentinel.id, expected_updated_properties ) mock_get_formatted_entity.assert_called_once_with( - mock_replica.update.return_value) + mock_transfer.update.return_value) @mock.patch.object(cli_utils, 'compose_user_scripts') @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') @@ -574,18 +574,18 @@ def __getattr__(self, name): args = CustomMock() args.global_scripts = mock.sentinel.global_scripts args.instance_scripts = mock.sentinel.instance_scripts - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers = mock_transfer mock_get_option_value_from_args.return_value = None mock_get_storage_mappings_dict_from_args.return_value = None mock_compose_user_scripts.return_value = None self.assertRaises( ValueError, - self.replica.take_action, + self.transfer.take_action, args ) mock_compose_user_scripts.assert_called_once_with( mock.sentinel.global_scripts, mock.sentinel.instance_scripts) - mock_replica.update.assert_not_called() + mock_transfer.update.assert_not_called() diff --git a/coriolisclient/tests/v1/test_migrations.py b/coriolisclient/tests/v1/test_migrations.py deleted file mode 100644 index e139530..0000000 --- a/coriolisclient/tests/v1/test_migrations.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright 2024 Cloudbase Solutions Srl -# All Rights Reserved. - -from unittest import mock - -from coriolisclient.tests import test_base -from coriolisclient.v1 import migrations - - -class MigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis v1 Migration.""" - - def test_properties(self): - mock_client = mock.Mock() - self.migration = migrations.Migration( - mock_client, - { - "source_environment": { - "source_environment1": mock.sentinel.source_environment}, - "destination_environment": { - "destination_environment1": - mock.sentinel.destination_environment}, - "transfer_result": { - "transfer_result1": mock.sentinel.transfer_result}, - "tasks": [{"task1": mock.sentinel.task1}, - {"task2": mock.sentinel.task2}] - } - ) - self.assertEqual( - ( - mock.sentinel.source_environment, - mock.sentinel.destination_environment, - mock.sentinel.transfer_result, - mock.sentinel.task1, - mock.sentinel.task2 - ), - ( - self.migration.source_environment.source_environment1, - (self.migration.destination_environment. - destination_environment1), - self.migration.transfer_result.transfer_result1, - self.migration.tasks[0].task1, - self.migration.tasks[1].task2 - ) - ) - - @mock.patch.object(migrations.Migration, "get") - def test_properties_none(self, mock_get): - mock_client = mock.Mock() - self.migration = migrations.Migration( - mock_client, - {} - ) - self.assertEqual( - ( - None, - None, - None, - [] - ), - ( - self.migration.source_environment, - self.migration.destination_environment, - self.migration.transfer_result, - self.migration.tasks, - ) - ) - mock_get.assert_called_once() - - -class MigrationManagerTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis v1 Migration Manager.""" - - def setUp(self): - mock_client = mock.Mock() - super(MigrationManagerTestCase, self).setUp() - self.migration = migrations.MigrationManager(mock_client) - - @mock.patch.object(migrations.MigrationManager, "_list") - def test_list(self, mock_list): - result = self.migration.list(detail=True) - - self.assertEqual( - mock_list.return_value, - result - ) - mock_list.assert_called_once_with("/migrations/detail", "migrations") - - @mock.patch.object(migrations.MigrationManager, "_get") - def test_get(self, mock_get): - result = self.migration.get(mock.sentinel.migration) - - self.assertEqual( - mock_get.return_value, - result - ) - mock_get.assert_called_once_with( - "/migrations/sentinel.migration", "migration") - - @mock.patch.object(migrations.MigrationManager, "_post") - def test_create(self, mock_post): - expected_data = { - "origin_endpoint_id": mock.sentinel.origin_endpoint_id, - "destination_endpoint_id": mock.sentinel.destination_endpoint_id, - "source_environment": mock.sentinel.source_environment, - "destination_environment": { - "network_map": mock.sentinel.network_map, - "storage_mappings": mock.sentinel.storage_mappings - }, - "instances": mock.sentinel.instances, - "network_map": mock.sentinel.network_map, - "notes": mock.sentinel.notes, - "storage_mappings": mock.sentinel.storage_mappings, - "skip_os_morphing": False, - "replication_count": mock.sentinel.replication_count, - "shutdown_instances": mock.sentinel.shutdown_instances, - "user_scripts": mock.sentinel.user_scripts, - "origin_minion_pool_id": mock.sentinel.origin_minion_pool_id, - "destination_minion_pool_id": - mock.sentinel.destination_minion_pool_id, - "instance_osmorphing_minion_pool_mappings": - mock.sentinel.instance_osmorphing_minion_pool_mappings, - } - expected_data = {"migration": expected_data} - - result = self.migration.create( - mock.sentinel.origin_endpoint_id, - mock.sentinel.destination_endpoint_id, - mock.sentinel.source_environment, - { - "network_map": mock.sentinel.network_map, - "storage_mappings": mock.sentinel.storage_mappings - }, - mock.sentinel.instances, - network_map=None, - notes=mock.sentinel.notes, - storage_mappings=None, - skip_os_morphing=False, - replication_count=mock.sentinel.replication_count, - shutdown_instances=mock.sentinel.shutdown_instances, - user_scripts=mock.sentinel.user_scripts, - origin_minion_pool_id=mock.sentinel.origin_minion_pool_id, - destination_minion_pool_id= - mock.sentinel.destination_minion_pool_id, - instance_osmorphing_minion_pool_mappings= - mock.sentinel.instance_osmorphing_minion_pool_mappings, - ) - - self.assertEqual( - mock_post.return_value, - result - ) - mock_post.assert_called_once_with( - "/migrations", expected_data, "migration") - - @mock.patch.object(migrations.MigrationManager, "_post") - def test_create_from_replica(self, mock_post): - expected_data = { - "replica_id": mock.sentinel.replica_id, - "clone_disks": False, - "force": False, - "skip_os_morphing": False, - "user_scripts": mock.sentinel.user_scripts, - "instance_osmorphing_minion_pool_mappings": - mock.sentinel.instance_osmorphing_minion_pool_mappings, - } - expected_data = {"migration": expected_data} - - result = self.migration.create_from_replica( - mock.sentinel.replica_id, - clone_disks=False, - force=False, - skip_os_morphing=False, - user_scripts=mock.sentinel.user_scripts, - instance_osmorphing_minion_pool_mappings= - mock.sentinel.instance_osmorphing_minion_pool_mappings, - ) - - self.assertEqual( - mock_post.return_value, - result - ) - mock_post.assert_called_once_with( - "/migrations", expected_data, "migration") - - @mock.patch.object(migrations.MigrationManager, "_delete") - def test_delete(self, mock_delete): - result = self.migration.delete(mock.sentinel.migration) - - self.assertEqual( - mock_delete.return_value, - result - ) - mock_delete.assert_called_once_with( - "/migrations/%s" % mock.sentinel.migration) - - def test_cancel(self): - result = self.migration.cancel(mock.sentinel.migration, force=False) - - self.assertEqual( - self.migration.client.post.return_value, - result - ) - self.migration.client.post.assert_called_once_with( - "/migrations/%s/actions" % mock.sentinel.migration, - json={'cancel': {'force': False}}) diff --git a/coriolisclient/tests/v1/test_replica_executions.py b/coriolisclient/tests/v1/test_replica_executions.py deleted file mode 100644 index 53e3d95..0000000 --- a/coriolisclient/tests/v1/test_replica_executions.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2024 Cloudbase Solutions Srl -# All Rights Reserved. - -from unittest import mock - -from coriolisclient.tests import test_base -from coriolisclient.v1 import replica_executions - - -class ReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis v1 Replica Execution.""" - - def test_tasks(self): - mock_client = mock.Mock() - self.replica_execution = replica_executions.ReplicaExecution( - mock_client, - { - "tasks": [{"task1": mock.sentinel.task1}, - {"task2": mock.sentinel.task2}], - } - ) - self.assertEqual( - ( - mock.sentinel.task1, - mock.sentinel.task2 - ), - ( - self.replica_execution.tasks[0].task1, - self.replica_execution.tasks[1].task2 - ) - ) - - -class ReplicaExecutionManagerTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis v1 Replica Execution Manager.""" - - def setUp(self): - mock_client = mock.Mock() - super(ReplicaExecutionManagerTestCase, self).setUp() - self.replica_execution = replica_executions.ReplicaExecutionManager( - mock_client) - - @mock.patch.object(replica_executions.ReplicaExecutionManager, "_list") - def test_list(self, mock_list): - result = self.replica_execution.list(mock.sentinel.replica) - - self.assertEqual( - mock_list.return_value, - result - ) - mock_list.assert_called_once_with( - '/replicas/%s/executions' % mock.sentinel.replica, "executions") - - @mock.patch.object(replica_executions.ReplicaExecutionManager, "_get") - def test_get(self, mock_get): - result = self.replica_execution.get( - mock.sentinel.replica, mock.sentinel.execution) - - self.assertEqual( - mock_get.return_value, - result - ) - mock_get.assert_called_once_with( - "/replicas/%s/executions/%s" % (mock.sentinel.replica, - mock.sentinel.execution), - "execution") - - @mock.patch.object(replica_executions.ReplicaExecutionManager, "_post") - def test_create(self, mock_post): - expected_data = { - "shutdown_instances": mock.sentinel.shutdown_instances - } - expected_data = {"execution": expected_data} - - result = self.replica_execution.create( - mock.sentinel.replica, - shutdown_instances=mock.sentinel.shutdown_instances - ) - - self.assertEqual( - mock_post.return_value, - result - ) - mock_post.assert_called_once_with( - '/replicas/%s/executions' % mock.sentinel.replica, - expected_data, "execution") - - @mock.patch.object(replica_executions.ReplicaExecutionManager, "_delete") - def test_delete(self, mock_delete): - result = self.replica_execution.delete( - mock.sentinel.replica, mock.sentinel.execution) - - self.assertEqual( - mock_delete.return_value, - result - ) - mock_delete.assert_called_once_with( - "/replicas/%s/executions/%s" % (mock.sentinel.replica, - mock.sentinel.execution),) - - def test_cancel(self): - result = self.replica_execution.cancel( - mock.sentinel.replica, mock.sentinel.execution, force=False) - - self.assertEqual( - self.replica_execution.client.post.return_value, - result - ) - self.replica_execution.client.post.assert_called_once_with( - "/replicas/%s/executions/%s/actions" % (mock.sentinel.replica, - mock.sentinel.execution), - json={'cancel': {'force': False}}) diff --git a/coriolisclient/tests/v1/test_transfer_executions.py b/coriolisclient/tests/v1/test_transfer_executions.py new file mode 100644 index 0000000..90c2044 --- /dev/null +++ b/coriolisclient/tests/v1/test_transfer_executions.py @@ -0,0 +1,112 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import transfer_executions + + +class TransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Execution.""" + + def test_tasks(self): + mock_client = mock.Mock() + self.transfer_execution = transfer_executions.TransferExecution( + mock_client, + { + "tasks": [{"task1": mock.sentinel.task1}, + {"task2": mock.sentinel.task2}], + } + ) + self.assertEqual( + ( + mock.sentinel.task1, + mock.sentinel.task2 + ), + ( + self.transfer_execution.tasks[0].task1, + self.transfer_execution.tasks[1].task2 + ) + ) + + +class TransferExecutionManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Execution Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(TransferExecutionManagerTestCase, self).setUp() + self.transfer_execution = transfer_executions.TransferExecutionManager( + mock_client) + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_list") + def test_list(self, mock_list): + result = self.transfer_execution.list(mock.sentinel.transfer) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + '/transfers/%s/executions' % mock.sentinel.transfer, "executions") + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_get") + def test_get(self, mock_get): + result = self.transfer_execution.get( + mock.sentinel.transfer, mock.sentinel.execution) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/transfers/%s/executions/%s" % (mock.sentinel.transfer, + mock.sentinel.execution), + "execution") + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_post") + def test_create(self, mock_post): + expected_data = { + "shutdown_instances": mock.sentinel.shutdown_instances + } + expected_data = {"execution": expected_data} + + result = self.transfer_execution.create( + mock.sentinel.transfer, + shutdown_instances=mock.sentinel.shutdown_instances + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + '/transfers/%s/executions' % mock.sentinel.transfer, + expected_data, "execution") + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_delete") + def test_delete(self, mock_delete): + result = self.transfer_execution.delete( + mock.sentinel.transfer, mock.sentinel.execution) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/transfers/%s/executions/%s" % (mock.sentinel.transfer, + mock.sentinel.execution)) + + def test_cancel(self): + result = self.transfer_execution.cancel( + mock.sentinel.transfer, mock.sentinel.execution, force=False) + + self.assertEqual( + self.transfer_execution.client.post.return_value, + result + ) + self.transfer_execution.client.post.assert_called_once_with( + "/transfers/%s/executions/%s/actions" % (mock.sentinel.transfer, + mock.sentinel.execution), + json={'cancel': {'force': False}}) diff --git a/coriolisclient/tests/v1/test_replica_schedules.py b/coriolisclient/tests/v1/test_transfer_schedules.py similarity index 51% rename from coriolisclient/tests/v1/test_replica_schedules.py rename to coriolisclient/tests/v1/test_transfer_schedules.py index 6275b6d..b5dbd7d 100644 --- a/coriolisclient/tests/v1/test_replica_schedules.py +++ b/coriolisclient/tests/v1/test_transfer_schedules.py @@ -5,58 +5,58 @@ from unittest import mock from coriolisclient.tests import test_base -from coriolisclient.v1 import replica_schedules +from coriolisclient.v1 import transfer_schedules -class ReplicaScheduleManagerTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis v1 Replica Schedule Manager.""" +class TransferScheduleManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Schedule Manager.""" def setUp(self): mock_client = mock.Mock() - super(ReplicaScheduleManagerTestCase, self).setUp() - self.replica_schedule = replica_schedules.ReplicaScheduleManager( + super(TransferScheduleManagerTestCase, self).setUp() + self.transfer_schedule = transfer_schedules.TransferScheduleManager( mock_client) - @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_list") + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_list") def test_list(self, mock_list): - result = self.replica_schedule.list( - mock.sentinel.replica, hide_expired=False) + result = self.transfer_schedule.list( + mock.sentinel.transfer, hide_expired=False) self.assertEqual( mock_list.return_value, result ) mock_list.assert_called_once_with( - '/replicas/%s/schedules' % mock.sentinel.replica, "schedules") + '/transfers/%s/schedules' % mock.sentinel.transfer, "schedules") - @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_list") + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_list") def test_list_hide_expired(self, mock_list): - result = self.replica_schedule.list( - mock.sentinel.replica, hide_expired=True) + result = self.transfer_schedule.list( + mock.sentinel.transfer, hide_expired=True) self.assertEqual( mock_list.return_value, result ) mock_list.assert_called_once_with( - '/replicas/%s/schedules?show_expired=False' - % mock.sentinel.replica, "schedules") + '/transfers/%s/schedules?show_expired=False' + % mock.sentinel.transfer, "schedules") - @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_get") + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_get") def test_get(self, mock_get): - result = self.replica_schedule.get( - mock.sentinel.replica, mock.sentinel.schedule) + result = self.transfer_schedule.get( + mock.sentinel.transfer, mock.sentinel.schedule) self.assertEqual( mock_get.return_value, result ) mock_get.assert_called_once_with( - "/replicas/%s/schedules/%s" % (mock.sentinel.replica, - mock.sentinel.schedule), + "/transfers/%s/schedules/%s" % (mock.sentinel.transfer, + mock.sentinel.schedule), "schedule") - @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_post") + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_post") def test_create(self, mock_post): expiration_date = datetime.datetime.fromisoformat("2034-11-26") expected_data = { @@ -66,8 +66,8 @@ def test_create(self, mock_post): "shutdown_instance": mock.sentinel.shutdown_instance } - result = self.replica_schedule.create( - mock.sentinel.replica, + result = self.transfer_schedule.create( + mock.sentinel.transfer, mock.sentinel.schedule, True, expiration_date, @@ -79,15 +79,15 @@ def test_create(self, mock_post): result ) mock_post.assert_called_once_with( - '/replicas/%s/schedules' % mock.sentinel.replica, + '/transfers/%s/schedules' % mock.sentinel.transfer, expected_data, "schedule") - @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_put") + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_put") def test_update(self, mock_put): expiration_date = datetime.datetime.fromisoformat("2034-11-26") updated_values = {"expiration_date": expiration_date} - result = self.replica_schedule.update( - mock.sentinel.replica_id, + result = self.transfer_schedule.update( + mock.sentinel.transfer_id, mock.sentinel.schedule_id, updated_values ) @@ -97,28 +97,28 @@ def test_update(self, mock_put): result ) mock_put.assert_called_once_with( - "/replicas/%s/schedules/%s" % (mock.sentinel.replica_id, - mock.sentinel.schedule_id), + "/transfers/%s/schedules/%s" % (mock.sentinel.transfer_id, + mock.sentinel.schedule_id), {"expiration_date": '2034-11-26T00:00:00Z'}, "schedule") - @mock.patch.object(replica_schedules.ReplicaScheduleManager, "_delete") + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_delete") def test_delete(self, mock_delete): - result = self.replica_schedule.delete( - mock.sentinel.replica, mock.sentinel.schedule) + result = self.transfer_schedule.delete( + mock.sentinel.transfer, mock.sentinel.schedule) self.assertEqual( mock_delete.return_value, result ) mock_delete.assert_called_once_with( - "/replicas/%s/schedules/%s" % (mock.sentinel.replica, - mock.sentinel.schedule),) + "/transfers/%s/schedules/%s" % (mock.sentinel.transfer, + mock.sentinel.schedule),) def test_format_rfc3339_datetime(self): dt = datetime.date(2024, 1, 1) - result = self.replica_schedule._format_rfc3339_datetime(dt) + result = self.transfer_schedule._format_rfc3339_datetime(dt) self.assertEqual( "2024-01-01Z", diff --git a/coriolisclient/tests/v1/test_replicas.py b/coriolisclient/tests/v1/test_transfers.py similarity index 58% rename from coriolisclient/tests/v1/test_replicas.py rename to coriolisclient/tests/v1/test_transfers.py index d8f92c0..9d76646 100644 --- a/coriolisclient/tests/v1/test_replicas.py +++ b/coriolisclient/tests/v1/test_transfers.py @@ -4,17 +4,17 @@ from unittest import mock from coriolisclient.tests import test_base -from coriolisclient.v1 import replica_executions -from coriolisclient.v1 import replicas +from coriolisclient.v1 import transfer_executions +from coriolisclient.v1 import transfers -class ReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis v1 Replica.""" +class TransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer.""" - @mock.patch.object(replicas.Replica, "get") + @mock.patch.object(transfers.Transfer, "get") def test_properties(self, mock_get): mock_client = mock.Mock() - self.replica = replicas.Replica( + self.transfer = transfers.Transfer( mock_client, { "source_environment": { @@ -38,18 +38,18 @@ def test_properties(self, mock_get): mock.sentinel.execution2 ), ( - self.replica.source_environment.source_environment1, - self.replica.destination_environment.destination_environment1, - self.replica.executions[0].execution1, - self.replica.executions[1].execution2, + self.transfer.source_environment.source_environment1, + self.transfer.destination_environment.destination_environment1, + self.transfer.executions[0].execution1, + self.transfer.executions[1].execution2, ) ) mock_get.assert_not_called() - @mock.patch.object(replicas.Replica, "get") + @mock.patch.object(transfers.Transfer, "get") def test_properties_none(self, mock_get): mock_client = mock.Mock() - self.replica = replicas.Replica( + self.transfer = transfers.Transfer( mock_client, {} ) @@ -61,54 +61,54 @@ def test_properties_none(self, mock_get): [] ), ( - self.replica.source_environment, - self.replica.destination_environment, - self.replica.executions + self.transfer.source_environment, + self.transfer.destination_environment, + self.transfer.executions ) ) mock_get.assert_called_once() -class ReplicaManagerTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis v1 Replica Manager.""" +class TransferManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Manager.""" def setUp(self): mock_client = mock.Mock() - super(ReplicaManagerTestCase, self).setUp() - self.replica = replicas.ReplicaManager(mock_client) + super(TransferManagerTestCase, self).setUp() + self.transfer = transfers.TransferManager(mock_client) - @mock.patch.object(replicas.ReplicaManager, "_list") + @mock.patch.object(transfers.TransferManager, "_list") def test_list(self, mock_list): - result = self.replica.list(detail=False) + result = self.transfer.list(detail=False) self.assertEqual( mock_list.return_value, result ) - mock_list.assert_called_once_with("/replicas", "replicas") + mock_list.assert_called_once_with("/transfers", "transfers") - @mock.patch.object(replicas.ReplicaManager, "_list") + @mock.patch.object(transfers.TransferManager, "_list") def test_list_details(self, mock_list): - result = self.replica.list(detail=True) + result = self.transfer.list(detail=True) self.assertEqual( mock_list.return_value, result ) - mock_list.assert_called_once_with("/replicas/detail", "replicas") + mock_list.assert_called_once_with("/transfers/detail", "transfers") - @mock.patch.object(replicas.ReplicaManager, "_get") + @mock.patch.object(transfers.TransferManager, "_get") def test_get(self, mock_get): - result = self.replica.get(mock.sentinel.replica) + result = self.transfer.get(mock.sentinel.transfer) self.assertEqual( mock_get.return_value, result ) mock_get.assert_called_once_with( - "/replicas/%s" % (mock.sentinel.replica), "replica") + "/transfers/%s" % mock.sentinel.transfer, "transfer") - @mock.patch.object(replicas.ReplicaManager, "_post") + @mock.patch.object(transfers.TransferManager, "_post") def test_create(self, mock_post): expected_data = { "origin_endpoint_id": mock.sentinel.origin_endpoint_id, @@ -119,6 +119,7 @@ def test_create(self, mock_post): "storage_mappings": mock.sentinel.storage_mappings }, "instances": mock.sentinel.instances, + "scenario": mock.sentinel.scenario, "network_map": mock.sentinel.network_map, "notes": mock.sentinel.notes, "storage_mappings": mock.sentinel.storage_mappings, @@ -129,9 +130,9 @@ def test_create(self, mock_post): "instance_osmorphing_minion_pool_mappings": mock.sentinel.instance_osmorphing_minion_pool_mappings, } - expected_data = {"replica": expected_data} + expected_data = {"transfer": expected_data} - result = self.replica.create( + result = self.transfer.create( mock.sentinel.origin_endpoint_id, mock.sentinel.destination_endpoint_id, mock.sentinel.source_environment, @@ -140,6 +141,7 @@ def test_create(self, mock_post): "storage_mappings": mock.sentinel.storage_mappings }, mock.sentinel.instances, + mock.sentinel.scenario, network_map=None, notes=mock.sentinel.notes, storage_mappings=None, @@ -156,52 +158,52 @@ def test_create(self, mock_post): result ) mock_post.assert_called_once_with( - "/replicas", expected_data, "replica") + "/transfers", expected_data, "transfer") - @mock.patch.object(replicas.ReplicaManager, "_delete") + @mock.patch.object(transfers.TransferManager, "_delete") def test_delete(self, mock_delete): - result = self.replica.delete(mock.sentinel.replica) + result = self.transfer.delete(mock.sentinel.transfer) self.assertEqual( mock_delete.return_value, result ) mock_delete.assert_called_once_with( - "/replicas/%s" % mock.sentinel.replica) + "/transfers/%s" % mock.sentinel.transfer) - @mock.patch.object(replica_executions, "ReplicaExecution") - def test_delete_disks(self, mock_ReplicaExecution): - result = self.replica.delete_disks(mock.sentinel.replica) + @mock.patch.object(transfer_executions, "TransferExecution") + def test_delete_disks(self, mock_TransferExecution): + result = self.transfer.delete_disks(mock.sentinel.transfer) self.assertEqual( - mock_ReplicaExecution.return_value, + mock_TransferExecution.return_value, result ) - self.replica.client.post.assert_called_once_with( - "/replicas/%s/actions" % mock.sentinel.replica, + self.transfer.client.post.assert_called_once_with( + "/transfers/%s/actions" % mock.sentinel.transfer, json={'delete-disks': None}) - mock_ReplicaExecution.assert_called_once_with( - self.replica, - (self.replica.client.post.return_value.json.return_value. + mock_TransferExecution.assert_called_once_with( + self.transfer, + (self.transfer.client.post.return_value.json.return_value. get("execution")), loaded=True ) - @mock.patch.object(replica_executions, "ReplicaExecution") - def test_update(self, mock_ReplicaExecution): + @mock.patch.object(transfer_executions, "TransferExecution") + def test_update(self, mock_TransferExecution): updated_values = {"network_map": mock.sentinel.network_map} - result = self.replica.update(mock.sentinel.replica, updated_values) + result = self.transfer.update(mock.sentinel.transfer, updated_values) self.assertEqual( - mock_ReplicaExecution.return_value, + mock_TransferExecution.return_value, result ) - self.replica.client.put.assert_called_once_with( - "/replicas/%s" % mock.sentinel.replica, - json={"replica": updated_values}) - mock_ReplicaExecution.assert_called_once_with( - self.replica, - (self.replica.client.put.return_value.json.return_value. + self.transfer.client.put.assert_called_once_with( + "/transfers/%s" % mock.sentinel.transfer, + json={"transfer": updated_values}) + mock_TransferExecution.assert_called_once_with( + self.transfer, + (self.transfer.client.put.return_value.json.return_value. get("execution")), loaded=True ) diff --git a/coriolisclient/v1/deployments.py b/coriolisclient/v1/deployments.py index 8887921..78558e7 100644 --- a/coriolisclient/v1/deployments.py +++ b/coriolisclient/v1/deployments.py @@ -62,12 +62,12 @@ def get(self, deployment): return self._get( '/deployments/%s' % base.getid(deployment), 'deployment') - def create_from_replica(self, replica_id, clone_disks=True, force=False, - skip_os_morphing=False, user_scripts=None, - instance_osmorphing_minion_pool_mappings=None): + def create_from_transfer(self, transfer_id, clone_disks=True, force=False, + skip_os_morphing=False, user_scripts=None, + instance_osmorphing_minion_pool_mappings=None): data = { "deployment": { - "replica_id": replica_id, + "transfer_id": transfer_id, "clone_disks": clone_disks, "force": force, "skip_os_morphing": skip_os_morphing, @@ -78,7 +78,7 @@ def create_from_replica(self, replica_id, clone_disks=True, force=False, return self._post('/deployments', data, 'deployment') def delete(self, deployment): - return self._delete('/deployment/%s' % base.getid(deployment)) + return self._delete('/deployments/%s' % base.getid(deployment)) def cancel(self, deployment, force=False): return self.client.post( diff --git a/coriolisclient/v1/replica_executions.py b/coriolisclient/v1/transfer_executions.py similarity index 59% rename from coriolisclient/v1/replica_executions.py rename to coriolisclient/v1/transfer_executions.py index 2f906f8..731fc38 100644 --- a/coriolisclient/v1/replica_executions.py +++ b/coriolisclient/v1/transfer_executions.py @@ -17,7 +17,7 @@ from coriolisclient.v1 import common -class ReplicaExecution(base.Resource): +class TransferExecution(base.Resource): _tasks = None @property @@ -28,37 +28,38 @@ def tasks(self): self._info.get('tasks', [])] -class ReplicaExecutionManager(base.BaseManager): - resource_class = ReplicaExecution +class TransferExecutionManager(base.BaseManager): + resource_class = TransferExecution def __init__(self, api): - super(ReplicaExecutionManager, self).__init__(api) + super(TransferExecutionManager, self).__init__(api) - def list(self, replica): + def list(self, transfer): return self._list( - '/replicas/%s/executions' % base.getid(replica), 'executions') + '/transfers/%s/executions' % base.getid(transfer), 'executions') - def get(self, replica, execution): + def get(self, transfer, execution): return self._get( - '/replicas/%(replica_id)s/executions/%(execution_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/executions/%(execution_id)s' % + {"transfer_id": base.getid(transfer), "execution_id": base.getid(execution)}, 'execution') - def create(self, replica, shutdown_instances=False): + def create(self, transfer, shutdown_instances=False): data = {"execution": {"shutdown_instances": shutdown_instances}} return self._post( - '/replicas/%s/executions' % base.getid(replica), data, 'execution') + '/transfers/%s/executions' % + base.getid(transfer), data, 'execution') - def delete(self, replica, execution): + def delete(self, transfer, execution): return self._delete( - '/replicas/%(replica_id)s/executions/%(execution_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/executions/%(execution_id)s' % + {"transfer_id": base.getid(transfer), "execution_id": base.getid(execution)}) - def cancel(self, replica, execution, force=False): + def cancel(self, transfer, execution, force=False): return self.client.post( - '/replicas/%(replica_id)s/executions/%(execution_id)s/actions' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/executions/%(execution_id)s/actions' % + {"transfer_id": base.getid(transfer), "execution_id": base.getid(execution)}, json={'cancel': {'force': force}}) diff --git a/coriolisclient/v1/replica_schedules.py b/coriolisclient/v1/transfer_schedules.py similarity index 68% rename from coriolisclient/v1/replica_schedules.py rename to coriolisclient/v1/transfer_schedules.py index 5b83f82..c02e785 100644 --- a/coriolisclient/v1/replica_schedules.py +++ b/coriolisclient/v1/transfer_schedules.py @@ -18,32 +18,33 @@ from coriolisclient import base -class ReplicaSchedule(base.Resource): +class TransferSchedule(base.Resource): _tasks = None -class ReplicaScheduleManager(base.BaseManager): - resource_class = ReplicaSchedule +class TransferScheduleManager(base.BaseManager): + resource_class = TransferSchedule def __init__(self, api): - super(ReplicaScheduleManager, self).__init__(api) + super(TransferScheduleManager, self).__init__(api) - def list(self, replica, hide_expired=False): + def list(self, transfer, hide_expired=False): query = {} - url = '/replicas/%s/schedules' % base.getid(replica) if hide_expired: query["show_expired"] = hide_expired is False + url = '/transfers/%s/schedules' % base.getid(transfer) + if query: url += "?" + urlparse.urlencode(query) return self._list(url, 'schedules') - def get(self, replica, schedule): + def get(self, transfer, schedule): return self._get( - '/replicas/%(replica_id)s/schedules/%(schedule_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/schedules/%(schedule_id)s' % + {"transfer_id": base.getid(transfer), "schedule_id": base.getid(schedule)}, 'schedule') - def create(self, replica, schedule, enabled, expiration_date, + def create(self, transfer, schedule, enabled, expiration_date, shutdown_instance): data = { "schedule": schedule, @@ -54,9 +55,9 @@ def create(self, replica, schedule, enabled, expiration_date, data["expiration_date"] = self._format_rfc3339_datetime( expiration_date) return self._post( - '/replicas/%s/schedules' % base.getid(replica), data, 'schedule') + '/transfers/%s/schedules' % base.getid(transfer), data, 'schedule') - def update(self, replica_id, schedule_id, updated_values): + def update(self, transfer_id, schedule_id, updated_values): expiration_date = updated_values.get("expiration_date") if expiration_date: updated_values = updated_values.copy() @@ -64,15 +65,15 @@ def update(self, replica_id, schedule_id, updated_values): expiration_date) return self._put( - '/replicas/%(replica_id)s/schedules/%(schedule_id)s' % { - "replica_id": base.getid(replica_id), + '/transfers/%(transfer_id)s/schedules/%(schedule_id)s' % { + "transfer_id": base.getid(transfer_id), "schedule_id": base.getid(schedule_id)}, updated_values, 'schedule') - def delete(self, replica, schedule): + def delete(self, transfer, schedule): return self._delete( - '/replicas/%(replica_id)s/schedules/%(schedule_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/schedules/%(schedule_id)s' % + {"transfer_id": base.getid(transfer), "schedule_id": base.getid(schedule)}) @staticmethod diff --git a/coriolisclient/v1/replicas.py b/coriolisclient/v1/transfers.py similarity index 70% rename from coriolisclient/v1/replicas.py rename to coriolisclient/v1/transfers.py index 48a3b5c..ebba87d 100644 --- a/coriolisclient/v1/replicas.py +++ b/coriolisclient/v1/transfers.py @@ -15,10 +15,10 @@ from coriolisclient import base from coriolisclient.v1 import common -from coriolisclient.v1 import replica_executions +from coriolisclient.v1 import transfer_executions -class Replica(base.Resource): +class Transfer(base.Resource): _tasks = None @property @@ -41,24 +41,24 @@ def executions(self): self._info.get('executions', [])] -class ReplicaManager(base.BaseManager): - resource_class = Replica +class TransferManager(base.BaseManager): + resource_class = Transfer def __init__(self, api): - super(ReplicaManager, self).__init__(api) + super(TransferManager, self).__init__(api) def list(self, detail=False): - path = "/replicas" + path = "/transfers" if detail: path = "%s/detail" % path - return self._list(path, 'replicas') + return self._list(path, 'transfers') - def get(self, replica): - return self._get('/replicas/%s' % base.getid(replica), 'replica') + def get(self, transfer): + return self._get('/transfers/%s' % base.getid(transfer), 'transfer') def create(self, origin_endpoint_id, destination_endpoint_id, source_environment, destination_environment, instances, - replica_scenario, + transfer_scenario, network_map=None, notes=None, storage_mappings=None, origin_minion_pool_id=None, destination_minion_pool_id=None, instance_osmorphing_minion_pool_mappings=None, @@ -69,12 +69,12 @@ def create(self, origin_endpoint_id, destination_endpoint_id, storage_mappings = destination_environment.get( 'storage_mappings', {}) data = { - "replica": { + "transfer": { "origin_endpoint_id": origin_endpoint_id, "destination_endpoint_id": destination_endpoint_id, "destination_environment": destination_environment, "instances": instances, - "scenario": replica_scenario, + "scenario": transfer_scenario, "network_map": network_map, "notes": notes, "storage_mappings": storage_mappings, @@ -82,35 +82,35 @@ def create(self, origin_endpoint_id, destination_endpoint_id, } } if source_environment: - data['replica']['source_environment'] = source_environment + data['transfer']['source_environment'] = source_environment if origin_minion_pool_id is not None: - data['replica']['origin_minion_pool_id'] = origin_minion_pool_id + data['transfer']['origin_minion_pool_id'] = origin_minion_pool_id if destination_minion_pool_id is not None: - data['replica']['destination_minion_pool_id'] = ( + data['transfer']['destination_minion_pool_id'] = ( destination_minion_pool_id) if instance_osmorphing_minion_pool_mappings: - data['replica']['instance_osmorphing_minion_pool_mappings'] = ( + data['transfer']['instance_osmorphing_minion_pool_mappings'] = ( instance_osmorphing_minion_pool_mappings) - return self._post('/replicas', data, 'replica') + return self._post('/transfers', data, 'transfer') - def delete(self, replica): - return self._delete('/replicas/%s' % base.getid(replica)) + def delete(self, transfer): + return self._delete('/transfers/%s' % base.getid(transfer)) - def delete_disks(self, replica): + def delete_disks(self, transfer): response = self.client.post( - '/replicas/%s/actions' % base.getid(replica), + '/transfers/%s/actions' % base.getid(transfer), json={'delete-disks': None}) - return replica_executions.ReplicaExecution( + return transfer_executions.TransferExecution( self, response.json().get("execution"), loaded=True) - def update(self, replica, updated_values): + def update(self, transfer, updated_values): data = { - "replica": updated_values + "transfer": updated_values } response = self.client.put( - '/replicas/%s' % base.getid(replica), json=data) + '/transfers/%s' % base.getid(transfer), json=data) - return replica_executions.ReplicaExecution( + return transfer_executions.TransferExecution( self, response.json().get("execution"), loaded=True) diff --git a/samples/replica_samples.py b/samples/replica_samples.py index 1df76dc..7fec360 100644 --- a/samples/replica_samples.py +++ b/samples/replica_samples.py @@ -102,7 +102,7 @@ def create_vm_replicas(coriolis, source_endpoint, destination_endpoint, print("Creating Replica for VM(s): %s" % vm_group) replicas.append( - coriolis.replicas.create( + coriolis.transfers.create( source_endpoint, destination_endpoint, source_options, destination_options, vm_group, @@ -118,7 +118,7 @@ def get_replicas_for_endpoint(coriolis, endpoint, endpoint = coriolis.endpoints.get(endpoint) found = [] - for replica in coriolis.replicas.list(): + for replica in coriolis.transfers.list(): if as_source and replica.origin_endpoint_id == endpoint.id: found.append(replica) @@ -131,7 +131,7 @@ def get_replicas_for_endpoint(coriolis, endpoint, def get_replicas_for_vm(coriolis, vm_name): """ Returns all Replicas which include the given VM name. """ replicas = [] - for replica in coriolis.replicas.list(): + for replica in coriolis.transfers.list(): if vm_name in replica.instances: replicas.append(replica) @@ -141,7 +141,7 @@ def get_replicas_for_vm(coriolis, vm_name): def get_errord_replicas(coriolis): """ Returns a list of all the Replicas whose last execution error'd. """ errord = [] - for replica in coriolis.replicas.list(): + for replica in coriolis.transfers.list(): if not replica.executions: # Replica was never executed continue @@ -162,13 +162,13 @@ def wait_for_replica_execution(coriolis, replica, execution, """ tries = 0 - execution = coriolis.replica_executions.get( + execution = coriolis.transfer_executions.get( replica, execution) while tries < max_tries and execution.status == "RUNNING": print("Waiting on execution %s (currently %s)" % ( execution.id, execution.status)) time.sleep(retry_period) - execution = coriolis.replica_executions.get( + execution = coriolis.transfer_executions.get( replica, execution) if execution.status == "ERROR": @@ -220,15 +220,15 @@ def delete_replica(coriolis, replica, delete_replica_disks=True): the Replica itself. """ if delete_replica_disks: - execution = coriolis.replicas.delete_disks(replica) + execution = coriolis.transfers.delete_disks(replica) wait_for_replica_execution(coriolis, replica, execution) # NOTE: this is redundant as executions are cascade-deleted on # the deletion of the parent Replica anyway: for execution in reversed(replica.executions): - coriolis.replica_executions.delete(execution) + coriolis.transfer_executions.delete(execution) - coriolis.replicas.delete(replica) + coriolis.transfers.delete(replica) def main(): @@ -252,14 +252,14 @@ def main(): # execute the Replica: test_replica = replicas[0] - execution = coriolis.replica_executions.create( + execution = coriolis.transfer_executions.create( test_replica, shutdown_instances=True) # wait for execution to finish: wait_for_replica_execution(coriolis, test_replica, execution) # create a Migration from the Replica: - migration = coriolis.migrations.create_from_replica( + migration = coriolis.migrations.create_from_transfer( test_replica.id, clone_disks=True, skip_os_morphing=False) migration = wait_for_replica_migration(coriolis, migration) print("Migrated VM info is: %s" % ( diff --git a/setup.cfg b/setup.cfg index e39bbbe..b67cc70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,13 +44,6 @@ coriolis.v1 = endpoint_source_minion_pool_options_list = coriolisclient.cli.endpoint_source_minion_pool_options:ListEndpointSourceMinionPoolOptions endpoint_destination_minion_pool_options_list = coriolisclient.cli.endpoint_destination_minion_pool_options:ListEndpointDestinationMinionPoolOptions - migration_cancel = coriolisclient.cli.migrations:CancelMigration - migration_create = coriolisclient.cli.migrations:CreateMigration - migration_deploy_replica = coriolisclient.cli.migrations:CreateMigrationFromReplica - migration_delete = coriolisclient.cli.migrations:DeleteMigration - migration_list = coriolisclient.cli.migrations:ListMigration - migration_show = coriolisclient.cli.migrations:ShowMigration - deployment_cancel = coriolisclient.cli.deployments:CancelDeployment deployment_create = coriolisclient.cli.deployments:CreateDeployment deployment_delete = coriolisclient.cli.deployments:DeleteDeployment @@ -60,24 +53,24 @@ coriolis.v1 = provider_list = coriolisclient.cli.providers:ListProvider provider_schema_list = coriolisclient.cli.providers:ListProviderSchemas - replica_create = coriolisclient.cli.replicas:CreateReplica - replica_delete = coriolisclient.cli.replicas:DeleteReplica - replica_disks_delete = coriolisclient.cli.replicas:DeleteReplicaDisks - replica_list = coriolisclient.cli.replicas:ListReplica - replica_show = coriolisclient.cli.replicas:ShowReplica - replica_update = coriolisclient.cli.replicas:UpdateReplica - - replica_execution_cancel = coriolisclient.cli.replica_executions:CancelReplicaExecution - replica_execute = coriolisclient.cli.replica_executions:CreateReplicaExecution - replica_execution_delete = coriolisclient.cli.replica_executions:DeleteReplicaExecution - replica_execution_list = coriolisclient.cli.replica_executions:ListReplicaExecution - replica_execution_show = coriolisclient.cli.replica_executions:ShowReplicaExecution - - replica_schedule_delete = coriolisclient.cli.replica_schedules:DeleteReplicaSchedule - replica_schedule_list = coriolisclient.cli.replica_schedules:ListReplicaSchedule - replica_schedule_show = coriolisclient.cli.replica_schedules:ShowReplicaSchedule - replica_schedule_create = coriolisclient.cli.replica_schedules:CreateReplicaSchedule - replica_schedule_update = coriolisclient.cli.replica_schedules:UpdateReplicaSchedule + transfer_create = coriolisclient.cli.transfers:CreateTransfer + transfer_delete = coriolisclient.cli.transfers:DeleteTransfer + transfer_disks_delete = coriolisclient.cli.transfers:DeleteTransferDisks + transfer_list = coriolisclient.cli.transfers:ListTransfer + transfer_show = coriolisclient.cli.transfers:ShowTransfer + transfer_update = coriolisclient.cli.transfers:UpdateTransfer + + transfer_execution_cancel = coriolisclient.cli.transfer_executions:CancelTransferExecution + transfer_execute = coriolisclient.cli.transfer_executions:CreateTransferExecution + transfer_execution_delete = coriolisclient.cli.transfer_executions:DeleteTransferExecution + transfer_execution_list = coriolisclient.cli.transfer_executions:ListTransferExecution + transfer_execution_show = coriolisclient.cli.transfer_executions:ShowTransferExecution + + transfer_schedule_delete = coriolisclient.cli.transfer_schedules:DeleteTransferSchedule + transfer_schedule_list = coriolisclient.cli.transfer_schedules:ListTransferSchedule + transfer_schedule_show = coriolisclient.cli.transfer_schedules:ShowTransferSchedule + transfer_schedule_create = coriolisclient.cli.transfer_schedules:CreateTransferSchedule + transfer_schedule_update = coriolisclient.cli.transfer_schedules:UpdateTransferSchedule region_create = coriolisclient.cli.regions:CreateRegion region_list = coriolisclient.cli.regions:ListRegions From 9ad867f7330d81fccb4e2fd49f8f410d176ff711 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Fri, 29 Nov 2024 17:30:12 +0200 Subject: [PATCH 42/51] Add deployments unit tests --- coriolisclient/cli/deployments.py | 4 +- coriolisclient/tests/cli/test_deployments.py | 340 +++++++++++++++++++ coriolisclient/tests/v1/test_deployments.py | 124 +++++++ 3 files changed, 466 insertions(+), 2 deletions(-) create mode 100644 coriolisclient/tests/cli/test_deployments.py create mode 100644 coriolisclient/tests/v1/test_deployments.py diff --git a/coriolisclient/cli/deployments.py b/coriolisclient/cli/deployments.py index 31eb7ee..74c6139 100644 --- a/coriolisclient/cli/deployments.py +++ b/coriolisclient/cli/deployments.py @@ -200,7 +200,7 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - m = self.app.client_manager.coriolis.deployments + d = self.app.client_manager.coriolis.deployments user_scripts = cli_utils.compose_user_scripts( args.global_scripts, args.instance_scripts) instance_osmorphing_minion_pool_mappings = None @@ -209,7 +209,7 @@ def take_action(self, args): mp['instance_id']: mp['pool_id'] for mp in args.instance_osmorphing_minion_pool_mappings} - deployment = m.create_from_transfer( + deployment = d.create_from_transfer( args.transfer, args.clone_disks, args.force, diff --git a/coriolisclient/tests/cli/test_deployments.py b/coriolisclient/tests/cli/test_deployments.py new file mode 100644 index 0000000..f00065f --- /dev/null +++ b/coriolisclient/tests/cli/test_deployments.py @@ -0,0 +1,340 @@ +# Copyright (c) 2024 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import copy +import os +from unittest import mock + +from coriolisclient.cli import deployments +from coriolisclient.tests import test_base +from coriolisclient.v1 import common +from coriolisclient.v1 import deployments as v1_deployments + +DEPLOYMENT_ID = "depl_id" +DEPLOYMENT_DATA = { + "storage_mappings": None, + "id": DEPLOYMENT_ID, + "last_execution_status": "COMPLETED", + "created_at": None, + "updated_at": None, + "transfer_id": "transfer_1", + "transfer_scenario_type": "replica", + "reservation_id": "res1", + "instances": ["instance1"], + "notes": "", + "origin_endpoint_id": "source1", + "origin_minion_pool_id": None, + "destination_endpoint_id": "dest1", + "destination_minion_pool_id": None, + "instance_osmorphing_minion_pool_mappings": None, + "shutdown_instances": False, + "destination_environment": {"opt1": "env1"}, + "source_environment": None, + "network_map": {"net_source": "net_dest"}, + "user_scripts": None, + "tasks": [], + "transfer_result": None, +} +DEPLOYMENT_FORMATTED_DATA = [ + "depl_id", "COMPLETED", None, None, "transfer_1", "replica", "res1", + "instance1", "", "source1", None, "dest1", None, '{}', False, + '{\n "opt1": "env1"\n}', '{}', '{\n "net_source": "net_dest"\n}', "", "", + None, '{}', "", '{}'] + +DEPLOYMENT_LIST_DATA = { + "id": "1", + "transfer_id": "2", + "last_execution_status": "RUNNING", + "instances": ["instance1", "instance2"], + "notes": "test_notes", + "created_at": "2024-11-28T15:18:25.000000", +} +DEPLOYMENT_LIST_FORMATTED_DATA = ( + "1", "2", "RUNNING", "instance1\ninstance2", "test_notes", + "2024-11-28T15:18:25.000000") +APP_MOCK = mock.MagicMock() + + +class DeploymentFormatterTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentFormatterTestCase, self).setUp() + self.formatter = deployments.DeploymentFormatter() + + def test__get_sorted_list(self): + obj1 = mock.Mock(created_at="2024-10-24T18:51:32.000000") + obj2 = mock.Mock(created_at="2024-11-18T15:18:25.000000") + obj3 = mock.Mock(created_at="2024-11-28T15:18:25.000000") + obj_list = [obj1, obj3, obj2] + self.assertEqual( + [obj1, obj2, obj3], self.formatter._get_sorted_list(obj_list)) + + def test__get_formatted_data(self): + obj = mock.Mock(**DEPLOYMENT_LIST_DATA) + self.assertEqual( + DEPLOYMENT_LIST_FORMATTED_DATA, + self.formatter._get_formatted_data(obj)) + + +class DeploymentDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentDetailFormatterTestCase, self).setUp() + self.formatter = deployments.DeploymentDetailFormatter( + show_instances_data=True) + self.progress_updates = [ + {"created_at": "2024-10-24T18:51:32.000000", + "index": 0, + "message": "message 0"}, + {"created_at": "2024-11-18T15:18:25.000000", + "index": 2, + "message": "message 2"}, + {"created_at": "2024-11-28T15:18:25.000000", + "index": 1, + "message": "message 1"}, + ] + self.formatted_progress_updates = ( + f"2024-10-24T18:51:32.000000 message 0{os.linesep}" + f"2024-11-28T15:18:25.000000 message 1{os.linesep}" + f"2024-11-18T15:18:25.000000 message 2") + self.tasks_data = [ + { + "id": "1", + "task_type": "type1", + "instance": "instance1", + "status": "COMPLETED", + "depends_on": ["task0"], + "exception_details": None, + "progress_updates": self.progress_updates, + }, + { + "id": "2", + "task_type": "type2", + "instance": "instance1", + "status": "COMPLETED", + "depends_on": ["task0"], + "exception_details": None, + "progress_updates": self.progress_updates, + } + ] + self.formatted_tasks = [ + f'id: 1{os.linesep}' + f'task_type: type1{os.linesep}' + f'instance: instance1{os.linesep}' + f'status: COMPLETED{os.linesep}' + f'depends_on: task0{os.linesep}' + f'exception_details: {os.linesep}' + f'progress_updates:{os.linesep}' + f'{self.formatted_progress_updates}', + + f'id: 2{os.linesep}' + f'task_type: type2{os.linesep}' + f'instance: instance1{os.linesep}' + f'status: COMPLETED{os.linesep}' + f'depends_on: task0{os.linesep}' + f'exception_details: {os.linesep}' + f'progress_updates:{os.linesep}' + f'{self.formatted_progress_updates}' + ] + self.manager_mock = mock.MagicMock() + + def test_init_no_instances_data(self): + formatter = deployments.DeploymentDetailFormatter( + show_instances_data=False) + self.assertNotIn('instances_data', formatter.columns) + + def test__format_instances(self): + obj = mock.Mock(instances=["instance2", "instance3", "instance1"]) + self.assertEqual( + f"instance1{os.linesep}instance2{os.linesep}instance3", + self.formatter._format_instances(obj)) + + def test__format_progress_updates(self): + task_dict = { + "progress_updates": self.progress_updates} + self.assertEqual( + self.formatted_progress_updates, + self.formatter._format_progress_updates(task_dict)) + + def test__format_task(self): + task_data = self.tasks_data[0] + + task = common.Task(self.manager_mock, task_data) + + self.assertEqual( + self.formatted_tasks[0], self.formatter._format_task(task)) + + def test__format_tasks(self): + obj = mock.Mock( + tasks=[common.Task(self.manager_mock, task_data) + for task_data in self.tasks_data]) + expected_result = (f'{self.formatted_tasks[0]}{os.linesep}{os.linesep}' + f'{self.formatted_tasks[1]}') + self.assertEqual(expected_result, self.formatter._format_tasks(obj)) + + def test__get_formatted_data(self): + obj_data = { + **DEPLOYMENT_DATA, + "info": {"depl": "info"}, + } + obj = mock.Mock(**obj_data) + obj.to_dict.return_value = obj_data + expected_result = copy.copy(DEPLOYMENT_FORMATTED_DATA) + expected_result.append(obj_data['info']) + + self.assertEqual( + expected_result, self.formatter._get_formatted_data(obj)) + + +class CreateDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(CreateDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.CreateDeployment(self.mock_app, 'app_arg') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + global_script = "linux=/linux/path" + instance_script = "instance1=/instance1/path" + args = parser.parse_args([ + 'transfer_id', '--force', '--dont-clone-disks', + '--skip-os-morphing', '--user-script-global', global_script, + '--user-script-instance', instance_script]) + self.assertEqual( + ('transfer_id', True, False, True, [global_script], + [instance_script]), + (args.transfer, args.force, args.clone_disks, + args.skip_os_morphing, args.global_scripts, + args.instance_scripts)) + + def test_take_action(self): + args = mock.Mock(global_scripts=None, instance_scripts=None) + args.instance_osmorphing_minion_pool_mappings = [ + {"instance_id": "instance1", "pool_id": "pool1"}] + mock_fun = (self.mock_app.client_manager.coriolis.deployments. + create_from_transfer) + mock_fun.return_value = ( + v1_deployments.Deployment(mock.MagicMock(), DEPLOYMENT_DATA)) + expected_pool_mappings = {"instance1": "pool1"} + expected_user_scripts = {"global": {}, "instances": {}} + expected_data = copy.copy(DEPLOYMENT_FORMATTED_DATA) + + columns, data = self.cli.take_action(args) + + self.assertEqual( + deployments.DeploymentDetailFormatter().columns, columns) + self.assertEqual(expected_data, data) + mock_fun.assert_called_once_with( + args.transfer, args.clone_disks, args.force, args.skip_os_morphing, + user_scripts=expected_user_scripts, + instance_osmorphing_minion_pool_mappings=expected_pool_mappings) + + +class ShowDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(ShowDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.ShowDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + args = parser.parse_args([DEPLOYMENT_ID, '--show-instances-data']) + self.assertEqual( + (DEPLOYMENT_ID, True), + (args.id, args.show_instances_data)) + + def test_take_action(self): + show_instances_data = False + args = mock.Mock( + id=DEPLOYMENT_ID, show_instances_data=show_instances_data) + mock_fun = self.mock_app.client_manager.coriolis.deployments.get + mock_fun.return_value = v1_deployments.Deployment( + mock.MagicMock(), DEPLOYMENT_DATA) + expected_data = DEPLOYMENT_FORMATTED_DATA + + columns, data = self.cli.take_action(args) + + self.assertEqual( + deployments.DeploymentDetailFormatter( + show_instances_data=show_instances_data).columns, columns) + self.assertEqual(expected_data, data) + mock_fun.assert_called_once_with(DEPLOYMENT_ID) + + +class CancelDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(CancelDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.CancelDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + args = parser.parse_args([DEPLOYMENT_ID, '--force']) + self.assertEqual((DEPLOYMENT_ID, True), (args.id, args.force)) + + def test_take_action(self): + force = True + args = mock.Mock(id=DEPLOYMENT_ID, force=force) + mock_fun = self.mock_app.client_manager.coriolis.deployments.cancel + + self.cli.take_action(args) + + mock_fun.assert_called_once_with(DEPLOYMENT_ID, force) + + +class DeleteDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeleteDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.DeleteDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + args = parser.parse_args([DEPLOYMENT_ID]) + self.assertEqual(DEPLOYMENT_ID, args.id) + + def test_take_action(self): + args = mock.Mock(id=DEPLOYMENT_ID) + mock_fun = self.mock_app.client_manager.coriolis.deployments.delete + + self.cli.take_action(args) + mock_fun.assert_called_once_with(DEPLOYMENT_ID) + + +class ListDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(ListDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.ListDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + self.assertIsInstance(parser, argparse.ArgumentParser) + + def test_take_action(self): + mock_fun = self.mock_app.client_manager.coriolis.deployments.list + mock_fun.return_value = [ + v1_deployments.Deployment(mock.MagicMock(), DEPLOYMENT_LIST_DATA)] + + columns, data = self.cli.take_action(mock.ANY) + + self.assertEqual(deployments.DeploymentFormatter().columns, columns) + self.assertEqual([DEPLOYMENT_LIST_FORMATTED_DATA], list(data)) diff --git a/coriolisclient/tests/v1/test_deployments.py b/coriolisclient/tests/v1/test_deployments.py new file mode 100644 index 0000000..f92fe9a --- /dev/null +++ b/coriolisclient/tests/v1/test_deployments.py @@ -0,0 +1,124 @@ +# Copyright (c) 2024 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import common +from coriolisclient.v1 import deployments + +DEPLOYMENT_ID = "1" + + +class DeploymentResourceTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentResourceTestCase, self).setUp() + self.source_env = {"source_opt": "env_value"} + self.dest_env = {"dest_opt": "env_value"} + self.transfer_result = {"result": "value"} + self.task = {"task": "type_1"} + self.info = { + "source_environment": self.source_env, + "destination_environment": self.dest_env, + "transfer_result": self.transfer_result, + "tasks": [self.task], + } + self.deployment = deployments.Deployment(None, self.info) + + def test_source_environment(self): + result = self.deployment.source_environment + + self.assertIsInstance(result, common.SourceEnvironment) + self.assertEqual(self.source_env, result._info) + + def test_destination_environment(self): + result = self.deployment.destination_environment + + self.assertIsInstance(result, common.DestinationEnvironment) + self.assertEqual(self.dest_env, result._info) + + def test_transfer_result(self): + result = self.deployment.transfer_result + + self.assertIsInstance(result, common.TransferResult) + self.assertEqual(self.transfer_result, result._info) + + def test_task(self): + result = self.deployment.tasks + + [self.assertIsInstance(t, common.Task) for t in result] + self.assertEqual(self.task, result[0]._info) + + +class DeploymentManagerTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentManagerTestCase, self).setUp() + mock_client = mock.Mock() + self.deployments = deployments.DeploymentManager(mock_client) + + def test_list(self): + with mock.patch.object(self.deployments, '_list') as mock_list: + result = self.deployments.list(detail=True) + self.assertEqual(mock_list.return_value, result) + mock_list.assert_called_once_with( + '/deployments/detail', 'deployments') + + def test_get(self): + deployment = mock.Mock(uuid=DEPLOYMENT_ID) + with mock.patch.object(self.deployments, '_get') as mock_get: + result = self.deployments.get(deployment) + self.assertEqual(mock_get.return_value, result) + mock_get.assert_called_once_with( + f'/deployments/{DEPLOYMENT_ID}', 'deployment') + + def test_create_from_transfer(self): + with mock.patch.object(self.deployments, '_post') as mock_post: + expected_data = { + "deployment": { + "transfer_id": mock.sentinel.transfer_id, + "clone_disks": True, + "force": False, + "skip_os_morphing": False, + "user_scripts": None, + "instance_osmorphing_minion_pool_mappings": + mock.sentinel.pool_mappings, + } + } + result = self.deployments.create_from_transfer( + mock.sentinel.transfer_id, clone_disks=True, force=False, + skip_os_morphing=False, user_scripts=None, + instance_osmorphing_minion_pool_mappings=( + mock.sentinel.pool_mappings)) + self.assertEqual(mock_post.return_value, result) + mock_post.assert_called_once_with( + "/deployments", expected_data, "deployment") + + def test_delete(self): + with mock.patch.object(self.deployments, '_delete') as mock_delete: + result = self.deployments.delete(DEPLOYMENT_ID) + self.assertEqual(mock_delete.return_value, result) + mock_delete.assert_called_once_with( + f'/deployments/{DEPLOYMENT_ID}') + + def test_cancel(self): + force = False + expected_data = {"cancel": {"force": force}} + with mock.patch.object(self.deployments.client, 'post') as mock_post: + result = self.deployments.cancel(DEPLOYMENT_ID, force=force) + self.assertEqual(mock_post.return_value, result) + mock_post.assert_called_once_with( + f"/deployments/{DEPLOYMENT_ID}/actions", json=expected_data) From f7e0f27d39ee3bbac3117c52dced5d7a9c733b01 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 29 Jan 2025 14:27:15 +0200 Subject: [PATCH 43/51] Expose `auto_deploy` transfer execution option. --- coriolisclient/cli/transfer_executions.py | 7 ++++++- coriolisclient/v1/transfer_executions.py | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/coriolisclient/cli/transfer_executions.py b/coriolisclient/cli/transfer_executions.py index a585f84..0b1e199 100644 --- a/coriolisclient/cli/transfer_executions.py +++ b/coriolisclient/cli/transfer_executions.py @@ -110,12 +110,17 @@ def get_parser(self, prog_name): help='Shutdown instances before executing the ' 'transfer', action='store_true', default=False) + parser.add_argument('--auto-deploy', + help="Automatically execute deployment after the " + "transfer execution finishes", + action='store_true', + default=False) return parser def take_action(self, args): execution = ( self.app.client_manager.coriolis.transfer_executions.create( - args.transfer, args.shutdown_instances)) + args.transfer, args.shutdown_instances, args.auto_deploy)) return TransferExecutionDetailFormatter().get_formatted_entity( execution) diff --git a/coriolisclient/v1/transfer_executions.py b/coriolisclient/v1/transfer_executions.py index 731fc38..51d4e0a 100644 --- a/coriolisclient/v1/transfer_executions.py +++ b/coriolisclient/v1/transfer_executions.py @@ -45,8 +45,10 @@ def get(self, transfer, execution): "execution_id": base.getid(execution)}, 'execution') - def create(self, transfer, shutdown_instances=False): - data = {"execution": {"shutdown_instances": shutdown_instances}} + def create(self, transfer, shutdown_instances=False, auto_deploy=False): + data = {"execution": + {"shutdown_instances": shutdown_instances, + "auto_deploy": auto_deploy}} return self._post( '/transfers/%s/executions' % base.getid(transfer), data, 'execution') From ea41438aa4188dd9b42098508674479af3282026 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Thu, 30 Jan 2025 13:35:31 +0200 Subject: [PATCH 44/51] Expose default deployment options to transfers creation and update --- coriolisclient/cli/transfers.py | 41 +++++++++++++++++++++++++++++++-- coriolisclient/v1/transfers.py | 4 +++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/coriolisclient/cli/transfers.py b/coriolisclient/cli/transfers.py index c8ede4c..9ab4009 100644 --- a/coriolisclient/cli/transfers.py +++ b/coriolisclient/cli/transfers.py @@ -27,11 +27,36 @@ from coriolisclient.cli import transfer_executions from coriolisclient.cli import utils as cli_utils - TRANSFER_SCENARIO_REPLICA = "replica" TRANSFER_SCENARIO_LIVE_MIGRATION = "live_migration" +def _add_default_deployment_args_to_parser(parser): + cd_group = parser.add_mutually_exclusive_group() + cd_group.add_argument('--clone-disks', + help='Retain the transfer disks by cloning them ' + 'when launching deployment', + action='store_true', dest="clone_disks", + default=None) + cd_group.add_argument('--dont-clone-disks', + help="Deploy directly on transfer disks, without " + "cloning them.", + action="store_false", dest="clone_disks", + default=None) + + osm_group = parser.add_mutually_exclusive_group() + osm_group.add_argument('--os-morphing', + help="Include the OSMorphing process on the " + "deployments of this transfer.", + action="store_false", dest="skip_os_morphing", + default=None) + osm_group.add_argument('--skip-os-morphing', + help='Skip the OS morphing process on the ' + 'deployments of this transfer', + action='store_true', default=None, + dest="skip_os_morphing") + + class TransferFormatter(formatter.EntityFormatter): columns = ("ID", @@ -85,6 +110,8 @@ def __init__(self, show_instances_data=False): "storage_backend_mappings", "default_storage_backend", "user_scripts", + "clone_disks", + "skip_os_morphing", "executions", ] @@ -129,6 +156,8 @@ def _get_formatted_data(self, obj): cli_utils.format_mapping(backend_mappings), default_storage, cli_utils.format_json_for_object_property(obj, 'user_scripts'), + obj.clone_disks, + obj.skip_os_morphing, self._format_executions(obj.executions)] if "instances-data" in self.columns: @@ -195,6 +224,7 @@ def get_parser(self, prog_name): include_osmorphing_pool_mappings_arg=True) cli_utils.add_storage_mappings_arguments_to_parser(parser) + _add_default_deployment_args_to_parser(parser) return parser @@ -233,7 +263,9 @@ def take_action(self, args): destination_minion_pool_id=args.destination_minion_pool_id, instance_osmorphing_minion_pool_mappings=( instance_osmorphing_minion_pool_mappings), - user_scripts=user_scripts) + user_scripts=user_scripts, + clone_disks=args.clone_disks, + skip_os_morphing=args.skip_os_morphing) return TransferDetailFormatter().get_formatted_entity(transfer) @@ -330,6 +362,7 @@ def get_parser(self, prog_name): parser, include_origin_pool_arg=True, include_destination_pool_arg=True, include_osmorphing_pool_mappings_arg=True) + _add_default_deployment_args_to_parser(parser) return parser @@ -372,6 +405,10 @@ def take_action(self, args): instance_osmorphing_minion_pool_mappings) if user_scripts: updated_properties['user_scripts'] = user_scripts + if args.clone_disks is not None: + updated_properties['clone_disks'] = args.clone_disks + if args.skip_os_morphing is not None: + updated_properties['skip_os_morphing'] = args.skip_os_morphing if not updated_properties: raise ValueError( diff --git a/coriolisclient/v1/transfers.py b/coriolisclient/v1/transfers.py index ebba87d..d4559d5 100644 --- a/coriolisclient/v1/transfers.py +++ b/coriolisclient/v1/transfers.py @@ -62,7 +62,7 @@ def create(self, origin_endpoint_id, destination_endpoint_id, network_map=None, notes=None, storage_mappings=None, origin_minion_pool_id=None, destination_minion_pool_id=None, instance_osmorphing_minion_pool_mappings=None, - user_scripts=None): + user_scripts=None, clone_disks=True, skip_os_morphing=False): if not network_map: network_map = destination_environment.get('network_map', {}) if not storage_mappings: @@ -79,6 +79,8 @@ def create(self, origin_endpoint_id, destination_endpoint_id, "notes": notes, "storage_mappings": storage_mappings, "user_scripts": user_scripts, + "clone_disks": clone_disks, + "skip_os_morphing": skip_os_morphing, } } if source_environment: From 3cf34f7fb9b6fa06166022787bac7a9c1a4d5c55 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Mon, 3 Feb 2025 14:34:12 +0200 Subject: [PATCH 45/51] Expose 'auto_deploy' execution option to transfer schedules commands --- coriolisclient/cli/transfer_schedules.py | 28 +++++++++++++++++++++--- coriolisclient/v1/transfer_schedules.py | 3 ++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/coriolisclient/cli/transfer_schedules.py b/coriolisclient/cli/transfer_schedules.py index 90f8a67..c4d758a 100644 --- a/coriolisclient/cli/transfer_schedules.py +++ b/coriolisclient/cli/transfer_schedules.py @@ -72,7 +72,8 @@ class TransferScheduleDetailFormatter(formatter.EntityFormatter): "last_updated", "enabled", "expires", - "shutdown_instance") + "shutdown_instance", + "auto_deploy") def _get_formatted_data(self, obj): data = (obj.id, @@ -82,7 +83,8 @@ def _get_formatted_data(self, obj): obj.updated_at, obj.enabled, obj.expiration_date, - obj.shutdown_instance) + obj.shutdown_instance, + obj.auto_deploy) return data @@ -104,6 +106,11 @@ def get_parser(self, prog_name): help='Shutdown instance', action='store_true', default=False) + parser.add_argument('--auto-deploy', + help="Auto Deploy transfer after scheduled " + "execution completes.", + action="store_true", + default=False) return parser def take_action(self, args): @@ -115,7 +122,8 @@ def take_action(self, args): exp = _parse_expiration_date(args.expires_at) schedule = self.app.client_manager.coriolis.transfer_schedules.create( args.transfer, parsed_schedule, - args.disabled is False, exp, args.shutdown_instance) + args.disabled is False, exp, args.shutdown_instance, + args.auto_deploy) return TransferScheduleDetailFormatter().get_formatted_entity( schedule) @@ -179,6 +187,18 @@ def get_parser(self, prog_name): help="Don't shutdown instance", dest="shutdown", action='store_false') + auto_deploy_parser = parser.add_mutually_exclusive_group( + required=False) + auto_deploy_parser.add_argument( + "--auto-deploy", + help="Auto Deploy transfer after scheduled execution completes.", + dest="auto_deploy", + action="store_true") + auto_deploy_parser.add_argument( + "--dont-auto-deploy", + help="Stops auto deployment when starting scheduled execution", + dest="auto_deploy", + action="store_false") return parser def take_action(self, args): @@ -198,6 +218,8 @@ def take_action(self, args): updated_values["shutdown_instance"] = args.shutdown if args.enabled is not None: updated_values["enabled"] = args.enabled + if args.auto_deploy is not None: + updated_values['auto_deploy'] = args.auto_deploy schedule = self.app.client_manager.coriolis.transfer_schedules.update( args.transfer, args.id, updated_values) diff --git a/coriolisclient/v1/transfer_schedules.py b/coriolisclient/v1/transfer_schedules.py index c02e785..0dca527 100644 --- a/coriolisclient/v1/transfer_schedules.py +++ b/coriolisclient/v1/transfer_schedules.py @@ -45,11 +45,12 @@ def get(self, transfer, schedule): 'schedule') def create(self, transfer, schedule, enabled, expiration_date, - shutdown_instance): + shutdown_instance, auto_deploy): data = { "schedule": schedule, "enabled": enabled, "shutdown_instance": shutdown_instance, + "auto_deploy": auto_deploy, } if expiration_date: data["expiration_date"] = self._format_rfc3339_datetime( From fed41742862b6a9ebf3b3d358c9c9601a562f195 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Mon, 17 Feb 2025 13:43:01 +0200 Subject: [PATCH 46/51] Fix `flake8` errors --- coriolisclient/cli/transfers.py | 16 ++++++++-------- coriolisclient/v1/transfer_executions.py | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/coriolisclient/cli/transfers.py b/coriolisclient/cli/transfers.py index 9ab4009..37e1bbb 100644 --- a/coriolisclient/cli/transfers.py +++ b/coriolisclient/cli/transfers.py @@ -34,15 +34,15 @@ def _add_default_deployment_args_to_parser(parser): cd_group = parser.add_mutually_exclusive_group() cd_group.add_argument('--clone-disks', - help='Retain the transfer disks by cloning them ' - 'when launching deployment', - action='store_true', dest="clone_disks", - default=None) + help='Retain the transfer disks by cloning them ' + 'when launching deployment', + action='store_true', dest="clone_disks", + default=None) cd_group.add_argument('--dont-clone-disks', - help="Deploy directly on transfer disks, without " - "cloning them.", - action="store_false", dest="clone_disks", - default=None) + help="Deploy directly on transfer disks, without " + "cloning them.", + action="store_false", dest="clone_disks", + default=None) osm_group = parser.add_mutually_exclusive_group() osm_group.add_argument('--os-morphing', diff --git a/coriolisclient/v1/transfer_executions.py b/coriolisclient/v1/transfer_executions.py index 51d4e0a..0552aae 100644 --- a/coriolisclient/v1/transfer_executions.py +++ b/coriolisclient/v1/transfer_executions.py @@ -46,9 +46,9 @@ def get(self, transfer, execution): 'execution') def create(self, transfer, shutdown_instances=False, auto_deploy=False): - data = {"execution": - {"shutdown_instances": shutdown_instances, - "auto_deploy": auto_deploy}} + data = {"execution": { + "shutdown_instances": shutdown_instances, + "auto_deploy": auto_deploy}} return self._post( '/transfers/%s/executions' % base.getid(transfer), data, 'execution') From d72133f59f7d6e1d396237d98c761339e94976e1 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Mon, 17 Feb 2025 13:43:10 +0200 Subject: [PATCH 47/51] Update unit tests --- .../tests/cli/test_transfer_executions.py | 4 +- .../tests/cli/test_transfer_schedules.py | 59 +++++++++++-------- coriolisclient/tests/cli/test_transfers.py | 17 +++++- .../tests/v1/test_transfer_executions.py | 6 +- .../tests/v1/test_transfer_schedules.py | 6 +- coriolisclient/tests/v1/test_transfers.py | 4 ++ 6 files changed, 63 insertions(+), 33 deletions(-) diff --git a/coriolisclient/tests/cli/test_transfer_executions.py b/coriolisclient/tests/cli/test_transfer_executions.py index 3366846..1f3dc97 100644 --- a/coriolisclient/tests/cli/test_transfer_executions.py +++ b/coriolisclient/tests/cli/test_transfer_executions.py @@ -258,6 +258,7 @@ def test_take_action( args = mock.Mock() args.transfer = mock.sentinel.transfer args.shutdown_instances = mock.sentinel.shutdown_instances + args.auto_deploy = mock.sentinel.auto_deploy mock_execution = mock.Mock() self.mock_app.client_manager.coriolis.transfer_executions.create = \ mock_execution @@ -269,7 +270,8 @@ def test_take_action( result ) mock_execution.assert_called_once_with( - mock.sentinel.transfer, mock.sentinel.shutdown_instances) + mock.sentinel.transfer, mock.sentinel.shutdown_instances, + mock.sentinel.auto_deploy) mock_get_formatted_entity.assert_called_once_with( mock_execution.return_value) diff --git a/coriolisclient/tests/cli/test_transfer_schedules.py b/coriolisclient/tests/cli/test_transfer_schedules.py index 25bb2b9..2bf301f 100644 --- a/coriolisclient/tests/cli/test_transfer_schedules.py +++ b/coriolisclient/tests/cli/test_transfer_schedules.py @@ -53,7 +53,8 @@ class TransferScheduleFormatterTestCase(test_base.CoriolisBaseTestCase): def setUp(self): super(TransferScheduleFormatterTestCase, self).setUp() - self.transfer = transfer_schedules.TransferScheduleFormatter() + self.transfer_schedules = ( + transfer_schedules.TransferScheduleFormatter()) def test_get_sorted_list(self): obj1 = mock.Mock() @@ -64,7 +65,7 @@ def test_get_sorted_list(self): obj3.created_at = "date3" obj_list = [obj2, obj1, obj3] - result = self.transfer._get_sorted_list(obj_list) + result = self.transfer_schedules._get_sorted_list(obj_list) self.assertEqual( [obj1, obj2, obj3], @@ -79,7 +80,7 @@ def test_get_formatted_data(self): obj.created_at = mock.sentinel.created_at obj.expiration_date = mock.sentinel.expiration_date - result = self.transfer._get_formatted_data(obj) + result = self.transfer_schedules._get_formatted_data(obj) self.assertEqual( ( @@ -98,7 +99,8 @@ class TransferScheduleDetailFormatterTestCase(test_base.CoriolisBaseTestCase): def setUp(self): super(TransferScheduleDetailFormatterTestCase, self).setUp() - self.transfer = transfer_schedules.TransferScheduleDetailFormatter() + self.transfer_schedules = ( + transfer_schedules.TransferScheduleDetailFormatter()) def test_get_formatted_data(self): obj = mock.Mock() @@ -110,8 +112,9 @@ def test_get_formatted_data(self): obj.enabled = False obj.expiration_date = mock.sentinel.expiration_date obj.shutdown_instance = mock.sentinel.shutdown_instance + obj.auto_deploy = mock.sentinel.auto_deploy - result = self.transfer._get_formatted_data(obj) + result = self.transfer_schedules._get_formatted_data(obj) self.assertEqual( ( @@ -122,7 +125,8 @@ def test_get_formatted_data(self): mock.sentinel.updated_at, False, mock.sentinel.expiration_date, - mock.sentinel.shutdown_instance + mock.sentinel.shutdown_instance, + mock.sentinel.auto_deploy, ), result ) @@ -134,7 +138,7 @@ class CreateTransferScheduleTestCase(test_base.CoriolisBaseTestCase): def setUp(self): self.mock_app = mock.Mock() super(CreateTransferScheduleTestCase, self).setUp() - self.transfer = transfer_schedules.CreateTransferSchedule( + self.transfer_schedules = transfer_schedules.CreateTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -142,7 +146,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.transfer.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -163,14 +167,15 @@ def test_take_action( args = mock.Mock() args.transfer = mock.sentinel.transfer args.disabled = False - args.shutdown_instance = mock.sentinel.shutdown_instance + args.shutdown_instance = False + args.auto_deploy = False mock_schedule_group_args = {"minute": mock.sentinel.minute} mock_parse_schedule_group_args.return_value = mock_schedule_group_args mock_schedule = mock.Mock() self.mock_app.client_manager.coriolis.transfer_schedules.create = \ mock_schedule - result = self.transfer.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -181,7 +186,8 @@ def test_take_action( mock_schedule_group_args, True, mock_parse_expiration_date.return_value, - mock.sentinel.shutdown_instance + False, + False, ) mock_get_formatted_entity.assert_called_once_with( mock_schedule.return_value) @@ -198,7 +204,7 @@ def test_take_action_no_parsed_schedule( self.assertRaises( exceptions.CoriolisException, - self.transfer.take_action, + self.transfer_schedules.take_action, args ) mock_parse_expiration_date.assert_not_called() @@ -210,7 +216,7 @@ class ShowTransferScheduleTestCase(test_base.CoriolisBaseTestCase): def setUp(self): self.mock_app = mock.Mock() super(ShowTransferScheduleTestCase, self).setUp() - self.transfer = transfer_schedules.ShowTransferSchedule( + self.transfer_schedules = transfer_schedules.ShowTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -218,7 +224,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.transfer.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -239,7 +245,7 @@ def test_take_action( self.mock_app.client_manager.coriolis.transfer_schedules.get = \ schedule - result = self.transfer.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -256,7 +262,7 @@ class UpdateTransferScheduleTestCase(test_base.CoriolisBaseTestCase): def setUp(self): self.mock_app = mock.Mock() super(UpdateTransferScheduleTestCase, self).setUp() - self.transfer = transfer_schedules.UpdateTransferSchedule( + self.transfer_schedules = transfer_schedules.UpdateTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -264,7 +270,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.transfer.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -288,6 +294,7 @@ def test_take_action( args.enabled = True args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id + args.auto_deploy = False mock_parse_schedule_group_args.return_value = \ {"minute": mock.sentinel.minute} transfer_schedule = mock.Mock() @@ -298,9 +305,10 @@ def test_take_action( "expiration_date": mock_parse_expiration_date.return_value, "shutdown_instance": True, "enabled": True, + "auto_deploy": False, } - result = self.transfer.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -329,13 +337,14 @@ def test_take_action_no_updated_values( args.expires = False args.shutdown = None args.enabled = None + args.auto_deploy = None mock_parse_schedule_group_args.return_value = {} transfer_schedule = mock.Mock() self.mock_app.client_manager.coriolis.transfer_schedules = \ transfer_schedule expected_updated_values = {"expiration_date": None} - result = self.transfer.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -356,7 +365,7 @@ class DeleteTransferScheduleTestCase(test_base.CoriolisBaseTestCase): def setUp(self): self.mock_app = mock.Mock() super(DeleteTransferScheduleTestCase, self).setUp() - self.transfer = transfer_schedules.DeleteTransferSchedule( + self.transfer_schedules = transfer_schedules.DeleteTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') @@ -364,7 +373,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.transfer.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -380,7 +389,7 @@ def test_take_action(self): self.mock_app.client_manager.coriolis.transfer_schedules = \ transfer_schedule - self.transfer.take_action(args) + self.transfer_schedules.take_action(args) transfer_schedule.delete.assert_called_once_with( mock.sentinel.transfer, mock.sentinel.id) @@ -392,7 +401,7 @@ class ListTransferScheduleTestCase(test_base.CoriolisBaseTestCase): def setUp(self): self.mock_app = mock.Mock() super(ListTransferScheduleTestCase, self).setUp() - self.transfer = transfer_schedules.ListTransferSchedule( + self.transfer_schedules = transfer_schedules.ListTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') @@ -400,7 +409,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.transfer.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -421,7 +430,7 @@ def test_take_action( self.mock_app.client_manager.coriolis.transfer_schedules.list = \ mock_transfer_list - result = self.transfer.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_list_objects.return_value, diff --git a/coriolisclient/tests/cli/test_transfers.py b/coriolisclient/tests/cli/test_transfers.py index 4e6b8d6..9a49423 100644 --- a/coriolisclient/tests/cli/test_transfers.py +++ b/coriolisclient/tests/cli/test_transfers.py @@ -214,6 +214,8 @@ def test_get_formatted_data( mock.sentinel.formatted_executions mock_obj.info = mock.sentinel.info mock_obj.scenario = mock.sentinel.scenario + mock_obj.clone_disks = mock.sentinel.clone_disks + mock_obj.skip_os_morphing = mock.sentinel.skip_os_morphing expected_result = [ mock.sentinel.id, mock.sentinel.created_at, @@ -234,8 +236,10 @@ def test_get_formatted_data( mock.sentinel.backend_mappings, mock.sentinel.default_storage, mock.sentinel.user_scripts, + mock.sentinel.clone_disks, + mock.sentinel.skip_os_morphing, mock.sentinel.formatted_executions, - mock.sentinel.info + mock.sentinel.info, ] result = self.transfer._get_formatted_data(mock_obj) @@ -293,6 +297,8 @@ def test_take_action( {'instance_id': "instance_id1", 'pool_id': "pool_id1"}, {'instance_id': "instance_id2", 'pool_id': "pool_id2"} ] + args.clone_disks = True + args.skip_os_morphing = False mock_endpoints = mock.Mock() mock_transfers = mock.Mock() self.mock_app.client_manager.coriolis.endpoints = mock_endpoints @@ -330,7 +336,8 @@ def test_take_action( mock.sentinel.destination_minion_pool_id, instance_osmorphing_minion_pool_mappings={ 'instance_id1': 'pool_id1', 'instance_id2': 'pool_id2'}, - user_scripts=mock_compose_user_scripts.return_value + user_scripts=mock_compose_user_scripts.return_value, + clone_disks=True, skip_os_morphing=False, ) mock_get_formatted_entity.assert_called_once_with( mock_transfers.return_value) @@ -520,6 +527,8 @@ def test_take_action( {"instance_id": "mock_instance1", "pool_id": "mock_pool1"}, {"instance_id": "mock_instance2", "pool_id": "mock_pool2"} ] + args.clone_disks = True + args.skip_os_morphing = False mock_transfer = mock.Mock() self.mock_app.client_manager.coriolis.transfers = mock_transfer mock_get_option_value_from_args.side_effect = [ @@ -541,7 +550,9 @@ def test_take_action( mock.sentinel.destination_minion_pool_id, "instance_osmorphing_minion_pool_mappings": {"mock_instance1": "mock_pool1", "mock_instance2": "mock_pool2"}, - "user_scripts": mock.sentinel.user_scripts + "user_scripts": mock.sentinel.user_scripts, + "clone_disks": True, + "skip_os_morphing": False, } result = self.transfer.take_action(args) diff --git a/coriolisclient/tests/v1/test_transfer_executions.py b/coriolisclient/tests/v1/test_transfer_executions.py index 90c2044..ab5a7f0 100644 --- a/coriolisclient/tests/v1/test_transfer_executions.py +++ b/coriolisclient/tests/v1/test_transfer_executions.py @@ -68,13 +68,15 @@ def test_get(self, mock_get): @mock.patch.object(transfer_executions.TransferExecutionManager, "_post") def test_create(self, mock_post): expected_data = { - "shutdown_instances": mock.sentinel.shutdown_instances + "shutdown_instances": mock.sentinel.shutdown_instances, + "auto_deploy": mock.sentinel.auto_deploy, } expected_data = {"execution": expected_data} result = self.transfer_execution.create( mock.sentinel.transfer, - shutdown_instances=mock.sentinel.shutdown_instances + shutdown_instances=mock.sentinel.shutdown_instances, + auto_deploy=mock.sentinel.auto_deploy, ) self.assertEqual( diff --git a/coriolisclient/tests/v1/test_transfer_schedules.py b/coriolisclient/tests/v1/test_transfer_schedules.py index b5dbd7d..4cebaa2 100644 --- a/coriolisclient/tests/v1/test_transfer_schedules.py +++ b/coriolisclient/tests/v1/test_transfer_schedules.py @@ -63,7 +63,8 @@ def test_create(self, mock_post): "schedule": mock.sentinel.schedule, "enabled": True, "expiration_date": '2034-11-26T00:00:00Z', - "shutdown_instance": mock.sentinel.shutdown_instance + "shutdown_instance": mock.sentinel.shutdown_instance, + "auto_deploy": mock.sentinel.auto_deploy, } result = self.transfer_schedule.create( @@ -71,7 +72,8 @@ def test_create(self, mock_post): mock.sentinel.schedule, True, expiration_date, - mock.sentinel.shutdown_instance + mock.sentinel.shutdown_instance, + mock.sentinel.auto_deploy, ) self.assertEqual( diff --git a/coriolisclient/tests/v1/test_transfers.py b/coriolisclient/tests/v1/test_transfers.py index 9d76646..7966dc3 100644 --- a/coriolisclient/tests/v1/test_transfers.py +++ b/coriolisclient/tests/v1/test_transfers.py @@ -129,6 +129,8 @@ def test_create(self, mock_post): mock.sentinel.destination_minion_pool_id, "instance_osmorphing_minion_pool_mappings": mock.sentinel.instance_osmorphing_minion_pool_mappings, + "clone_disks": True, + "skip_os_morphing": False, } expected_data = {"transfer": expected_data} @@ -151,6 +153,8 @@ def test_create(self, mock_post): mock.sentinel.destination_minion_pool_id, instance_osmorphing_minion_pool_mappings= mock.sentinel.instance_osmorphing_minion_pool_mappings, + clone_disks=True, + skip_os_morphing=False, ) self.assertEqual( From c8156adde96da529110351e983244f822c7499ec Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Mon, 17 Feb 2025 13:45:07 +0200 Subject: [PATCH 48/51] Update tox configuration --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 63e5067..82ae014 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.0.2 -envlist = py3,pep8 +envlist = py3,pep8,cover skipsdist = True [testenv] @@ -21,7 +21,7 @@ setenv = commands = stestr run --no-subunit-trace {posargs} coverage combine - coverage report --skip-covered + coverage report --fail-under=82 --skip-covered coverage html -d cover coverage xml -o cover/coverage.xml From 75a81088fef0bd7446d7e1c927b91a45156f6537 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Tue, 25 Feb 2025 15:34:57 +0200 Subject: [PATCH 49/51] Unit tests: cli: shell: Add 'password' argument to keystone v3.Password --- coriolisclient/tests/cli/data/shell_create_keystone_auth.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml index c36fcf3..b5503cf 100644 --- a/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml +++ b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml @@ -44,9 +44,11 @@ os_tenant_id: "mock_os_tenant_id" kwargs: auth_url: "mock_auth_url" + password: "mock_password" expected_kwargs: auth_url: "mock_auth_url" project_id: "mock_os_project_id" + password: "mock_password" api_version: '3' auth_type: 'other' auth_fun: 'keystoneauth1.identity.v3.Password' @@ -58,9 +60,11 @@ os_tenant_id: "mock_os_tenant_id" kwargs: auth_url: "mock_auth_url" + password: "mock_password" expected_kwargs: auth_url: "mock_auth_url" project_id: "mock_os_project_id" + password: "mock_password" api_version: '3' auth_type: auth_fun: 'keystoneauth1.identity.v3.Password' From fa4c0946468f0d495d0de57429fae97176821b8a Mon Sep 17 00:00:00 2001 From: Mihaela Balutoiu Date: Wed, 17 Dec 2025 08:56:35 +0000 Subject: [PATCH 50/51] Add `--refresh` flag to endpoint instance list command Signed-off-by: Mihaela Balutoiu --- coriolisclient/cli/endpoint_instances.py | 8 +++++++- coriolisclient/v1/endpoint_instances.py | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/coriolisclient/cli/endpoint_instances.py b/coriolisclient/cli/endpoint_instances.py index 749a94b..5d2ac5b 100644 --- a/coriolisclient/cli/endpoint_instances.py +++ b/coriolisclient/cli/endpoint_instances.py @@ -103,6 +103,11 @@ def get_parser(self, prog_name): parser.add_argument( '--name', help='Filter results based on regular expression search') + parser.add_argument( + '--refresh', + action='store_true', + default=False, + help='Force refresh of cached instance data') cli_utils.add_args_for_json_option_to_parser(parser, 'environment') @@ -116,7 +121,8 @@ def take_action(self, args): args, 'environment', error_on_no_value=False) obj_list = ei.list( - endpoint_id, env, args.marker, args.limit, args.name) + endpoint_id, env, args.marker, args.limit, args.name, + refresh=args.refresh) return EndpointInstanceFormatter().list_objects(obj_list) diff --git a/coriolisclient/v1/endpoint_instances.py b/coriolisclient/v1/endpoint_instances.py index 4a62a55..0f36993 100644 --- a/coriolisclient/v1/endpoint_instances.py +++ b/coriolisclient/v1/endpoint_instances.py @@ -33,7 +33,7 @@ def __init__(self, api): def list( self, endpoint, env=None, marker=None, - limit=None, name=None): + limit=None, name=None, refresh=False): query = {} if marker is not None: @@ -42,6 +42,8 @@ def list( query['limit'] = limit if name is not None: query["name"] = name + if refresh: + query['refresh'] = True if env is not None: if not isinstance(env, dict): raise ValueError("'env' param must be a dict") From 986140c14c2ff0b91d6489fe9db19b8d93ed9b76 Mon Sep 17 00:00:00 2001 From: Mihaela Balutoiu Date: Thu, 18 Dec 2025 12:41:39 +0200 Subject: [PATCH 51/51] Update `unit test` Signed-off-by: Mihaela Balutoiu --- coriolisclient/tests/cli/test_endpoint_instances.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coriolisclient/tests/cli/test_endpoint_instances.py b/coriolisclient/tests/cli/test_endpoint_instances.py index 6f30705..4a0ce2c 100644 --- a/coriolisclient/tests/cli/test_endpoint_instances.py +++ b/coriolisclient/tests/cli/test_endpoint_instances.py @@ -134,6 +134,7 @@ def test_take_action( args.marker = mock.sentinel.marker args.limit = mock.sentinel.limit args.name = mock.sentinel.name + args.refresh = mock.sentinel.refresh mock_endpoints = mock.Mock() mock_ei = mock.Mock() self.mock_app.client_manager.coriolis.endpoints = mock_endpoints @@ -152,7 +153,8 @@ def test_take_action( mock_get_option_value_from_args.return_value, mock.sentinel.marker, mock.sentinel.limit, - mock.sentinel.name + mock.sentinel.name, + refresh=mock.sentinel.refresh, ) mock_list_objects.assert_called_once_with(mock_ei.list.return_value)