diff --git a/nova/db/main/api.py b/nova/db/main/api.py index 90452631d9..e361ef2da5 100644 --- a/nova/db/main/api.py +++ b/nova/db/main/api.py @@ -1521,6 +1521,18 @@ def _instances_fill_metadata(context, instances, manual_joins=None): return filled_instances +@require_context +@pick_context_manager_reader +def instances_fill_metadata(context, instances, manual_joins=None): + """Selectively fill instances with manually-joined metadata. + + See _instances_fill_metadata(). This is only for use as a standalone + operation in its own transaction. + """ + return _instances_fill_metadata(context, instances, + manual_joins=manual_joins) + + def _manual_join_columns(columns_to_join): """Separate manually joined columns from columns_to_join diff --git a/nova/objects/instance.py b/nova/objects/instance.py index 8f76fd4f5a..cf72d76d68 100644 --- a/nova/objects/instance.py +++ b/nova/objects/instance.py @@ -1588,6 +1588,26 @@ class InstanceList(base.ObjectListBase, base.NovaObject): return faults_by_uuid.keys() + def fill_metadata(self): + # NOTE(danms): This only fills system_metadata currently, but could + # be extended to support user metadata if needed in the future. + # Make a uuid-indexed dict of non-object instance dicts that the DB + # layer can use. They need only contain the uuid of the instances + # we are looking up. Any of them that already have system_metadata + # need not be included. + db_inst_shells = {inst.uuid: {'uuid': inst.uuid} for inst in self + if 'system_metadata' not in inst} + if db_inst_shells: + updates = db.instances_fill_metadata( + self._context, + db_inst_shells.values(), + manual_joins=['system_metadata']) + updated = {i['uuid']: i for i in updates} + for inst in [i for i in self if i.uuid in updated]: + # Patch up our instances with system_metadata from the fill + # operation + inst.system_metadata = utils.instance_sys_meta(updated) + @base.remotable_classmethod def get_uuids_by_host(cls, context, host): return db.instance_get_all_uuids_by_hosts(context, [host])[host] diff --git a/nova/tests/unit/objects/test_instance.py b/nova/tests/unit/objects/test_instance.py index 998f6d224d..0e041ecd37 100644 --- a/nova/tests/unit/objects/test_instance.py +++ b/nova/tests/unit/objects/test_instance.py @@ -2060,6 +2060,49 @@ class TestInstanceListObject(test_objects._LocalTest, {}) self.assertEqual(2, len(insts)) + def test_fill_metadata(self): + values = {'user_id': self.context.user_id, + 'project_id': self.context.project_id, + 'host': 'foo'} + + for i in range(5): + db.instance_create(self.context, + dict(values)) + + insts = objects.InstanceList.get_all(self.context) + self.assertEqual(5, len(insts)) + + # One instance has system_metadata populated *and* saved in the DB + insts[1].system_metadata = {'BTTF1': '1955'} + insts[1].save() + + # One instance has system_metadata populated but dirty + insts[2].system_metadata = {'bttf3': '1885'} + + # Three do not have system_metadata populated + self.assertEqual(3, len([i for i in insts + if 'system_metadata' not in i])) + + insts.fill_metadata() + + # Now all five have it populated + self.assertEqual(0, len([i for i in insts + if 'system_metadata' not in i])) + + # Inst 2 should have not had its in-memory copy clobbered + self.assertEqual({'bttf3': '1885'}, insts[2].system_metadata) + + # Inst 1 should have system_metadata loaded, but empty + self.assertIn('system_metadata', insts[0]) + self.assertEqual({}, insts[0].system_metadata) + + def test_fill_metadata_nop(self): + insts = objects.InstanceList([objects.Instance(uuid=uuids.inst, + system_metadata={})]) + # This would fail if it tries to actually load anything + # because context is None + insts.fill_metadata() + class TestRemoteInstanceListObject(test_objects._RemoteTest, _TestInstanceListObject):