From 35080929ea8c419e96cd77cc8f61580469225a12 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:30:31 +0300 Subject: [PATCH 1/9] 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 e62f23cd36dc40ca05883288bca0eb3ac9984272 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:30:53 +0300 Subject: [PATCH 2/9] 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 51aec388f89a5e923bad377d830c9cd286162d72 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:31:16 +0300 Subject: [PATCH 3/9] 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 a74fe0cebec3bcfc9b0395bc5f5016477a3b36bb Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:31:29 +0300 Subject: [PATCH 4/9] 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 9e017e0feb427b2875a04689c1c976e2184ac62f Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Thu, 30 May 2024 17:31:51 +0300 Subject: [PATCH 5/9] 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 b3e73885e3181e9c6bbe178ddc2131bc9b4a033a Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 16:23:11 +0300 Subject: [PATCH 6/9] `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 24bd71afca3e49b877bd953739da745bcddbd362 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 16:23:29 +0300 Subject: [PATCH 7/9] 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 5efc05b600e2e4f6e0a881879d0c2019434c323b Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 17:05:59 +0300 Subject: [PATCH 8/9] 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 006bcfbfedd9946c6385be631b312544188558c1 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Fri, 31 May 2024 17:48:11 +0300 Subject: [PATCH 9/9] 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 + )