From 23971d6b7f752f0926d285d59b05e4acc9330200 Mon Sep 17 00:00:00 2001 From: Sergey Novikov Date: Fri, 31 Jan 2014 20:30:08 +0400 Subject: [PATCH] Add benchmark tests for glance The patch adds the following benchmark tests for glance: * Add and delete image * Boot the several instances from added image Also some changes are added related with using fake Glance client the for following unit tests: * test_generic_cleanup in tests/benchmark/test_runner.py * test_snapshot_server in tests/benchmark/scenarios/nova/test_servers.py Implements: blueprint benchmark-scenarios Change-Id: I31471a91455bd20eea000a66c59320178455cb50 --- .../tasks/glance/create-and-delete-image.json | 11 +++ .../create-image-and-boot-instances.json | 12 +++ rally/benchmark/scenarios/glance/__init__.py | 0 rally/benchmark/scenarios/glance/images.py | 48 +++++++++++ rally/benchmark/scenarios/glance/utils.py | 68 ++++++++++++++++ tests/benchmark/scenarios/glance/__init__.py | 0 .../benchmark/scenarios/glance/test_images.py | 80 ++++++++++++++++++ .../benchmark/scenarios/glance/test_utils.py | 81 +++++++++++++++++++ .../benchmark/scenarios/nova/test_servers.py | 2 +- tests/benchmark/test_runner.py | 6 +- tests/fakes.py | 46 +++++++++-- 11 files changed, 344 insertions(+), 10 deletions(-) create mode 100644 doc/samples/tasks/glance/create-and-delete-image.json create mode 100644 doc/samples/tasks/glance/create-image-and-boot-instances.json create mode 100644 rally/benchmark/scenarios/glance/__init__.py create mode 100644 rally/benchmark/scenarios/glance/images.py create mode 100644 rally/benchmark/scenarios/glance/utils.py create mode 100644 tests/benchmark/scenarios/glance/__init__.py create mode 100644 tests/benchmark/scenarios/glance/test_images.py create mode 100644 tests/benchmark/scenarios/glance/test_utils.py diff --git a/doc/samples/tasks/glance/create-and-delete-image.json b/doc/samples/tasks/glance/create-and-delete-image.json new file mode 100644 index 0000000000..e2b56401ee --- /dev/null +++ b/doc/samples/tasks/glance/create-and-delete-image.json @@ -0,0 +1,11 @@ +{ + + "GlanceImages.create_and_delete_image": [ + {"args": {"image_url": "http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img", + "container_format": "bare", + "disk_format": "qcow2"}, + "execution": "continuous", + "config": {"times": 1, "active_users": 1, + "tenants": 1, "users_per_tenant": 1}} + ] +} diff --git a/doc/samples/tasks/glance/create-image-and-boot-instances.json b/doc/samples/tasks/glance/create-image-and-boot-instances.json new file mode 100644 index 0000000000..64a61eb426 --- /dev/null +++ b/doc/samples/tasks/glance/create-image-and-boot-instances.json @@ -0,0 +1,12 @@ +{ + "GlanceImages.create_image_and_boot_instances": [ + {"args": {"image_url": "http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img", + "container_format": "bare", + "disk_format": "qcow2", + "flavor_id": 42, + "number_instances": 2}, + "execution": "continuous", + "config": {"times": 1, "active_users": 1, + "tenants": 1, "users_per_tenant": 1}} + ] +} diff --git a/rally/benchmark/scenarios/glance/__init__.py b/rally/benchmark/scenarios/glance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rally/benchmark/scenarios/glance/images.py b/rally/benchmark/scenarios/glance/images.py new file mode 100644 index 0000000000..85e50b55dd --- /dev/null +++ b/rally/benchmark/scenarios/glance/images.py @@ -0,0 +1,48 @@ +# Copyright 2014: Mirantis Inc. +# 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. + + +from rally.benchmark.scenarios.glance import utils +from rally.benchmark.scenarios.nova import utils as nova_utils + + +class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): + + def create_and_delete_image(self, container_format, + image_url, disk_format, **kwargs): + """Test adds and then deletes image.""" + image_name = self._generate_random_name(16) + image = self._create_image(image_name, + container_format, + image_url, + disk_format, + **kwargs) + self._delete_image(image) + + def create_image_and_boot_instances(self, container_format, + image_url, disk_format, + flavor_id, number_instances, + **kwargs): + """Test adds image, boots instance from it and then deletes them.""" + image_name = self._generate_random_name(16) + image = self._create_image(image_name, + container_format, + image_url, + disk_format, + **kwargs) + image_id = image.id + server_name = self._generate_random_name(16) + self._boot_servers(server_name, image_id, + flavor_id, number_instances, **kwargs) diff --git a/rally/benchmark/scenarios/glance/utils.py b/rally/benchmark/scenarios/glance/utils.py new file mode 100644 index 0000000000..7ccad8dd1c --- /dev/null +++ b/rally/benchmark/scenarios/glance/utils.py @@ -0,0 +1,68 @@ +# Copyright 2014: Mirantis Inc. +# 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 +import string +import time + +from rally.benchmark import base +from rally.benchmark import utils as bench_utils +from rally import utils + + +class GlanceScenario(base.Scenario): + + def _create_image(self, image_name, container_format, + image_url, disk_format, **kwargs): + """Create a new image. + + :param image_name: String used to name the image + :param container_format: Container format of image. + Acceptable formats: ami, ari, aki, bare, and ovf. + :param image_url: URL for download image + :param disk_format: Disk format of image. Acceptable formats: + ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso. + :param **kwargs: optional parameters to create image + + returns: object of image + """ + image = self.clients("glance").images.create( + name=image_name, + copy_from=image_url, + container_format=container_format, + disk_format=disk_format, + **kwargs) + time.sleep(5) + image = utils.wait_for(image, + is_ready=bench_utils.resource_is("active"), + update_resource=bench_utils.get_from_manager(), + timeout=120, check_interval=3) + return image + + def _delete_image(self, image): + """Deletes the given image. + + Returns when the image is actually deleted. + + :param image: Image object + """ + image.delete() + utils.wait_for_delete(image, + update_resource=bench_utils.get_from_manager(), + timeout=120, check_interval=3) + + def _generate_random_name(self, length): + name = ''.join(random.choice(string.lowercase) for i in range(length)) + return 'test-rally-image' + name diff --git a/tests/benchmark/scenarios/glance/__init__.py b/tests/benchmark/scenarios/glance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/benchmark/scenarios/glance/test_images.py b/tests/benchmark/scenarios/glance/test_images.py new file mode 100644 index 0000000000..d9bdfee315 --- /dev/null +++ b/tests/benchmark/scenarios/glance/test_images.py @@ -0,0 +1,80 @@ +# Copyright 2014: Mirantis Inc. +# 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 mock + +from rally.benchmark.scenarios.glance import images +from rally.benchmark.scenarios.nova import servers +from rally.benchmark import utils as butils +from tests import fakes +from tests import test + +GLANCE_IMAGES = "rally.benchmark.scenarios.glance.images.GlanceImages" + + +class GlanceImagesTestCase(test.TestCase): + + @mock.patch(GLANCE_IMAGES + "._generate_random_name") + @mock.patch(GLANCE_IMAGES + "._delete_image") + @mock.patch(GLANCE_IMAGES + "._create_image") + def test_create_and_delete_image(self, mock_create, mock_delete, + mock_random_name): + glance_scenario = images.GlanceImages() + fake_image = object() + mock_create.return_value = fake_image + mock_random_name.return_value = "test-rally-image" + glance_scenario.create_and_delete_image("cf", "url", "df", + fakearg="f") + + mock_create.assert_called_once_with("test-rally-image", "cf", + "url", "df", fakearg="f") + mock_delete.assert_called_once_with(fake_image) + + @mock.patch(GLANCE_IMAGES + "._generate_random_name") + @mock.patch(GLANCE_IMAGES + "._boot_servers") + @mock.patch(GLANCE_IMAGES + "._create_image") + @mock.patch("rally.benchmark.utils.osclients") + def test_create_image_and_boot_instances(self, + mock_osclients, + mock_create_image, + mock_boot_servers, + mock_random_name): + glance_scenario = images.GlanceImages() + nova_scenario = servers.NovaServers() + fc = fakes.FakeClients() + mock_osclients.Clients.return_value = fc + fake_glance = fakes.FakeGlanceClient() + fc.get_glance_client = lambda: fake_glance + fake_nova = fakes.FakeNovaClient() + fc.get_nova_client = lambda: fake_nova + temp_keys = ["username", "password", "tenant_name", "uri"] + users_endpoints = [dict(zip(temp_keys, temp_keys))] + nova_scenario._clients = butils.create_openstack_clients( + users_endpoints, temp_keys)[0] + fake_image = fakes.FakeImage() + fake_servers = [object() for i in range(5)] + mock_create_image.return_value = fake_image + mock_boot_servers.return_value = fake_servers + mock_random_name.return_value = "random_name" + kwargs = {'fakearg': 'f'} + with mock.patch("rally.benchmark.scenarios.glance.utils.time.sleep"): + glance_scenario.\ + create_image_and_boot_instances("cf", "url", "df", + "fid", 5, **kwargs) + mock_create_image.assert_called_once_with("random_name", "cf", + "url", "df", **kwargs) + mock_boot_servers.assert_called_once_with("random_name", + "image-id-0", + "fid", 5, **kwargs) diff --git a/tests/benchmark/scenarios/glance/test_utils.py b/tests/benchmark/scenarios/glance/test_utils.py new file mode 100644 index 0000000000..a473717413 --- /dev/null +++ b/tests/benchmark/scenarios/glance/test_utils.py @@ -0,0 +1,81 @@ +# 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 mock + +from rally.benchmark.scenarios.glance import utils +from rally.benchmark import utils as butils +from rally import exceptions as rally_exceptions +from rally.openstack.common.fixture import mockpatch +from tests import fakes +from tests import test + +BM_UTILS = 'rally.benchmark.utils' +GLANCE_UTILS = 'rally.benchmark.scenarios.glance.utils' + + +class GlanceScenarioTestCase(test.TestCase): + + def setUp(self): + super(GlanceScenarioTestCase, self).setUp() + self.image = mock.Mock() + self.image1 = mock.Mock() + self.res_is = mockpatch.Patch(BM_UTILS + ".resource_is") + self.get_fm = mockpatch.Patch(BM_UTILS + '.get_from_manager') + self.wait_for = mockpatch.Patch(GLANCE_UTILS + ".utils.wait_for") + self.wait_for_delete = mockpatch.Patch( + GLANCE_UTILS + ".utils.wait_for_delete") + self.useFixture(self.wait_for) + self.useFixture(self.wait_for_delete) + self.useFixture(self.res_is) + self.useFixture(self.get_fm) + self.gfm = self.get_fm.mock + self.useFixture(mockpatch.Patch('time.sleep')) + self.scenario = utils.GlanceScenario() + + def test_generate_random_name(self): + for length in [8, 16, 32, 64]: + name = self.scenario._generate_random_name(length) + self.assertEqual(len(name), 16 + length) + + def test_failed_image_status(self): + self.get_fm.cleanUp() + image_manager = fakes.FakeFailedImageManager() + self.assertRaises(rally_exceptions.GetResourceFailure, + butils.get_from_manager(), + image_manager.create('fails', 'url', 'cf', 'df')) + + @mock.patch(GLANCE_UTILS + '.GlanceScenario.clients') + def test__create_image(self, mock_clients): + mock_clients("glance").images.create.return_value = self.image + return_image = utils.GlanceScenario()._create_image('image_name', + 'image_url', + 'container_format', + 'disk_format') + self.wait_for.mock.assert_called_once_with(self.image, + update_resource=self.gfm(), + is_ready=self.res_is.mock(), + check_interval=3, + timeout=120) + self.res_is.mock.assert_has_calls(mock.call('active')) + self.assertEqual(self.wait_for.mock(), return_image) + + def test__delete_image(self): + utils.GlanceScenario()._delete_image(self.image) + self.image.delete.assert_called_once_with() + self.wait_for_delete.\ + mock.assert_called_once_with(self.image, + update_resource=self.gfm(), + check_interval=3, + timeout=120) diff --git a/tests/benchmark/scenarios/nova/test_servers.py b/tests/benchmark/scenarios/nova/test_servers.py index c25d9ee74c..43934d4cc5 100644 --- a/tests/benchmark/scenarios/nova/test_servers.py +++ b/tests/benchmark/scenarios/nova/test_servers.py @@ -319,7 +319,7 @@ class NovaServersTestCase(test.TestCase): def test_snapshot_server(self): fake_server = object() - fake_image = fakes.FakeImageManager().create() + fake_image = fakes.FakeImageManager()._create() fake_image.id = "image_id" scenario = servers.NovaServers() diff --git a/tests/benchmark/test_runner.py b/tests/benchmark/test_runner.py index 17046a8dc3..4891786f30 100644 --- a/tests/benchmark/test_runner.py +++ b/tests/benchmark/test_runner.py @@ -378,9 +378,10 @@ class ResourceCleanerTestCase(test.TestCase): client = clients[index] nova = client["nova"] cinder = client["cinder"] + glance = client["glance"] for count in range(3): uid = index + count - img = nova.images.create() + img = glance.images._create() nova.servers.create("svr-%s" % (uid), img.uuid, index) nova.keypairs.create("keypair-%s" % (uid)) nova.security_groups.create("secgroup-%s" % (uid)) @@ -402,6 +403,7 @@ class ResourceCleanerTestCase(test.TestCase): for client in clients: nova = client["nova"] cinder = client["cinder"] + glance = client["glance"] _assert_purged(nova.servers, "servers") _assert_purged(nova.keypairs, "key pairs") _assert_purged(nova.security_groups, "security groups") @@ -413,7 +415,7 @@ class ResourceCleanerTestCase(test.TestCase): _assert_purged(cinder.transfers, "volume transfers") _assert_purged(cinder.volume_snapshots, "volume snapshots") - for image in nova.images.list(): + for image in glance.images.list(): self.assertEqual("DELETED", image.status, "image not purged: %s" % (image)) diff --git a/tests/fakes.py b/tests/fakes.py index 524243c204..ed0b792556 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -15,6 +15,7 @@ import uuid +from glanceclient import exc from novaclient import exceptions from rally.benchmark import base from rally import utils as rally_utils @@ -56,6 +57,14 @@ class FakeImage(FakeResource): def __init__(self, manager=None): super(FakeImage, self).__init__(manager) + self.id = "image-id-0" + + +class FakeFailedImage(FakeResource): + + def __init__(self, manager=None): + super(FakeFailedImage, self).__init__(manager) + self.status = "error" class FakeFloatingIP(FakeResource): @@ -174,7 +183,7 @@ class FakeServerManager(FakeManager): return self._create(name=name) def create_image(self, server, name): - image = self.images.create() + image = self.images._create() return image.uuid def add_floating_ip(self, server, fip): @@ -192,8 +201,29 @@ class FakeFailedServerManager(FakeServerManager): class FakeImageManager(FakeManager): - def create(self): - return self._cache(FakeImage(self)) + def __init__(self): + super(FakeImageManager, self).__init__() + + def get(self, resource_uuid): + image = self.cache.get(resource_uuid, None) + if image is not None: + return image + raise exc.HTTPNotFound("Image %s not found" % (resource_uuid)) + + def _create(self, image_class=FakeImage, name=None): + image = self._cache(image_class(self)) + if name is not None: + image.name = name + return image + + def create(self, name, copy_from, container_format, disk_format): + return self._create(name=name) + + +class FakeFailedImageManager(FakeImageManager): + + def create(self, name, copy_from, container_format, disk_format): + return self._create(FakeFailedImage, name) class FakeFloatingIPsManager(FakeManager): @@ -309,9 +339,11 @@ class FakeServiceCatalog(object): class FakeGlanceClient(object): - def __init__(self, nova_client=None): - if nova_client: - self.images = nova_client.images + def __init__(self, failed_image_manager=False): + if failed_image_manager: + self.images = FakeFailedImageManager() + else: + self.images = FakeImageManager() class FakeCinderClient(object): @@ -383,7 +415,7 @@ class FakeClients(object): def get_glance_client(self): if self.glance is not None: return self.glance - self.glance = FakeGlanceClient(self.get_nova_client()) + self.glance = FakeGlanceClient() return self.glance def get_cinder_client(self):