diff --git a/rally-jobs/rally-keystone-api-v2.yaml b/rally-jobs/rally-keystone-api-v2.yaml index 1ea177d1..9eb3859f 100644 --- a/rally-jobs/rally-keystone-api-v2.yaml +++ b/rally-jobs/rally-keystone-api-v2.yaml @@ -581,6 +581,24 @@ failure_rate: max: 0 + GlanceImages.create_and_update_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: 0 + SwiftObjects.create_container_and_object_then_list_objects: - args: diff --git a/rally-jobs/rally.yaml b/rally-jobs/rally.yaml index f9d71c5c..d64cbb9e 100644 --- a/rally-jobs/rally.yaml +++ b/rally-jobs/rally.yaml @@ -602,6 +602,24 @@ failure_rate: max: 0 + GlanceImages.create_and_update_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: 0 + SwiftObjects.create_container_and_object_then_list_objects: - args: diff --git a/rally/plugins/openstack/scenarios/glance/images.py b/rally/plugins/openstack/scenarios/glance/images.py index d88ee7c8..8717e912 100644 --- a/rally/plugins/openstack/scenarios/glance/images.py +++ b/rally/plugins/openstack/scenarios/glance/images.py @@ -198,3 +198,46 @@ class CreateImageAndBootInstances(GlanceBasic, nova_utils.NovaScenario): self._boot_servers(image.id, flavor, number_instances, **boot_server_kwargs) + + +@types.convert(image_location={"type": "path_or_url"}, + kwargs={"type": "glance_image_args"}) +@validation.required_services(consts.Service.GLANCE) +@validation.add("required_platform", platform="openstack", users=True) +@scenario.configure(context={"cleanup": ["glance"]}, + name="GlanceImages.create_and_update_image") +class CreateAndUpdateImage(GlanceBasic): + + def run(self, container_format, image_location, disk_format, + remove_props=None, visibility="private", create_min_disk=0, + create_min_ram=0, update_min_disk=0, update_min_ram=0): + """Create an image then update it. + + Measure the "glance image-create" and "glance image-update" commands + performance. + + :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 remove_props: List of property names to remove. + (It is only supported by Glance v2.) + :param visibility: The access permission for the created image + :param create_min_disk: The min disk of created images + :param create_min_ram: The min ram of created images + :param update_min_disk: The min disk of updated images + :param update_min_ram: The min ram of updated images + """ + image = self.glance.create_image( + container_format=container_format, + image_location=image_location, + disk_format=disk_format, + visibility=visibility, + min_disk=create_min_disk, + min_ram=create_min_ram) + + self.glance.update_image(image.id, + min_disk=update_min_disk, + min_ram=update_min_ram, + remove_props=remove_props) diff --git a/rally/plugins/openstack/services/image/glance_v1.py b/rally/plugins/openstack/services/image/glance_v1.py index df5ca833..58a0d948 100644 --- a/rally/plugins/openstack/services/image/glance_v1.py +++ b/rally/plugins/openstack/services/image/glance_v1.py @@ -79,6 +79,23 @@ class GlanceV1Service(service.Service): return image_obj + @atomic.action_timer("glance_v1.update_image") + def update_image(self, image_id, image_name=None, min_disk=0, + min_ram=0): + """Update image. + + :param image_id: ID of image to update + :param image_name: Image name to be updated to + :param min_disk: The min disk of updated image + :param min_ram: The min ram of updated image + """ + image_name = image_name or self.generate_random_name() + + return self._clients.glance("1").images.update(image_id=image_id, + name=image_name, + min_disk=min_disk, + min_ram=min_ram) + @atomic.action_timer("glance_v1.get_image") def get_image(self, image): """Get specified image. @@ -158,6 +175,27 @@ class UnifiedGlanceV1Service(image.Image): min_ram=min_ram) return self._unify_image(image_obj) + def update_image(self, image_id, image_name=None, min_disk=0, + min_ram=0, remove_props=None): + """Update image. + + :param image_id: ID of image to update + :param image_name: Image name to be updated to + :param min_disk: The min disk of updated image + :param min_ram: The min ram of updated image + :param remove_props: List of property names to remove + """ + if remove_props is not None: + raise image.RemovePropsException("Remove prop: %s is not" + "supported in" + "glance_v1" % remove_props) + image_obj = self._impl.update_image( + image_id=image_id, + image_name=image_name, + min_disk=min_disk, + min_ram=min_ram) + return self._unify_image(image_obj) + def list_images(self, status="active", visibility=None, owner=None): """List images. diff --git a/rally/plugins/openstack/services/image/glance_v2.py b/rally/plugins/openstack/services/image/glance_v2.py index 3ce5b851..8c0706ee 100644 --- a/rally/plugins/openstack/services/image/glance_v2.py +++ b/rally/plugins/openstack/services/image/glance_v2.py @@ -91,6 +91,26 @@ class GlanceV2Service(service.Service): check_interval=CONF.benchmark.glance_image_create_poll_interval) return image_obj + @atomic.action_timer("glance_v2.update_image") + def update_image(self, image_id, image_name=None, min_disk=0, + min_ram=0, remove_props=None): + """Update image. + + :param image_id: ID of image to update + :param image_name: Image name to be updated to + :param min_disk: The min disk of updated image + :param min_ram: The min ram of updated image + :param remove_props: List of property names to remove + """ + image_name = image_name or self.generate_random_name() + + return self._clients.glance("2").images.update( + image_id=image_id, + name=image_name, + min_disk=min_disk, + min_ram=min_ram, + remove_props=remove_props) + @atomic.action_timer("glance_v2.get_image") def get_image(self, image): """Get specified image. @@ -171,6 +191,24 @@ class UnifiedGlanceV2Service(image.Image): min_ram=min_ram) return self._unify_image(image_obj) + def update_image(self, image_id, image_name=None, min_disk=0, + min_ram=0, remove_props=None): + """Update image. + + :param image_id: ID of image to update + :param image_name: Image name to be updated to + :param min_disk: The min disk of updated image + :param min_ram: The min ram of updated image + :param remove_props: List of property names to remove + """ + image_obj = self._impl.update_image( + image_id=image_id, + image_name=image_name, + min_disk=min_disk, + min_ram=min_ram, + remove_props=remove_props) + return self._unify_image(image_obj) + def list_images(self, status="active", visibility=None, owner=None): """List images. diff --git a/rally/plugins/openstack/services/image/image.py b/rally/plugins/openstack/services/image/image.py index 002cea67..d64fbd00 100644 --- a/rally/plugins/openstack/services/image/image.py +++ b/rally/plugins/openstack/services/image/image.py @@ -29,6 +29,12 @@ class VisibilityException(Exception): """ +class RemovePropsException(Exception): + """Remove Props it not supported exception. + + """ + + class Image(service.UnifiedOpenStackService): @classmethod def is_applicable(cls, clients): @@ -72,6 +78,24 @@ class Image(service.UnifiedOpenStackService): min_ram=min_ram) return image + @service.should_be_overridden + def update_image(self, image_id, image_name=None, + min_disk=0, min_ram=0, remove_props=None): + """Update image. + + :param image_id: ID of image to update + :param image_name: Image name to be updated to + :param min_disk: The min disk of updated image + :param min_ram: The min ram of updated image + :param remove_props: List of property names to remove + """ + return self._impl.update_image( + image_id, + image_name=image_name, + min_disk=min_disk, + min_ram=min_ram, + remove_props=remove_props) + @service.should_be_overridden def list_images(self, status="active", visibility=None, owner=None): """List images. diff --git a/samples/tasks/scenarios/glance/create-and-update-image.json b/samples/tasks/scenarios/glance/create-and-update-image.json new file mode 100644 index 00000000..8f5085f3 --- /dev/null +++ b/samples/tasks/scenarios/glance/create-and-update-image.json @@ -0,0 +1,32 @@ +{ + "GlanceImages.create_and_update_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": 4, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + }, + "api_versions": { + "glance": { + "version": 2 + } + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/glance/create-and-update-image.yaml b/samples/tasks/scenarios/glance/create-and-update-image.yaml new file mode 100644 index 00000000..5f0729e2 --- /dev/null +++ b/samples/tasks/scenarios/glance/create-and-update-image.yaml @@ -0,0 +1,21 @@ +--- + GlanceImages.create_and_update_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: 4 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + api_versions: + glance: + version: 2 + sla: + failure_rate: + max: 0 diff --git a/tests/unit/plugins/openstack/scenarios/glance/test_images.py b/tests/unit/plugins/openstack/scenarios/glance/test_images.py index 17e0eb08..3b996008 100644 --- a/tests/unit/plugins/openstack/scenarios/glance/test_images.py +++ b/tests/unit/plugins/openstack/scenarios/glance/test_images.py @@ -167,3 +167,22 @@ class GlanceBasicTestCase(test.ScenarioTestCase): image_service.create_image.assert_called_once_with(**call_args) mock_boot_servers.assert_called_once_with("image-id-0", "fid", 5, **boot_server_kwargs) + + def test_create_and_update_image(self): + image_service = self.mock_image.return_value + + fake_image = fakes.FakeImage(id=1, name="imagexxx") + image_service.create_image.return_value = fake_image + create_args = {"container_format": "cf", + "image_location": "url", + "disk_format": "df", + "visibility": "vs", + "min_disk": 0, + "min_ram": 0} + + images.CreateAndUpdateImage(self.context).run( + "cf", "url", "df", None, "vs", 0, 0, 0, 0) + + 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) diff --git a/tests/unit/plugins/openstack/services/image/test_glance_v1.py b/tests/unit/plugins/openstack/services/image/test_glance_v1.py index e5f36cff..9a0251ee 100755 --- a/tests/unit/plugins/openstack/services/image/test_glance_v1.py +++ b/tests/unit/plugins/openstack/services/image/test_glance_v1.py @@ -76,6 +76,37 @@ class GlanceV1ServiceTestCase(test.TestCase): self.gc.images.create.assert_called_once_with(**call_args) self.assertEqual(image, self.mock_wait_for_status.mock.return_value) + def test_update_image(self): + image_id = "image_id" + image_name1 = self.name_generator.return_value + image_name2 = "image_name" + min_disk = 0 + min_ram = 0 + + # case: image_name is None: + call_args1 = {"image_id": image_id, + "name": image_name1, + "min_disk": min_disk, + "min_ram": min_ram} + image1 = self.service.update_image(image_id=image_id, + image_name=None, + min_disk=min_disk, + min_ram=min_ram) + self.assertEqual(self.gc.images.update.return_value, image1) + self.gc.images.update.assert_called_once_with(**call_args1) + + # case: image_name is not None: + call_args2 = {"image_id": image_id, + "name": image_name2, + "min_disk": min_disk, + "min_ram": min_ram} + image2 = self.service.update_image(image_id=image_id, + image_name=image_name2, + min_disk=min_disk, + min_ram=min_ram) + self.assertEqual(self.gc.images.update.return_value, image2) + self.gc.images.update.assert_called_with(**call_args2) + def test_get_image(self): image_id = "image_id" self.service.get_image(image_id) @@ -146,6 +177,21 @@ class UnifiedGlanceV1ServiceTestCase(test.TestCase): self.service._impl.create_image.assert_called_once_with(**callargs) self.assertEqual(mock_image__unify_image.return_value, image) + @mock.patch(PATH) + def test_update_image(self, mock_image__unify_image): + image_id = "image_id" + image_name = "image_name" + callargs = {"image_id": image_id, + "image_name": image_name, + "min_disk": 0, + "min_ram": 0} + + image = self.service.update_image(image_id, + image_name=image_name) + + self.assertEqual(mock_image__unify_image.return_value, image) + self.service._impl.update_image.assert_called_once_with(**callargs) + @mock.patch(PATH) def test_get_image(self, mock_image__unify_image): image_id = "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 a1643f4b..ab8af858 100755 --- a/tests/unit/plugins/openstack/services/image/test_glance_v2.py +++ b/tests/unit/plugins/openstack/services/image/test_glance_v2.py @@ -75,6 +75,42 @@ class GlanceV2ServiceTestCase(test.TestCase): self.gc.images.create.assert_called_once_with(**call_args) self.assertEqual(image, self.mock_wait_for_status.mock.return_value) + def test_update_image(self): + image_id = "image_id" + image_name1 = self.name_generator.return_value + image_name2 = "image_name" + min_disk = 0 + min_ram = 0 + remove_props = None + + # case: image_name is None: + call_args1 = {"image_id": image_id, + "name": image_name1, + "min_disk": min_disk, + "min_ram": min_ram, + "remove_props": remove_props} + image1 = self.service.update_image(image_id=image_id, + image_name=None, + min_disk=min_disk, + min_ram=min_ram, + remove_props=remove_props) + self.assertEqual(self.gc.images.update.return_value, image1) + self.gc.images.update.assert_called_once_with(**call_args1) + + # case: image_name is not None: + call_args2 = {"image_id": image_id, + "name": image_name2, + "min_disk": min_disk, + "min_ram": min_ram, + "remove_props": remove_props} + image2 = self.service.update_image(image_id=image_id, + image_name=image_name2, + min_disk=min_disk, + min_ram=min_ram, + remove_props=remove_props) + self.assertEqual(self.gc.images.update.return_value, image2) + self.gc.images.update.assert_called_with(**call_args2) + def test_get_image(self): image_id = "image_id" self.service.get_image(image_id) @@ -143,6 +179,22 @@ class UnifiedGlanceV2ServiceTestCase(test.TestCase): self.assertEqual(mock_image__unify_image.return_value, image) self.service._impl.create_image.assert_called_once_with(**callargs) + @mock.patch(PATH) + def test_update_image(self, mock_image__unify_image): + image_id = "image_id" + image_name = "image_name" + callargs = {"image_id": image_id, + "image_name": image_name, + "min_disk": 0, + "min_ram": 0, + "remove_props": None} + + image = self.service.update_image(image_id, + image_name=image_name) + + self.assertEqual(mock_image__unify_image.return_value, image) + self.service._impl.update_image.assert_called_once_with(**callargs) + @mock.patch(PATH) def test_get_image(self, mock_image__unify_image): image_id = "image_id" diff --git a/tests/unit/plugins/openstack/services/image/test_image.py b/tests/unit/plugins/openstack/services/image/test_image.py index 552fc47d..2c20500c 100755 --- a/tests/unit/plugins/openstack/services/image/test_image.py +++ b/tests/unit/plugins/openstack/services/image/test_image.py @@ -55,6 +55,20 @@ class ImageTestCase(test.TestCase): image_location=image_location, disk_format=disk_format, visibility=visibility, min_disk=min_disk, min_ram=min_ram) + @ddt.data(("image_id", "image_name", "min_disk", "min_ram", + "remove_props")) + def test_update_image(self, params): + (image_id, image_name, min_disk, min_ram, remove_props) = params + service = self.get_service_with_fake_impl() + service.update_image(image_id, + image_name=image_name, + min_disk=min_disk, + min_ram=min_ram, + remove_props=remove_props) + service._impl.update_image.assert_called_once_with( + image_id, image_name=image_name, min_disk=min_disk, + min_ram=min_ram, remove_props=remove_props) + @ddt.data("image_id") def test_get_image(self, param): image_id = param