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
This commit is contained in:
Markus Zoeller 2016-11-22 17:08:14 +01:00
parent 7b0db52b45
commit 898bb13304
2 changed files with 73 additions and 17 deletions

View 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)

View 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'):