From db9a718f0f649a604a856dc98057aed7c45671eb Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 7 Aug 2017 13:17:50 -0500 Subject: [PATCH] Add GlanceImages.create_and_deactivate_image This tests image deactivation. It also makes Glance cleanup aware of deactivated images, since they must be reactivated before they can be deleted. Change-Id: I262519f0eac753288f6537271739e4359879a669 --- rally-jobs/rally.yaml | 18 ++++++++++ rally/plugins/openstack/cleanup/resources.py | 9 ++++- .../openstack/scenarios/glance/images.py | 35 +++++++++++++++++++ .../openstack/services/image/glance_v2.py | 10 ++++++ .../glance/create-and-deactivate-image.json | 22 ++++++++++++ .../glance/create-and-deactivate-image.yaml | 15 ++++++++ .../openstack/cleanup/test_resources.py | 34 +++++++++++++++--- .../openstack/scenarios/glance/test_images.py | 20 +++++++++++ .../services/image/test_glance_v2.py | 10 ++++++ 9 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 samples/tasks/scenarios/glance/create-and-deactivate-image.json create mode 100644 samples/tasks/scenarios/glance/create-and-deactivate-image.yaml diff --git a/rally-jobs/rally.yaml b/rally-jobs/rally.yaml index e012f6bb..d2655fa0 100644 --- a/rally-jobs/rally.yaml +++ b/rally-jobs/rally.yaml @@ -620,6 +620,24 @@ failure_rate: max: 100 + GlanceImages.create_and_deactivate_image: + - + args: + image_location: "{{ cirros_image_url }}" + container_format: "bare" + disk_format: "qcow2" + runner: + type: "constant" + times: 4 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 100 + SwiftObjects.create_container_and_object_then_list_objects: - args: diff --git a/rally/plugins/openstack/cleanup/resources.py b/rally/plugins/openstack/cleanup/resources.py index 92e9487f..52ba94dc 100755 --- a/rally/plugins/openstack/cleanup/resources.py +++ b/rally/plugins/openstack/cleanup/resources.py @@ -23,6 +23,7 @@ from rally.common import logging from rally import consts from rally.plugins.openstack.cleanup import base from rally.plugins.openstack.services.identity import identity +from rally.plugins.openstack.services.image import glance_v2 from rally.plugins.openstack.services.image import image from rally.task import utils as task_utils @@ -587,10 +588,16 @@ class GlanceImage(base.ResourceManager): return image.Image(self.admin or self.user) def list(self): - return self._client().list_images(owner=self.tenant_uuid) + images = (self._client().list_images(owner=self.tenant_uuid) + + self._client().list_images(status="deactivated", + owner=self.tenant_uuid)) + return images def delete(self): client = self._client() + if self.raw_resource.status == "deactivated": + glancev2 = glance_v2.GlanceV2Service(self.admin or self.user) + glancev2.reactivate_image(self.raw_resource.id) client.delete_image(self.raw_resource.id) task_utils.wait_for_status( self.raw_resource, ["deleted"], diff --git a/rally/plugins/openstack/scenarios/glance/images.py b/rally/plugins/openstack/scenarios/glance/images.py index eb8d09a6..a9161d32 100644 --- a/rally/plugins/openstack/scenarios/glance/images.py +++ b/rally/plugins/openstack/scenarios/glance/images.py @@ -17,6 +17,7 @@ from rally.common import logging from rally import consts from rally.plugins.openstack import scenario from rally.plugins.openstack.scenarios.nova import utils as nova_utils +from rally.plugins.openstack.services.image import glance_v2 from rally.plugins.openstack.services.image import image from rally.task import types from rally.task import validation @@ -284,3 +285,37 @@ class CreateAndUpdateImage(GlanceBasic): min_disk=update_min_disk, min_ram=update_min_ram, remove_props=remove_props) + + +@validation.add("required_services", services=(consts.Service.GLANCE, )) +@validation.add("required_platform", platform="openstack", users=True) +@validation.add("required_api_versions", component="glance", versions=["2"]) +@scenario.configure(context={"cleanup": ["glance"]}, + name="GlanceImages.create_and_deactivate_image", + platform="openstack") +class CreateAndDeactivateImage(GlanceBasic): + def run(self, container_format, image_location, disk_format, + visibility="private", min_disk=0, min_ram=0): + """Create an image, then deactivate it. + + :param container_format: container format of image. Acceptable + formats: ami, ari, aki, bare, and ovf + :param image_location: image file location + :param disk_format: disk format of image. Acceptable formats: + ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso + :param visibility: The access permission for the created image + :param min_disk: The min disk of created images + :param min_ram: The min ram of created images + """ + service = glance_v2.GlanceV2Service(self._clients, + self.generate_random_name, + atomic_inst=self.atomic_actions()) + + image = service.create_image( + container_format=container_format, + image_location=image_location, + disk_format=disk_format, + visibility=visibility, + min_disk=min_disk, + min_ram=min_ram) + service.deactivate_image(image.id) diff --git a/rally/plugins/openstack/services/image/glance_v2.py b/rally/plugins/openstack/services/image/glance_v2.py index afe1be8c..a6fe1cad 100644 --- a/rally/plugins/openstack/services/image/glance_v2.py +++ b/rally/plugins/openstack/services/image/glance_v2.py @@ -137,6 +137,16 @@ class GlanceV2Service(service.Service, glance_common.GlanceMixin): self._clients.glance("2").images.update(image_id, visibility=visibility) + @atomic.action_timer("glance_v2.deactivate_image") + def deactivate_image(self, image_id): + """deactivate image.""" + self._clients.glance("2").images.deactivate(image_id) + + @atomic.action_timer("glance_v2.reactivate_image") + def reactivate_image(self, image_id): + """reactivate image.""" + self._clients.glance("2").images.reactivate(image_id) + @service.compat_layer(GlanceV2Service) class UnifiedGlanceV2Service(glance_common.UnifiedGlanceMixin, image.Image): diff --git a/samples/tasks/scenarios/glance/create-and-deactivate-image.json b/samples/tasks/scenarios/glance/create-and-deactivate-image.json new file mode 100644 index 00000000..fd5de7cc --- /dev/null +++ b/samples/tasks/scenarios/glance/create-and-deactivate-image.json @@ -0,0 +1,22 @@ +{ + "GlanceImages.create_and_deactivate_image": [ + { + "args": { + "image_location": "http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img", + "container_format": "bare", + "disk_format": "qcow2" + }, + "runner": { + "type": "constant", + "times": 10, + "concurrency": 1 + }, + "context": { + "users": { + "tenants": 1, + "users_per_tenant": 1 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/glance/create-and-deactivate-image.yaml b/samples/tasks/scenarios/glance/create-and-deactivate-image.yaml new file mode 100644 index 00000000..57b906f1 --- /dev/null +++ b/samples/tasks/scenarios/glance/create-and-deactivate-image.yaml @@ -0,0 +1,15 @@ +--- + GlanceImages.create_and_deactivate_image: + - + args: + image_location: "http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img" + container_format: "bare" + disk_format: "qcow2" + runner: + type: "constant" + times: 10 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 diff --git a/tests/unit/plugins/openstack/cleanup/test_resources.py b/tests/unit/plugins/openstack/cleanup/test_resources.py index 45137833..b52487de 100755 --- a/tests/unit/plugins/openstack/cleanup/test_resources.py +++ b/tests/unit/plugins/openstack/cleanup/test_resources.py @@ -27,6 +27,8 @@ from rally.plugins.openstack.cleanup import resources from tests.unit import test BASE = "rally.plugins.openstack.cleanup.resources" +GLANCE_V2_PATH = ("rally.plugins.openstack.services.image.glance_v2." + "GlanceV2Service") class SynchronizedDeletionTestCase(test.TestCase): @@ -624,12 +626,18 @@ class GlanceImageTestCase(test.TestCase): def test_list(self): glance = resources.GlanceImage() glance._client = mock.Mock() + list_images = glance._client.return_value.list_images + list_images.side_effect = ( + ["active-image1", "active-image2"], + ["deactivated-image1"]) glance.tenant_uuid = mock.Mock() - self.assertEqual(glance.list(), - glance._client.return_value.list_images.return_value) - glance._client.return_value.list_images.assert_called_once_with( - owner=glance.tenant_uuid) + self.assertEqual( + glance.list(), + ["active-image1", "active-image2", "deactivated-image1"]) + list_images.assert_has_calls([ + mock.call(owner=glance.tenant_uuid), + mock.call(status="deactivated", owner=glance.tenant_uuid)]) def test_delete(self): glance = resources.GlanceImage() @@ -644,6 +652,24 @@ class GlanceImageTestCase(test.TestCase): glance.delete() client.delete_image.assert_called_once_with(glance.raw_resource.id) + self.assertFalse(client.reactivate_image.called) + + @mock.patch("%s.reactivate_image" % GLANCE_V2_PATH) + def test_delete_deactivated_image(self, mock_reactivate_image): + glance = resources.GlanceImage() + glance._client = mock.Mock() + glance._wrapper = mock.Mock() + glance.raw_resource = mock.Mock(status="deactivated") + + client = glance._client.return_value + + deleted_image = mock.Mock(status="DELETED") + client.get_image.side_effect = [glance.raw_resource, deleted_image] + + glance.delete() + + mock_reactivate_image.assert_called_once_with(glance.raw_resource.id) + client.delete_image.assert_called_once_with(glance.raw_resource.id) class CeilometerTestCase(test.TestCase): diff --git a/tests/unit/plugins/openstack/scenarios/glance/test_images.py b/tests/unit/plugins/openstack/scenarios/glance/test_images.py index 28c28248..47102854 100644 --- a/tests/unit/plugins/openstack/scenarios/glance/test_images.py +++ b/tests/unit/plugins/openstack/scenarios/glance/test_images.py @@ -21,6 +21,8 @@ from tests.unit import fakes from tests.unit import test BASE = "rally.plugins.openstack.scenarios.glance.images" +GLANCE_V2_PATH = ("rally.plugins.openstack.services.image.glance_v2." + "GlanceV2Service") class GlanceBasicTestCase(test.ScenarioTestCase): @@ -189,3 +191,21 @@ class GlanceBasicTestCase(test.ScenarioTestCase): image_service.create_image.assert_called_once_with(**create_args) image_service.update_image.assert_called_once_with( fake_image.id, min_disk=0, min_ram=0, remove_props=None) + + @mock.patch("%s.create_image" % GLANCE_V2_PATH) + @mock.patch("%s.deactivate_image" % GLANCE_V2_PATH) + def test_create_and_deactivate_image(self, mock_deactivate_image, + mock_create_image): + fake_image = fakes.FakeImage(id=1, name="img_name1") + mock_create_image.return_value = fake_image + call_args = {"container_format": "cf", + "image_location": "url", + "disk_format": "df", + "visibility": "vs", + "min_disk": 0, + "min_ram": 0} + + images.CreateAndDeactivateImage(self.context).run( + "cf", "url", "df", "vs", 0, 0) + mock_create_image.assert_called_once_with(**call_args) + mock_deactivate_image.assert_called_once_with(fake_image.id) diff --git a/tests/unit/plugins/openstack/services/image/test_glance_v2.py b/tests/unit/plugins/openstack/services/image/test_glance_v2.py index fea700a5..1dd50aa2 100755 --- a/tests/unit/plugins/openstack/services/image/test_glance_v2.py +++ b/tests/unit/plugins/openstack/services/image/test_glance_v2.py @@ -127,6 +127,16 @@ class GlanceV2ServiceTestCase(test.TestCase): image_id, visibility=visibility) + def test_deactivate_image(self): + image_id = "image_id" + self.service.deactivate_image(image_id) + self.gc.images.deactivate.assert_called_once_with(image_id) + + def test_reactivate_image(self): + image_id = "image_id" + self.service.reactivate_image(image_id) + self.gc.images.reactivate.assert_called_once_with(image_id) + @ddt.ddt class UnifiedGlanceV2ServiceTestCase(test.TestCase):