From 4635a212e673ceb7dbc3b02957ac5a2173281ccf Mon Sep 17 00:00:00 2001 From: Wataru Takase Date: Mon, 19 Jan 2015 17:21:33 +0100 Subject: [PATCH] Add cold migrate scenario for Nova test Current Rally provides "boot_and_live_migrate_server" scenario, but doesn't provide cold migration scenario. This patch adds the scenario named "boot_and_migrate_server". Change-Id: I681b66475f1a455e33d78e63abd73f67d3960e52 --- rally/benchmark/scenarios/nova/servers.py | 30 ++++++++++++++ rally/benchmark/scenarios/nova/utils.py | 36 +++++++++++++++-- rally/exceptions.py | 4 ++ .../scenarios/nova/boot-and-migrate.json | 25 ++++++++++++ .../scenarios/nova/boot-and-migrate.yaml | 15 +++++++ .../benchmark/scenarios/nova/test_servers.py | 39 +++++++++++++++++++ .../benchmark/scenarios/nova/test_utils.py | 20 ++++++++++ 7 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 samples/tasks/scenarios/nova/boot-and-migrate.json create mode 100644 samples/tasks/scenarios/nova/boot-and-migrate.yaml diff --git a/rally/benchmark/scenarios/nova/servers.py b/rally/benchmark/scenarios/nova/servers.py index 0fd456bea7..3f0d6f77db 100644 --- a/rally/benchmark/scenarios/nova/servers.py +++ b/rally/benchmark/scenarios/nova/servers.py @@ -349,3 +349,33 @@ class NovaServers(utils.NovaScenario, block_migration, disk_over_commit) self._delete_server(server) + + @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_migrate_server(self, image, flavor, **kwargs): + """Migrate a server. + + This scenario launches a VM on a compute node available in + the availability zone and stops the VM, and then migrates the VM + to another compute node on the same availability zone. + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :param kwargs: Optional additional arguments for server creation + """ + server = self._boot_server(self._generate_random_name(), + image, flavor, **kwargs) + self._stop_server(server) + self._migrate(server) + # NOTE(wtakase): This is required because cold migration and resize + # share same code path. + confirm = kwargs.get("confirm", True) + if confirm: + self._resize_confirm(server, status="SHUTOFF") + else: + self._resize_revert(server, status="SHUTOFF") + self._delete_server(server) diff --git a/rally/benchmark/scenarios/nova/utils.py b/rally/benchmark/scenarios/nova/utils.py index 273b8132c1..75633a0d20 100644 --- a/rally/benchmark/scenarios/nova/utils.py +++ b/rally/benchmark/scenarios/nova/utils.py @@ -41,6 +41,7 @@ option_names_and_defaults = [ ('resize_confirm', 0, 200, 2), ('resize_revert', 0, 200, 2), ('live_migrate', 1, 400, 2), + ('migrate', 1, 400, 2), ] for action, prepoll, timeout, poll in option_names_and_defaults: @@ -454,11 +455,11 @@ class NovaScenario(base.Scenario): ) @base.atomic_action_timer('nova.resize_confirm') - def _resize_confirm(self, server): + def _resize_confirm(self, server, status="ACTIVE"): server.confirm_resize() bench_utils.wait_for( server, - is_ready=bench_utils.resource_is("ACTIVE"), + is_ready=bench_utils.resource_is(status), update_resource=bench_utils.get_from_manager(), timeout=CONF.benchmark.nova_server_resize_confirm_timeout, check_interval=( @@ -466,11 +467,11 @@ class NovaScenario(base.Scenario): ) @base.atomic_action_timer('nova.resize_revert') - def _resize_revert(self, server): + def _resize_revert(self, server, status="ACTIVE"): server.revert_resize() bench_utils.wait_for( server, - is_ready=bench_utils.resource_is("ACTIVE"), + is_ready=bench_utils.resource_is(status), update_resource=bench_utils.get_from_manager(), timeout=CONF.benchmark.nova_server_resize_revert_timeout, check_interval=( @@ -565,6 +566,33 @@ class NovaScenario(base.Scenario): raise exceptions.InvalidHostException( "No valid host found to migrate") + @base.atomic_action_timer('nova.migrate') + def _migrate(self, server, skip_host_check=False): + """Run migration of the given server. + + :param server: Server object + :param skip_host_check: Specifies whether to verify the targeted host + availability + """ + server_admin = self.admin_clients("nova").servers.get(server.id) + host_pre_migrate = getattr(server_admin, "OS-EXT-SRV-ATTR:host") + server_admin.migrate() + bench_utils.wait_for( + server, + is_ready=bench_utils.resource_is("VERIFY_RESIZE"), + update_resource=bench_utils.get_from_manager(), + timeout=CONF.benchmark.nova_server_migrate_timeout, + check_interval=( + CONF.benchmark.nova_server_migrate_poll_interval) + ) + if not skip_host_check: + server_admin = self.admin_clients("nova").servers.get(server.id) + host_after_migrate = getattr(server_admin, "OS-EXT-SRV-ATTR:host") + if host_pre_migrate == host_after_migrate: + raise exceptions.MigrateException( + "Migration complete but instance did not change host: %s" % + host_pre_migrate) + def _create_security_groups(self, security_group_count): security_groups = [] with base.AtomicAction(self, "nova.create_%s_security_groups" % diff --git a/rally/exceptions.py b/rally/exceptions.py index 96f4f8b563..81ec3851a5 100644 --- a/rally/exceptions.py +++ b/rally/exceptions.py @@ -260,5 +260,9 @@ class LiveMigrateException(RallyException): msg_fmt = _("Live Migration failed: %(message)s") +class MigrateException(RallyException): + msg_fmt = _("Migration failed: %(message)s") + + class InvalidHostException(RallyException): msg_fmt = _("Live Migration failed: %(message)s") diff --git a/samples/tasks/scenarios/nova/boot-and-migrate.json b/samples/tasks/scenarios/nova/boot-and-migrate.json new file mode 100644 index 0000000000..06e35dc115 --- /dev/null +++ b/samples/tasks/scenarios/nova/boot-and-migrate.json @@ -0,0 +1,25 @@ +{ + "NovaServers.boot_and_migrate_server": [ + { + "args": { + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "^cirros.*uec$" + } + }, + "runner": { + "type": "constant", + "times": 10, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 1, + "users_per_tenant": 1 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/nova/boot-and-migrate.yaml b/samples/tasks/scenarios/nova/boot-and-migrate.yaml new file mode 100644 index 0000000000..c33f760909 --- /dev/null +++ b/samples/tasks/scenarios/nova/boot-and-migrate.yaml @@ -0,0 +1,15 @@ +--- +NovaServers.boot_and_migrate_server: +- args: + flavor: + name: m1.nano + image: + name: "^cirros.*uec$" + runner: + type: constant + times: 10 + concurrency: 2 + context: + users: + tenants: 1 + users_per_tenant: 1 diff --git a/tests/unit/benchmark/scenarios/nova/test_servers.py b/tests/unit/benchmark/scenarios/nova/test_servers.py index 0770656500..b797448049 100644 --- a/tests/unit/benchmark/scenarios/nova/test_servers.py +++ b/tests/unit/benchmark/scenarios/nova/test_servers.py @@ -350,3 +350,42 @@ class NovaServersTestCase(test.TestCase): "host_name", False, False) scenario._delete_server.assert_called_once_with(fake_server) + + def _test_boot_and_migrate_server(self, confirm=False): + fake_server = mock.MagicMock() + + scenario = servers.NovaServers() + scenario._generate_random_name = mock.MagicMock(return_value="name") + scenario._boot_server = mock.MagicMock(return_value=fake_server) + scenario._stop_server = mock.MagicMock() + scenario._migrate = mock.MagicMock() + scenario._resize_confirm = mock.MagicMock() + scenario._resize_revert = mock.MagicMock() + scenario._delete_server = mock.MagicMock() + + kwargs = {"confirm": confirm} + scenario.boot_and_migrate_server("img", 0, + fakearg="fakearg", **kwargs) + + scenario._boot_server.assert_called_once_with("name", "img", 0, + fakearg="fakearg", + confirm=confirm) + + scenario._stop_server.assert_called_once_with(fake_server) + + scenario._migrate.assert_called_once_with(fake_server) + + if confirm: + scenario._resize_confirm.assert_called_once_with(fake_server, + status="SHUTOFF") + else: + scenario._resize_revert.assert_called_once_with(fake_server, + status="SHUTOFF") + + scenario._delete_server.assert_called_once_with(fake_server) + + def test_boot_and_migrate_server_with_confirm(self): + self._test_boot_and_migrate_server(confirm=True) + + def test_boot_and_migrate_server_with_revert(self): + self._test_boot_and_migrate_server(confirm=False) diff --git a/tests/unit/benchmark/scenarios/nova/test_utils.py b/tests/unit/benchmark/scenarios/nova/test_utils.py index 20d5a77d15..8a6d942394 100644 --- a/tests/unit/benchmark/scenarios/nova/test_utils.py +++ b/tests/unit/benchmark/scenarios/nova/test_utils.py @@ -567,6 +567,26 @@ class NovaScenarioTestCase(test.TestCase): self.assertIn( nova_scenario._find_host_to_migrate(fake_server), ["b1", "b3"]) + @mock.patch(NOVA_UTILS + '.NovaScenario.clients') + def test__migrate_server(self, mock_clients): + fake_server = self.server + setattr(fake_server, "OS-EXT-SRV-ATTR:host", "a1") + mock_clients("nova").servers.get(return_value=fake_server) + nova_scenario = utils.NovaScenario(admin_clients=mock_clients) + nova_scenario._migrate(fake_server, skip_host_check=True) + + self._test_assert_called_once_with( + self.wait_for.mock, fake_server, + CONF.benchmark.nova_server_migrate_poll_interval, + CONF.benchmark.nova_server_migrate_timeout) + self.res_is.mock.assert_has_calls([mock.call("VERIFY_RESIZE")]) + self._test_atomic_action_timer(nova_scenario.atomic_actions(), + "nova.migrate") + + self.assertRaises(rally_exceptions.MigrateException, + nova_scenario._migrate, + fake_server, skip_host_check=False) + def test__create_security_groups(self): clients = mock.MagicMock() nova_scenario = utils.NovaScenario()