diff --git a/.zuul.d/zuul.yaml b/.zuul.d/zuul.yaml index 7d5db7d0..11ae5c6a 100644 --- a/.zuul.d/zuul.yaml +++ b/.zuul.d/zuul.yaml @@ -54,6 +54,7 @@ - rally-task-basic-with-existing-users: # use_existing_users key did not trigger proper ansible tasks voting: false + - rally-task-cinder # NOTE(andreykurilin): this requires more thing to configure before # launching. #- rally-task-designate @@ -103,6 +104,7 @@ - rally-dsvm-tox-functional - rally-docker-check - rally-task-simple-job + - rally-task-cinder #- rally-task-heat - rally-task-ironic - rally-task-keystone-glance-swift diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b62c5793..7b91189c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,6 +28,9 @@ Added key files. Also the support for appropriate system environment variables ( ``OS_CERT``, ``OS_KEY``) is added. * Support client api option while deploying platform. +* Added Cinder V3 support and make it as the default version. You could use + api_versions context or api_info option of the spec to choose the proper + version. Changed ~~~~~~~ diff --git a/rally_openstack/consts.py b/rally_openstack/consts.py index 028d5a87..ba068b84 100644 --- a/rally_openstack/consts.py +++ b/rally_openstack/consts.py @@ -92,6 +92,8 @@ class _Service(utils.ImmutableMixin, utils.EnumMixin): NOVA = "nova" NOVA_NET = "nova-network" CINDER = "cinder" + CINDERV2 = "cinderv2" + CINDERV3 = "cinderv3" MANILA = "manila" EC2 = "ec2" GLANCE = "glance" @@ -119,6 +121,8 @@ class _ServiceType(utils.ImmutableMixin, utils.EnumMixin): """OpenStack services types, mapped to service names.""" VOLUME = "volume" + VOLUMEV2 = "volumev2" + VOLUMEV3 = "volumev3" SHARE = "share" EC2 = "ec2" IMAGE = "image" @@ -148,6 +152,8 @@ class _ServiceType(utils.ImmutableMixin, utils.EnumMixin): self.CLUSTERING: _Service.SENLIN, self.COMPUTE: _Service.NOVA, self.VOLUME: _Service.CINDER, + self.VOLUMEV2: _Service.CINDERV2, + self.VOLUMEV3: _Service.CINDERV3, self.SHARE: _Service.MANILA, self.EC2: _Service.EC2, self.IMAGE: _Service.GLANCE, diff --git a/rally_openstack/osclients.py b/rally_openstack/osclients.py index f360df8a..ba48feff 100644 --- a/rally_openstack/osclients.py +++ b/rally_openstack/osclients.py @@ -500,12 +500,13 @@ class Heat(OSClient): return client -@configure("cinder", default_version="2", default_service_type="volumev2", - supported_versions=["1", "2"]) +@configure("cinder", default_version="3", default_service_type="volumev3", + supported_versions=["1", "2", "3"]) class Cinder(OSClient): """Wrapper for CinderClient which returns an authenticated native client. """ + def create_client(self, version=None, service_type=None): """Return cinder client.""" from cinderclient import client as cinder diff --git a/rally_openstack/scenarios/cinder/volume_types.py b/rally_openstack/scenarios/cinder/volume_types.py index fda65c84..43c395cd 100644 --- a/rally_openstack/scenarios/cinder/volume_types.py +++ b/rally_openstack/scenarios/cinder/volume_types.py @@ -66,7 +66,8 @@ class CreateAndGetVolumeType(cinder_utils.CinderBasic): @validation.add("required_services", services=[consts.Service.CINDER]) -@validation.add("required_api_versions", component="cinder", versions=["2"]) +@validation.add("required_api_versions", component="cinder", + versions=["2", "3"]) @validation.add("required_platform", platform="openstack", admin=True) @scenario.configure(context={"admin_cleanup@openstack": ["cinder"]}, name="CinderVolumeTypes.create_and_update_volume_type", @@ -380,7 +381,8 @@ class CreateAndUpdateEncryptionType(cinder_utils.CinderBasic): @validation.add("required_platform", platform="openstack", admin=True) -@validation.add("required_api_versions", component="cinder", versions=["2"]) +@validation.add("required_api_versions", component="cinder", + versions=["2", "3"]) @validation.add("required_services", services=consts.Service.CINDER) @scenario.configure( context={"admin_cleanup@openstack": ["cinder"]}, diff --git a/rally_openstack/scenarios/cinder/volumes.py b/rally_openstack/scenarios/cinder/volumes.py index faea07a3..2cfeb467 100644 --- a/rally_openstack/scenarios/cinder/volumes.py +++ b/rally_openstack/scenarios/cinder/volumes.py @@ -635,7 +635,8 @@ class CreateAndUploadVolumeToImage(cinder_utils.CinderBasic, volume = self.cinder.create_volume(size, **kwargs) image = self.cinder.upload_volume_to_image( volume, force=force, container_format=container_format, - disk_format=disk_format) + disk_format=disk_format + ) if do_delete: self.cinder.delete_volume(volume) diff --git a/rally_openstack/services/storage/block.py b/rally_openstack/services/storage/block.py index de690e95..16f313e6 100644 --- a/rally_openstack/services/storage/block.py +++ b/rally_openstack/services/storage/block.py @@ -44,7 +44,7 @@ class BlockStorage(service.UnifiedService): volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None, scheduler_hints=None, - source_replica=None, multiattach=False): + source_replica=None, multiattach=False, backup_id=None): """Creates a volume. :param size: Size of volume in GB @@ -65,6 +65,7 @@ class BlockStorage(service.UnifiedService): specified by the client to help boot an instance :param multiattach: Allow the volume to be attached to more than one instance + :param backup_id: ID of the backup :returns: Return a new volume. """ @@ -78,7 +79,7 @@ class BlockStorage(service.UnifiedService): user_id=user_id, project_id=project_id, availability_zone=availability_zone, metadata=metadata, imageRef=imageRef, scheduler_hints=scheduler_hints, - multiattach=multiattach) + multiattach=multiattach, backup_id=backup_id) @service.should_be_overridden def list_volumes(self, detailed=True): diff --git a/rally_openstack/services/storage/cinder_v1.py b/rally_openstack/services/storage/cinder_v1.py index 96ff979c..d32363b9 100644 --- a/rally_openstack/services/storage/cinder_v1.py +++ b/rally_openstack/services/storage/cinder_v1.py @@ -177,7 +177,7 @@ class UnifiedCinderV1Service(cinder_common.UnifiedCinderMixin, volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None, scheduler_hints=None, - multiattach=False): + multiattach=False, backup_id=None): """Creates a volume. :param size: Size of volume in GB @@ -197,6 +197,7 @@ class UnifiedCinderV1Service(cinder_common.UnifiedCinderMixin, specified by the client to help boot an instance :param multiattach: Allow the volume to be attached to more than one instance + :param backup_id: ID of the backup(IGNORED) :returns: Return a new volume. """ diff --git a/rally_openstack/services/storage/cinder_v2.py b/rally_openstack/services/storage/cinder_v2.py index d31bceb5..0bb20114 100644 --- a/rally_openstack/services/storage/cinder_v2.py +++ b/rally_openstack/services/storage/cinder_v2.py @@ -234,7 +234,7 @@ class UnifiedCinderV2Service(cinder_common.UnifiedCinderMixin, volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None, scheduler_hints=None, - multiattach=False): + multiattach=False, backup_id=None): """Creates a volume. :param size: Size of volume in GB @@ -254,6 +254,7 @@ class UnifiedCinderV2Service(cinder_common.UnifiedCinderMixin, specified by the client to help boot an instance :param multiattach: Allow the volume to be attached to more than one instance + :param backup_id: ID of the backup(IGNORED) :returns: Return a new volume. """ diff --git a/rally_openstack/services/storage/cinder_v3.py b/rally_openstack/services/storage/cinder_v3.py new file mode 100644 index 00000000..c7fd01d7 --- /dev/null +++ b/rally_openstack/services/storage/cinder_v3.py @@ -0,0 +1,380 @@ +# All Rights Reserved. +# +# 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 random + +from rally.common import utils as rutils +from rally.task import atomic + +from rally_openstack import service +from rally_openstack.services.storage import block +from rally_openstack.services.storage import cinder_common + + +CONF = block.CONF + + +@service.service("cinder", service_type="block-storage", version="3") +class CinderV3Service(service.Service, cinder_common.CinderMixin): + + @atomic.action_timer("cinder_v3.create_volume") + def create_volume(self, size, consistencygroup_id=None, + snapshot_id=None, source_volid=None, name=None, + description=None, volume_type=None, + availability_zone=None, metadata=None, imageRef=None, + scheduler_hints=None, multiattach=False, backup_id=None): + """Creates a volume. + + :param size: Size of volume in GB + :param consistencygroup_id: ID of the consistencygroup + :param snapshot_id: ID of the snapshot + :param name: Name of the volume + :param description: Description of the volume + :param volume_type: Type of volume + :param availability_zone: Availability Zone to use + :param metadata: Optional metadata to set on volume creation + :param imageRef: reference to an image stored in glance + :param source_volid: ID of source volume to clone from + :param scheduler_hints: (optional extension) arbitrary key-value pairs + specified by the client to help boot an instance + :param multiattach: Allow the volume to be attached to more than + one instance + :param backup_id: ID of the backup + + :returns: Return a new volume. + """ + kwargs = {"name": name or self.generate_random_name(), + "description": description, + "consistencygroup_id": consistencygroup_id, + "snapshot_id": snapshot_id, + "source_volid": source_volid, + "volume_type": volume_type, + "availability_zone": availability_zone, + "metadata": metadata, + "imageRef": imageRef, + "scheduler_hints": scheduler_hints, + "multiattach": multiattach, + "backup_id": backup_id} + if isinstance(size, dict): + size = random.randint(size["min"], size["max"]) + + volume = (self._get_client() + .volumes.create(size, **kwargs)) + + # NOTE(msdubov): It is reasonable to wait 5 secs before starting to + # check whether the volume is ready => less API calls. + rutils.interruptable_sleep( + CONF.openstack.cinder_volume_create_prepoll_delay) + + return self._wait_available_volume(volume) + + @atomic.action_timer("cinder_v3.update_volume") + def update_volume(self, volume_id, name=None, description=None): + """Update the name or description for a volume. + + :param volume_id: The updated volume id. + :param name: The volume name. + :param description: The volume description. + + :returns: The updated volume. + """ + kwargs = {} + if name is not None: + kwargs["name"] = name + if description is not None: + kwargs["description"] = description + updated_volume = self._get_client().volumes.update( + volume_id, **kwargs) + return updated_volume["volume"] + + @atomic.action_timer("cinder_v3.list_types") + def list_types(self, search_opts=None, is_public=None): + """Lists all volume types.""" + return (self._get_client() + .volume_types.list(search_opts=search_opts, + is_public=is_public)) + + @atomic.action_timer("cinder_v3.create_snapshot") + def create_snapshot(self, volume_id, force=False, + name=None, description=None, metadata=None): + """Create one snapshot. + + Returns when the snapshot is actually created and is in the "Available" + state. + + :param volume_id: volume uuid for creating snapshot + :param force: flag to indicate whether to snapshot a volume even if + it's attached to an instance + :param name: Name of the snapshot + :param description: Description of the snapshot + :returns: Created snapshot object + """ + kwargs = {"force": force, + "name": name or self.generate_random_name(), + "description": description, + "metadata": metadata} + + snapshot = self._get_client().volume_snapshots.create(volume_id, + **kwargs) + rutils.interruptable_sleep( + CONF.openstack.cinder_volume_create_prepoll_delay) + snapshot = self._wait_available_volume(snapshot) + return snapshot + + @atomic.action_timer("cinder_v3.create_backup") + def create_backup(self, volume_id, container=None, + name=None, description=None, + incremental=False, force=False, + snapshot_id=None): + """Create a volume backup of the given volume. + + :param volume_id: The ID of the volume to backup. + :param container: The name of the backup service container. + :param name: The name of the backup. + :param description: The description of the backup. + :param incremental: Incremental backup. + :param force: If True, allows an in-use volume to be backed up. + :param snapshot_id: The ID of the snapshot to backup. + """ + kwargs = {"force": force, + "name": name or self.generate_random_name(), + "description": description, + "container": container, + "incremental": incremental, + "force": force, + "snapshot_id": snapshot_id} + backup = self._get_client().backups.create(volume_id, **kwargs) + return self._wait_available_volume(backup) + + @atomic.action_timer("cinder_v3.create_volume_type") + def create_volume_type(self, name=None, description=None, is_public=True): + """create volume type. + + :param name: Descriptive name of the volume type + :param description: Description of the volume type + :param is_public: Volume type visibility + :returns: Return the created volume type. + :returns: VolumeType object + """ + kwargs = {"name": name or self.generate_random_name(), + "description": description, + "is_public": is_public} + return self._get_client().volume_types.create(**kwargs) + + @atomic.action_timer("cinder_v3.update_volume_type") + def update_volume_type(self, volume_type, update_name=False, + description=None, is_public=None): + """Update the name and/or description for a volume type. + + :param volume_type: The ID or a instance of the :class:`VolumeType` + to update. + :param update_name: if True, can update name by generating random name. + if False, don't update name. + :param description: Description of the the volume type. + :rtype: :class:`VolumeType` + """ + name = None + if update_name: + name = self.generate_random_name() + return self._get_client().volume_types.update( + volume_type=volume_type, name=name, description=description, + is_public=is_public) + + @atomic.action_timer("cinder_v3.add_type_access") + def add_type_access(self, volume_type, project): + """Add a project to the given volume type access list. + + :param volume_type: Volume type name or ID to add access for the given + project + :project: Project ID to add volume type access for + :return: An instance of cinderclient.apiclient.base.TupleWithMeta + """ + return self._get_client().volume_type_access.add_project_access( + volume_type=volume_type, project=project) + + @atomic.action_timer("cinder_v3.list_type_access") + def list_type_access(self, volume_type): + """Print access information about the given volume type + + :param volume_type: Filter results by volume type name or ID + :return: VolumeTypeAcces of specific project + """ + return self._get_client().volume_type_access.list(volume_type) + + +@service.compat_layer(CinderV3Service) +class UnifiedCinderV3Service(cinder_common.UnifiedCinderMixin, + block.BlockStorage): + + @staticmethod + def _unify_volume(volume): + if isinstance(volume, dict): + return block.Volume(id=volume["id"], name=volume["name"], + size=volume["size"], status=volume["status"]) + else: + return block.Volume(id=volume.id, name=volume.name, + size=volume.size, status=volume.status) + + @staticmethod + def _unify_snapshot(snapshot): + return block.VolumeSnapshot(id=snapshot.id, name=snapshot.name, + volume_id=snapshot.volume_id, + status=snapshot.status) + + def create_volume(self, size, consistencygroup_id=None, + group_id=None, snapshot_id=None, source_volid=None, + name=None, description=None, + volume_type=None, user_id=None, + project_id=None, availability_zone=None, + metadata=None, imageRef=None, scheduler_hints=None, + source_replica=None, multiattach=False, backup_id=None): + """Creates a volume. + + :param size: Size of volume in GB + :param consistencygroup_id: ID of the consistencygroup + :param group_id: ID of the group + :param snapshot_id: ID of the snapshot + :param name: Name of the volume + :param description: Description of the volume + :param volume_type: Type of volume + :param user_id: User id derived from context(IGNORED) + :param project_id: Project id derived from context(IGNORED) + :param availability_zone: Availability Zone to use + :param metadata: Optional metadata to set on volume creation + :param imageRef: reference to an image stored in glance + :param source_volid: ID of source volume to clone from + :param scheduler_hints: (optional extension) arbitrary key-value pairs + specified by the client to help boot an instance + :param multiattach: Allow the volume to be attached to more than + one instance + :param backup_id: ID of the backup + + :returns: Return a new volume. + """ + return self._unify_volume(self._impl.create_volume( + size, consistencygroup_id=consistencygroup_id, + snapshot_id=snapshot_id, + source_volid=source_volid, name=name, + description=description, volume_type=volume_type, + availability_zone=availability_zone, metadata=metadata, + imageRef=imageRef, scheduler_hints=scheduler_hints, + multiattach=multiattach, backup_id=backup_id)) + + def list_volumes(self, detailed=True): + """Lists all volumes. + + :param detailed: Whether to return detailed volume info. + :returns: Return volumes list. + """ + return [self._unify_volume(volume) + for volume in self._impl.list_volumes(detailed=detailed)] + + def get_volume(self, volume_id): + """Get a volume. + + :param volume_id: The ID of the volume to get. + + :returns: Return the volume. + """ + return self._unify_volume(self._impl.get_volume(volume_id)) + + def extend_volume(self, volume, new_size): + """Extend the size of the specified volume.""" + return self._unify_volume( + self._impl.extend_volume(volume, new_size=new_size)) + + def update_volume(self, volume_id, + name=None, description=None): + """Update the name or description for a volume. + + :param volume_id: The updated volume id. + :param name: The volume name. + :param description: The volume description. + + :returns: The updated volume. + """ + return self._unify_volume(self._impl.update_volume( + volume_id, name=name, description=description)) + + def list_types(self, search_opts=None, is_public=None): + """Lists all volume types.""" + return self._impl.list_types(search_opts=search_opts, + is_public=is_public) + + def create_snapshot(self, volume_id, force=False, + name=None, description=None, metadata=None): + """Create one snapshot. + + Returns when the snapshot is actually created and is in the "Available" + state. + + :param volume_id: volume uuid for creating snapshot + :param force: If force is True, create a snapshot even if the volume is + attached to an instance. Default is False. + :param name: Name of the snapshot + :param description: Description of the snapshot + :param metadata: Metadata of the snapshot + :returns: Created snapshot object + """ + return self._unify_snapshot(self._impl.create_snapshot( + volume_id, force=force, name=name, + description=description, metadata=metadata)) + + def list_snapshots(self, detailed=True): + """Get a list of all snapshots.""" + return [self._unify_snapshot(snapshot) + for snapshot in self._impl.list_snapshots(detailed=detailed)] + + def create_backup(self, volume_id, container=None, + name=None, description=None, + incremental=False, force=False, + snapshot_id=None): + """Creates a volume backup. + + :param volume_id: The ID of the volume to backup. + :param container: The name of the backup service container. + :param name: The name of the backup. + :param description: The description of the backup. + :param incremental: Incremental backup. + :param force: If True, allows an in-use volume to be backed up. + :param snapshot_id: The ID of the snapshot to backup. + + :returns: The created backup object. + """ + return self._unify_backup(self._impl.create_backup( + volume_id, container=container, name=name, description=description, + incremental=incremental, force=force, snapshot_id=snapshot_id)) + + def create_volume_type(self, name=None, description=None, is_public=True): + """Creates a volume type. + + :param name: Descriptive name of the volume type + :param description: Description of the volume type + :param is_public: Volume type visibility + :returns: Return the created volume type. + """ + return self._impl.create_volume_type(name=name, + description=description, + is_public=is_public) + + def restore_backup(self, backup_id, volume_id=None): + """Restore the given backup. + + :param backup_id: The ID of the backup to restore. + :param volume_id: The ID of the volume to restore the backup to. + + :returns: Return the restored backup. + """ + return self._unify_volume(self._impl.restore_backup( + backup_id, volume_id=volume_id)) diff --git a/tests/unit/services/storage/test_block.py b/tests/unit/services/storage/test_block.py index 3d9d28c8..47965e7c 100644 --- a/tests/unit/services/storage/test_block.py +++ b/tests/unit/services/storage/test_block.py @@ -40,7 +40,7 @@ class BlockTestCase(test.TestCase): description=None, group_id=None, imageRef=None, metadata=None, multiattach=False, name=None, project_id=None, scheduler_hints=None, snapshot_id=None, - source_volid=None, user_id=None, volume_type=None) + source_volid=None, user_id=None, volume_type=None, backup_id=None) def test_list_volumes(self): self.assertEqual(self.service._impl.list_volumes.return_value, diff --git a/tests/unit/services/storage/test_cinder_v3.py b/tests/unit/services/storage/test_cinder_v3.py new file mode 100644 index 00000000..33598d67 --- /dev/null +++ b/tests/unit/services/storage/test_cinder_v3.py @@ -0,0 +1,414 @@ +# All Rights Reserved. +# +# 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 ddt +import mock +from rally.common import cfg + +from rally_openstack.services.storage import cinder_v3 +from tests.unit import fakes +from tests.unit import test + +BASE_PATH = "rally_openstack.services.storage" +CONF = cfg.CONF + + +@ddt.ddt +class CinderV3ServiceTestCase(test.ScenarioTestCase): + def setUp(self): + super(CinderV3ServiceTestCase, self).setUp() + self.clients = mock.MagicMock() + self.cinder = self.clients.cinder.return_value + self.name_generator = mock.MagicMock() + self.service = cinder_v3.CinderV3Service( + self.clients, name_generator=self.name_generator) + + def atomic_actions(self): + return self.service._atomic_actions + + def test_create_volume(self): + self.service.generate_random_name = mock.MagicMock( + return_value="volume") + self.service._wait_available_volume = mock.MagicMock() + self.service._wait_available_volume.return_value = fakes.FakeVolume() + + return_volume = self.service.create_volume(1) + + kwargs = {"name": "volume", + "description": None, + "consistencygroup_id": None, + "snapshot_id": None, + "source_volid": None, + "volume_type": None, + "availability_zone": None, + "metadata": None, + "imageRef": None, + "scheduler_hints": None, + "multiattach": False, + "backup_id": None} + self.cinder.volumes.create.assert_called_once_with(1, **kwargs) + self.service._wait_available_volume.assert_called_once_with( + self.cinder.volumes.create.return_value) + self.assertEqual(self.service._wait_available_volume.return_value, + return_volume) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.create_volume") + + @mock.patch("%s.cinder_v3.random" % BASE_PATH) + def test_create_volume_with_size_range(self, mock_random): + mock_random.randint.return_value = 3 + self.service._wait_available_volume = mock.MagicMock() + self.service._wait_available_volume.return_value = fakes.FakeVolume() + + return_volume = self.service.create_volume( + size={"min": 1, "max": 5}, name="volume") + + kwargs = {"name": "volume", + "description": None, + "consistencygroup_id": None, + "snapshot_id": None, + "source_volid": None, + "volume_type": None, + "availability_zone": None, + "metadata": None, + "imageRef": None, + "scheduler_hints": None, + "multiattach": False, + "backup_id": None} + self.cinder.volumes.create.assert_called_once_with( + 3, **kwargs) + self.service._wait_available_volume.assert_called_once_with( + self.cinder.volumes.create.return_value) + self.assertEqual(self.service._wait_available_volume.return_value, + return_volume) + + def test_update_volume(self): + return_value = {"volume": fakes.FakeVolume()} + self.cinder.volumes.update.return_value = return_value + + self.assertEqual(return_value["volume"], + self.service.update_volume(1)) + self.cinder.volumes.update.assert_called_once_with(1) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.update_volume") + + def test_update_volume_with_name_description(self): + return_value = {"volume": fakes.FakeVolume()} + self.cinder.volumes.update.return_value = return_value + + return_volume = self.service.update_volume( + 1, name="volume", description="fake") + + self.cinder.volumes.update.assert_called_once_with( + 1, name="volume", description="fake") + self.assertEqual(return_value["volume"], return_volume) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.update_volume") + + def test_list_types(self): + self.assertEqual(self.cinder.volume_types.list.return_value, + self.service.list_types(search_opts=None, + is_public=None)) + + self.cinder.volume_types.list.assert_called_once_with( + search_opts=None, is_public=None) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.list_types") + + def test_create_snapshot(self): + self.service._wait_available_volume = mock.MagicMock() + self.service._wait_available_volume.return_value = fakes.FakeVolume() + self.service.generate_random_name = mock.MagicMock( + return_value="snapshot") + + return_snapshot = self.service.create_snapshot(1) + + self.cinder.volume_snapshots.create.assert_called_once_with( + 1, name="snapshot", description=None, force=False, + metadata=None) + self.service._wait_available_volume.assert_called_once_with( + self.cinder.volume_snapshots.create.return_value) + self.assertEqual(self.service._wait_available_volume.return_value, + return_snapshot) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.create_snapshot") + + def test_create_snapshot_with_name(self): + self.service._wait_available_volume = mock.MagicMock() + self.service._wait_available_volume.return_value = fakes.FakeVolume() + + return_snapshot = self.service.create_snapshot(1, name="snapshot") + + self.cinder.volume_snapshots.create.assert_called_once_with( + 1, name="snapshot", description=None, force=False, + metadata=None) + self.service._wait_available_volume.assert_called_once_with( + self.cinder.volume_snapshots.create.return_value) + self.assertEqual(self.service._wait_available_volume.return_value, + return_snapshot) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.create_snapshot") + + def test_create_backup(self): + self.service._wait_available_volume = mock.MagicMock() + self.service._wait_available_volume.return_value = fakes.FakeVolume() + self.service.generate_random_name = mock.MagicMock( + return_value="backup") + + return_backup = self.service.create_backup(1) + + self.cinder.backups.create.assert_called_once_with( + 1, name="backup", description=None, container=None, + incremental=False, force=False, snapshot_id=None) + self.service._wait_available_volume.assert_called_once_with( + self.cinder.backups.create.return_value) + self.assertEqual(self.service._wait_available_volume.return_value, + return_backup) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.create_backup") + + def test_create_backup_with_name(self): + self.service._wait_available_volume = mock.MagicMock() + self.service._wait_available_volume.return_value = fakes.FakeVolume() + + return_backup = self.service.create_backup(1, name="backup") + + self.cinder.backups.create.assert_called_once_with( + 1, name="backup", description=None, container=None, + incremental=False, force=False, snapshot_id=None) + self.service._wait_available_volume.assert_called_once_with( + self.cinder.backups.create.return_value) + self.assertEqual(self.service._wait_available_volume.return_value, + return_backup) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.create_backup") + + def test_create_volume_type(self): + self.service.generate_random_name = mock.MagicMock( + return_value="volume_type") + return_type = self.service.create_volume_type(name=None, + description=None, + is_public=True) + + self.cinder.volume_types.create.assert_called_once_with( + name="volume_type", description=None, is_public=True) + self.assertEqual(self.cinder.volume_types.create.return_value, + return_type) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.create_volume_type") + + def test_create_volume_type_with_name_(self): + return_type = self.service.create_volume_type(name="type", + description=None, + is_public=True) + + self.cinder.volume_types.create.assert_called_once_with( + name="type", description=None, is_public=True) + self.assertEqual(self.cinder.volume_types.create.return_value, + return_type) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.create_volume_type") + + def test_update_volume_type(self): + volume_type = mock.Mock() + name = "random_name" + self.service.generate_random_name = mock.MagicMock( + return_value=name) + description = "test update" + + result = self.service.update_volume_type(volume_type, + description=description, + update_name=True, + is_public=None) + self.assertEqual( + self.cinder.volume_types.update.return_value, + result) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.update_volume_type") + + def test_add_type_access(self): + volume_type = mock.Mock() + project = mock.Mock() + type_access = self.service.add_type_access(volume_type, + project=project) + add_project_access = self.cinder.volume_type_access.add_project_access + add_project_access.assert_called_once_with( + volume_type=volume_type, project=project) + self.assertEqual(add_project_access.return_value, + type_access) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.add_type_access") + + def test_list_type_access(self): + volume_type = mock.Mock() + type_access = self.service.list_type_access(volume_type) + self.cinder.volume_type_access.list.assert_called_once_with( + volume_type) + self.assertEqual(self.cinder.volume_type_access.list.return_value, + type_access) + self._test_atomic_action_timer(self.atomic_actions(), + "cinder_v3.list_type_access") + + +class UnifiedCinderV3ServiceTestCase(test.TestCase): + def setUp(self): + super(UnifiedCinderV3ServiceTestCase, self).setUp() + self.clients = mock.MagicMock() + self.service = cinder_v3.UnifiedCinderV3Service(self.clients) + self.service._impl = mock.MagicMock() + + def test__unify_volume(self): + class SomeVolume(object): + id = 1 + name = "volume" + size = 1 + status = "st" + volume = self.service._unify_volume(SomeVolume()) + self.assertEqual(1, volume.id) + self.assertEqual("volume", volume.name) + self.assertEqual(1, volume.size) + self.assertEqual("st", volume.status) + + def test__unify_volume_with_dict(self): + some_volume = {"name": "volume", "id": 1, "size": 1, "status": "st"} + volume = self.service._unify_volume(some_volume) + self.assertEqual(1, volume.id) + self.assertEqual("volume", volume.name) + self.assertEqual(1, volume.size) + self.assertEqual("st", volume.status) + + def test__unify_snapshot(self): + class SomeSnapshot(object): + id = 1 + name = "snapshot" + volume_id = "volume" + status = "st" + snapshot = self.service._unify_snapshot(SomeSnapshot()) + self.assertEqual(1, snapshot.id) + self.assertEqual("snapshot", snapshot.name) + self.assertEqual("volume", snapshot.volume_id) + self.assertEqual("st", snapshot.status) + + def test_create_volume(self): + self.service._unify_volume = mock.MagicMock() + self.assertEqual(self.service._unify_volume.return_value, + self.service.create_volume(1)) + self.service._impl.create_volume.assert_called_once_with( + 1, availability_zone=None, consistencygroup_id=None, + description=None, imageRef=None, + metadata=None, multiattach=False, name=None, + scheduler_hints=None, snapshot_id=None, + source_volid=None, volume_type=None, backup_id=None) + self.service._unify_volume.assert_called_once_with( + self.service._impl.create_volume.return_value) + + def test_list_volumes(self): + self.service._unify_volume = mock.MagicMock() + self.service._impl.list_volumes.return_value = ["vol"] + self.assertEqual([self.service._unify_volume.return_value], + self.service.list_volumes(detailed=True)) + self.service._impl.list_volumes.assert_called_once_with(detailed=True) + self.service._unify_volume.assert_called_once_with("vol") + + def test_get_volume(self): + self.service._unify_volume = mock.MagicMock() + self.assertEqual(self.service._unify_volume.return_value, + self.service.get_volume(1)) + self.service._impl.get_volume.assert_called_once_with(1) + self.service._unify_volume.assert_called_once_with( + self.service._impl.get_volume.return_value) + + def test_extend_volume(self): + self.service._unify_volume = mock.MagicMock() + self.assertEqual(self.service._unify_volume.return_value, + self.service.extend_volume("volume", new_size=1)) + self.service._impl.extend_volume.assert_called_once_with("volume", + new_size=1) + self.service._unify_volume.assert_called_once_with( + self.service._impl.extend_volume.return_value) + + def test_update_volume(self): + self.service._unify_volume = mock.MagicMock() + self.assertEqual( + self.service._unify_volume.return_value, + self.service.update_volume(1, name="volume", + description="fake")) + self.service._impl.update_volume.assert_called_once_with( + 1, description="fake", name="volume") + self.service._unify_volume.assert_called_once_with( + self.service._impl.update_volume.return_value) + + def test_list_types(self): + self.assertEqual( + self.service._impl.list_types.return_value, + self.service.list_types(search_opts=None, is_public=True)) + self.service._impl.list_types.assert_called_once_with( + search_opts=None, is_public=True) + + def test_create_snapshot(self): + self.service._unify_snapshot = mock.MagicMock() + self.assertEqual( + self.service._unify_snapshot.return_value, + self.service.create_snapshot(1, force=False, + name=None, + description=None, + metadata=None)) + self.service._impl.create_snapshot.assert_called_once_with( + 1, force=False, name=None, description=None, metadata=None) + self.service._unify_snapshot.assert_called_once_with( + self.service._impl.create_snapshot.return_value) + + def test_list_snapshots(self): + self.service._unify_snapshot = mock.MagicMock() + self.service._impl.list_snapshots.return_value = ["snapshot"] + self.assertEqual([self.service._unify_snapshot.return_value], + self.service.list_snapshots(detailed=True)) + self.service._impl.list_snapshots.assert_called_once_with( + detailed=True) + self.service._unify_snapshot.assert_called_once_with( + "snapshot") + + def test_create_backup(self): + self.service._unify_backup = mock.MagicMock() + self.assertEqual( + self.service._unify_backup.return_value, + self.service.create_backup(1, container=None, + name=None, + description=None, + incremental=False, + force=False, + snapshot_id=None)) + self.service._impl.create_backup.assert_called_once_with( + 1, container=None, name=None, description=None, + incremental=False, force=False, snapshot_id=None) + self.service._unify_backup( + self.service._impl.create_backup.return_value) + + def test_create_volume_type(self): + self.assertEqual( + self.service._impl.create_volume_type.return_value, + self.service.create_volume_type(name="type", + description="desp", + is_public=True)) + self.service._impl.create_volume_type.assert_called_once_with( + name="type", description="desp", is_public=True) + + def test_restore_backup(self): + self.service._unify_volume = mock.MagicMock() + self.assertEqual(self.service._unify_volume.return_value, + self.service.restore_backup(1, volume_id=1)) + self.service._impl.restore_backup.assert_called_once_with( + 1, volume_id=1) + self.service._unify_volume.assert_called_once_with( + self.service._impl.restore_backup.return_value) diff --git a/tests/unit/test_osclients.py b/tests/unit/test_osclients.py index 1cfc1344..b86c5239 100644 --- a/tests/unit/test_osclients.py +++ b/tests/unit/test_osclients.py @@ -644,7 +644,7 @@ class OSClientsTestCase(test.TestCase): "session": mock_keystoneauth1.session.Session(), "endpoint_override": mock_cinder__get_endpoint.return_value} mock_cinder.client.Client.assert_called_once_with( - "2", **kw) + "3", **kw) self.assertEqual(fake_cinder, self.clients.cache["cinder"]) @mock.patch("%s.Manila._get_endpoint" % PATH)