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:
parent
7b0db52b45
commit
898bb13304
|
@ -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)
|
||||
|
|
|
@ -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'):
|
||||
|
|
Loading…
Reference in New Issue