diff --git a/rally-jobs/rally.yaml b/rally-jobs/rally.yaml index ac5764fe55..9baf9578ab 100755 --- a/rally-jobs/rally.yaml +++ b/rally-jobs/rally.yaml @@ -1278,6 +1278,25 @@ failure_rate: max: 0 + NovaServers.boot_lock_unlock_and_delete: + - + args: + flavor: + name: "m1.tiny" + image: + name: {{image_name}} + runner: + type: "constant" + times: 4 + concurrency: 4 + context: + users: + tenants: 2 + users_per_tenant: 1 + sla: + failure_rate: + max: 0 + NovaServers.boot_server_from_volume_and_delete: - args: diff --git a/rally/benchmark/context/cleanup/resources.py b/rally/benchmark/context/cleanup/resources.py index 8dc525bea4..602b50ab17 100644 --- a/rally/benchmark/context/cleanup/resources.py +++ b/rally/benchmark/context/cleanup/resources.py @@ -62,7 +62,10 @@ _nova_order = get_order(200) @base.resource("nova", "servers", order=next(_nova_order)) class NovaServer(base.ResourceManager): - pass + def delete(self): + if getattr(self.raw_resource, "OS-EXT-STS:locked", False): + self.raw_resource.unlock() + super(NovaServer, self).delete() @base.resource("nova", "keypairs", order=next(_nova_order)) diff --git a/rally/benchmark/scenarios/nova/servers.py b/rally/benchmark/scenarios/nova/servers.py index 9f829e0d92..4a1fc486e4 100644 --- a/rally/benchmark/scenarios/nova/servers.py +++ b/rally/benchmark/scenarios/nova/servers.py @@ -173,6 +173,37 @@ class NovaServers(utils.NovaScenario, action() self._delete_server(server, force=force_delete) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @validation.image_valid_on_flavor("flavor", "image") + @validation.required_services(consts.Service.NOVA) + @validation.required_openstack(users=True) + @base.scenario(context={"cleanup": ["nova"]}) + def boot_lock_unlock_and_delete(self, image, flavor, + min_sleep=0, max_sleep=0, + force_delete=False, + **kwargs): + """Boot a server, lock it, then unlock and delete it. + + Optional 'min_sleep' and 'max_sleep' parameters allow the + scenario to simulate a pause between locking and unlocking the + server (of random duration from min_sleep to max_sleep). + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :param min_sleep: Minimum sleep time between locking and unlocking + in seconds + :param max_sleep: Maximum sleep time between locking and unlocking + in seconds + :param force_delete: True if force_delete should be used + :param kwargs: Optional additional arguments for server creation + """ + server = self._boot_server(image, flavor, **kwargs) + self._lock_server(server) + self.sleep_between(min_sleep, max_sleep) + self._unlock_server(server) + self._delete_server(server, force=force_delete) + @types.set(image=types.ImageResourceType, flavor=types.FlavorResourceType) @validation.image_valid_on_flavor("flavor", "image") diff --git a/rally/benchmark/scenarios/nova/utils.py b/rally/benchmark/scenarios/nova/utils.py index a3cf202b86..81d060e80b 100644 --- a/rally/benchmark/scenarios/nova/utils.py +++ b/rally/benchmark/scenarios/nova/utils.py @@ -747,3 +747,19 @@ class NovaScenario(base.Scenario): def _list_hypervisors(self, detailed=True): """List hypervisors.""" return self.admin_clients("nova").hypervisors.list(detailed) + + @base.atomic_action_timer("nova.lock_server") + def _lock_server(self, server): + """Lock the given server. + + :param server: Server to lock + """ + server.lock() + + @base.atomic_action_timer("nova.unlock_server") + def _unlock_server(self, server): + """Unlock the given server. + + :param server: Server to unlock + """ + server.unlock() diff --git a/samples/tasks/scenarios/nova/boot-lock-unlock-and-delete.json b/samples/tasks/scenarios/nova/boot-lock-unlock-and-delete.json new file mode 100644 index 0000000000..ffa3acd1b9 --- /dev/null +++ b/samples/tasks/scenarios/nova/boot-lock-unlock-and-delete.json @@ -0,0 +1,25 @@ +{ + "NovaServers.boot_lock_unlock_and_delete": [ + { + "args": { + "flavor": { + "name": "m1.tiny" + }, + "image": { + "name": "cirros-0.3.2-x86_64-uec" + } + }, + "runner": { + "type": "constant", + "times": 10, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 1, + "users_per_tenant": 1 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/nova/boot-lock-unlock-and-delete.yaml b/samples/tasks/scenarios/nova/boot-lock-unlock-and-delete.yaml new file mode 100644 index 0000000000..e7e01a854e --- /dev/null +++ b/samples/tasks/scenarios/nova/boot-lock-unlock-and-delete.yaml @@ -0,0 +1,16 @@ +--- + NovaServers.boot_lock_unlock_and_delete: + - + args: + flavor: + name: "m1.tiny" + image: + name: "cirros-0.3.2-x86_64-uec" + runner: + type: "constant" + times: 10 + concurrency: 2 + context: + users: + tenants: 1 + users_per_tenant: 1 diff --git a/tests/unit/benchmark/context/cleanup/test_resources.py b/tests/unit/benchmark/context/cleanup/test_resources.py index 3ea0c4cebe..be2aee339e 100644 --- a/tests/unit/benchmark/context/cleanup/test_resources.py +++ b/tests/unit/benchmark/context/cleanup/test_resources.py @@ -82,6 +82,29 @@ class QuotaMixinTestCase(test.TestCase): self.assertEqual([quota.tenant_uuid], quota.list()) +class NovaServerTestCase(test.TestCase): + + def test_delete(self): + server = resources.NovaServer() + server.raw_resource = mock.Mock() + server._manager = mock.Mock() + server.delete() + + server._manager.return_value.delete.assert_called_once_with( + server.raw_resource.id) + + def test_delete_locked(self): + server = resources.NovaServer() + server.raw_resource = mock.Mock() + setattr(server.raw_resource, "OS-EXT-STS:locked", True) + server._manager = mock.Mock() + server.delete() + + server.raw_resource.unlock.assert_called_once_with() + server._manager.return_value.delete.assert_called_once_with( + server.raw_resource.id) + + class NovaSecurityGroupTestCase(test.TestCase): @mock.patch("%s.base.ResourceManager._manager" % BASE) diff --git a/tests/unit/benchmark/scenarios/nova/test_servers.py b/tests/unit/benchmark/scenarios/nova/test_servers.py index beb4816d08..73d24529b5 100644 --- a/tests/unit/benchmark/scenarios/nova/test_servers.py +++ b/tests/unit/benchmark/scenarios/nova/test_servers.py @@ -106,6 +106,27 @@ class NovaServersTestCase(test.TestCase): scenario._delete_server.assert_called_once_with(fake_server, force=False) + def test_boot_lock_unlock_and_delete(self): + server = fakes.FakeServer() + image = fakes.FakeImage() + flavor = fakes.FakeFlavor() + + scenario = servers.NovaServers() + scenario._boot_server = mock.Mock(return_value=server) + scenario._lock_server = mock.Mock(side_effect=lambda s: s.lock()) + scenario._unlock_server = mock.Mock(side_effect=lambda s: s.unlock()) + scenario._delete_server = mock.Mock( + side_effect=lambda s, **kwargs: + self.assertFalse(getattr(s, "OS-EXT-STS:locked", False))) + + scenario.boot_lock_unlock_and_delete(image, flavor, fakearg="fakearg") + + scenario._boot_server.assert_called_once_with(image, flavor, + fakearg="fakearg") + scenario._lock_server.assert_called_once_with(server) + scenario._unlock_server.assert_called_once_with(server) + scenario._delete_server.assert_called_once_with(server, force=False) + def test_validate_actions(self): actions = [{"hardd_reboot": 6}] scenario = servers.NovaServers() diff --git a/tests/unit/benchmark/scenarios/nova/test_utils.py b/tests/unit/benchmark/scenarios/nova/test_utils.py index b167d2008c..d28c206d25 100644 --- a/tests/unit/benchmark/scenarios/nova/test_utils.py +++ b/tests/unit/benchmark/scenarios/nova/test_utils.py @@ -757,3 +757,19 @@ class NovaScenarioTestCase(test.TestCase): mock_clients("nova").hypervisors.list.assert_called_once_with(False) self._test_atomic_action_timer(nova_scenario.atomic_actions(), "nova.list_hypervisors") + + def test__lock_server(self): + server = mock.Mock() + nova_scenario = utils.NovaScenario() + nova_scenario._lock_server(server) + server.lock.assert_called_once_with() + self._test_atomic_action_timer(nova_scenario.atomic_actions(), + "nova.lock_server") + + def test__unlock_server(self): + server = mock.Mock() + nova_scenario = utils.NovaScenario() + nova_scenario._unlock_server(server) + server.unlock.assert_called_once_with() + self._test_atomic_action_timer(nova_scenario.atomic_actions(), + "nova.unlock_server") diff --git a/tests/unit/fakes.py b/tests/unit/fakes.py index fbd8016a9a..ed3e3ac8e5 100644 --- a/tests/unit/fakes.py +++ b/tests/unit/fakes.py @@ -105,10 +105,15 @@ class FakeResource(object): class FakeServer(FakeResource): - def suspend(self): self.status = "SUSPENDED" + def lock(self): + setattr(self, "OS-EXT-STS:locked", True) + + def unlock(self): + setattr(self, "OS-EXT-STS:locked", False) + class FakeFailedServer(FakeResource):