Support instance_extra fields in expected_attrs on Instance object

This makes the fields that are stored in instance_extra specifyable
in expected_attrs for efficient queries.

Related to blueprint flavor-from-sysmeta-to-blob

Change-Id: I73567280dc1d35a3eb3badfafbe7271943e8ed57
This commit is contained in:
Dan Smith 2014-10-01 13:39:27 -07:00
parent d1d806e57f
commit 9fd9644fd2
10 changed files with 134 additions and 73 deletions

View File

@ -1768,7 +1768,10 @@ def _build_instance_get(context, session=None,
if column in ['info_cache', 'security_groups']:
# Already always joined above
continue
query = query.options(joinedload(column))
if 'extra.' in column:
query = query.options(undefer(column))
else:
query = query.options(joinedload(column))
# NOTE(alaski) Stop lazy loading of columns not needed.
for col in ['metadata', 'system_metadata']:
if col not in columns_to_join:
@ -2205,8 +2208,11 @@ def _instance_get_all_query(context, project_only=False,
models.Instance,
project_only=project_only,
use_slave=use_slave)
for join in joins:
query = query.options(joinedload(join))
for column in joins:
if 'extra.' in column:
query = query.options(undefer(column))
else:
query = query.options(joinedload(column))
return query

View File

@ -39,12 +39,14 @@ _INSTANCE_OPTIONAL_JOINED_FIELDS = ['metadata', 'system_metadata',
'info_cache', 'security_groups',
'pci_devices']
# These are fields that are optional but don't translate to db columns
_INSTANCE_OPTIONAL_NON_COLUMN_FIELDS = ['fault', 'numa_topology',
'pci_requests']
_INSTANCE_OPTIONAL_NON_COLUMN_FIELDS = ['fault']
# These are fields that are optional and in instance_extra
_INSTANCE_EXTRA_FIELDS = ['numa_topology', 'pci_requests']
# These are fields that can be specified as expected_attrs
INSTANCE_OPTIONAL_ATTRS = (_INSTANCE_OPTIONAL_JOINED_FIELDS +
_INSTANCE_OPTIONAL_NON_COLUMN_FIELDS)
_INSTANCE_OPTIONAL_NON_COLUMN_FIELDS +
_INSTANCE_EXTRA_FIELDS)
# These are fields that most query calls load by default
INSTANCE_DEFAULT_FIELDS = ['metadata', 'system_metadata',
'info_cache', 'security_groups']
@ -54,8 +56,15 @@ def _expected_cols(expected_attrs):
"""Return expected_attrs that are columns needing joining."""
if not expected_attrs:
return expected_attrs
return [attr for attr in expected_attrs
if attr in _INSTANCE_OPTIONAL_JOINED_FIELDS]
simple_cols = [attr for attr in expected_attrs
if attr in _INSTANCE_OPTIONAL_JOINED_FIELDS]
complex_cols = ['extra.%s' % field
for field in _INSTANCE_EXTRA_FIELDS
if field in expected_attrs]
if complex_cols:
simple_cols.append('extra')
return simple_cols + complex_cols
class Instance(base.NovaPersistentObject, base.NovaObject):
@ -292,9 +301,11 @@ class Instance(base.NovaPersistentObject, base.NovaObject):
objects.InstanceFault.get_latest_for_instance(
context, instance.uuid))
if 'numa_topology' in expected_attrs:
instance._load_numa_topology()
instance._load_numa_topology(
db_inst.get('extra').get('numa_topology'))
if 'pci_requests' in expected_attrs:
instance._load_pci_requests()
instance._load_pci_requests(
db_inst.get('extra').get('pci_requests'))
if 'info_cache' in expected_attrs:
if db_inst['info_cache'] is None:
@ -568,18 +579,28 @@ class Instance(base.NovaPersistentObject, base.NovaObject):
self.fault = objects.InstanceFault.get_latest_for_instance(
self._context, self.uuid)
def _load_numa_topology(self):
try:
def _load_numa_topology(self, db_topology=None):
if db_topology is not None:
self.numa_topology = \
objects.InstanceNUMATopology.get_by_instance_uuid(
self._context, self.uuid)
except exception.NumaTopologyNotFound:
self.numa_topology = None
objects.InstanceNUMATopology.obj_from_db_obj(self.uuid,
db_topology)
else:
try:
self.numa_topology = \
objects.InstanceNUMATopology.get_by_instance_uuid(
self._context, self.uuid)
except exception.NumaTopologyNotFound:
self.numa_topology = None
def _load_pci_requests(self):
self.pci_requests = \
objects.InstancePCIRequests.get_by_instance_uuid(
self._context, self.uuid)
def _load_pci_requests(self, db_requests=None):
# FIXME: also do this if none!
if db_requests is not None:
self.pci_requests = objects.InstancePCIRequests.obj_from_db(
self._context, self.uuid, db_requests)
else:
self.pci_requests = \
objects.InstancePCIRequests.get_by_instance_uuid(
self._context, self.uuid)
def obj_load_attr(self, attrname):
if attrname not in INSTANCE_OPTIONAL_ATTRS:

