diff --git a/rally-jobs/rally-mos_neutron_v3.yaml b/rally-jobs/rally-mos_neutron_v3.yaml index eae6a19801..deb9f53473 100644 --- a/rally-jobs/rally-mos_neutron_v3.yaml +++ b/rally-jobs/rally-mos_neutron_v3.yaml @@ -258,3 +258,19 @@ sla: failure_rate: max: 0 + + CinderVolumes.create_and_upload_volume_to_image: + - + args: + size: 1 + runner: + type: "constant" + times: 1 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 + sla: + failure_rate: + max: 0 diff --git a/rally/benchmark/scenarios/cinder/utils.py b/rally/benchmark/scenarios/cinder/utils.py index e280dcf28f..b28f55133f 100644 --- a/rally/benchmark/scenarios/cinder/utils.py +++ b/rally/benchmark/scenarios/cinder/utils.py @@ -125,6 +125,46 @@ class CinderScenario(base.Scenario): check_interval=CONF.benchmark.cinder_volume_create_poll_interval ) + @base.atomic_action_timer("cinder.upload_volume_to_image") + def _upload_volume_to_image(self, volume, force=False, + container_format="bare", disk_format="raw"): + """Upload the given volume to image. + + Returns created image. + + :param volume: volume object + :param force: flag to indicate whether to snapshot a volume even if + it's attached to an instance + :param container_format: container format of image. Acceptable + formats: ami, ari, aki, bare, and ovf + :param: disk_format: disk format of image. Acceptable formats: + ami, ari, aki, vhd, vmdk, raw, qcow2, vdi + and iso + :returns: Returns created image object + """ + resp, img = volume.upload_to_image(force, self._generate_random_name(), + container_format, disk_format) + # NOTE (e0ne): upload_to_image changes volume status to uploading so + # we need to wait until it will be available. + volume = bench_utils.wait_for( + volume, + is_ready=bench_utils.resource_is("available"), + update_resource=bench_utils.get_from_manager(), + timeout=CONF.benchmark.cinder_volume_create_timeout, + check_interval=CONF.benchmark.cinder_volume_create_poll_interval + ) + image_id = img["os-volume_upload_image"]["image_id"] + image = self.clients("glance").images.get(image_id) + image = bench_utils.wait_for( + image, + is_ready=bench_utils.resource_is("active"), + update_resource=bench_utils.get_from_manager(), + timeout=CONF.benchmark.glance_image_create_prepoll_delay, + check_interval=CONF.benchmark.glance_image_create_poll_interval + ) + + return image + @base.atomic_action_timer("cinder.create_snapshot") def _create_snapshot(self, volume_id, force=False, **kwargs): """Create one snapshot. diff --git a/rally/benchmark/scenarios/cinder/volumes.py b/rally/benchmark/scenarios/cinder/volumes.py index 473db5e9d6..0b05789853 100755 --- a/rally/benchmark/scenarios/cinder/volumes.py +++ b/rally/benchmark/scenarios/cinder/volumes.py @@ -15,6 +15,7 @@ from rally.benchmark.scenarios import base from rally.benchmark.scenarios.cinder import utils +from rally.benchmark.scenarios.glance import utils as glance_utils from rally.benchmark.scenarios.nova import utils as nova_utils from rally.benchmark import types as types from rally.benchmark import validation @@ -27,7 +28,8 @@ LOG = logging.getLogger(__name__) class CinderVolumes(utils.CinderScenario, - nova_utils.NovaScenario): + nova_utils.NovaScenario, + glance_utils.GlanceScenario): """Benchmark scenarios for Cinder Volumes.""" @types.set(image=types.ImageResourceType) @@ -359,3 +361,30 @@ class CinderVolumes(utils.CinderScenario, volume = random.choice(self.context["tenant"]["volumes"]) self._create_snapshot(volume["id"], force=force, **kwargs) self._list_snapshots(detailed) + + @validation.required_services(consts.Service.CINDER, consts.Service.GLANCE) + @validation.required_openstack(users=True) + @validation.required_parameters("size") + @base.scenario(context={"cleanup": ["cinder", "glance"]}) + def create_and_upload_volume_to_image(self, size, force=False, + container_format="bare", + disk_format="raw", + do_delete=True, + **kwargs): + """Create and upload a volume to image. + + :param size: volume size (integers, in GB) + :param force: when set to True volume that is attached to an instance + could be uploaded to image + :param container_format: image container format + :param disk_format: disk format for image + :param do_delete: deletes image and volume after uploading if True + :param kwargs: optional args to create a volume + """ + volume = self._create_volume(size, **kwargs) + image = self._upload_volume_to_image(volume, force, container_format, + disk_format) + + if do_delete: + self._delete_volume(volume) + self._delete_image(image) diff --git a/samples/tasks/scenarios/cinder/create-and-upload-volume-to-image.json b/samples/tasks/scenarios/cinder/create-and-upload-volume-to-image.json new file mode 100644 index 0000000000..ee9344a97d --- /dev/null +++ b/samples/tasks/scenarios/cinder/create-and-upload-volume-to-image.json @@ -0,0 +1,24 @@ +{ + "CinderVolumes.create_and_upload_volume_to_image": [ + { + "args": { + "size": 1, + "force": false, + "container_format": "bare", + "disk_format": "raw", + "do_delete": true + }, + "runner": { + "type": "constant", + "times": 3, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/cinder/create-and-upload-volume-to-image.yaml b/samples/tasks/scenarios/cinder/create-and-upload-volume-to-image.yaml new file mode 100644 index 0000000000..42f631e741 --- /dev/null +++ b/samples/tasks/scenarios/cinder/create-and-upload-volume-to-image.yaml @@ -0,0 +1,17 @@ +--- + CinderVolumes.create_and_upload_volume_to_image: + - + args: + size: 1 + force: false + container_format: "bare" + disk_format: "raw" + do_delete: true + runner: + type: "constant" + times: 3 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 diff --git a/tests/unit/benchmark/scenarios/cinder/test_utils.py b/tests/unit/benchmark/scenarios/cinder/test_utils.py index 81e85bf7ec..1cbbd72474 100644 --- a/tests/unit/benchmark/scenarios/cinder/test_utils.py +++ b/tests/unit/benchmark/scenarios/cinder/test_utils.py @@ -107,6 +107,23 @@ class CinderScenarioTestCase(test.TestCase): self._test_atomic_action_timer(self.scenario.atomic_actions(), "cinder.extend_volume") + @mock.patch(CINDER_UTILS + ".CinderScenario.clients") + def test__upload_volume_to_image(self, mock_clients): + volume = mock.Mock() + image = {"os-volume_upload_image": {"image_id": 1}} + volume.upload_to_image.return_value = (None, image) + mock_clients("cinder").images.get.return_value = image + + self.scenario._generate_random_name = mock.Mock( + return_value="test_vol") + self.scenario._upload_volume_to_image(volume, False, + "container", "disk") + + volume.upload_to_image.assert_called_once_with(False, "test_vol", + "container", "disk") + self.assertTrue(self.wait_for.mock.called) + self.assertEqual(2, self.wait_for.mock.call_count) + @mock.patch(CINDER_UTILS + ".CinderScenario.clients") def test__create_snapshot(self, mock_clients): snapshot = mock.Mock() diff --git a/tests/unit/benchmark/scenarios/cinder/test_volumes.py b/tests/unit/benchmark/scenarios/cinder/test_volumes.py index 25d6aba5e2..2a826bdba7 100644 --- a/tests/unit/benchmark/scenarios/cinder/test_volumes.py +++ b/tests/unit/benchmark/scenarios/cinder/test_volumes.py @@ -180,6 +180,40 @@ class CinderServersTestCase(test.TestCase): scenario._delete_volume.assert_called_once_with(fake_volume) scenario._delete_server.assert_called_once_with(fake_server) + def test_create_and_upload_volume_to_image(self): + fake_volume = mock.Mock() + fake_image = mock.Mock() + scenario = volumes.CinderVolumes() + + scenario._create_volume = mock.MagicMock(return_value=fake_volume) + scenario._upload_volume_to_image = mock.MagicMock( + return_value=fake_image) + scenario._delete_volume = mock.MagicMock() + scenario._delete_image = mock.MagicMock() + + scenario.create_and_upload_volume_to_image(2, + container_format="fake", + disk_format="disk", + do_delete=False) + + scenario._create_volume.assert_called_once_with(2) + scenario._upload_volume_to_image.assert_called_once_with(fake_volume, + False, + "fake", + "disk") + scenario._create_volume.reset_mock() + scenario._upload_volume_to_image.reset_mock() + + scenario.create_and_upload_volume_to_image(1, do_delete=True) + + scenario._create_volume.assert_called_once_with(1) + scenario._upload_volume_to_image.assert_called_once_with(fake_volume, + False, + "bare", + "raw") + scenario._delete_volume.assert_called_once_with(fake_volume) + scenario._delete_image.assert_called_once_with(fake_image) + def test_create_snapshot_and_attach_volume(self): fake_volume = mock.MagicMock() fake_snapshot = mock.MagicMock()