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.

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

Co-Authored-By: Sahid Ferdjaoui <sahid.ferdjaoui@redhat.com>
Change-Id: Ie2524191d22cca2287eb7dbaa22b74d43e43c896
Closes-Bug: #1455252
(cherry picked from commit 898bb13304)
This commit is contained in:
Markus Zoeller 2016-11-22 17:08:14 +01:00 committed by Markus Zoeller (markus_z)
parent d663437aca
commit 7cdd8b2bf3
2 changed files with 64 additions and 9 deletions

View File

@ -3572,15 +3572,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,
@ -3589,19 +3595,22 @@ class LibvirtConnTestCase(test.NoDBTestCase):
for guest_arch, device_type in expected.items():
mock_get_arch.return_value = guest_arch
guest = vconfig.LibvirtConfigGuest()
drvr._create_serial_console_devices(guest, instance=None,
drvr._create_serial_console_devices(guest, instance=instance,
flavor={}, image_meta={})
self.assertEqual(1, 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'
@ -7840,24 +7849,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
@ -7872,7 +7884,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)

View File

@ -4064,6 +4064,19 @@ class LibvirtDriver(driver.ComputeDriver):
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)
@ -5938,12 +5951,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,
@ -6436,6 +6462,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)
@ -6567,6 +6600,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'):