diff --git a/rally-jobs/rally.yaml b/rally-jobs/rally.yaml index 1fe5f9d3..a9e2ae5b 100644 --- a/rally-jobs/rally.yaml +++ b/rally-jobs/rally.yaml @@ -1102,6 +1102,26 @@ failure_rate: max: 0 + NovaServers.boot_and_delete_multiple_servers: + - + args: + image: + name: {{image_name}} + flavor: + name: "m1.tiny" + count: 3 + runner: + type: "constant" + times: 3 + concurrency: 3 + context: + users: + tenants: 1 + users_per_tenant: 1 + sla: + failure_rate: + max: 0 + NovaServers.boot_and_list_server: - args: diff --git a/rally/plugins/openstack/scenarios/nova/servers.py b/rally/plugins/openstack/scenarios/nova/servers.py index 7c4c108e..8aa755ee 100644 --- a/rally/plugins/openstack/scenarios/nova/servers.py +++ b/rally/plugins/openstack/scenarios/nova/servers.py @@ -103,6 +103,33 @@ class NovaServers(utils.NovaScenario, self.sleep_between(min_sleep, max_sleep) 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(admin=True, users=True) + @base.scenario(context={"cleanup": ["nova"]}) + def boot_and_delete_multiple_servers(self, image, flavor, count=2, + min_sleep=0, max_sleep=0, + force_delete=False, **kwargs): + """Boot multiple servers in a single request and delete them. + + Deletion is done in parallel with one request per server, not + with a single request for all servers. + + :param image: The image to boot from + :param flavor: Flavor used to boot instance + :param count: Number of instances to boot + :param min_sleep: Minimum sleep time in seconds (non-negative) + :param max_sleep: Maximum sleep time in seconds (non-negative) + :param force_delete: True if force_delete should be used + :param kwargs: Optional additional arguments for instance creation + """ + servers = self._boot_servers(image, flavor, 1, instances_amount=count, + **kwargs) + self.sleep_between(min_sleep, max_sleep) + self._delete_servers(servers, force=force_delete) + @types.set(image=types.ImageResourceType, flavor=types.FlavorResourceType) @validation.image_valid_on_flavor("flavor", "image") diff --git a/rally/plugins/openstack/scenarios/nova/utils.py b/rally/plugins/openstack/scenarios/nova/utils.py index 58e5b443..a986fcca 100644 --- a/rally/plugins/openstack/scenarios/nova/utils.py +++ b/rally/plugins/openstack/scenarios/nova/utils.py @@ -393,6 +393,29 @@ class NovaScenario(base.Scenario): check_interval=CONF.benchmark.nova_server_delete_poll_interval ) + def _delete_servers(self, servers, force=False): + """Delete multiple servers. + + :param servers: A list of servers to delete + :param force: If True, force_delete will be used instead of delete. + """ + atomic_name = ("nova.%sdelete_servers") % (force and "force_" or "") + with base.AtomicAction(self, atomic_name): + for server in servers: + if force: + server.force_delete() + else: + server.delete() + + for server in servers: + bench_utils.wait_for_delete( + server, + update_resource=bench_utils.get_from_manager(), + timeout=CONF.benchmark.nova_server_delete_timeout, + check_interval=CONF. + benchmark.nova_server_delete_poll_interval + ) + @base.atomic_action_timer("nova.delete_image") def _delete_image(self, image): """Delete the given image. diff --git a/samples/tasks/scenarios/nova/boot-and-delete-multiple.json b/samples/tasks/scenarios/nova/boot-and-delete-multiple.json new file mode 100644 index 00000000..dbd6ddef --- /dev/null +++ b/samples/tasks/scenarios/nova/boot-and-delete-multiple.json @@ -0,0 +1,26 @@ +{ + "NovaServers.boot_and_delete_multiple_servers": [ + { + "runner": { + "type": "constant", + "concurrency": 1, + "times": 1 + }, + "args": { + "count": 5, + "image": { + "name": "^cirros.*uec$" + }, + "flavor": { + "name": "m1.tiny" + } + }, + "context": { + "users": { + "users_per_tenant": 1, + "tenants": 1 + } + } + } + ] +} \ No newline at end of file diff --git a/samples/tasks/scenarios/nova/boot-and-delete-multiple.yaml b/samples/tasks/scenarios/nova/boot-and-delete-multiple.yaml new file mode 100644 index 00000000..3ce7bbcb --- /dev/null +++ b/samples/tasks/scenarios/nova/boot-and-delete-multiple.yaml @@ -0,0 +1,17 @@ +--- + NovaServers.boot_and_delete_multiple_servers: + - + args: + image: + name: "^cirros.*uec$" + flavor: + name: "m1.tiny" + count: 5 + runner: + type: "constant" + times: 1 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 diff --git a/tests/unit/plugins/openstack/scenarios/nova/test_servers.py b/tests/unit/plugins/openstack/scenarios/nova/test_servers.py index 60e30d43..40743bdb 100644 --- a/tests/unit/plugins/openstack/scenarios/nova/test_servers.py +++ b/tests/unit/plugins/openstack/scenarios/nova/test_servers.py @@ -202,6 +202,27 @@ class NovaServersTestCase(test.TestCase): scenario._delete_server.assert_called_once_with(fake_server, force=False) + @mock.patch(NOVA_SERVERS_MODULE + ".NovaServers.clients") + def test_boot_and_delete_multiple_servers(self, mock_nova_clients): + mock_nova_clients.return_value = fakes.FakeNovaClient() + + scenario = servers.NovaServers() + scenario._boot_servers = mock.Mock() + scenario._delete_servers = mock.Mock() + scenario.sleep_between = mock.Mock() + + scenario.boot_and_delete_multiple_servers("img", "flavor", count=15, + min_sleep=10, + max_sleep=20, + fakearg="fakearg") + + scenario._boot_servers.assert_called_once_with("img", "flavor", 1, + instances_amount=15, + fakearg="fakearg") + scenario.sleep_between.assert_called_once_with(10, 20) + scenario._delete_servers.assert_called_once_with( + scenario._boot_servers.return_value, force=False) + def test_boot_and_list_server(self): scenario = servers.NovaServers() scenario._generate_random_name = mock.MagicMock(return_value="name") diff --git a/tests/unit/plugins/openstack/scenarios/nova/test_utils.py b/tests/unit/plugins/openstack/scenarios/nova/test_utils.py index 3d615281..f8877c69 100644 --- a/tests/unit/plugins/openstack/scenarios/nova/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/nova/test_utils.py @@ -416,6 +416,36 @@ class NovaScenarioTestCase(test.TestCase): self._test_atomic_action_timer(nova_scenario.atomic_actions(), "nova.unrescue_server") + @mock.patch(NOVA_UTILS + ".NovaScenario.clients") + def _test_delete_servers(self, mock_clients, force=False): + servers = [self.server, self.server1] + nova_scenario = utils.NovaScenario() + nova_scenario._delete_servers(servers, force=force) + check_interval = CONF.benchmark.nova_server_delete_poll_interval + expected = [] + for server in servers: + expected.append(mock.call( + server, update_resource=self.gfm(), + check_interval=check_interval, + timeout=CONF.benchmark.nova_server_delete_timeout)) + if force: + server.force_delete.assert_called_once_with() + self.assertFalse(server.delete.called) + else: + server.delete.assert_called_once_with() + self.assertFalse(server.force_delete.called) + + self.assertEqual(expected, self.wait_for_delete.mock.mock_calls) + timer_name = "nova.%sdelete_servers" % ("force_" if force else "") + self._test_atomic_action_timer(nova_scenario.atomic_actions(), + timer_name) + + def test__default_delete_servers(self): + self._test_delete_servers() + + def test__force_delete_servers(self): + self._test_delete_servers(force=True) + def test__delete_image(self): nova_scenario = utils.NovaScenario() nova_scenario._delete_image(self.image)