View File

@ -45,6 +45,17 @@ class InstanceNUMATopology(base.NovaObject):
'cells': fields.ListOfObjectsField('InstanceNUMACell'),
}
@classmethod
def obj_from_db_obj(cls, instance_uuid, db_obj):
topo = hardware.VirtNUMAInstanceTopology.from_json(db_obj)
obj_topology = cls.obj_from_topology(topo)
obj_topology.instance_uuid = instance_uuid
# NOTE (ndipanov) not really needed as we never save, but left for
# consistency
obj_topology.id = 0
obj_topology.obj_reset_changes()
return obj_topology
@classmethod
def obj_from_topology(cls, topology):
if not isinstance(topology, hardware.VirtNUMAInstanceTopology):
@ -86,20 +97,12 @@ class InstanceNUMATopology(base.NovaObject):
@base.remotable_classmethod
def get_by_instance_uuid(cls, context, instance_uuid):
db_topology = db.instance_extra_get_by_instance_uuid(
db_extra = db.instance_extra_get_by_instance_uuid(
context, instance_uuid, columns=['numa_topology'])
if not db_topology:
if not db_extra:
raise exception.NumaTopologyNotFound(instance_uuid=instance_uuid)
if db_topology['numa_topology'] is None:
if db_extra['numa_topology'] is None:
return None
topo = hardware.VirtNUMAInstanceTopology.from_json(
db_topology['numa_topology'])
obj_topology = cls.obj_from_topology(topo)
obj_topology.id = db_topology['id']
obj_topology.instance_uuid = db_topology['instance_uuid']
# NOTE (ndipanov) not really needed as we never save, but left for
# consistency
obj_topology.obj_reset_changes()
return obj_topology
return cls.obj_from_db_obj(instance_uuid, db_extra['numa_topology'])

View File

@ -67,30 +67,29 @@ class InstancePCIRequests(base.NovaObject):
primitive['requests'][index]['nova_object.data'], '1.0')
primitive['requests'][index]['nova_object.version'] = '1.0'
@classmethod
def obj_from_db(cls, context, instance_uuid, db_requests):
self = cls(context=context, requests=[],
instance_uuid=instance_uuid)
try:
requests = jsonutils.loads(db_requests['pci_requests'])
except TypeError:
requests = []
for request in requests:
request_obj = InstancePCIRequest(
count=request['count'], spec=request['spec'],
alias_name=request['alias_name'], is_new=request['is_new'],
request_id=request['request_id'])
request_obj.obj_reset_changes()
self.requests.append(request_obj)
self.obj_reset_changes()
return self
@base.remotable_classmethod
def get_by_instance_uuid(cls, context, instance_uuid):
obj_pci_requests = cls(instance_uuid=instance_uuid)
obj_pci_requests.requests = []
obj_pci_requests._context = context
db_pci_requests = db.instance_extra_get_by_instance_uuid(
context, instance_uuid, columns=['pci_requests'])
if db_pci_requests:
try:
requests = jsonutils.loads(db_pci_requests['pci_requests'])
except TypeError:
requests = []
for request in requests:
request_obj = InstancePCIRequest(
count=request['count'], spec=request['spec'],
alias_name=request['alias_name'], is_new=request['is_new'],
request_id=request['request_id'])
request_obj.obj_reset_changes()
obj_pci_requests.requests.append(request_obj)
obj_pci_requests.obj_reset_changes()
return obj_pci_requests
return cls.obj_from_db(context, instance_uuid, db_pci_requests)
@classmethod
def get_by_instance_uuid_and_newness(cls, context, instance_uuid, is_new):

View File

@ -1882,6 +1882,8 @@ class ServersControllerCreateTest(test.TestCase):
"task_state": "",
"vm_state": "",
"root_device_name": inst.get('root_device_name', 'vda'),
"extra": {"pci_requests": None,
"numa_topology": None},
})
self.instance_cache_by_id[instance['id']] = instance

View File

@ -1869,6 +1869,8 @@ class ServersControllerCreateTest(test.TestCase):
"vm_state": "",
"root_device_name": inst.get('root_device_name', 'vda'),
"security_groups": inst['security_groups'],
"extra": {"pci_requests": None,
"numa_topology": None},
})
self.instance_cache_by_id[instance['id']] = instance

View File

