Skip to content

Commit 898bb13

Browse files
markuszoellersahid
andcommitted
libvirt: Acquire TCP ports for console during live migration
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
1 parent 7b0db52 commit 898bb13

File tree

2 files changed

+73
-17
lines changed

2 files changed

+73
-17
lines changed

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

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3615,15 +3615,21 @@ def test_get_guest_config_with_spice_and_agent(self):
36153615
self.assertEqual(cfg.devices[5].type, "spice")
36163616
self.assertEqual(cfg.devices[6].type, "qxl")
36173617

3618+
@mock.patch.object(host.Host, 'get_guest')
3619+
@mock.patch.object(libvirt_driver.LibvirtDriver,
3620+
'_get_serial_ports_from_guest')
36183621
@mock.patch('nova.console.serial.acquire_port')
36193622
@mock.patch('nova.virt.hardware.get_number_of_serial_ports',
36203623
return_value=1)
36213624
@mock.patch.object(libvirt_driver.libvirt_utils, 'get_arch',)
36223625
def test_create_serial_console_devices_based_on_arch(self, mock_get_arch,
3623-
mock_get_port_number,
3624-
mock_acquire_port):
3626+
mock_get_port_number,
3627+
mock_acquire_port,
3628+
mock_ports,
3629+
mock_guest):
36253630
self.flags(enabled=True, group='serial_console')
36263631
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
3632+
instance = objects.Instance(**self.test_instance)
36273633

36283634
expected = {arch.X86_64: vconfig.LibvirtConfigGuestSerial,
36293635
arch.S390: vconfig.LibvirtConfigGuestConsole,
@@ -3635,40 +3641,44 @@ def test_create_serial_console_devices_based_on_arch(self, mock_get_arch,
36353641
mock_get_arch.return_value = guest_arch
36363642
guest = vconfig.LibvirtConfigGuest()
36373643

3638-
drvr._create_consoles(virt_type="kvm", guest=guest, log_path="",
3639-
flavor={}, image_meta={}, caps=caps)
3644+
drvr._create_consoles(virt_type="kvm", guest=guest,
3645+
instance=instance, flavor={},
3646+
image_meta={}, caps=caps)
36403647
self.assertEqual(2, len(guest.devices))
36413648
console_device = guest.devices[0]
36423649
self.assertIsInstance(console_device, device_type)
36433650
self.assertEqual("tcp", console_device.type)
36443651

3652+
@mock.patch.object(host.Host, 'get_guest')
3653+
@mock.patch.object(libvirt_driver.LibvirtDriver,
3654+
'_get_serial_ports_from_guest')
36453655
@mock.patch('nova.virt.hardware.get_number_of_serial_ports',
36463656
return_value=4)
36473657
@mock.patch.object(libvirt_driver.libvirt_utils, 'get_arch',
36483658
side_effect=[arch.X86_64, arch.S390, arch.S390X])
36493659
def test_create_serial_console_devices_with_limit_exceeded_based_on_arch(
3650-
self, mock_get_arch, mock_get_port_number):
3660+
self, mock_get_arch, mock_get_port_number, mock_ports, mock_guest):
36513661
self.flags(enabled=True, group='serial_console')
36523662
self.flags(virt_type="qemu", group='libvirt')
36533663
flavor = 'fake_flavor'
36543664
image_meta = objects.ImageMeta()
36553665
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
36563666
caps = drvr._host.get_capabilities()
36573667
guest = vconfig.LibvirtConfigGuest()
3658-
log_path = ""
3668+
instance = objects.Instance(**self.test_instance)
36593669
self.assertRaises(exception.SerialPortNumberLimitExceeded,
36603670
drvr._create_consoles,
3661-
"kvm", guest, log_path, flavor, image_meta, caps)
3671+
"kvm", guest, instance, flavor, image_meta, caps)
36623672
mock_get_arch.assert_called_with(image_meta)
36633673
mock_get_port_number.assert_called_with(flavor,
36643674
image_meta)
36653675

3666-
drvr._create_consoles("kvm", guest, log_path, flavor, image_meta, caps)
3676+
drvr._create_consoles("kvm", guest, instance, flavor, image_meta, caps)
36673677
mock_get_arch.assert_called_with(image_meta)
36683678
mock_get_port_number.assert_called_with(flavor,
36693679
image_meta)
36703680

3671-
drvr._create_consoles("kvm", guest, log_path, flavor, image_meta, caps)
3681+
drvr._create_consoles("kvm", guest, instance, flavor, image_meta, caps)
36723682
mock_get_arch.assert_called_with(image_meta)
36733683
mock_get_port_number.assert_called_with(flavor,
36743684
image_meta)
@@ -7903,24 +7913,27 @@ def test_update_volume_xml_no_connection_info(self):
79037913
drvr._get_volume_config)
79047914
self.assertEqual(target_xml, config)
79057915

7916+
@mock.patch.object(libvirt_driver.LibvirtDriver,
7917+
'_get_serial_ports_from_guest')
79067918
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI2")
79077919
@mock.patch.object(fakelibvirt.virDomain, "XMLDesc")
79087920
def test_live_migration_update_serial_console_xml(self, mock_xml,
7909-
mock_migrate):
7921+
mock_migrate, mock_get):
79107922
self.compute = importutils.import_object(CONF.compute_manager)
79117923
instance_ref = self.test_instance
79127924

79137925
xml_tmpl = ("<domain type='kvm'>"
79147926
"<devices>"
79157927
"<console type='tcp'>"
7916-
"<source mode='bind' host='{addr}' service='10000'/>"
7928+
"<source mode='bind' host='{addr}' service='{port}'/>"
7929+
"<target type='serial' port='0'/>"
79177930
"</console>"
79187931
"</devices>"
79197932
"</domain>")
79207933

