# Copyright (c) 2016 Red Hat, Inc # # 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 collections import deque from lxml import etree import mock from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import units import six from nova.compute import power_state from nova import exception from nova.network import model as network_model from nova import objects from nova import test from nova.tests.unit import matchers from nova.tests.unit.virt.libvirt import fakelibvirt from nova.virt.libvirt import config as vconfig from nova.virt.libvirt import guest as libvirt_guest from nova.virt.libvirt import host from nova.virt.libvirt import migration class UtilityMigrationTestCase(test.NoDBTestCase): def test_graphics_listen_addrs(self): data = objects.LibvirtLiveMigrateData( graphics_listen_addr_vnc='127.0.0.1', graphics_listen_addr_spice='127.0.0.2') addrs = migration.graphics_listen_addrs(data) self.assertEqual({ 'vnc': '127.0.0.1', 'spice': '127.0.0.2'}, addrs) def test_graphics_listen_addrs_empty(self): data = objects.LibvirtLiveMigrateData() addrs = migration.graphics_listen_addrs(data) self.assertIsNone(addrs) def test_graphics_listen_addrs_spice(self): data = objects.LibvirtLiveMigrateData( graphics_listen_addr_spice='127.0.0.2') addrs = migration.graphics_listen_addrs(data) self.assertEqual({ 'vnc': None, 'spice': '127.0.0.2'}, addrs) def test_graphics_listen_addrs_vnc(self): data = objects.LibvirtLiveMigrateData( graphics_listen_addr_vnc='127.0.0.1') addrs = migration.graphics_listen_addrs(data) self.assertEqual({ 'vnc': '127.0.0.1', 'spice': None}, addrs) def test_serial_listen_addr(self): data = objects.LibvirtLiveMigrateData( serial_listen_addr='127.0.0.1') addr = migration.serial_listen_addr(data) self.assertEqual('127.0.0.1', addr) def test_serial_listen_addr_emtpy(self): data = objects.LibvirtLiveMigrateData() addr = migration.serial_listen_addr(data) self.assertIsNone(addr) def test_serial_listen_addr_None(self): data = objects.LibvirtLiveMigrateData() data.serial_listen_addr = None addr = migration.serial_listen_addr(data) self.assertIsNone(addr) def test_serial_listen_ports(self): data = objects.LibvirtLiveMigrateData( serial_listen_ports=[1, 2, 3]) ports = migration.serial_listen_ports(data) self.assertEqual([1, 2, 3], ports) def test_serial_listen_ports_emtpy(self): data = objects.LibvirtLiveMigrateData() ports = migration.serial_listen_ports(data) self.assertEqual([], ports) @mock.patch('lxml.etree.tostring') @mock.patch.object(migration, '_update_memory_backing_xml') @mock.patch.object(migration, '_update_perf_events_xml') @mock.patch.object(migration, '_update_graphics_xml') @mock.patch.object(migration, '_update_serial_xml') @mock.patch.object(migration, '_update_volume_xml') def test_get_updated_guest_xml( self, mock_volume, mock_serial, mock_graphics, mock_perf_events_xml, mock_memory_backing, mock_tostring): data = objects.LibvirtLiveMigrateData() mock_guest = mock.Mock(spec=libvirt_guest.Guest) get_volume_config = mock.MagicMock() mock_guest.get_xml_desc.return_value = '' migration.get_updated_guest_xml(mock_guest, data, get_volume_config) mock_graphics.assert_called_once_with(mock.ANY, data) mock_serial.assert_called_once_with(mock.ANY, data) mock_volume.assert_called_once_with(mock.ANY, data, get_volume_config) mock_perf_events_xml.assert_called_once_with(mock.ANY, data) mock_memory_backing.assert_called_once_with(mock.ANY, data) self.assertEqual(1, mock_tostring.called) def test_update_serial_xml_serial(self): data = objects.LibvirtLiveMigrateData( serial_listen_addr='127.0.0.100', serial_listen_ports=[2001]) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_serial_xml(doc, data), encoding='unicode') new_xml = xml.replace("127.0.0.1", "127.0.0.100").replace( "2000", "2001") self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_serial_xml_console(self): data = objects.LibvirtLiveMigrateData( serial_listen_addr='127.0.0.100', serial_listen_ports=[299, 300]) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_serial_xml(doc, data), encoding='unicode') new_xml = xml.replace("127.0.0.1", "127.0.0.100").replace( "2001", "299").replace("2002", "300") self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_serial_xml_without_ports(self): # This test is for backwards compatibility when we don't # get the serial ports from the target node. data = objects.LibvirtLiveMigrateData( serial_listen_addr='127.0.0.100', serial_listen_ports=[]) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_serial_xml(doc, data), encoding='unicode') new_xml = xml.replace("127.0.0.1", "127.0.0.100") self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_graphics(self): data = objects.LibvirtLiveMigrateData( graphics_listen_addr_vnc='127.0.0.100', graphics_listen_addr_spice='127.0.0.200') xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_graphics_xml(doc, data), encoding='unicode') new_xml = xml.replace("127.0.0.1", "127.0.0.100") new_xml = new_xml.replace("127.0.0.2", "127.0.0.200") self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_volume_xml(self): connection_info = { 'driver_volume_type': 'iscsi', 'serial': '58a84f6d-3f0c-4e19-a0af-eb657b790657', 'data': { 'access_mode': 'rw', 'target_discovered': False, 'target_iqn': 'ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z', 'volume_id': '58a84f6d-3f0c-4e19-a0af-eb657b790657', 'device_path': '/dev/disk/by-path/ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z'}} bdm = objects.LibvirtLiveMigrateBDMInfo( serial='58a84f6d-3f0c-4e19-a0af-eb657b790657', bus='virtio', type='disk', dev='vdb', connection_info=connection_info) data = objects.LibvirtLiveMigrateData( target_connect_addr='127.0.0.1', bdms=[bdm], block_migration=False) xml = """ 58a84f6d-3f0c-4e19-a0af-eb657b790657
""" conf = vconfig.LibvirtConfigGuestDisk() conf.source_device = bdm.type conf.driver_name = "qemu" conf.driver_format = "raw" conf.driver_cache = "none" conf.target_dev = bdm.dev conf.target_bus = bdm.bus conf.serial = bdm.connection_info.get('serial') conf.source_type = "block" conf.source_path = bdm.connection_info['data'].get('device_path') get_volume_config = mock.MagicMock(return_value=conf) doc = etree.fromstring(xml) res = etree.tostring(migration._update_volume_xml( doc, data, get_volume_config), encoding='unicode') new_xml = xml.replace('ip-1.2.3.4:3260-iqn.abc.12345.opst-lun-X', 'ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z') self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_volume_xml_keeps_address(self): # Now test to make sure address isn't altered for virtio-scsi and rbd connection_info = { 'driver_volume_type': 'rbd', 'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192', 'data': { 'access_mode': 'rw', 'secret_type': 'ceph', 'name': 'cinder-volumes/volume-d299a078', 'encrypted': False, 'discard': True, 'cluster_name': 'ceph', 'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9', 'qos_specs': None, 'auth_enabled': True, 'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192', 'hosts': ['172.16.128.101', '172.16.128.121'], 'auth_username': 'cinder', 'ports': ['6789', '6789', '6789']}} bdm = objects.LibvirtLiveMigrateBDMInfo( serial='d299a078-f0db-4993-bf03-f10fe44fd192', bus='scsi', type='disk', dev='sdc', connection_info=connection_info) data = objects.LibvirtLiveMigrateData( target_connect_addr=None, bdms=[bdm], block_migration=False) xml = """ d299a078-f0db-4993-bf03-f10fe44fd192
""" conf = vconfig.LibvirtConfigGuestDisk() conf.source_device = bdm.type conf.driver_name = "qemu" conf.driver_format = "raw" conf.driver_cache = "writeback" conf.target_dev = bdm.dev conf.target_bus = bdm.bus conf.serial = bdm.connection_info.get('serial') conf.source_type = "network" conf.driver_discard = 'unmap' conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive() conf.device_addr.controller = 0 get_volume_config = mock.MagicMock(return_value=conf) doc = etree.fromstring(xml) res = etree.tostring(migration._update_volume_xml( doc, data, get_volume_config), encoding='unicode') new_xml = xml.replace('sdb', 'sdc') self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_volume_xml_add_encryption(self): connection_info = { 'driver_volume_type': 'rbd', 'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192', 'data': { 'access_mode': 'rw', 'secret_type': 'ceph', 'name': 'cinder-volumes/volume-d299a078', 'encrypted': False, 'discard': True, 'cluster_name': 'ceph', 'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9', 'qos_specs': None, 'auth_enabled': True, 'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192', 'hosts': ['172.16.128.101', '172.16.128.121'], 'auth_username': 'cinder', 'ports': ['6789', '6789', '6789']}} bdm = objects.LibvirtLiveMigrateBDMInfo( serial='d299a078-f0db-4993-bf03-f10fe44fd192', bus='scsi', type='disk', dev='sdb', connection_info=connection_info, encryption_secret_uuid=uuids.encryption_secret_uuid) data = objects.LibvirtLiveMigrateData( target_connect_addr=None, bdms=[bdm], block_migration=False) xml = """ d299a078-f0db-4993-bf03-f10fe44fd192
""" new_xml = """ d299a078-f0db-4993-bf03-f10fe44fd192
""" % {'encryption_secret_uuid': uuids.encryption_secret_uuid} conf = vconfig.LibvirtConfigGuestDisk() conf.source_device = bdm.type conf.driver_name = "qemu" conf.driver_format = "raw" conf.driver_cache = "writeback" conf.target_dev = bdm.dev conf.target_bus = bdm.bus conf.serial = bdm.connection_info.get('serial') conf.source_type = "network" conf.driver_discard = 'unmap' conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive() conf.device_addr.controller = 0 get_volume_config = mock.MagicMock(return_value=conf) doc = etree.fromstring(xml) res = etree.tostring(migration._update_volume_xml( doc, data, get_volume_config), encoding='unicode') self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_volume_xml_update_encryption(self): connection_info = { 'driver_volume_type': 'rbd', 'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192', 'data': { 'access_mode': 'rw', 'secret_type': 'ceph', 'name': 'cinder-volumes/volume-d299a078', 'encrypted': False, 'discard': True, 'cluster_name': 'ceph', 'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9', 'qos_specs': None, 'auth_enabled': True, 'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192', 'hosts': ['172.16.128.101', '172.16.128.121'], 'auth_username': 'cinder', 'ports': ['6789', '6789', '6789']}} bdm = objects.LibvirtLiveMigrateBDMInfo( serial='d299a078-f0db-4993-bf03-f10fe44fd192', bus='scsi', type='disk', dev='sdb', connection_info=connection_info, encryption_secret_uuid=uuids.encryption_secret_uuid_new) data = objects.LibvirtLiveMigrateData( target_connect_addr=None, bdms=[bdm], block_migration=False) xml = """ d299a078-f0db-4993-bf03-f10fe44fd192
""" % {'encryption_secret_uuid': uuids.encryption_secret_uuid_old} conf = vconfig.LibvirtConfigGuestDisk() conf.source_device = bdm.type conf.driver_name = "qemu" conf.driver_format = "raw" conf.driver_cache = "writeback" conf.target_dev = bdm.dev conf.target_bus = bdm.bus conf.serial = bdm.connection_info.get('serial') conf.source_type = "network" conf.driver_discard = 'unmap' conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive() conf.device_addr.controller = 0 get_volume_config = mock.MagicMock(return_value=conf) doc = etree.fromstring(xml) res = etree.tostring(migration._update_volume_xml( doc, data, get_volume_config), encoding='unicode') new_xml = xml.replace(uuids.encryption_secret_uuid_old, uuids.encryption_secret_uuid_new) self.assertThat(res, matchers.XMLMatches(new_xml)) def test_update_perf_events_xml(self): data = objects.LibvirtLiveMigrateData( supported_perf_events=['cmt']) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_perf_events_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_perf_events_xml_add_new_events(self): data = objects.LibvirtLiveMigrateData( supported_perf_events=['cmt']) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_perf_events_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_perf_events_xml_add_new_events1(self): data = objects.LibvirtLiveMigrateData( supported_perf_events=['cmt', 'mbml']) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_perf_events_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_perf_events_xml_remove_all_events(self): data = objects.LibvirtLiveMigrateData( supported_perf_events=[]) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_perf_events_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_memory_backing_xml_remove(self): data = objects.LibvirtLiveMigrateData( dst_wants_file_backed_memory=False) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_memory_backing_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_memory_backing_xml_add(self): data = objects.LibvirtLiveMigrateData( dst_wants_file_backed_memory=True) xml = """""" doc = etree.fromstring(xml) res = etree.tostring(migration._update_memory_backing_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_memory_backing_xml_keep(self): data = objects.LibvirtLiveMigrateData( dst_wants_file_backed_memory=True) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_memory_backing_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_memory_backing_discard_add(self): data = objects.LibvirtLiveMigrateData( dst_wants_file_backed_memory=True, file_backed_memory_discard=True) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_memory_backing_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_memory_backing_discard_remove(self): data = objects.LibvirtLiveMigrateData( dst_wants_file_backed_memory=True, file_backed_memory_discard=False) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_memory_backing_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def test_update_memory_backing_discard_keep(self): data = objects.LibvirtLiveMigrateData( dst_wants_file_backed_memory=True, file_backed_memory_discard=True) xml = """ """ doc = etree.fromstring(xml) res = etree.tostring(migration._update_memory_backing_xml(doc, data), encoding='unicode') self.assertThat(res, matchers.XMLMatches(""" """)) def _test_update_vif_xml(self, conf, original_xml, expected_xml): """Simulates updating the guest xml for live migrating from a host using OVS to a host using vhostuser as the networking backend. """ vif_ovs = network_model.VIF(id=uuids.vif, address='DE:AD:BE:EF:CA:FE', details={'port_filter': False}, devname='tap-xxx-yyy-zzz', ovs_interfaceid=uuids.ovs) source_vif = vif_ovs migrate_vifs = [ objects.VIFMigrateData( port_id=uuids.port_id, vnic_type=network_model.VNIC_TYPE_NORMAL, vif_type=network_model.VIF_TYPE_VHOSTUSER, vif_details={ 'vhostuser_socket': '/vhost-user/test.sock' }, profile={}, host='dest.host', source_vif=source_vif) ] data = objects.LibvirtLiveMigrateData(vifs=migrate_vifs) get_vif_config = mock.MagicMock(return_value=conf) doc = etree.fromstring(original_xml) updated_xml = etree.tostring( migration._update_vif_xml(doc, data, get_vif_config), encoding='unicode') self.assertThat(updated_xml, matchers.XMLMatches(expected_xml)) def test_update_vif_xml_to_vhostuser(self): conf = vconfig.LibvirtConfigGuestInterface() conf.net_type = "vhostuser" conf.vhostuser_type = "unix" conf.vhostuser_mode = "server" conf.mac_addr = "DE:AD:BE:EF:CA:FE" conf.vhostuser_path = "/vhost-user/test.sock" conf.model = "virtio" original_xml = """ 3de6550a-8596-4937-8046-9d862036bca5
""" % uuids.ovs # Note that and are dropped from the ovs source # interface xml since they aren't applicable to the vhostuser # destination interface xml. The type attribute value changes and the # hardware address element is retained. expected_xml = """ 3de6550a-8596-4937-8046-9d862036bca5
""" self._test_update_vif_xml(conf, original_xml, expected_xml) def test_update_vif_xml_to_bridge_without_mtu(self): """This test validates _update_vif_xml to make sure it does not add MTU to the interface if it does not exist in the source XML. """ conf = vconfig.LibvirtConfigGuestInterface() conf.net_type = "bridge" conf.source_dev = "qbra188171c-ea" conf.target_dev = "tapa188171c-ea" conf.mac_addr = "DE:AD:BE:EF:CA:FE" conf.vhostuser_path = "/vhost-user/test.sock" conf.model = "virtio" conf.mtu = 9000 original_xml = """ 3de6550a-8596-4937-8046-9d862036bca5
""" % uuids.ovs expected_xml = """ 3de6550a-8596-4937-8046-9d862036bca5
""" self._test_update_vif_xml(conf, original_xml, expected_xml) def test_update_vif_xml_to_bridge_with_mtu(self): """This test validates _update_vif_xml to make sure it maintains the interface MTU if it exists in the source XML """ conf = vconfig.LibvirtConfigGuestInterface() conf.net_type = "bridge" conf.source_dev = "qbra188171c-ea" conf.target_dev = "tapa188171c-ea" conf.mac_addr = "DE:AD:BE:EF:CA:FE" conf.vhostuser_path = "/vhost-user/test.sock" conf.model = "virtio" conf.mtu = 9000 original_xml = """ 3de6550a-8596-4937-8046-9d862036bca5
""" % uuids.ovs expected_xml = """ 3de6550a-8596-4937-8046-9d862036bca5
""" self._test_update_vif_xml(conf, original_xml, expected_xml) def test_update_vif_xml_no_mac_address_in_xml(self): """Tests that the is not found in the XML which results in an error. """ data = objects.LibvirtLiveMigrateData(vifs=[ objects.VIFMigrateData(source_vif=network_model.VIF( address="DE:AD:BE:EF:CA:FE"))]) original_xml = """ 3de6550a-8596-4937-8046-9d862036bca5 """ get_vif_config = mock.MagicMock(new_callable=mock.NonCallableMock) doc = etree.fromstring(original_xml) ex = self.assertRaises(exception.NovaException, migration._update_vif_xml, doc, data, get_vif_config) self.assertIn('Unable to find MAC address in interface XML', six.text_type(ex)) def test_update_vif_xml_no_matching_vif(self): """Tests that the vif in the migrate data is not found in the existing guest interfaces. """ data = objects.LibvirtLiveMigrateData(vifs=[ objects.VIFMigrateData(source_vif=network_model.VIF( address="DE:AD:BE:EF:CA:FE"))]) original_xml = """ 3de6550a-8596-4937-8046-9d862036bca5 """ get_vif_config = mock.MagicMock(new_callable=mock.NonCallableMock) doc = etree.fromstring(original_xml) ex = self.assertRaises(KeyError, migration._update_vif_xml, doc, data, get_vif_config) self.assertIn("CA:FE:DE:AD:BE:EF", six.text_type(ex)) class MigrationMonitorTestCase(test.NoDBTestCase): def setUp(self): super(MigrationMonitorTestCase, self).setUp() self.useFixture(fakelibvirt.FakeLibvirtFixture()) flavor = objects.Flavor(memory_mb=2048, swap=0, vcpu_weight=None, root_gb=1, id=2, name=u'm1.small', ephemeral_gb=0, rxtx_factor=1.0, flavorid=u'1', vcpus=1, extra_specs={}) instance = { 'id': 1, 'uuid': '32dfcb37-5af1-552b-357c-be8c3aa38310', 'memory_kb': '1024000', 'basepath': '/some/path', 'bridge_name': 'br100', 'display_name': "Acme webserver", 'vcpus': 2, 'project_id': 'fake', 'bridge': 'br101', 'image_ref': '155d900f-4e14-4e4c-a73d-069cbf4541e6', 'root_gb': 10, 'ephemeral_gb': 20, 'instance_type_id': '5', # m1.small 'extra_specs': {}, 'system_metadata': { 'image_disk_format': 'raw', }, 'flavor': flavor, 'new_flavor': None, 'old_flavor': None, 'pci_devices': objects.PciDeviceList(), 'numa_topology': None, 'config_drive': None, 'vm_mode': None, 'kernel_id': None, 'ramdisk_id': None, 'os_type': 'linux', 'user_id': '838a72b0-0d54-4827-8fd6-fb1227633ceb', 'ephemeral_key_uuid': None, 'vcpu_model': None, 'host': 'fake-host', 'task_state': None, } self.instance = objects.Instance(**instance) self.conn = fakelibvirt.Connection("qemu:///system") self.dom = fakelibvirt.Domain(self.conn, "", True) self.host = host.Host("qemu:///system") self.guest = libvirt_guest.Guest(self.dom) @mock.patch.object(libvirt_guest.Guest, "is_active", return_value=True) def test_live_migration_find_type_active(self, mock_active): self.assertEqual(migration.find_job_type(self.guest, self.instance), fakelibvirt.VIR_DOMAIN_JOB_FAILED) @mock.patch.object(libvirt_guest.Guest, "is_active", return_value=False) def test_live_migration_find_type_inactive(self, mock_active): self.assertEqual(migration.find_job_type(self.guest, self.instance), fakelibvirt.VIR_DOMAIN_JOB_COMPLETED) @mock.patch.object(libvirt_guest.Guest, "is_active") def test_live_migration_find_type_no_domain(self, mock_active): mock_active.side_effect = fakelibvirt.make_libvirtError( fakelibvirt.libvirtError, "No domain with ID", error_code=fakelibvirt.VIR_ERR_NO_DOMAIN) self.assertEqual(migration.find_job_type(self.guest, self.instance), fakelibvirt.VIR_DOMAIN_JOB_COMPLETED) @mock.patch.object(libvirt_guest.Guest, "is_active") def test_live_migration_find_type_bad_err(self, mock_active): mock_active.side_effect = fakelibvirt.make_libvirtError( fakelibvirt.libvirtError, "Something weird happened", error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR) self.assertEqual(migration.find_job_type(self.guest, self.instance), fakelibvirt.VIR_DOMAIN_JOB_FAILED) def test_live_migration_abort_stuck(self): # Progress time exceeds progress timeout self.assertTrue(migration.should_abort(self.instance, 5000, 1000, 2000, 4500, 9000, "running")) def test_live_migration_abort_no_prog_timeout(self): # Progress timeout is disabled self.assertFalse(migration.should_abort(self.instance, 5000, 1000, 0, 4500, 9000, "running")) def test_live_migration_abort_not_stuck(self): # Progress time is less than progress timeout self.assertFalse(migration.should_abort(self.instance, 5000, 4500, 2000, 4500, 9000, "running")) def test_live_migration_abort_too_long(self): # Elapsed time is over completion timeout self.assertTrue(migration.should_abort(self.instance, 5000, 4500, 2000, 4500, 2000, "running")) def test_live_migration_abort_no_comp_timeout(self): # Completion timeout is disabled self.assertFalse(migration.should_abort(self.instance, 5000, 4500, 2000, 4500, 0, "running")) def test_live_migration_abort_still_working(self): # Elapsed time is less than completion timeout self.assertFalse(migration.should_abort(self.instance, 5000, 4500, 2000, 4500, 9000, "running")) def test_live_migration_postcopy_switch(self): # Migration progress is not fast enough self.assertTrue(migration.should_switch_to_postcopy( 2, 100, 105, "running")) def test_live_migration_postcopy_switch_already_switched(self): # Migration already running in postcopy mode self.assertFalse(migration.should_switch_to_postcopy( 2, 100, 105, "running (post-copy)")) def test_live_migration_postcopy_switch_too_soon(self): # First memory iteration not completed yet self.assertFalse(migration.should_switch_to_postcopy( 1, 100, 105, "running")) def test_live_migration_postcopy_switch_fast_progress(self): # Migration progress is good self.assertFalse(migration.should_switch_to_postcopy( 2, 100, 155, "running")) @mock.patch.object(libvirt_guest.Guest, "migrate_configure_max_downtime") def test_live_migration_update_downtime_no_steps(self, mock_dt): steps = [] newdt = migration.update_downtime(self.guest, self.instance, None, steps, 5000) self.assertIsNone(newdt) self.assertFalse(mock_dt.called) @mock.patch.object(libvirt_guest.Guest, "migrate_configure_max_downtime") def test_live_migration_update_downtime_too_early(self, mock_dt): steps = [ (9000, 50), (18000, 200), ] # We shouldn't change downtime since haven't hit first time newdt = migration.update_downtime(self.guest, self.instance, None, steps, 5000) self.assertIsNone(newdt) self.assertFalse(mock_dt.called) @mock.patch.object(libvirt_guest.Guest, "migrate_configure_max_downtime") def test_live_migration_update_downtime_step1(self, mock_dt): steps = [ (9000, 50), (18000, 200), ] # We should pick the first downtime entry newdt = migration.update_downtime(self.guest, self.instance, None, steps, 11000) self.assertEqual(newdt, 50) mock_dt.assert_called_once_with(50) @mock.patch.object(libvirt_guest.Guest, "migrate_configure_max_downtime") def test_live_migration_update_downtime_nostep1(self, mock_dt): steps = [ (9000, 50), (18000, 200), ] # We shouldn't change downtime, since its already set newdt = migration.update_downtime(self.guest, self.instance, 50, steps, 11000) self.assertEqual(newdt, 50) self.assertFalse(mock_dt.called) @mock.patch.object(libvirt_guest.Guest, "migrate_configure_max_downtime") def test_live_migration_update_downtime_step2(self, mock_dt): steps = [ (9000, 50), (18000, 200), ] newdt = migration.update_downtime(self.guest, self.instance, 50, steps, 22000) self.assertEqual(newdt, 200) mock_dt.assert_called_once_with(200) @mock.patch.object(libvirt_guest.Guest, "migrate_configure_max_downtime") def test_live_migration_update_downtime_err(self, mock_dt): steps = [ (9000, 50), (18000, 200), ] mock_dt.side_effect = fakelibvirt.make_libvirtError( fakelibvirt.libvirtError, "Failed to set downtime", error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR) newdt = migration.update_downtime(self.guest, self.instance, 50, steps, 22000) self.assertEqual(newdt, 200) mock_dt.assert_called_once_with(200) @mock.patch.object(objects.Instance, "save") @mock.patch.object(objects.Migration, "save") def test_live_migration_save_stats(self, mock_isave, mock_msave): mig = objects.Migration() info = libvirt_guest.JobInfo( memory_total=1 * units.Gi, memory_processed=5 * units.Gi, memory_remaining=500 * units.Mi, disk_total=15 * units.Gi, disk_processed=10 * units.Gi, disk_remaining=14 * units.Gi) migration.save_stats(self.instance, mig, info, 75) self.assertEqual(mig.memory_total, 1 * units.Gi) self.assertEqual(mig.memory_processed, 5 * units.Gi) self.assertEqual(mig.memory_remaining, 500 * units.Mi) self.assertEqual(mig.disk_total, 15 * units.Gi) self.assertEqual(mig.disk_processed, 10 * units.Gi) self.assertEqual(mig.disk_remaining, 14 * units.Gi) self.assertEqual(self.instance.progress, 25) mock_msave.assert_called_once_with() mock_isave.assert_called_once_with() @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy") @mock.patch.object(libvirt_guest.Guest, "pause") def test_live_migration_run_tasks_empty_tasks(self, mock_pause, mock_postcopy): tasks = deque() active_migrations = {self.instance.uuid: tasks} on_migration_failure = deque() mig = objects.Migration(id=1, status="running") migration.run_tasks(self.guest, self.instance, active_migrations, on_migration_failure, mig, False) self.assertFalse(mock_pause.called) self.assertFalse(mock_postcopy.called) self.assertEqual(len(on_migration_failure), 0) @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy") @mock.patch.object(libvirt_guest.Guest, "pause") def test_live_migration_run_tasks_no_tasks(self, mock_pause, mock_postcopy): active_migrations = {} on_migration_failure = deque() mig = objects.Migration(id=1, status="running") migration.run_tasks(self.guest, self.instance, active_migrations, on_migration_failure, mig, False) self.assertFalse(mock_pause.called) self.assertFalse(mock_postcopy.called) self.assertEqual(len(on_migration_failure), 0) @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy") @mock.patch.object(libvirt_guest.Guest, "pause") def test_live_migration_run_tasks_no_force_complete(self, mock_pause, mock_postcopy): tasks = deque() # Test to ensure unknown tasks are ignored tasks.append("wibble") active_migrations = {self.instance.uuid: tasks} on_migration_failure = deque() mig = objects.Migration(id=1, status="running") migration.run_tasks(self.guest, self.instance, active_migrations, on_migration_failure, mig, False) self.assertFalse(mock_pause.called) self.assertFalse(mock_postcopy.called) self.assertEqual(len(on_migration_failure), 0) @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy") @mock.patch.object(libvirt_guest.Guest, "pause") def test_live_migration_run_tasks_force_complete(self, mock_pause, mock_postcopy): tasks = deque() tasks.append("force-complete") active_migrations = {self.instance.uuid: tasks} on_migration_failure = deque() mig = objects.Migration(id=1, status="running") migration.run_tasks(self.guest, self.instance, active_migrations, on_migration_failure, mig, False) mock_pause.assert_called_once_with() self.assertFalse(mock_postcopy.called) self.assertEqual(len(on_migration_failure), 1) self.assertEqual(on_migration_failure.pop(), "unpause") @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy") @mock.patch.object(libvirt_guest.Guest, "pause") def test_live_migration_run_tasks_force_complete_postcopy_running(self, mock_pause, mock_postcopy): tasks = deque() tasks.append("force-complete") active_migrations = {self.instance.uuid: tasks} on_migration_failure = deque() mig = objects.Migration(id=1, status="running (post-copy)") migration.run_tasks(self.guest, self.instance, active_migrations, on_migration_failure, mig, True) self.assertFalse(mock_pause.called) self.assertFalse(mock_postcopy.called) self.assertEqual(len(on_migration_failure), 0) @mock.patch.object(objects.Migration, "save") @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy") @mock.patch.object(libvirt_guest.Guest, "pause") def test_live_migration_run_tasks_force_complete_postcopy(self, mock_pause, mock_postcopy, mock_msave): tasks = deque() tasks.append("force-complete") active_migrations = {self.instance.uuid: tasks} on_migration_failure = deque() mig = objects.Migration(id=1, status="running") migration.run_tasks(self.guest, self.instance, active_migrations, on_migration_failure, mig, True) mock_postcopy.assert_called_once_with() self.assertFalse(mock_pause.called) self.assertEqual(len(on_migration_failure), 0) @mock.patch.object(libvirt_guest.Guest, "resume") @mock.patch.object(libvirt_guest.Guest, "get_power_state", return_value=power_state.PAUSED) def test_live_migration_recover_tasks_resume(self, mock_ps, mock_resume): tasks = deque() tasks.append("unpause") migration.run_recover_tasks(self.host, self.guest, self.instance, tasks) mock_resume.assert_called_once_with() @mock.patch.object(libvirt_guest.Guest, "resume") @mock.patch.object(libvirt_guest.Guest, "get_power_state", return_value=power_state.RUNNING) def test_live_migration_recover_tasks_no_resume(self, mock_ps, mock_resume): tasks = deque() tasks.append("unpause") migration.run_recover_tasks(self.host, self.guest, self.instance, tasks) self.assertFalse(mock_resume.called) @mock.patch.object(libvirt_guest.Guest, "resume") def test_live_migration_recover_tasks_empty_tasks(self, mock_resume): tasks = deque() migration.run_recover_tasks(self.host, self.guest, self.instance, tasks) self.assertFalse(mock_resume.called) @mock.patch.object(libvirt_guest.Guest, "resume") def test_live_migration_recover_tasks_no_pause(self, mock_resume): tasks = deque() # Test to ensure unknown tasks are ignored tasks.append("wibble") migration.run_recover_tasks(self.host, self.guest, self.instance, tasks) self.assertFalse(mock_resume.called) def test_live_migration_downtime_steps(self): self.flags(live_migration_downtime=400, group='libvirt') self.flags(live_migration_downtime_steps=10, group='libvirt') self.flags(live_migration_downtime_delay=30, group='libvirt') steps = migration.downtime_steps(3.0) self.assertEqual([ (0, 40), (90, 76), (180, 112), (270, 148), (360, 184), (450, 220), (540, 256), (630, 292), (720, 328), (810, 364), (900, 400), ], list(steps))