diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index b6c5e58d69b3..034aa0d8e683 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -3623,15 +3623,21 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(cfg.devices[5].type, "spice") self.assertEqual(cfg.devices[6].type, "qxl") + @mock.patch.object(host.Host, 'get_guest') + @mock.patch.object(libvirt_driver.LibvirtDriver, + '_get_serial_ports_from_guest') @mock.patch('nova.console.serial.acquire_port') @mock.patch('nova.virt.hardware.get_number_of_serial_ports', return_value=1) @mock.patch.object(libvirt_driver.libvirt_utils, 'get_arch',) def test_create_serial_console_devices_based_on_arch(self, mock_get_arch, - mock_get_port_number, - mock_acquire_port): + mock_get_port_number, + mock_acquire_port, + mock_ports, + mock_guest): self.flags(enabled=True, group='serial_console') drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance = objects.Instance(**self.test_instance) expected = { fields.Architecture.X86_64: vconfig.LibvirtConfigGuestSerial, @@ -3644,13 +3650,17 @@ class LibvirtConnTestCase(test.NoDBTestCase): mock_get_arch.return_value = guest_arch guest = vconfig.LibvirtConfigGuest() - drvr._create_consoles(virt_type="kvm", guest=guest, log_path="", - flavor={}, image_meta={}, caps=caps) + drvr._create_consoles(virt_type="kvm", guest=guest, + instance=instance, flavor={}, + image_meta={}, caps=caps) self.assertEqual(2, len(guest.devices)) console_device = guest.devices[0] self.assertIsInstance(console_device, device_type) self.assertEqual("tcp", console_device.type) + @mock.patch.object(host.Host, 'get_guest') + @mock.patch.object(libvirt_driver.LibvirtDriver, + '_get_serial_ports_from_guest') @mock.patch('nova.virt.hardware.get_number_of_serial_ports', return_value=4) @mock.patch.object(libvirt_driver.libvirt_utils, 'get_arch', @@ -3658,7 +3668,7 @@ class LibvirtConnTestCase(test.NoDBTestCase): fields.Architecture.S390, fields.Architecture.S390X]) def test_create_serial_console_devices_with_limit_exceeded_based_on_arch( - self, mock_get_arch, mock_get_port_number): + self, mock_get_arch, mock_get_port_number, mock_ports, mock_guest): self.flags(enabled=True, group='serial_console') self.flags(virt_type="qemu", group='libvirt') flavor = 'fake_flavor' @@ -3666,20 +3676,20 @@ class LibvirtConnTestCase(test.NoDBTestCase): drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) caps = drvr._host.get_capabilities() guest = vconfig.LibvirtConfigGuest() - log_path = "" + instance = objects.Instance(**self.test_instance) self.assertRaises(exception.SerialPortNumberLimitExceeded, drvr._create_consoles, - "kvm", guest, log_path, flavor, image_meta, caps) + "kvm", guest, instance, flavor, image_meta, caps) mock_get_arch.assert_called_with(image_meta) mock_get_port_number.assert_called_with(flavor, image_meta) - drvr._create_consoles("kvm", guest, log_path, flavor, image_meta, caps) + drvr._create_consoles("kvm", guest, instance, flavor, image_meta, caps) mock_get_arch.assert_called_with(image_meta) mock_get_port_number.assert_called_with(flavor, image_meta) - drvr._create_consoles("kvm", guest, log_path, flavor, image_meta, caps) + drvr._create_consoles("kvm", guest, instance, flavor, image_meta, caps) mock_get_arch.assert_called_with(image_meta) mock_get_port_number.assert_called_with(flavor, image_meta) @@ -7915,24 +7925,27 @@ class LibvirtConnTestCase(test.NoDBTestCase): drvr._get_volume_config) self.assertEqual(target_xml, config) + @mock.patch.object(libvirt_driver.LibvirtDriver, + '_get_serial_ports_from_guest') @mock.patch.object(fakelibvirt.virDomain, "migrateToURI2") @mock.patch.object(fakelibvirt.virDomain, "XMLDesc") def test_live_migration_update_serial_console_xml(self, mock_xml, - mock_migrate): + mock_migrate, mock_get): self.compute = importutils.import_object(CONF.compute_manager) instance_ref = self.test_instance xml_tmpl = ("" "" "" - "" + "" + "" "" "" "") - initial_xml = xml_tmpl.format(addr='9.0.0.1') + initial_xml = xml_tmpl.format(addr='9.0.0.1', port='10100') - target_xml = xml_tmpl.format(addr='9.0.0.12') + target_xml = xml_tmpl.format(addr='9.0.0.12', port='10200') target_xml = etree.tostring(etree.fromstring(target_xml)) # Preparing mocks @@ -7947,7 +7960,8 @@ class LibvirtConnTestCase(test.NoDBTestCase): serial_listen_addr='9.0.0.12', target_connect_addr=None, bdms=[], - block_migration=False) + block_migration=False, + serial_listen_ports=[10200]) dom = fakelibvirt.virDomain guest = libvirt_guest.Guest(dom) drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 83b47a3c59c5..d6bb455b5896 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -4349,13 +4349,27 @@ class LibvirtDriver(driver.ComputeDriver): else: guest.os_boot_dev = blockinfo.get_boot_order(disk_info) - def _create_consoles(self, virt_type, guest, log_path, flavor, image_meta, + def _create_consoles(self, virt_type, guest, instance, flavor, image_meta, caps): + log_path = self._get_console_log_path(instance) if virt_type in ("qemu", "kvm"): # Create the serial console char devices guest_arch = libvirt_utils.get_arch(image_meta) if CONF.serial_console.enabled: + try: + # TODO(sahid): the guest param of this method should + # be renamed as guest_cfg then guest_obj to guest. + guest_obj = self._host.get_guest(instance) + if list(self._get_serial_ports_from_guest(guest_obj)): + # Serial port are already configured for instance that + # means we are in a context of migration. + return + except exception.InstanceNotFound: + LOG.debug( + "Instance does not exist yet on libvirt, we can " + "safely pass on looking for already defined serial " + "ports in its domain XML", instance=instance) num_ports = hardware.get_number_of_serial_ports( flavor, image_meta) @@ -4558,8 +4572,7 @@ class LibvirtDriver(driver.ComputeDriver): flavor, virt_type, self._host) guest.add_device(config) - log_path = self._get_console_log_path(instance) - self._create_consoles(virt_type, guest, log_path, flavor, + self._create_consoles(virt_type, guest, instance, flavor, image_meta, caps) pointer = self._get_guest_pointer_model(guest.os_type, image_meta) @@ -5950,12 +5963,25 @@ class LibvirtDriver(driver.ComputeDriver): libvirt.VIR_MIGRATE_TUNNELLED != 0): params.pop('migrate_disks') + # TODO(sahid): This should be in + # post_live_migration_at_source but no way to retrieve + # ports acquired on the host for the guest at this + # step. Since the domain is going to be removed from + # libvird on source host after migration, we backup the + # serial ports to release them if all went well. + serial_ports = [] + if CONF.serial_console.enabled: + serial_ports = list(self._get_serial_ports_from_guest(guest)) + guest.migrate(self._live_migration_uri(dest), migrate_uri=migrate_uri, flags=migration_flags, params=params, domain_xml=new_xml_str, bandwidth=CONF.libvirt.live_migration_bandwidth) + + for hostname, port in serial_ports: + serial_console.release_port(host=hostname, port=port) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE("Live Migration failure: %s"), e, @@ -6464,6 +6490,13 @@ class LibvirtDriver(driver.ComputeDriver): is_shared_instance_path = True if migrate_data: is_shared_instance_path = migrate_data.is_shared_instance_path + if (migrate_data.obj_attr_is_set("serial_listen_ports") + and migrate_data.serial_listen_ports): + # Releases serial ports reserved. + for port in migrate_data.serial_listen_ports: + serial_console.release_port( + host=migrate_data.serial_listen_addr, port=port) + if not is_shared_instance_path: instance_dir = libvirt_utils.get_instance_path_at_destination( instance, migrate_data) @@ -6591,6 +6624,15 @@ class LibvirtDriver(driver.ComputeDriver): CONF.libvirt.live_migration_inbound_addr migrate_data.supported_perf_events = self._supported_perf_events + migrate_data.serial_listen_ports = [] + if CONF.serial_console.enabled: + num_ports = hardware.get_number_of_serial_ports( + instance.flavor, instance.image_meta) + for port in six.moves.range(num_ports): + migrate_data.serial_listen_ports.append( + serial_console.acquire_port( + migrate_data.serial_listen_addr)) + for vol in block_device_mapping: connection_info = vol['connection_info'] if connection_info.get('serial'):