diff --git a/whitebox_tempest_plugin/api/compute/test_live_migration.py b/whitebox_tempest_plugin/api/compute/test_live_migration.py new file mode 100644 index 00000000..275a3440 --- /dev/null +++ b/whitebox_tempest_plugin/api/compute/test_live_migration.py @@ -0,0 +1,123 @@ +# Copyright 2019 Red Hat, Inc. +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log as logging +import testtools +import xml.etree.ElementTree as ET + +from tempest.common import utils +from tempest.common import waiters +from tempest import config +from tempest.lib import decorators + +from whitebox_tempest_plugin.api.compute import base +from whitebox_tempest_plugin.common import utils as whitebox_utils +from whitebox_tempest_plugin.services import clients + +CONF = config.CONF +LOG = logging.getLogger(__name__) + +# NOTE(mdbooth): This test was originally based on +# tempest.api.compute.admin.test_live_migration + + +class LiveMigrationTest(base.BaseTest): + # First support for block_migration='auto': since Mitaka (OSP9) + min_microversion = '2.25' + + @classmethod + def skip_checks(cls): + super(LiveMigrationTest, cls).skip_checks() + + if not CONF.compute_feature_enabled.live_migration: + skip_msg = ("%s skipped as live-migration is " + "not available" % cls.__name__) + raise cls.skipException(skip_msg) + if CONF.compute.min_compute_nodes < 2: + raise cls.skipException( + "Less than 2 compute nodes, skipping migration test.") + + @classmethod + def setup_credentials(cls): + # These tests don't attempt any SSH validation nor do they use + # floating IPs on the instance, so all we need is a network and + # a subnet so the instance being migrated has a single port, but + # we need that to make sure we are properly updating the port + # host bindings during the live migration. + # TODO(mriedem): SSH validation before and after the instance is + # live migrated would be a nice test wrinkle addition. + cls.set_network_resources(network=True, subnet=True) + super(LiveMigrationTest, cls).setup_credentials() + + @classmethod + def setup_clients(cls): + super(LiveMigrationTest, cls).setup_clients() + cls.admin_migration_client = cls.os_admin.migrations_client + + def _live_migrate(self, server_id, target_host, state): + self.admin_servers_client.live_migrate_server( + server_id, host=target_host, block_migration='auto') + waiters.wait_for_server_status(self.servers_client, server_id, state) + migration_list = (self.admin_migration_client.list_migrations() + ['migrations']) + + msg = ("Live Migration failed. Migrations list for Instance " + "%s: [" % server_id) + for live_migration in migration_list: + if (live_migration['instance_uuid'] == server_id): + msg += "\n%s" % live_migration + msg += "]" + self.assertEqual(target_host, self.get_host_for_server(server_id), + msg) + + def _get_server_libvirt_domain(self, server_id): + compute_node_address = whitebox_utils.get_hypervisor_ip( + self.servers_client, server_id) + + LOG.debug("Connecting to hypervisor %s for server %s", + compute_node_address, server_id) + + virshxml = clients.VirshXMLClient(compute_node_address) + xml = virshxml.dumpxml(server_id) + return ET.fromstring(xml) + + @testtools.skipUnless(CONF.compute_feature_enabled. + volume_backed_live_migration, + 'Volume-backed live migration not available') + @decorators.idempotent_id('41e92884-ed04-42da-89fc-ef8922646542') + @utils.services('volume') + def test_volume_backed_live_migration(self): + # Live migrate an instance to another host + server_id = self.create_test_server(wait_until="ACTIVE", + volume_backed=True)['id'] + + def root_disk_cache(): + domain = self._get_server_libvirt_domain(server_id) + return domain.find( + "devices/disk/target[@dev='vda']/../driver").attrib['cache'] + + # The initial value of disk cache depends on config and the storage in + # use. We can't guess it, so fetch it before we start. + cache_type = root_disk_cache() + + source_host = self.get_host_for_server(server_id) + destination_host = self.get_host_other_than(server_id) + LOG.info("Live migrate from source %s to destination %s", + source_host, destination_host) + self._live_migrate(server_id, destination_host, 'ACTIVE') + + # Assert cache-mode has not changed during live migration + self.assertEqual(cache_type, root_disk_cache())