Add fill_metadata() to InstanceList

This adds a non-remotable method to InstanceList which will batch-
fill system_metadata for all the instances in the list that are
missing it in as efficient of a manner as possible. This does not
require an object bump because no remotable methods or fields are
changed.

Related to blueprint image-metadata-props-weigher

Change-Id: Icc47de2b677b3d212a7f6faa61a85ea9bff9f412
This commit is contained in:
Dan Smith
2025-02-19 07:45:20 -08:00
parent ae87118f98
commit 420050cf33
3 changed files with 75 additions and 0 deletions
+12
View File
@@ -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
+20
View File
@@ -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]
+43
View File
@@ -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):