Merge "libvirt: introduces module to handle domain xml migration"
This commit is contained in:
commit
e701653bbc
@ -96,6 +96,7 @@ from nova.virt.libvirt import firewall
|
||||
from nova.virt.libvirt import guest as libvirt_guest
|
||||
from nova.virt.libvirt import host
|
||||
from nova.virt.libvirt import imagebackend
|
||||
from nova.virt.libvirt import migration as libvirt_migrate
|
||||
from nova.virt.libvirt.storage import dmcrypt
|
||||
from nova.virt.libvirt.storage import lvm
|
||||
from nova.virt.libvirt.storage import rbd_utils
|
||||
@ -7042,7 +7043,8 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||
mget_info,\
|
||||
mock.patch.object(drvr._host, 'get_domain') as mget_domain,\
|
||||
mock.patch.object(fakelibvirt.virDomain, 'migrateToURI2'),\
|
||||
mock.patch.object(drvr, '_update_xml') as mupdate:
|
||||
mock.patch.object(
|
||||
libvirt_migrate, 'get_updated_guest_xml') as mupdate:
|
||||
|
||||
mget_info.side_effect = exception.InstanceNotFound(
|
||||
instance_id='foo')
|
||||
@ -7051,8 +7053,10 @@ 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, migrate_data.bdms,
|
||||
{}, '')
|
||||
# guest object is created under this method we do not have
|
||||
# other choice than using mock.ANY
|
||||
mupdate.assert_called_once_with(
|
||||
mock.ANY, migrate_data, mock.ANY)
|
||||
|
||||
def test_live_migration_with_valid_target_connect_addr(self):
|
||||
self.compute = importutils.import_object(CONF.compute_manager)
|
||||
@ -7090,7 +7094,8 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||
test_mock = mock.MagicMock()
|
||||
|
||||
with mock.patch.object(drvr, '_update_xml') as mupdate:
|
||||
with mock.patch.object(libvirt_migrate,
|
||||
'get_updated_guest_xml') as mupdate:
|
||||
|
||||
test_mock.XMLDesc.return_value = target_xml
|
||||
drvr._live_migration_operation(self.context, instance_ref,
|
||||
@ -7137,13 +7142,20 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||
conf.source_type = "block"
|
||||
conf.source_path = bdmi.connection_info['data'].get('device_path')
|
||||
|
||||
with mock.patch.object(drvr, '_get_volume_config',
|
||||
return_value=conf):
|
||||
guest = libvirt_guest.Guest(mock.MagicMock())
|
||||
with test.nested(
|
||||
mock.patch.object(drvr, '_get_volume_config',
|
||||
return_value=conf),
|
||||
mock.patch.object(guest, 'get_xml_desc',
|
||||
return_value=initial_xml)):
|
||||
config = libvirt_migrate.get_updated_guest_xml(guest,
|
||||
objects.LibvirtLiveMigrateData(bdms=[bdmi]),
|
||||
drvr._get_volume_config)
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
xml_doc = etree.fromstring(initial_xml, parser)
|
||||
config = drvr._update_volume_xml(xml_doc, [bdmi])
|
||||
xml_doc = etree.fromstring(target_xml, parser)
|
||||
self.assertEqual(etree.tostring(xml_doc), etree.tostring(config))
|
||||
config = etree.fromstring(config, parser)
|
||||
target_xml = etree.fromstring(target_xml, parser)
|
||||
self.assertEqual(etree.tostring(target_xml),
|
||||
etree.tostring(config))
|
||||
|
||||
def test_live_migration_uri(self):
|
||||
hypervisor_uri_map = (
|
||||
@ -7232,11 +7244,16 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||
conf.source_type = "block"
|
||||
conf.source_path = bdmi.connection_info['data'].get('device_path')
|
||||
|
||||
with mock.patch.object(drvr, '_get_volume_config',
|
||||
return_value=conf):
|
||||
xml_doc = etree.fromstring(initial_xml)
|
||||
config = drvr._update_volume_xml(xml_doc, [bdmi])
|
||||
self.assertEqual(target_xml, etree.tostring(config))
|
||||
guest = libvirt_guest.Guest(mock.MagicMock())
|
||||
with test.nested(
|
||||
mock.patch.object(drvr, '_get_volume_config',
|
||||
return_value=conf),
|
||||
mock.patch.object(guest, 'get_xml_desc',
|
||||
return_value=initial_xml)):
|
||||
config = libvirt_migrate.get_updated_guest_xml(guest,
|
||||
objects.LibvirtLiveMigrateData(bdms=[bdmi]),
|
||||
drvr._get_volume_config)
|
||||
self.assertEqual(target_xml, config)
|
||||
|
||||
def test_update_volume_xml_no_connection_info(self):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||
@ -7257,11 +7274,17 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||
format='qcow')
|
||||
bdmi.connection_info = {}
|
||||
conf = vconfig.LibvirtConfigGuestDisk()
|
||||
with mock.patch.object(drvr, '_get_volume_config',
|
||||
return_value=conf):
|
||||
xml_doc = etree.fromstring(initial_xml)
|
||||
config = drvr._update_volume_xml(xml_doc, [bdmi])
|
||||
self.assertEqual(target_xml, etree.tostring(config))
|
||||
guest = libvirt_guest.Guest(mock.MagicMock())
|
||||
with test.nested(
|
||||
mock.patch.object(drvr, '_get_volume_config',
|
||||
return_value=conf),
|
||||
mock.patch.object(guest, 'get_xml_desc',
|
||||
return_value=initial_xml)):
|
||||
config = libvirt_migrate.get_updated_guest_xml(
|
||||
guest,
|
||||
objects.LibvirtLiveMigrateData(bdms=[bdmi]),
|
||||
drvr._get_volume_config)
|
||||
self.assertEqual(target_xml, config)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI2")
|
||||
@mock.patch.object(fakelibvirt.virDomain, "XMLDesc")
|
||||
@ -7378,9 +7401,10 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||
|
||||
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
|
||||
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._update_xml',
|
||||
@mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
|
||||
return_value='')
|
||||
@mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', return_value='')
|
||||
@mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc',
|
||||
return_value='<xml></xml>')
|
||||
def test_live_migration_uses_migrateToURI3(
|
||||
self, mock_old_xml, mock_new_xml, mock_migrateToURI3,
|
||||
mock_min_version):
|
||||
|
181
nova/tests/unit/virt/libvirt/test_migration.py
Normal file
181
nova/tests/unit/virt/libvirt/test_migration.py
Normal file
@ -0,0 +1,181 @@
|
||||
# Copyright (c) 2016 Red Hat, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
|
||||
import six
|
||||
|
||||
from nova import objects
|
||||
from nova import test
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
from nova.virt.libvirt import guest as libvirt_guest
|
||||
from nova.virt.libvirt import migration
|
||||
|
||||
|
||||
class UtilityMigrationTestCase(test.NoDBTestCase):
|
||||
|
||||
def test_graphics_listen_addrs(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
graphics_listen_addr_vnc='127.0.0.1',
|
||||
graphics_listen_addr_spice='127.0.0.2')
|
||||
addrs = migration.graphics_listen_addrs(data)
|
||||
self.assertEqual({
|
||||
'vnc': '127.0.0.1',
|
||||
'spice': '127.0.0.2'}, addrs)
|
||||
|
||||
def test_graphics_listen_addrs_empty(self):
|
||||
data = objects.LibvirtLiveMigrateData()
|
||||
addrs = migration.graphics_listen_addrs(data)
|
||||
self.assertIsNone(None, addrs)
|
||||
|
||||
def test_graphics_listen_addrs_spice(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
graphics_listen_addr_spice='127.0.0.2')
|
||||
addrs = migration.graphics_listen_addrs(data)
|
||||
self.assertEqual({
|
||||
'vnc': None,
|
||||
'spice': '127.0.0.2'}, addrs)
|
||||
|
||||
def test_graphics_listen_addrs_vnc(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
graphics_listen_addr_vnc='127.0.0.1')
|
||||
addrs = migration.graphics_listen_addrs(data)
|
||||
self.assertEqual({
|
||||
'vnc': '127.0.0.1',
|
||||
'spice': None}, addrs)
|
||||
|
||||
def test_serial_listen_addr(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
serial_listen_addr='127.0.0.1')
|
||||
addr = migration.serial_listen_addr(data)
|
||||
self.assertEqual('127.0.0.1', addr)
|
||||
|
||||
def test_serial_listen_addr_emtpy(self):
|
||||
data = objects.LibvirtLiveMigrateData()
|
||||
addr = migration.serial_listen_addr(data)
|
||||
self.assertIsNone(addr)
|
||||
|
||||
@mock.patch('lxml.etree.tostring')
|
||||
@mock.patch.object(migration, '_update_graphics_xml')
|
||||
@mock.patch.object(migration, '_update_serial_xml')
|
||||
@mock.patch.object(migration, '_update_volume_xml')
|
||||
def test_get_updated_guest_xml(
|
||||
self, mock_volume, mock_serial, mock_graphics,
|
||||
mock_tostring):
|
||||
data = objects.LibvirtLiveMigrateData()
|
||||
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
|
||||
get_volume_config = mock.MagicMock()
|
||||
mock_guest.get_xml_desc.return_value = '<domain></domain>'
|
||||
|
||||
migration.get_updated_guest_xml(mock_guest, data, get_volume_config)
|
||||
mock_graphics.assert_called_once_with(mock.ANY, data)
|
||||
mock_serial.assert_called_once_with(mock.ANY, data)
|
||||
mock_volume.assert_called_once_with(mock.ANY, data, get_volume_config)
|
||||
self.assertEqual(1, mock_tostring.called)
|
||||
|
||||
def test_update_serial_xml_serial(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
serial_listen_addr='127.0.0.100')
|
||||
xml = """<domain>
|
||||
<devices>
|
||||
<serial type="tcp">
|
||||
<source host="127.0.0.1"/>
|
||||
</serial>
|
||||
</devices>
|
||||
</domain>"""
|
||||
doc = etree.fromstring(xml)
|
||||
res = etree.tostring(migration._update_serial_xml(doc, data))
|
||||
self.assertIn('127.0.0.100', six.text_type(res))
|
||||
|
||||
def test_update_serial_xml_console(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
serial_listen_addr='127.0.0.100')
|
||||
xml = """<domain>
|
||||
<devices>
|
||||
<console type="tcp">
|
||||
<source host="127.0.0.1"/>
|
||||
</console>
|
||||
</devices>
|
||||
</domain>"""
|
||||
doc = etree.fromstring(xml)
|
||||
res = etree.tostring(migration._update_serial_xml(doc, data))
|
||||
self.assertIn('127.0.0.100', six.text_type(res))
|
||||
|
||||
def test_update_graphics(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
graphics_listen_addr_vnc='127.0.0.100',
|
||||
graphics_listen_addr_spice='127.0.0.200')
|
||||
xml = """<domain>
|
||||
<devices>
|
||||
<graphics type="vnc">
|
||||
<listen type="address" address="127.0.0.1"/>
|
||||
</graphics>
|
||||
<graphics type="spice">
|
||||
<listen type="address" address="127.0.0.2"/>
|
||||
</graphics>
|
||||
</devices>
|
||||
</domain>"""
|
||||
doc = etree.fromstring(xml)
|
||||
res = etree.tostring(migration._update_graphics_xml(doc, data))
|
||||
self.assertIn('127.0.0.100', six.text_type(res))
|
||||
self.assertIn('127.0.0.200', six.text_type(res))
|
||||
|
||||
def test_update_volume_xml(self):
|
||||
connection_info = {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'serial': '58a84f6d-3f0c-4e19-a0af-eb657b790657',
|
||||
'data': {
|
||||
'access_mode': 'rw',
|
||||
'target_discovered': False,
|
||||
'target_iqn': 'ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z',
|
||||
'volume_id': '58a84f6d-3f0c-4e19-a0af-eb657b790657',
|
||||
'device_path':
|
||||
'/dev/disk/by-path/ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z'}}
|
||||
bdm = objects.LibvirtLiveMigrateBDMInfo(
|
||||
serial='58a84f6d-3f0c-4e19-a0af-eb657b790657',
|
||||
bus='virtio', type='disk', dev='vdb',
|
||||
connection_info=connection_info)
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
target_connect_addr='127.0.0.1',
|
||||
bdms=[bdm],
|
||||
block_migration=False)
|
||||
xml = """<domain>
|
||||
<devices>
|
||||
<disk type='block' device='disk'>
|
||||
<driver name='qemu' type='raw' cache='none'/>
|
||||
<source dev='/dev/disk/by-path/ip-1.2.3.4:3260-iqn.abc.12345.opst-lun-X'/>
|
||||
<target bus='virtio' dev='vdb'/>
|
||||
<serial>58a84f6d-3f0c-4e19-a0af-eb657b790657</serial>
|
||||
<address type='pci' domain='0x0' bus='0x0' slot='0x04' function='0x0'/>
|
||||
</disk>
|
||||
</devices>
|
||||
</domain>"""
|
||||
conf = vconfig.LibvirtConfigGuestDisk()
|
||||
conf.source_device = bdm.type
|
||||
conf.driver_name = "qemu"
|
||||
conf.driver_format = "raw"
|
||||
conf.driver_cache = "none"
|
||||
conf.target_dev = bdm.dev
|
||||
conf.target_bus = bdm.bus
|
||||
conf.serial = bdm.connection_info.get('serial')
|
||||
conf.source_type = "block"
|
||||
conf.source_path = bdm.connection_info['data'].get('device_path')
|
||||
|
||||
get_volume_config = mock.MagicMock(return_value=conf)
|
||||
doc = etree.fromstring(xml)
|
||||
res = etree.tostring(migration._update_volume_xml(
|
||||
doc, data, get_volume_config))
|
||||
self.assertIn('ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z',
|
||||
six.text_type(res))
|
@ -101,6 +101,7 @@ from nova.virt.libvirt import host
|
||||
from nova.virt.libvirt import imagebackend
|
||||
from nova.virt.libvirt import imagecache
|
||||
from nova.virt.libvirt import instancejobtracker
|
||||
from nova.virt.libvirt import migration as libvirt_migrate
|
||||
from nova.virt.libvirt.storage import dmcrypt
|
||||
from nova.virt.libvirt.storage import lvm
|
||||
from nova.virt.libvirt.storage import rbd_utils
|
||||
@ -5304,22 +5305,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
md_obj.from_legacy_dict(dest_check_data)
|
||||
dest_check_data = md_obj
|
||||
|
||||
listen_addrs = None
|
||||
# We are building listen_addrs of vnc/spice from
|
||||
# LibvirtLiveMigrateData; in some certains (e.g. an old-code
|
||||
# destination host) those fields may have not been set and we
|
||||
# want to avoid any unfortunates exceptions raised.
|
||||
# TODO(sahid): The method
|
||||
# _check_graphics_addresses_can_live_migrate_should to take an
|
||||
# object LibvirtLiveMigrate itself.
|
||||
if (dest_check_data.obj_attr_is_set('graphics_listen_addr_vnc')
|
||||
or dest_check_data.obj_attr_is_set('graphics_listen_addr_spice')):
|
||||
listen_addrs = {'vnc': None, 'spice': None}
|
||||
if dest_check_data.obj_attr_is_set('graphics_listen_addr_vnc'):
|
||||
listen_addrs['vnc'] = dest_check_data.graphics_listen_addr_vnc
|
||||
if dest_check_data.obj_attr_is_set('graphics_listen_addr_spice'):
|
||||
listen_addrs['spice'] = dest_check_data.graphics_listen_addr_spice
|
||||
|
||||
listen_addrs = libvirt_migrate.graphics_listen_addrs(
|
||||
dest_check_data)
|
||||
migratable_flag = self._host.is_migratable_xml_flag()
|
||||
if not migratable_flag or not listen_addrs:
|
||||
# In this context want to ensure we do not have to migrate
|
||||
@ -5644,90 +5631,6 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
e, instance=instance)
|
||||
raise
|
||||
|
||||
def _update_xml(self, xml_str, migrate_bdm_info, listen_addrs,
|
||||
serial_listen_addr):
|
||||
xml_doc = etree.fromstring(xml_str)
|
||||
|
||||
if migrate_bdm_info:
|
||||
xml_doc = self._update_volume_xml(xml_doc, migrate_bdm_info)
|
||||
if listen_addrs:
|
||||
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)
|
||||
|
||||
def _update_graphics_xml(self, xml_doc, listen_addrs):
|
||||
|
||||
# change over listen addresses
|
||||
for dev in xml_doc.findall('./devices/graphics'):
|
||||
gr_type = dev.get('type')
|
||||
listen_tag = dev.find('listen')
|
||||
if gr_type in ('vnc', 'spice'):
|
||||
if listen_tag is not None:
|
||||
listen_tag.set('address', listen_addrs[gr_type])
|
||||
if dev.get('listen') is not None:
|
||||
dev.set('listen', listen_addrs[gr_type])
|
||||
|
||||
return xml_doc
|
||||
|
||||
def _update_volume_xml(self, xml_doc, migrate_bdm_info):
|
||||
"""Update XML using device information of destination host."""
|
||||
|
||||
# Update volume xml
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
disk_nodes = xml_doc.findall('./devices/disk')
|
||||
|
||||
bdm_info_by_serial = {x.serial: x for x in migrate_bdm_info}
|
||||
for pos, disk_dev in enumerate(disk_nodes):
|
||||
serial_source = disk_dev.findtext('serial')
|
||||
bdm_info = bdm_info_by_serial.get(serial_source)
|
||||
if (serial_source is None or
|
||||
not bdm_info or not bdm_info.connection_info or
|
||||
serial_source not in bdm_info_by_serial):
|
||||
continue
|
||||
conf = self._get_volume_config(
|
||||
bdm_info.connection_info, bdm_info.as_disk_info())
|
||||
xml_doc2 = etree.XML(conf.to_xml(), parser)
|
||||
serial_dest = xml_doc2.findtext('serial')
|
||||
|
||||
# Compare source serial and destination serial number.
|
||||
# If these serial numbers match, continue the process.
|
||||
if (serial_dest and (serial_source == serial_dest)):
|
||||
LOG.debug("Find same serial number: pos=%(pos)s, "
|
||||
"serial=%(num)s",
|
||||
{'pos': pos, 'num': serial_source})
|
||||
for cnt, item_src in enumerate(disk_dev):
|
||||
# If source and destination have same item, update
|
||||
# the item using destination value.
|
||||
for item_dst in xml_doc2.findall(item_src.tag):
|
||||
disk_dev.remove(item_src)
|
||||
item_dst.tail = None
|
||||
disk_dev.insert(cnt, item_dst)
|
||||
|
||||
# If destination has additional items, thses items should be
|
||||
# added here.
|
||||
for item_dst in list(xml_doc2):
|
||||
item_dst.tail = None
|
||||
disk_dev.insert(cnt, item_dst)
|
||||
|
||||
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')
|
||||
|
||||
@ -5808,15 +5711,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
else:
|
||||
migration_flags = self._live_migration_flags
|
||||
|
||||
listen_addrs = {}
|
||||
if 'graphics_listen_addr_vnc' in migrate_data:
|
||||
listen_addrs['vnc'] = str(
|
||||
migrate_data.graphics_listen_addr_vnc)
|
||||
if 'graphics_listen_addr_spice' in migrate_data:
|
||||
listen_addrs['spice'] = str(
|
||||
migrate_data.graphics_listen_addr_spice)
|
||||
serial_listen_addr = (migrate_data.serial_listen_addr if
|
||||
'serial_listen_addr' in migrate_data else None)
|
||||
listen_addrs = libvirt_migrate.graphics_listen_addrs(
|
||||
migrate_data)
|
||||
if ('target_connect_addr' in migrate_data and
|
||||
migrate_data.target_connect_addr is not None):
|
||||
dest = migrate_data.target_connect_addr
|
||||
@ -5828,11 +5724,11 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
None,
|
||||
CONF.libvirt.live_migration_bandwidth)
|
||||
else:
|
||||
old_xml_str = guest.get_xml_desc(dump_migratable=True)
|
||||
new_xml_str = self._update_xml(old_xml_str,
|
||||
migrate_data.bdms,
|
||||
listen_addrs,
|
||||
serial_listen_addr)
|
||||
new_xml_str = libvirt_migrate.get_updated_guest_xml(
|
||||
# TODO(sahid): It's not a really well idea to pass
|
||||
# the method _get_volume_config and we should to find
|
||||
# a way to avoid this in future.
|
||||
guest, migrate_data, self._get_volume_config)
|
||||
if self._host.has_min_version(
|
||||
MIN_LIBVIRT_BLOCK_LM_WITH_VOLUMES_VERSION):
|
||||
params = {
|
||||
|
122
nova/virt/libvirt/migration.py
Normal file
122
nova/virt/libvirt/migration.py
Normal file
@ -0,0 +1,122 @@
|
||||
# Copyright (c) 2016 Red Hat, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
"""Utility methods to manage guests migration
|
||||
|
||||
"""
|
||||
|
||||
from lxml import etree
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def graphics_listen_addrs(migrate_data):
|
||||
"""Returns listen addresses of vnc/spice from a LibvirtLiveMigrateData"""
|
||||
listen_addrs = None
|
||||
if (migrate_data.obj_attr_is_set('graphics_listen_addr_vnc')
|
||||
or migrate_data.obj_attr_is_set('graphics_listen_addr_spice')):
|
||||
listen_addrs = {'vnc': None, 'spice': None}
|
||||
if migrate_data.obj_attr_is_set('graphics_listen_addr_vnc'):
|
||||
listen_addrs['vnc'] = str(migrate_data.graphics_listen_addr_vnc)
|
||||
if migrate_data.obj_attr_is_set('graphics_listen_addr_spice'):
|
||||
listen_addrs['spice'] = str(
|
||||
migrate_data.graphics_listen_addr_spice)
|
||||
return listen_addrs
|
||||
|
||||
|
||||
def serial_listen_addr(migrate_data):
|
||||
"""Returns listen address serial from a LibvirtLiveMigrateData"""
|
||||
listen_addr = None
|
||||
if migrate_data.obj_attr_is_set('serial_listen_addr'):
|
||||
listen_addr = str(migrate_data.serial_listen_addr)
|
||||
return listen_addr
|
||||
|
||||
|
||||
def get_updated_guest_xml(guest, migrate_data, get_volume_config):
|
||||
xml_doc = etree.fromstring(guest.get_xml_desc(dump_migratable=True))
|
||||
xml_doc = _update_graphics_xml(xml_doc, migrate_data)
|
||||
xml_doc = _update_serial_xml(xml_doc, migrate_data)
|
||||
xml_doc = _update_volume_xml(xml_doc, migrate_data, get_volume_config)
|
||||
return etree.tostring(xml_doc)
|
||||
|
||||
|
||||
def _update_graphics_xml(xml_doc, migrate_data):
|
||||
listen_addrs = graphics_listen_addrs(migrate_data)
|
||||
|
||||
# change over listen addresses
|
||||
for dev in xml_doc.findall('./devices/graphics'):
|
||||
gr_type = dev.get('type')
|
||||
listen_tag = dev.find('listen')
|
||||
if gr_type in ('vnc', 'spice'):
|
||||
if listen_tag is not None:
|
||||
listen_tag.set('address', listen_addrs[gr_type])
|
||||
if dev.get('listen') is not None:
|
||||
dev.set('listen', listen_addrs[gr_type])
|
||||
return xml_doc
|
||||
|
||||
|
||||
def _update_serial_xml(xml_doc, migrate_data):
|
||||
listen_addr = serial_listen_addr(migrate_data)
|
||||
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 _update_volume_xml(xml_doc, migrate_data, get_volume_config):
|
||||
"""Update XML using device information of destination host."""
|
||||
migrate_bdm_info = migrate_data.bdms
|
||||
|
||||
# Update volume xml
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
disk_nodes = xml_doc.findall('./devices/disk')
|
||||
|
||||
bdm_info_by_serial = {x.serial: x for x in migrate_bdm_info}
|
||||
for pos, disk_dev in enumerate(disk_nodes):
|
||||
serial_source = disk_dev.findtext('serial')
|
||||
bdm_info = bdm_info_by_serial.get(serial_source)
|
||||
if (serial_source is None or
|
||||
not bdm_info or not bdm_info.connection_info or
|
||||
serial_source not in bdm_info_by_serial):
|
||||
continue
|
||||
conf = get_volume_config(
|
||||
bdm_info.connection_info, bdm_info.as_disk_info())
|
||||
xml_doc2 = etree.XML(conf.to_xml(), parser)
|
||||
serial_dest = xml_doc2.findtext('serial')
|
||||
|
||||
# Compare source serial and destination serial number.
|
||||
# If these serial numbers match, continue the process.
|
||||
if (serial_dest and (serial_source == serial_dest)):
|
||||
LOG.debug("Find same serial number: pos=%(pos)s, "
|
||||
"serial=%(num)s",
|
||||
{'pos': pos, 'num': serial_source})
|
||||
for cnt, item_src in enumerate(disk_dev):
|
||||
# If source and destination have same item, update
|
||||
# the item using destination value.
|
||||
for item_dst in xml_doc2.findall(item_src.tag):
|
||||
disk_dev.remove(item_src)
|
||||
item_dst.tail = None
|
||||
disk_dev.insert(cnt, item_dst)
|
||||
|
||||
# If destination has additional items, thses items should be
|
||||
# added here.
|
||||
for item_dst in list(xml_doc2):
|
||||
item_dst.tail = None
|
||||
disk_dev.insert(cnt, item_dst)
|
||||
return xml_doc
|
Loading…
x
Reference in New Issue
Block a user