diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 1b388713600b..899ab3f58e54 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -923,7 +923,9 @@ class HostCommands(object): class DbCommands(object): """Class for managing the main database.""" - online_migrations = () + online_migrations = ( + db.pcidevice_online_data_migration, + ) def __init__(self): pass diff --git a/nova/db/api.py b/nova/db/api.py index 1cc6486dedb4..594f2e48a88a 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1969,6 +1969,10 @@ def archive_deleted_rows(max_rows=None): return IMPL.archive_deleted_rows(max_rows=max_rows) +def pcidevice_online_data_migration(context, max_count): + return IMPL.pcidevice_online_data_migration(context, max_count) + + #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ed4b406a61d0..6a514509c907 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -6382,6 +6382,31 @@ def archive_deleted_rows(max_rows=None): return table_to_rows_archived +@main_context_manager.writer +def pcidevice_online_data_migration(context, max_count): + from nova.objects import pci_device as pci_dev_obj + + count_all = 0 + count_hit = 0 + + if not pci_dev_obj.PciDevice.should_migrate_data(): + LOG.error(_LE("Data migrations for PciDevice are not safe, likely " + "because not all services that access the DB directly " + "are updated to the latest version")) + else: + results = model_query(context, models.PciDevice).filter_by( + parent_addr=None).limit(max_count) + + for db_dict in results: + count_all += 1 + pci_dev = pci_dev_obj.PciDevice._from_db_object( + context, pci_dev_obj.PciDevice(), db_dict) + if pci_dev.obj_what_changed(): + pci_dev.save() + count_hit += 1 + return count_all, count_hit + + #################### diff --git a/nova/tests/unit/db/test_db_api.py b/nova/tests/unit/db/test_db_api.py index 08a609fb6236..0d6897fd3dd3 100644 --- a/nova/tests/unit/db/test_db_api.py +++ b/nova/tests/unit/db/test_db_api.py @@ -9122,7 +9122,7 @@ class PciDeviceDBApiTestCase(test.TestCase, ModelsObjectComparatorMixin): 'numa_node': 1, 'dev_type': fields.PciDeviceType.SRIOV_VF, 'dev_id': 'pci_0000:0f:08.7', - 'extra_info': None, + 'extra_info': '{}', 'label': 'label_8086_1520', 'status': fields.PciDeviceStatus.AVAILABLE, 'instance_uuid': '00000000-0000-0000-0000-000000000010', @@ -9137,7 +9137,7 @@ class PciDeviceDBApiTestCase(test.TestCase, ModelsObjectComparatorMixin): 'numa_node': 0, 'dev_type': fields.PciDeviceType.SRIOV_VF, 'dev_id': 'pci_0000:0f:08.7', - 'extra_info': None, + 'extra_info': '{}', 'label': 'label_8086_1520', 'status': fields.PciDeviceStatus.AVAILABLE, 'instance_uuid': '00000000-0000-0000-0000-000000000010', @@ -9278,6 +9278,56 @@ class PciDeviceDBApiTestCase(test.TestCase, ModelsObjectComparatorMixin): v1['compute_node_id'], v1['address']) + def _create_fake_pci_devs_old_format(self): + v1, v2 = self._get_fake_pci_devs() + + for v in (v1, v2): + v['parent_addr'] = None + v['extra_info'] = jsonutils.dumps( + {'phys_function': 'fake-phys-func'}) + + db.pci_device_update(self.admin_context, v['compute_node_id'], + v['address'], v) + + @mock.patch.object(objects.PciDevice, 'should_migrate_data', + return_value=False) + def test_pcidevice_online_mig_not_ready(self, mock_should_migrate): + self._create_fake_pci_devs_old_format() + + found, done = db.pcidevice_online_data_migration(self.admin_context, + None) + self.assertEqual(0, found) + self.assertEqual(0, done) + + @mock.patch.object(objects.PciDevice, 'should_migrate_data', + return_value=True) + def test_pcidevice_online_mig_data_migrated_limit(self, + mock_should_migrate): + self._create_fake_pci_devs_old_format() + + found, done = db.pcidevice_online_data_migration(self.admin_context, 1) + self.assertEqual(1, found) + self.assertEqual(1, done) + + @mock.patch.object(objects.PciDevice, 'should_migrate_data', + return_value=True) + def test_pcidevice_online_mig(self, mock_should_migrate): + self._create_fake_pci_devs_old_format() + + found, done = db.pcidevice_online_data_migration(self.admin_context, + 50) + self.assertEqual(2, found) + self.assertEqual(2, done) + results = db.pci_device_get_all_by_node(self.admin_context, + self.compute_node['id']) + for result in results: + self.assertEqual('fake-phys-func', result['parent_addr']) + + found, done = db.pcidevice_online_data_migration(self.admin_context, + 50) + self.assertEqual(0, found) + self.assertEqual(0, done) + class RetryOnDeadlockTestCase(test.TestCase): def test_without_deadlock(self):