]> xenbits.xensource.com Git - osstest/openstack-nova.git/commitdiff
libvirt: Acquire TCP ports for console during live migration
authorMarkus Zoeller <mzoeller@de.ibm.com>
Tue, 22 Nov 2016 16:08:14 +0000 (17:08 +0100)
committerMarkus Zoeller <mzoeller@de.ibm.com>
Mon, 28 Nov 2016 16:42:20 +0000 (17:42 +0100)
During a live migration process we reserve serial ports on
destination host and update domain XML during migration to
use the new reserved ports from destination.

If the migration succeeds we release ports on the source host.
If the migration fails we release reserved ports on the
destination host.

Co-Authored-By: Sahid Ferdjaoui <sahid.ferdjaoui@redhat.com>
Change-Id: Ie2524191d22cca2287eb7dbaa22b74d43e43c896
Closes-Bug: #1455252

nova/tests/unit/virt/libvirt/test_driver.py
nova/virt/libvirt/driver.py

index a9c69d3ca34751cf7b9fece87cb1805bd41ae270..856340ae109e6255ffbc9f3fe4e164ab56f289f1 100644 (file)
@@ -3615,15 +3615,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 = {arch.X86_64: vconfig.LibvirtConfigGuestSerial,
                     arch.S390: vconfig.LibvirtConfigGuestConsole,
@@ -3635,19 +3641,23 @@ 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',
                        side_effect=[arch.X86_64, arch.S390, arch.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'
@@ -3655,20 +3665,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)
@@ -7903,24 +7913,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 = ("<domain type='kvm'>"
                     "<devices>"
                     "<console type='tcp'>"
-                    "<source mode='bind' host='{addr}' service='10000'/>"
+                    "<source mode='bind' host='{addr}' service='{port}'/>"
+                    "<target type='serial' port='0'/>"
                     "</console>"
                     "</devices>"
                     "</domain>")
 
-        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
@@ -7935,7 +7948,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)
index a51f23f9f5cec45c2c646e3b8868f83c17ebb1f4..d6b38030dd99456c5abe2555d49fc5815115d80a 100644 (file)
@@ -4334,13 +4334,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)
 
@@ -4540,8 +4554,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)
@@ -5927,12 +5940,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,
@@ -6441,6 +6467,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)
@@ -6568,6 +6601,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'):