diff --git a/etc/rally/rally.conf.sample b/etc/rally/rally.conf.sample index 87a4ac07e6..a3594762f8 100644 --- a/etc/rally/rally.conf.sample +++ b/etc/rally/rally.conf.sample @@ -280,6 +280,16 @@ # Server suspend poll interval (floating point value) #nova_server_suspend_poll_interval = 2.0 +# Time to sleep after resume before polling for status (floating +# point value) +#nova_server_resume_prepoll_delay = 2.0 + +# Server resume timeout (floating point value) +#nova_server_resume_timeout = 300.0 + +# Server resume poll interval (floating point value) +#nova_server_resume_poll_interval = 2.0 + # Time to sleep after image_create before polling for status (floating # point value) #nova_server_image_create_prepoll_delay = 0.0 diff --git a/rally-jobs/rally.yaml b/rally-jobs/rally.yaml index 4a758334b0..3a7e0f4354 100755 --- a/rally-jobs/rally.yaml +++ b/rally-jobs/rally.yaml @@ -1086,6 +1086,26 @@ failure_rate: max: 0 + NovaServers.suspend_and_resume_server: + - + args: + flavor: + name: "m1.tiny" + image: + name: {{image_name}} + detailed: True + runner: + type: "constant" + times: 3 + concurrency: 3 + context: + users: + tenants: 3 + users_per_tenant: 1 + sla: + failure_rate: + max: 0 + NovaServers.list_servers: - args: @@ -1476,4 +1496,4 @@ users_per_tenant: 1 sla: failure_rate: - max: 0 \ No newline at end of file + max: 0 diff --git a/rally/benchmark/scenarios/nova/servers.py b/rally/benchmark/scenarios/nova/servers.py index b2b3e5de59..f4b8b5dfd9 100644 --- a/rally/benchmark/scenarios/nova/servers.py +++ b/rally/benchmark/scenarios/nova/servers.py @@ -311,6 +311,27 @@ class NovaServers(utils.NovaScenario, self._resize_revert(server) self._delete_server(server, force=force_delete) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType, + to_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 suspend_and_resume_server(self, image, flavor, + force_delete=False, **kwargs): + """Create a server, suspend, resume and then delete it + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :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._suspend_server(server) + self._resume_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 43cbf3c281..125491e2b4 100644 --- a/rally/benchmark/scenarios/nova/utils.py +++ b/rally/benchmark/scenarios/nova/utils.py @@ -35,6 +35,7 @@ option_names_and_defaults = [ ("rescue", 2, 300, 2), ("unrescue", 2, 300, 2), ("suspend", 2, 300, 2), + ("resume", 2, 300, 2), ("image_create", 0, 300, 2), ("image_delete", 0, 300, 2), ("resize", 2, 400, 5), @@ -247,6 +248,24 @@ class NovaScenario(base.Scenario): check_interval=CONF.benchmark.nova_server_suspend_poll_interval ) + @base.atomic_action_timer("nova.resume_server") + def _resume_server(self, server): + """Resumes the suspended server. + + Returns when the server is actually resumed and is in the "ACTIVE" + state. + + :param server: Server object + """ + server.resume() + time.sleep(CONF.benchmark.nova_server_resume_prepoll_delay) + bench_utils.wait_for( + server, is_ready=bench_utils.resource_is("ACTIVE"), + update_resource=bench_utils.get_from_manager(), + timeout=CONF.benchmark.nova_server_resume_timeout, + check_interval=CONF.benchmark.nova_server_resume_poll_interval + ) + def _delete_server(self, server, force=False): """Delete the given server. diff --git a/samples/tasks/scenarios/nova/suspend-and-resume.json b/samples/tasks/scenarios/nova/suspend-and-resume.json new file mode 100644 index 0000000000..221b78602c --- /dev/null +++ b/samples/tasks/scenarios/nova/suspend-and-resume.json @@ -0,0 +1,26 @@ +{ + "NovaServers.suspend_and_resume_server": [ + { + "args": { + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "^cirros.*uec$" + }, + "force_delete": false + }, + "runner": { + "type": "constant", + "times": 10, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 3, + "users_per_tenant": 2 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/nova/suspend-and-resume.yaml b/samples/tasks/scenarios/nova/suspend-and-resume.yaml new file mode 100644 index 0000000000..b7f6045787 --- /dev/null +++ b/samples/tasks/scenarios/nova/suspend-and-resume.yaml @@ -0,0 +1,17 @@ +--- + NovaServers.suspend_and_resume_server: + - + args: + flavor: + name: "m1.nano" + image: + name: "^cirros.*uec$" + force_delete: false + runner: + type: "constant" + times: 10 + concurrency: 2 + context: + users: + tenants: 3 + users_per_tenant: 2 diff --git a/tests/unit/benchmark/scenarios/nova/test_servers.py b/tests/unit/benchmark/scenarios/nova/test_servers.py index c7c2daf938..bddaf3f69d 100644 --- a/tests/unit/benchmark/scenarios/nova/test_servers.py +++ b/tests/unit/benchmark/scenarios/nova/test_servers.py @@ -193,6 +193,26 @@ class NovaServersTestCase(test.TestCase): fakearg="fakearg") scenario._list_servers.assert_called_once_with(True) + def test_suspend_and_resume_server(self): + fake_server = object() + + scenario = servers.NovaServers() + scenario._generate_random_name = mock.MagicMock(return_value="name") + scenario._boot_server = mock.MagicMock(return_value=fake_server) + scenario._suspend_server = mock.MagicMock() + scenario._resume_server = mock.MagicMock() + scenario._delete_server = mock.MagicMock() + + scenario.suspend_and_resume_server("img", 0, fakearg="fakearg") + + scenario._boot_server.assert_called_once_with("img", 0, + fakearg="fakearg") + + scenario._suspend_server.assert_called_once_with(fake_server) + scenario._resume_server.assert_called_once_with(fake_server) + scenario._delete_server.assert_called_once_with(fake_server, + force=False) + def test_list_servers(self): scenario = servers.NovaServers() scenario._list_servers = mock.MagicMock() diff --git a/tests/unit/benchmark/scenarios/nova/test_utils.py b/tests/unit/benchmark/scenarios/nova/test_utils.py index f395a4c61b..f10d92ce9b 100644 --- a/tests/unit/benchmark/scenarios/nova/test_utils.py +++ b/tests/unit/benchmark/scenarios/nova/test_utils.py @@ -235,6 +235,18 @@ class NovaScenarioTestCase(test.TestCase): self._test_atomic_action_timer(nova_scenario.atomic_actions(), "nova.suspend_server") + def test__resume_server(self): + nova_scenario = utils.NovaScenario() + nova_scenario._resume_server(self.server) + self.server.resume.assert_called_once_with() + self._test_assert_called_once_with( + self.wait_for.mock, self.server, + CONF.benchmark.nova_server_resume_poll_interval, + CONF.benchmark.nova_server_resume_timeout) + self.res_is.mock.assert_has_calls([mock.call("ACTIVE")]) + self._test_atomic_action_timer(nova_scenario.atomic_actions(), + "nova.resume_server") + @mock.patch(NOVA_UTILS + ".NovaScenario.clients") def test__create_image(self, mock_clients): mock_clients("nova").images.get.return_value = self.image