@ -282,18 +282,23 @@ class BaseTestCase(test.TestCase):
'image_ref': None,
'root_device_name': None,
}
extra = {
'id': 1, 'created_at': None, 'updated_at': None,
'deleted_at': None, 'deleted': None,
'instance_uuid': instance['uuid'],
'numa_topology': None,
'pci_requests': None,
}
numa_topology = kwargs.pop('numa_topology', None)
if numa_topology:
numa_topology = {
'id': 1, 'created_at': None, 'updated_at': None,
'deleted_at': None, 'deleted': None,
'instance_uuid': instance['uuid'],
'numa_topology': numa_topology.to_json()
}
extra['numa_topology'] = numa_topology.to_json()
instance.update(kwargs)
instance['extra'] = extra
self._instances[instance_uuid] = instance
self._numa_topologies[instance_uuid] = numa_topology
self._numa_topologies[instance_uuid] = extra
return instance
def _fake_flavor_create(self, **kwargs):

View File

@ -54,7 +54,9 @@ def fake_db_instance(**updates):
'metadata': {},
'system_metadata': {},
'root_gb': 0,
'ephemeral_gb': 0
'ephemeral_gb': 0,
'extra': {'pci_requests': None,
'numa_topology': None},
}
for name, field in objects.Instance.fields.items():

View File

@ -18,6 +18,7 @@ import iso8601
import mock
import mox
import netaddr
from oslo.serialization import jsonutils
from oslo.utils import timeutils
from nova.cells import rpcapi as cells_rpcapi
@ -126,26 +127,26 @@ class _TestInstanceObject(object):
exp_cols.remove('fault')
exp_cols.remove('numa_topology')
exp_cols.remove('pci_requests')
exp_cols.extend(['extra', 'extra.numa_topology', 'extra.pci_requests'])
fake_topology = (test_instance_numa_topology.
fake_db_topology['numa_topology'])
fake_requests = jsonutils.dumps(test_instance_pci_requests.
fake_pci_requests)
fake_instance = dict(self.fake_instance,
extra={
'numa_topology': fake_topology,
'pci_requests': fake_requests,
})
db.instance_get_by_uuid(
self.context, 'uuid',
columns_to_join=exp_cols,
use_slave=False
).AndReturn(self.fake_instance)
).AndReturn(fake_instance)
fake_faults = test_instance_fault.fake_faults
db.instance_fault_get_by_instance_uuids(
self.context, [self.fake_instance['uuid']]
self.context, [fake_instance['uuid']]
).AndReturn(fake_faults)
fake_topology = test_instance_numa_topology.fake_db_topology
db.instance_extra_get_by_instance_uuid(
self.context, self.fake_instance['uuid'],
columns=['numa_topology']
).AndReturn(fake_topology)
fake_requests = test_instance_pci_requests.fake_pci_requests
db.instance_extra_get_by_instance_uuid(
self.context, self.fake_instance['uuid'],
columns=['pci_requests']
).AndReturn(fake_requests)
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(
@ -950,6 +951,21 @@ class _TestInstanceObject(object):
self.assertEqual(fake_fault['id'], fault.id)
self.assertNotIn('metadata', inst.obj_what_changed())
def test_get_with_extras(self):
pci_requests = objects.InstancePCIRequests(requests=[
objects.InstancePCIRequest(count=123, spec=[])])
inst = objects.Instance(context=self.context,
user_id=self.context.user_id,
project_id=self.context.project_id,
pci_requests=pci_requests)
inst.create()
uuid = inst.uuid
inst = objects.Instance.get_by_uuid(self.context, uuid)
self.assertFalse(inst.obj_attr_is_set('pci_requests'))
inst = objects.Instance.get_by_uuid(
self.context, uuid, expected_attrs=['pci_requests'])
self.assertTrue(inst.obj_attr_is_set('pci_requests'))
class TestInstanceObject(test_objects._LocalTest,
_TestInstanceObject):
@ -1254,3 +1270,8 @@ class TestInstanceObjectMisc(test.NoDBTestCase):
self.stubs.Set(instance, '_INSTANCE_OPTIONAL_JOINED_FIELDS', ['bar'])
self.assertEqual(['bar'], instance._expected_cols(['foo', 'bar']))
self.assertIsNone(instance._expected_cols(None))
def test_expected_cols_extra(self):
self.assertEqual(['metadata', 'extra', 'extra.numa_topology'],
instance._expected_cols(['metadata',
'numa_topology']))

View File

@ -49,7 +49,7 @@ class _TestInstanceNUMATopology(object):
def test_get_by_instance_uuid(self, mock_get):
mock_get.return_value = fake_db_topology
numa_topology = objects.InstanceNUMATopology.get_by_instance_uuid(
self.context, 'fake_uuid')
self.context, fake_db_topology['instance_uuid'])
self.assertEqual(fake_db_topology['instance_uuid'],
numa_topology.instance_uuid)
for obj_cell, topo_cell in zip(