VYPR
High severityNVD Advisory· Published Aug 26, 2020· Updated Aug 4, 2024

CVE-2020-17376

CVE-2020-17376

Description

An issue was discovered in Guest.migrate in virt/libvirt/guest.py in OpenStack Nova before 19.3.1, 20.x before 20.3.1, and 21.0.0. By performing a soft reboot of an instance that has previously undergone live migration, a user may gain access to destination host devices that share the same paths as host devices previously referenced by the virtual machine on the source host. This can include block devices that map to different Cinder volumes at the destination than at the source. Only deployments allowing host-based connections (for instance, root and ephemeral devices) are affected.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OpenStack Nova fails to supply VIR_MIGRATE_PARAM_PERSIST_XML during live migration, allowing a soft reboot on the destination to revert volume device mappings and potentially access wrong volumes.

Vulnerability

Overview

CVE-2020-17376 is a vulnerability in OpenStack Nova's live migration handling, affecting versions before 19.3.1, 20.x before 20.3.1, and 21.0.0. When an instance with attached volumes (e.g., iSCSI or Fibre Channel) is live-migrated, Nova ensures the correct host block devices are connected on the destination by updating the domain XML. However, Nova does not pass the VIR_MIGRATE_PARAM_PERSIST_XML parameter to libvirt during the migration. This means that after a successful live migration, if a user requests a soft reboot of the instance, the libvirt domain on the destination reverts to the persistent XML definition used on the source host [1][2].

Exploitation

Conditions and Impact

An authenticated user with the ability to perform a soft reboot of an instance that has undergone live migration can trigger the vulnerability. The revert to the source XML causes the instance to use the host device paths that were valid on the source, which may now point to different block devices on the destination host. This can include Cinder volumes that are mapped to different logical volumes than intended, leading to potential data exfiltration or corruption [1][3]. The issue only affects deployments that allow host-based connections, such as root or ephemeral devices [3].

Mitigation

Nova was patched by providing VIR_MIGRATE_PARAM_PERSIST_XML during live migration for libvirt versions >= 1.3.4 [2][4]. No workaround is available beyond applying the update. The fix was included in Nova releases 19.3.1, 20.3.1, and 21.0.0.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
novaPyPI
< 19.3.119.3.1
novaPyPI
>= 20.0.0, < 20.3.120.3.1

Affected products

208

Patches

5
2faf17995dd9

libvirt: Provide VIR_MIGRATE_PARAM_PERSIST_XML during live migration