7921-
initial_xml = xml_tmpl.format(addr='9.0.0.1')
7934+
initial_xml = xml_tmpl.format(addr='9.0.0.1', port='10100')
79227935

7923-
target_xml = xml_tmpl.format(addr='9.0.0.12')
7936+
target_xml = xml_tmpl.format(addr='9.0.0.12', port='10200')
79247937
target_xml = etree.tostring(etree.fromstring(target_xml))
79257938

79267939
# Preparing mocks
@@ -7935,7 +7948,8 @@ def test_live_migration_update_serial_console_xml(self, mock_xml,
79357948
serial_listen_addr='9.0.0.12',
79367949
target_connect_addr=None,
79377950
bdms=[],
7938-
block_migration=False)
7951+
block_migration=False,
7952+
serial_listen_ports=[10200])
79397953
dom = fakelibvirt.virDomain
79407954
guest = libvirt_guest.Guest(dom)
79417955
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)

nova/virt/libvirt/driver.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4334,13 +4334,27 @@ def _conf_non_lxc_uml(self, virt_type, guest, root_device_name, rescue,
43344334
else:
43354335
guest.os_boot_dev = blockinfo.get_boot_order(disk_info)
43364336

4337-
def _create_consoles(self, virt_type, guest, log_path, flavor, image_meta,
4337+
def _create_consoles(self, virt_type, guest, instance, flavor, image_meta,
43384338
caps):
4339+
log_path = self._get_console_log_path(instance)
43394340
if virt_type in ("qemu", "kvm"):
43404341
# Create the serial console char devices
43414342
guest_arch = libvirt_utils.get_arch(image_meta)
43424343

43434344
if CONF.serial_console.enabled:
4345+
try:
4346+
# TODO(sahid): the guest param of this method should
4347+
# be renamed as guest_cfg then guest_obj to guest.
4348+
guest_obj = self._host.get_guest(instance)
4349+
if list(self._get_serial_ports_from_guest(guest_obj)):
4350+
# Serial port are already configured for instance that
4351+
# means we are in a context of migration.
4352+
return
4353+
except exception.InstanceNotFound:
4354+
LOG.debug(
4355+
"Instance does not exist yet on libvirt, we can "
4356+
"safely pass on looking for already defined serial "
4357+
"ports in its domain XML", instance=instance)
43444358
num_ports = hardware.get_number_of_serial_ports(
43454359
flavor, image_meta)
43464360

@@ -4540,8 +4554,7 @@ def _get_guest_config(self, instance, network_info, image_meta,
45404554
flavor, virt_type, self._host)
45414555
guest.add_device(config)
45424556

4543-
log_path = self._get_console_log_path(instance)
4544-
self._create_consoles(virt_type, guest, log_path, flavor,
4557+
self._create_consoles(virt_type, guest, instance, flavor,
45454558
image_meta, caps)
45464559

45474560
pointer = self._get_guest_pointer_model(guest.os_type, image_meta)
@@ -5927,12 +5940,25 @@ def _live_migration_operation(self, context, instance, dest,
59275940
libvirt.VIR_MIGRATE_TUNNELLED != 0):
59285941
params.pop('migrate_disks')
59295942

5943+
# TODO(sahid): This should be in
5944+
# post_live_migration_at_source but no way to retrieve
5945+
# ports acquired on the host for the guest at this
5946+
# step. Since the domain is going to be removed from
5947+
# libvird on source host after migration, we backup the
5948+
# serial ports to release them if all went well.
5949+
serial_ports = []
5950+
if CONF.serial_console.enabled:
5951+
serial_ports = list(self._get_serial_ports_from_guest(guest))
5952+
59305953
guest.migrate(self._live_migration_uri(dest),
59315954
migrate_uri=migrate_uri,
59325955
flags=migration_flags,
59335956
params=params,
59345957
domain_xml=new_xml_str,
59355958
bandwidth=CONF.libvirt.live_migration_bandwidth)
5959+
5960+
for hostname, port in serial_ports:
5961+
serial_console.release_port(host=hostname, port=port)
59365962
except Exception as e:
59375963
with excutils.save_and_reraise_exception():
59385964
LOG.error(_LE("Live Migration failure: %s"), e,
@@ -6441,6 +6467,13 @@ def rollback_live_migration_at_destination(self, context, instance,
64416467
is_shared_instance_path = True
64426468
if migrate_data:
64436469
is_shared_instance_path = migrate_data.is_shared_instance_path
6470+
if (migrate_data.obj_attr_is_set("serial_listen_ports")
6471+
and migrate_data.serial_listen_ports):
6472+
# Releases serial ports reserved.
6473+
for port in migrate_data.serial_listen_ports:
6474+
serial_console.release_port(
6475+
host=migrate_data.serial_listen_addr, port=port)
6476+
64446477
if not is_shared_instance_path:
64456478
instance_dir = libvirt_utils.get_instance_path_at_destination(
64466479
instance, migrate_data)
@@ -6568,6 +6601,15 @@ def pre_live_migration(self, context, instance, block_device_info,
65686601
CONF.libvirt.live_migration_inbound_addr
65696602
migrate_data.supported_perf_events = self._supported_perf_events
65706603

6604+
migrate_data.serial_listen_ports = []
6605+
if CONF.serial_console.enabled:
6606+
num_ports = hardware.get_number_of_serial_ports(
6607+
instance.flavor, instance.image_meta)
6608+
for port in six.moves.range(num_ports):
6609+
migrate_data.serial_listen_ports.append(
6610+
serial_console.acquire_port(
6611+
migrate_data.serial_listen_addr))
6612+
65716613
for vol in block_device_mapping:
65726614
connection_info = vol['connection_info']
65736615
if connection_info.get('serial'):

0 commit comments

Comments
 (0)