diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 024559a70430..49c37ccb5060 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -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='')
def test_live_migration_uses_migrateToURI3(
self, mock_old_xml, mock_new_xml, mock_migrateToURI3,
mock_min_version):
diff --git a/nova/tests/unit/virt/libvirt/test_migration.py b/nova/tests/unit/virt/libvirt/test_migration.py
new file mode 100644
index 000000000000..15f8696efabc
--- /dev/null
+++ b/nova/tests/unit/virt/libvirt/test_migration.py
@@ -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 = ''
+
+ 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 = """
+
+
+
+
+
+"""
+ 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 = """
+
+
+
+
+
+"""
+ 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 = """
+
+
+
+
+
+
+
+
+"""
+ 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 = """
+
+
+
+
+
+ 58a84f6d-3f0c-4e19-a0af-eb657b790657
+
+
+
+"""
+ 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))
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 30c8be12b419..4c2a809aa5b1 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -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 = {
diff --git a/nova/virt/libvirt/migration.py b/nova/virt/libvirt/migration.py
new file mode 100644
index 000000000000..50f6cb3d0f1e
--- /dev/null
+++ b/nova/virt/libvirt/migration.py
@@ -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