https://github.com/openstack/novaLee YarwoodAug 5, 2020via ghsa
2 files changed · +93 6
  • nova/tests/unit/virt/libvirt/test_driver.py+82 6 modified
    @@ -8536,6 +8536,76 @@ def test_is_shared_block_storage_nfs(self):
                     'instance', data, block_device_info=bdi))
             self.assertEqual(0, mock_get_instance_disk_info.call_count)
     
    +    @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
    +    @mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
    +    def test_live_migration_persistent_xml(
    +        self, mock_get_version, mock_get_updated_xml, mock_migrateToURI3):
    +        """Assert that persistent_xml only provided when libvirt is >= v1.3.4
    +        """
    +        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
    +        instance = self.test_instance
    +        dest = '127.0.0.1'
    +        block_migration = False
    +        migrate_data = objects.LibvirtLiveMigrateData(
    +            graphics_listen_addr_vnc='10.0.0.1',
    +            graphics_listen_addr_spice='10.0.0.2',
    +            serial_listen_addr='127.0.0.1',
    +            target_connect_addr='127.0.0.1',
    +            bdms=[],
    +            block_migration=block_migration)
    +        guest = libvirt_guest.Guest(fakelibvirt.virDomain)
    +        device_names = ['vda']
    +
    +        mock_get_updated_xml.return_value = mock.sentinel.dest_xml
    +
    +        # persistent_xml was introduced in v1.3.4 so provide v1.3.3
    +        v1_3_3 = versionutils.convert_version_to_int((1, 3, 3))
    +        mock_get_version.return_value = v1_3_3
    +
    +        drvr._live_migration_operation(
    +            self.context, instance, dest, block_migration, migrate_data,
    +            guest, device_names)
    +
    +        expected_uri = drvr._live_migration_uri(dest)
    +        expected_flags = 0
    +        expected_params = {
    +            'bandwidth': 0,
    +            'destination_xml': mock.sentinel.dest_xml,
    +            'migrate_disks': device_names,
    +            'migrate_uri': 'tcp://127.0.0.1'
    +        }
    +
    +        # Assert that migrateToURI3 is called without the persistent_xml param
    +        mock_get_version.assert_called()
    +        mock_migrateToURI3.assert_called_once_with(
    +            expected_uri, params=expected_params, flags=expected_flags)
    +
    +        # reset mocks and try again with v1.3.4
    +        mock_get_version.reset_mock()
    +        mock_migrateToURI3.reset_mock()
    +
    +        # persistent_xml was introduced in v1.3.4 so provide it this time
    +        v1_3_4 = versionutils.convert_version_to_int((1, 3, 4))
    +        mock_get_version.return_value = v1_3_4
    +
    +        drvr._live_migration_operation(
    +            self.context, instance, dest,
    +            block_migration, migrate_data, guest, device_names)
    +
    +        expected_params = {
    +            'bandwidth': 0,
    +            'destination_xml': mock.sentinel.dest_xml,
    +            'persistent_xml': mock.sentinel.dest_xml,
    +            'migrate_disks': device_names,
    +            'migrate_uri': 'tcp://127.0.0.1'
    +        }
    +
    +        # Assert that migrateToURI3 is called with the persistent_xml param
    +        mock_get_version.assert_called()
    +        mock_migrateToURI3.assert_called_once_with(
    +                expected_uri, params=expected_params, flags=expected_flags)
    +
         def test_live_migration_update_graphics_xml(self):
             self.compute = manager.ComputeManager()
             instance_dict = dict(self.test_instance)
    @@ -9059,19 +9129,21 @@ def test_live_migration_fails_without_serial_console_address(self):
     
         @mock.patch.object(host.Host, 'has_min_version', return_value=True)
         @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    -    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
    -                return_value='')
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
         @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):
    +
    +        mock_new_xml.return_value = mock.sentinel.new_xml
             # Preparing mocks
             disk_paths = ['vda', 'vdb']
             params = {
                 'migrate_disks': ['vda', 'vdb'],
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    -            'destination_xml': '',
    +            'destination_xml': mock.sentinel.new_xml,
    +            'persistent_xml': mock.sentinel.new_xml,
             }
             mock_migrateToURI3.side_effect = fakelibvirt.libvirtError("ERR")
     
    @@ -9126,6 +9198,7 @@ def _test_live_migration_block_migration_flags(self,
                 'migrate_disks': device_names,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': b'<xml/>',
    +            'persistent_xml': b'<xml/>',
             }
             mock_migrateToURI3.assert_called_once_with(
                 drvr._live_migration_uri('dest'), params=params,
    @@ -9154,18 +9227,21 @@ def test_live_migration_block_migration_all_filtered(self):
     
         @mock.patch.object(host.Host, 'has_min_version', return_value=True)
         @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    -    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
    -                return_value='')
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
         @mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', return_value='')
         def test_block_live_migration_tunnelled_migrateToURI3(
                 self, mock_old_xml, mock_new_xml,
                 mock_migrateToURI3, mock_min_version):
             self.flags(live_migration_tunnelled=True, group='libvirt')
    +
    +        mock_new_xml.return_value = mock.sentinel.new_xml
    +
             # Preparing mocks
             disk_paths = []
             params = {
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    -            'destination_xml': '',
    +            'destination_xml': mock.sentinel.new_xml,
    +            'persistent_xml': mock.sentinel.new_xml,
             }
             # Start test
             migrate_data = objects.LibvirtLiveMigrateData(
    
  • nova/virt/libvirt/driver.py+11 0 modified
    @@ -302,6 +302,11 @@ def repr_method(self):
                                     'mbmt': 'mbm_total',
                                    }
     
    +# libvirt >= v1.3.4 introduced VIR_MIGRATE_PARAM_PERSIST_XML that needs to be
    +# provided when the VIR_MIGRATE_PERSIST_DEST flag is used to ensure the updated
    +# domain XML is persisted on the destination.
    +MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML = (1, 3, 4)
    +
     
     class LibvirtDriver(driver.ComputeDriver):
         capabilities = {
    @@ -6519,6 +6524,12 @@ def _live_migration_operation(self, context, instance, dest,
                         libvirt.VIR_MIGRATE_TUNNELLED != 0):
                         params.pop('migrate_disks')
     
    +                # NOTE(lyarwood): Only available from v1.3.4
    +                if self._host.has_min_version(
    +                    MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML
    +                ):
    +                    params['persistent_xml'] = new_xml_str
    +
                 # 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
    
a721ca5f510c

libvirt: Provide VIR_MIGRATE_PARAM_PERSIST_XML during live migration

https://github.com/openstack/novaLee YarwoodAug 5, 2020via ghsa
2 files changed · +93 6
  • nova/tests/unit/virt/libvirt/test_driver.py+82 6 modified
    @@ -9160,6 +9160,76 @@ def test_is_shared_block_storage_nfs(self):
                     'instance', data, block_device_info=bdi))
             self.assertEqual(0, mock_get_instance_disk_info.call_count)
     
    +    @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
    +    @mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
    +    def test_live_migration_persistent_xml(
    +        self, mock_get_version, mock_get_updated_xml, mock_migrateToURI3):
    +        """Assert that persistent_xml only provided when libvirt is >= v1.3.4
    +        """
    +        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
    +        instance = self.test_instance
    +        dest = '127.0.0.1'
    +        block_migration = False
    +        migrate_data = objects.LibvirtLiveMigrateData(
    +            graphics_listen_addr_vnc='10.0.0.1',
    +            graphics_listen_addr_spice='10.0.0.2',
    +            serial_listen_addr='127.0.0.1',
    +            target_connect_addr='127.0.0.1',
    +            bdms=[],
    +            block_migration=block_migration)
    +        guest = libvirt_guest.Guest(fakelibvirt.virDomain)
    +        device_names = ['vda']
    +
    +        mock_get_updated_xml.return_value = mock.sentinel.dest_xml
    +
    +        # persistent_xml was introduced in v1.3.4 so provide v1.3.3
    +        v1_3_3 = versionutils.convert_version_to_int((1, 3, 3))
    +        mock_get_version.return_value = v1_3_3
    +
    +        drvr._live_migration_operation(
    +            self.context, instance, dest, block_migration, migrate_data,
    +            guest, device_names)
    +
    +        expected_uri = drvr._live_migration_uri(dest)
    +        expected_flags = 0
    +        expected_params = {
    +            'bandwidth': 0,
    +            'destination_xml': mock.sentinel.dest_xml,
    +            'migrate_disks': device_names,
    +            'migrate_uri': 'tcp://127.0.0.1'
    +        }
    +
    +        # Assert that migrateToURI3 is called without the persistent_xml param
    +        mock_get_version.assert_called()
    +        mock_migrateToURI3.assert_called_once_with(
    +            expected_uri, params=expected_params, flags=expected_flags)
    +
    +        # reset mocks and try again with v1.3.4
    +        mock_get_version.reset_mock()
    +        mock_migrateToURI3.reset_mock()
    +
    +        # persistent_xml was introduced in v1.3.4 so provide it this time
    +        v1_3_4 = versionutils.convert_version_to_int((1, 3, 4))
    +        mock_get_version.return_value = v1_3_4
    +
    +        drvr._live_migration_operation(
    +            self.context, instance, dest,
    +            block_migration, migrate_data, guest, device_names)
    +
    +        expected_params = {
    +            'bandwidth': 0,
    +            'destination_xml': mock.sentinel.dest_xml,
    +            'persistent_xml': mock.sentinel.dest_xml,
    +            'migrate_disks': device_names,
    +            'migrate_uri': 'tcp://127.0.0.1'
    +        }
    +
    +        # Assert that migrateToURI3 is called with the persistent_xml param
    +        mock_get_version.assert_called()
    +        mock_migrateToURI3.assert_called_once_with(
    +                expected_uri, params=expected_params, flags=expected_flags)
    +
         def test_live_migration_update_graphics_xml(self):
             self.compute = manager.ComputeManager()
             instance_dict = dict(self.test_instance)
    @@ -9731,19 +9801,21 @@ def test_live_migration_fails_without_serial_console_address(self):
     
         @mock.patch.object(host.Host, 'has_min_version', return_value=True)
         @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    -    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
    -                return_value='')
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
         @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):
    +
    +        mock_new_xml.return_value = mock.sentinel.new_xml
             # Preparing mocks
             disk_paths = ['vda', 'vdb']
             params = {
                 'migrate_disks': ['vda', 'vdb'],
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    -            'destination_xml': '',
    +            'destination_xml': mock.sentinel.new_xml,
    +            'persistent_xml': mock.sentinel.new_xml,
             }
             mock_migrateToURI3.side_effect = fakelibvirt.libvirtError("ERR")
     
    @@ -9798,6 +9870,7 @@ def _test_live_migration_block_migration_flags(self,
                 'migrate_disks': device_names,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': '<xml/>',
    +            'persistent_xml': '<xml/>',
             }
             mock_migrateToURI3.assert_called_once_with(
                 drvr._live_migration_uri('dest'), params=params,
    @@ -9826,18 +9899,21 @@ def test_live_migration_block_migration_all_filtered(self):
     
         @mock.patch.object(host.Host, 'has_min_version', return_value=True)
         @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    -    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
    -                return_value='')
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
         @mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', return_value='')
         def test_block_live_migration_tunnelled_migrateToURI3(
                 self, mock_old_xml, mock_new_xml,
                 mock_migrateToURI3, mock_min_version):
             self.flags(live_migration_tunnelled=True, group='libvirt')
    +
    +        mock_new_xml.return_value = mock.sentinel.new_xml
    +
             # Preparing mocks
             disk_paths = []
             params = {
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    -            'destination_xml': '',
    +            'destination_xml': mock.sentinel.new_xml,
    +            'persistent_xml': mock.sentinel.new_xml,
             }
             # Start test
             migrate_data = objects.LibvirtLiveMigrateData(
    
  • nova/virt/libvirt/driver.py+11 0 modified
    @@ -328,6 +328,11 @@ def repr_method(self):
     
     VGPU_RESOURCE_SEMAPHORE = "vgpu_resources"
     
    +# libvirt >= v1.3.4 introduced VIR_MIGRATE_PARAM_PERSIST_XML that needs to be
    +# provided when the VIR_MIGRATE_PERSIST_DEST flag is used to ensure the updated
    +# domain XML is persisted on the destination.
    +MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML = (1, 3, 4)
    +
     
     class LibvirtDriver(driver.ComputeDriver):
         capabilities = {
    @@ -7138,6 +7143,12 @@ def _live_migration_operation(self, context, instance, dest,
                         libvirt.VIR_MIGRATE_TUNNELLED != 0):
                         params.pop('migrate_disks')
     
    +                # NOTE(lyarwood): Only available from v1.3.4
    +                if self._host.has_min_version(
    +                    MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML
    +                ):
    +                    params['persistent_xml'] = new_xml_str
    +
                 # 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
    
b9ea91d17703

libvirt: Provide VIR_MIGRATE_PARAM_PERSIST_XML during live migration

https://github.com/openstack/novaLee YarwoodAug 5, 2020via ghsa
3 files changed · +10 1
  • nova/tests/unit/virt/libvirt/test_driver.py+7 1 modified
    @@ -9902,6 +9902,7 @@ def test_live_migration_update_graphics_xml(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'bandwidth': _bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -10009,7 +10010,8 @@ def test_live_migration_update_volume_xml(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'migrate_uri': 'tcp://127.0.0.2',
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    -            'destination_xml': target_xml
    +            'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # Start test
    @@ -10109,6 +10111,7 @@ def test_live_migration_with_valid_target_connect_addr(self, mock_xml,
                 'migrate_uri': 'tcp://127.0.0.2',
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -10459,6 +10462,7 @@ def test_live_migration_update_serial_console_xml(self, mock_xml,
                 'migrate_disks': ['vda', 'vdb'],
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -10585,6 +10589,7 @@ def _test_live_migration_block_migration_flags(self,
                 'migrate_disks': device_names,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': '<xml/>',
    +            'persistent_xml': '<xml/>',
             }
             if not params['migrate_disks']:
                 del params['migrate_disks']
    @@ -10724,6 +10729,7 @@ def test_live_migration_raises_exception(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': '<xml/>',
    +            'persistent_xml': '<xml/>',
             }
     
             # Prepare mocks
    
  • nova/tests/unit/virt/libvirt/test_guest.py+2 0 modified
    @@ -699,6 +699,7 @@ def test_migrate_v3(self):
                     'an-uri', flags=1, params={'migrate_uri': 'dest-uri',
                                                'migrate_disks': 'disk1',
                                                'destination_xml': '</xml>',
    +                                           'persistent_xml': '</xml>',
                                                'bandwidth': 2})
     
         @testtools.skipIf(not six.PY2, 'libvirt python3 bindings accept unicode')
    @@ -716,6 +717,7 @@ def test_migrate_v3_unicode(self):
                                                'migrate_disks': ['disk1',
                                                                  'disk2'],
                                                'destination_xml': expect_dest_xml,
    +                                           'persistent_xml': expect_dest_xml,
                                                'bandwidth': 2})
     
         def test_abort_job(self):
    
  • nova/virt/libvirt/guest.py+1 0 modified
    @@ -654,6 +654,7 @@ def migrate(self, destination, migrate_uri=None, migrate_disks=None,
     
             if destination_xml:
                 params['destination_xml'] = destination_xml
    +            params['persistent_xml'] = destination_xml
             if migrate_disks:
                 params['migrate_disks'] = migrate_disks
             if migrate_uri:
    
1bb8ee95d4c3

libvirt: Provide VIR_MIGRATE_PARAM_PERSIST_XML during live migration

https://github.com/openstack/novaLee YarwoodAug 5, 2020via ghsa
3 files changed · +9 1
  • nova/tests/unit/virt/libvirt/test_driver.py+7 1 modified
    @@ -11600,6 +11600,7 @@ def test_live_migration_update_graphics_xml(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'bandwidth': _bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -11710,7 +11711,8 @@ def test_live_migration_update_volume_xml(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'migrate_uri': 'tcp://127.0.0.2',
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    -            'destination_xml': target_xml
    +            'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # Start test
    @@ -11869,6 +11871,7 @@ def test_live_migration_with_valid_target_connect_addr(self, mock_xml,
                 'migrate_uri': 'tcp://127.0.0.2',
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -12229,6 +12232,7 @@ def test_live_migration_update_serial_console_xml(self, mock_xml,
                 'migrate_disks': ['vda', 'vdb'],
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -12357,6 +12361,7 @@ def _test_live_migration_block_migration_flags(self,
                 'migrate_disks': device_names,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': '<xml/>',
    +            'persistent_xml': '<xml/>',
             }
             if not params['migrate_disks']:
                 del params['migrate_disks']
    @@ -12498,6 +12503,7 @@ def test_live_migration_raises_exception(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': '<xml/>',
    +            'persistent_xml': '<xml/>',
             }
     
             # Prepare mocks
    
  • nova/tests/unit/virt/libvirt/test_guest.py+1 0 modified
    @@ -702,6 +702,7 @@ def test_migrate_v3(self):
                     'an-uri', flags=1, params={'migrate_uri': 'dest-uri',
                                                'migrate_disks': 'disk1',
                                                'destination_xml': '</xml>',
    +                                           'persistent_xml': '</xml>',
                                                'bandwidth': 2})
     
         def test_abort_job(self):
    
  • nova/virt/libvirt/guest.py+1 0 modified
    @@ -666,6 +666,7 @@ def migrate(self, destination, migrate_uri=None, migrate_disks=None,
     
             if destination_xml:
                 params['destination_xml'] = destination_xml
    +            params['persistent_xml'] = destination_xml
             if migrate_disks:
                 params['migrate_disks'] = migrate_disks
             if migrate_uri:
    
c438fd9a0eb1

libvirt: Provide VIR_MIGRATE_PARAM_PERSIST_XML during live migration

https://github.com/openstack/novaLee YarwoodAug 5, 2020via ghsa
4 files changed · +105 9
  • nova/tests/unit/virt/libvirt/test_driver.py+85 6 modified
    @@ -9736,6 +9736,76 @@ def test_is_shared_block_storage_nfs(self):
                     'instance', data, block_device_info=bdi))
             self.assertEqual(0, mock_get_instance_disk_info.call_count)
     
    +    @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
    +    @mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
    +    def test_live_migration_persistent_xml(
    +        self, mock_get_version, mock_get_updated_xml, mock_migrateToURI3):
    +        """Assert that persistent_xml only provided when libvirt is >= v1.3.4
    +        """
    +        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
    +        instance = self.test_instance
    +        dest = '127.0.0.1'
    +        block_migration = False
    +        migrate_data = objects.LibvirtLiveMigrateData(
    +            graphics_listen_addr_vnc='10.0.0.1',
    +            graphics_listen_addr_spice='10.0.0.2',
    +            serial_listen_addr='127.0.0.1',
    +            target_connect_addr='127.0.0.1',
    +            bdms=[],
    +            block_migration=block_migration)
    +        guest = libvirt_guest.Guest(fakelibvirt.virDomain)
    +        device_names = ['vda']
    +
    +        mock_get_updated_xml.return_value = mock.sentinel.dest_xml
    +
    +        # persistent_xml was introduced in v1.3.4 so provide v1.3.3
    +        v1_3_3 = versionutils.convert_version_to_int((1, 3, 3))
    +        mock_get_version.return_value = v1_3_3
    +
    +        drvr._live_migration_operation(
    +            self.context, instance, dest, block_migration, migrate_data,
    +            guest, device_names)
    +
    +        expected_uri = drvr._live_migration_uri(dest)
    +        expected_flags = 0
    +        expected_params = {
    +            'bandwidth': 0,
    +            'destination_xml': mock.sentinel.dest_xml,
    +            'migrate_disks': device_names,
    +            'migrate_uri': 'tcp://127.0.0.1'
    +        }
    +
    +        # Assert that migrateToURI3 is called without the persistent_xml param
    +        mock_get_version.assert_called()
    +        mock_migrateToURI3.assert_called_once_with(
    +            expected_uri, params=expected_params, flags=expected_flags)
    +
    +        # reset mocks and try again with v1.3.4
    +        mock_get_version.reset_mock()
    +        mock_migrateToURI3.reset_mock()
    +
    +        # persistent_xml was introduced in v1.3.4 so provide it this time
    +        v1_3_4 = versionutils.convert_version_to_int((1, 3, 4))
    +        mock_get_version.return_value = v1_3_4
    +
    +        drvr._live_migration_operation(
    +            self.context, instance, dest,
    +            block_migration, migrate_data, guest, device_names)
    +
    +        expected_params = {
    +            'bandwidth': 0,
    +            'destination_xml': mock.sentinel.dest_xml,
    +            'persistent_xml': mock.sentinel.dest_xml,
    +            'migrate_disks': device_names,
    +            'migrate_uri': 'tcp://127.0.0.1'
    +        }
    +
    +        # Assert that migrateToURI3 is called with the persistent_xml param
    +        mock_get_version.assert_called()
    +        mock_migrateToURI3.assert_called_once_with(
    +                expected_uri, params=expected_params, flags=expected_flags)
    +
         @mock.patch.object(host.Host, 'has_min_version', return_value=True)
         @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
         @mock.patch.object(fakelibvirt.virDomain, "XMLDesc")
    @@ -9778,6 +9848,7 @@ def test_live_migration_update_graphics_xml(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'bandwidth': _bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -9885,7 +9956,7 @@ def test_live_migration_update_volume_xml(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'migrate_uri': 'tcp://127.0.0.2',
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    -            'destination_xml': target_xml
    +            'destination_xml': target_xml,
             }
     
             # Start test
    @@ -9985,6 +10056,7 @@ def test_live_migration_with_valid_target_connect_addr(self, mock_xml,
                 'migrate_uri': 'tcp://127.0.0.2',
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -10335,6 +10407,7 @@ def test_live_migration_update_serial_console_xml(self, mock_xml,
                 'migrate_disks': ['vda', 'vdb'],
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': target_xml,
    +            'persistent_xml': target_xml,
             }
     
             # start test
    @@ -10388,21 +10461,23 @@ def test_live_migration_fails_without_serial_console_address(self):
     
         @mock.patch.object(host.Host, 'has_min_version', return_value=True)
         @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    -    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
    -                return_value='')
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
         @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):
     
    +        mock_new_xml.return_value = mock.sentinel.new_xml
             target_connection = '127.0.0.2'
             # Preparing mocks
             disk_paths = ['vda', 'vdb']
             params = {
                 'migrate_uri': 'tcp://127.0.0.2',
                 'migrate_disks': ['vda', 'vdb'],
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
    +            'destination_xml': mock.sentinel.new_xml,
    +            'persistent_xml': mock.sentinel.new_xml,
             }
             mock_migrateToURI3.side_effect = fakelibvirt.libvirtError("ERR")
     
    @@ -10461,6 +10536,7 @@ def _test_live_migration_block_migration_flags(self,
                 'migrate_disks': device_names,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': '<xml/>',
    +            'persistent_xml': '<xml/>',
             }
             if not params['migrate_disks']:
                 del params['migrate_disks']
    @@ -10492,22 +10568,24 @@ def test_live_migration_block_migration_all_filtered(self):
     
         @mock.patch.object(host.Host, 'has_min_version', return_value=True)
         @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
    -    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
    -                return_value='')
    +    @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
         @mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', return_value='')
         def test_block_live_migration_tunnelled_migrateToURI3(
                 self, mock_old_xml, mock_new_xml,
                 mock_migrateToURI3, mock_min_version):
             self.flags(live_migration_tunnelled=True, group='libvirt')
     
    +        mock_new_xml.return_value = mock.sentinel.new_xml
             target_connection = None
             device_names = ['disk1', 'disk2']
     
             # Preparing mocks
             # Since we are passing the VIR_MIGRATE_TUNNELLED flag, the
             # 'parms' dict will not (as expected) contain 'migrate_disks'
             params = {
    -            'bandwidth': CONF.libvirt.live_migration_bandwidth
    +            'bandwidth': CONF.libvirt.live_migration_bandwidth,
    +            'destination_xml': mock.sentinel.new_xml,
    +            'persistent_xml': mock.sentinel.new_xml,
             }
             # Start test
             migrate_data = objects.LibvirtLiveMigrateData(
    @@ -10554,6 +10632,7 @@ def test_live_migration_raises_exception(self, mock_xml,
                 'migrate_disks': disk_paths,
                 'bandwidth': CONF.libvirt.live_migration_bandwidth,
                 'destination_xml': '<xml/>',
    +            'persistent_xml': '<xml/>',
             }
     
             # Prepare mocks
    
  • nova/tests/unit/virt/test_virt_drivers.py+1 1 modified
    @@ -135,7 +135,7 @@ def fake_detach_device_with_retry(_self, get_device_conf_func, device,
             self.stub_out('nova.virt.libvirt.guest.Guest.migrate',
                           lambda self, destination, migrate_uri=None,
                           migrate_disks=None, destination_xml=None, flags=0,
    -                      bandwidth=0: None)
    +                      bandwidth=0, persistent_xml_param=False: None)
             # We can't actually make a config drive v2 because ensure_tree has
             # been faked out
             self.stub_out('nova.virt.configdrive.ConfigDriveBuilder.make_drive',
    
  • nova/virt/libvirt/driver.py+11 1 modified
    @@ -301,6 +301,11 @@ def repr_method(self):
     
     VGPU_RESOURCE_SEMAPHORE = "vgpu_resources"
     
    +# libvirt >= v1.3.4 introduced VIR_MIGRATE_PARAM_PERSIST_XML that needs to be
    +# provided when the VIR_MIGRATE_PERSIST_DEST flag is used to ensure the updated
    +# domain XML is persisted on the destination.
    +MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML = (1, 3, 4)
    +
     
     class LibvirtDriver(driver.ComputeDriver):
         capabilities = {
    @@ -7279,13 +7284,18 @@ def _live_migration_operation(self, context, instance, dest,
                 if CONF.serial_console.enabled:
                     serial_ports = list(self._get_serial_ports_from_guest(guest))
     
    +            # NOTE(lyarwood): Only available from v1.3.4
    +            persistent_xml_param = self._host.has_min_version(
    +                MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML)
    +
                 LOG.debug("About to invoke the migrate API", instance=instance)
                 guest.migrate(self._live_migration_uri(dest),
                               migrate_uri=migrate_uri,
                               flags=migration_flags,
                               migrate_disks=device_names,
                               destination_xml=new_xml_str,
    -                          bandwidth=CONF.libvirt.live_migration_bandwidth)
    +                          bandwidth=CONF.libvirt.live_migration_bandwidth,
    +                          persistent_xml_param=persistent_xml_param)
                 LOG.debug("Migrate API has completed", instance=instance)
     
                 for hostname, port in serial_ports:
    
  • nova/virt/libvirt/guest.py+8 1 modified
    @@ -610,7 +610,8 @@ def pause(self):
             self._domain.suspend()
     
         def migrate(self, destination, migrate_uri=None, migrate_disks=None,
    -                destination_xml=None, flags=0, bandwidth=0):
    +                destination_xml=None, flags=0, bandwidth=0,
    +                persistent_xml_param=False):
             """Migrate guest object from its current host to the destination
     
             :param destination: URI of host destination where guest will be migrate
    @@ -645,6 +646,9 @@ def migrate(self, destination, migrate_uri=None, migrate_disks=None,
                                   unsafe.
                VIR_MIGRATE_OFFLINE Migrate offline
             :param bandwidth: The maximum bandwidth in MiB/s
    +        :param persistent_xml_param: Boolean indicating if the
    +                                     VIR_MIGRATE_PARAM_PERSIST_XML param should
    +                                     be provided to migrateToURI3.
             """
             params = {}
             # In migrateToURI3 these parameters are extracted from the
    @@ -653,6 +657,9 @@ def migrate(self, destination, migrate_uri=None, migrate_disks=None,
     
             if destination_xml:
                 params['destination_xml'] = destination_xml
    +            # NOTE(lyarwood): Only available from v1.3.4
    +            if persistent_xml_param:
    +                params['persistent_xml'] = destination_xml
             if migrate_disks:
                 params['migrate_disks'] = migrate_disks
             if migrate_uri:
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.