libvirt: enable live migration with serial console

When migrating an instance that uses serial console, the domain XML
must be changed to reflect the serial console configuration of the
target host.

Also prevent live migration when serial console is enabled and the
libvirt version is not able to process modified domain XML.

Change-Id: I654e444584124334e34d4673db6464cc2f2b822e
Partial-Bug: #1455252
This commit is contained in:
Alexander Schmidt 2015-06-12 14:21:07 +02:00
parent a2d5492e8a
commit 984cc474ef
2 changed files with 101 additions and 4 deletions

View File

@ -6102,7 +6102,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
self.assertFalse(drvr._live_migration_operation(
self.context, instance_ref, 'dest', False,
migrate_data, test_mock))
mupdate.assert_called_once_with(target_xml, volume, None)
mupdate.assert_called_once_with(target_xml, volume, None, None)
def test_update_volume_xml(self):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
@ -6237,6 +6237,63 @@ class LibvirtConnTestCase(test.NoDBTestCase):
volume_xml['volume'])
self.assertEqual(target_xml, etree.tostring(config))
@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):
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'/>"
"</console>"
"</devices>"
"</domain>")
initial_xml = xml_tmpl.format(addr='9.0.0.1')
target_xml = xml_tmpl.format(addr='9.0.0.12')
target_xml = etree.tostring(etree.fromstring(target_xml))
# Preparing mocks
mock_xml.return_value = initial_xml
mock_migrate.side_effect = fakelibvirt.libvirtError("ERR")
# start test
bandwidth = CONF.libvirt.live_migration_bandwidth
migrate_data = {'pre_live_migration_result':
{'graphics_listen_addrs':
{'vnc': '10.0.0.1', 'spice': '10.0.0.2'},
'serial_listen_addr': '9.0.0.12'}}
dom = fakelibvirt.virDomain
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
self.assertRaises(fakelibvirt.libvirtError,
drvr._live_migration_operation,
self.context, instance_ref, 'dest',
False, migrate_data, dom)
mock_xml.assert_called_once_with(
flags=fakelibvirt.VIR_DOMAIN_XML_MIGRATABLE)
mock_migrate.assert_called_once_with(
CONF.libvirt.live_migration_uri % 'dest',
None, target_xml, mock.ANY, None, bandwidth)
@mock.patch.object(fakelibvirt, 'VIR_DOMAIN_XML_MIGRATABLE', None,
create=True)
def test_live_migration_fails_with_serial_console_without_migratable(self):
self.compute = importutils.import_object(CONF.compute_manager)
instance_ref = self.test_instance
CONF.set_override("enabled", True, "serial_console")
dom = fakelibvirt.virDomain
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
self.assertRaises(exception.MigrationError,
drvr._live_migration_operation,
self.context, instance_ref, 'dest',
False, None, dom)
@mock.patch.object(fakelibvirt, 'VIR_DOMAIN_XML_MIGRATABLE', None,
create=True)
def test_live_migration_uses_migrateToURI_without_migratable_flag(self):
@ -7048,6 +7105,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
target_ret = {
'graphics_listen_addrs': {'spice': '127.0.0.1', 'vnc': '127.0.0.1'},
'serial_listen_addr': '127.0.0.1',
'volume': {
'12345': {'connection_info': {u'data': {'device_path':
u'/dev/disk/by-path/ip-1.2.3.4:3260-iqn.abc.12345.opst-lun-X'},
@ -7110,6 +7168,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
)
self.assertEqual({'graphics_listen_addrs': {'spice': '127.0.0.1',
'vnc': '127.0.0.1'},
'serial_listen_addr': '127.0.0.1',
'volume': {}}, res_data)
def test_pre_live_migration_vol_backed_works_correctly_mocked(self):
@ -7162,6 +7221,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
target_ret = {
'graphics_listen_addrs': {'spice': '127.0.0.1',
'vnc': '127.0.0.1'},
'serial_listen_addr': '127.0.0.1',
'volume': {
'12345': {'connection_info': {u'data': {'device_path':
u'/dev/disk/by-path/ip-1.2.3.4:3260-iqn.abc.12345.opst-lun-X'},

View File

@ -5496,7 +5496,7 @@ class LibvirtDriver(driver.ComputeDriver):
post_method, recover_method, block_migration,
migrate_data)
def _update_xml(self, xml_str, volume, listen_addrs):
def _update_xml(self, xml_str, volume, listen_addrs, serial_listen_addr):
xml_doc = etree.fromstring(xml_str)
if volume:
@ -5505,6 +5505,10 @@ class LibvirtDriver(driver.ComputeDriver):
xml_doc = self._update_graphics_xml(xml_doc, listen_addrs)
else:
self._check_graphics_addresses_can_live_migrate(listen_addrs)
if serial_listen_addr:
xml_doc = self._update_serial_xml(xml_doc, serial_listen_addr)
else:
self._verify_serial_console_is_disabled()
return etree.tostring(xml_doc)
@ -5565,6 +5569,17 @@ class LibvirtDriver(driver.ComputeDriver):
return xml_doc
def _update_serial_xml(self, xml_doc, listen_addr):
for dev in xml_doc.findall("./devices/serial[@type='tcp']/source"):
if dev.get('host') is not None:
dev.set('host', listen_addr)
for dev in xml_doc.findall("./devices/console[@type='tcp']/source"):
if dev.get('host') is not None:
dev.set('host', listen_addr)
return xml_doc
def _check_graphics_addresses_can_live_migrate(self, listen_addrs):
LOCAL_ADDRS = ('0.0.0.0', '127.0.0.1', '::', '::1')
@ -5605,6 +5620,18 @@ class LibvirtDriver(driver.ComputeDriver):
' continue to listen on the current'
' addresses.'))
def _verify_serial_console_is_disabled(self):
if CONF.serial_console.enabled:
msg = _('Your libvirt version does not support the'
' VIR_DOMAIN_XML_MIGRATABLE flag or your'
' destination node does not support'
' retrieving listen addresses. In order'
' for live migration to work properly you'
' must either disable serial console or'
' upgrade your libvirt version.')
raise exception.MigrationError(reason=msg)
def _live_migration_operation(self, context, instance, dest,
block_migration, migrate_data, dom):
"""Invoke the live migration operation
@ -5636,13 +5663,18 @@ class LibvirtDriver(driver.ComputeDriver):
'pre_live_migration_result', {})
listen_addrs = pre_live_migrate_data.get('graphics_listen_addrs')
volume = pre_live_migrate_data.get('volume')
serial_listen_addr = pre_live_migrate_data.get(
'serial_listen_addr')
migratable_flag = getattr(libvirt, 'VIR_DOMAIN_XML_MIGRATABLE',
None)
if (migratable_flag is None or
(listen_addrs is None and not volume)):
# TODO(alexs-h): These checks could be moved to the
# check_can_live_migrate_destination/source phase
self._check_graphics_addresses_can_live_migrate(listen_addrs)
self._verify_serial_console_is_disabled()
dom.migrateToURI(CONF.libvirt.live_migration_uri % dest,
logical_sum,
None,
@ -5651,7 +5683,8 @@ class LibvirtDriver(driver.ComputeDriver):
old_xml_str = guest.get_xml_desc(dump_migratable=True)
new_xml_str = self._update_xml(old_xml_str,
volume,
listen_addrs)
listen_addrs,
serial_listen_addr)
try:
dom.migrateToURI2(CONF.libvirt.live_migration_uri % dest,
None,
@ -5679,6 +5712,7 @@ class LibvirtDriver(driver.ComputeDriver):
instance=instance)
self._check_graphics_addresses_can_live_migrate(
listen_addrs)
self._verify_serial_console_is_disabled()
dom.migrateToURI(
CONF.libvirt.live_migration_uri % dest,
logical_sum,
@ -6242,9 +6276,12 @@ class LibvirtDriver(driver.ComputeDriver):
greenthread.sleep(1)
# Store vncserver_listen and latest disk device info
res_data = {'graphics_listen_addrs': {}, 'volume': {}}
res_data = {'graphics_listen_addrs': {}, 'volume': {},
'serial_listen_addr': {}}
res_data['graphics_listen_addrs']['vnc'] = CONF.vnc.vncserver_listen
res_data['graphics_listen_addrs']['spice'] = CONF.spice.server_listen
res_data['serial_listen_addr'] = \
CONF.serial_console.proxyclient_address
for vol in block_device_mapping:
connection_info = vol['connection_info']
if connection_info.get('serial'):