Add vcpu_model to instance object

The vcpu_model gives the vcpu information for an instance like the
features, the cpu model and is saved as extra for the instance object.

Implements: blueprint resource-objects
Change-Id: I0a50dcd47899b15b2bea01c782109ec57ee88bfc
This commit is contained in:
yunhong jiang 2015-01-30 14:32:05 -08:00
parent afeb8a7b0e
commit 5c75862c48
11 changed files with 162 additions and 27 deletions

View File

@ -1597,6 +1597,7 @@ def instance_create(context, values):
instance_ref['extra'].update(
{'numa_topology': None,
'pci_requests': None,
'vcpu_model': None,
})
instance_ref['extra'].update(values.pop('extra', {}))
instance_ref.update(values)
@ -2601,7 +2602,7 @@ def instance_extra_get_by_instance_uuid(context, instance_uuid,
query = model_query(context, models.InstanceExtra).\
filter_by(instance_uuid=instance_uuid)
if columns is None:
columns = ['numa_topology', 'pci_requests', 'flavor']
columns = ['numa_topology', 'pci_requests', 'flavor', 'vcpu_model']
for column in columns:
query = query.options(undefer(column))
instance_extra = query.first()

View File

@ -0,0 +1,42 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import Column
from sqlalchemy import MetaData
from sqlalchemy import Table
from sqlalchemy import Text
BASE_TABLE_NAME = 'instance_extra'
NEW_COLUMN_NAME = 'vcpu_model'
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
for prefix in ('', 'shadow_'):
table = Table(prefix + BASE_TABLE_NAME, meta, autoload=True)
new_column = Column(NEW_COLUMN_NAME, Text, nullable=True)
if not hasattr(table.c, NEW_COLUMN_NAME):
table.create_column(new_column)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
for prefix in ('', 'shadow_'):
table = Table(prefix + BASE_TABLE_NAME, meta, autoload=True)
if hasattr(table.c, NEW_COLUMN_NAME):
getattr(table.c, NEW_COLUMN_NAME).drop()

View File

@ -364,6 +364,7 @@ class InstanceExtra(BASE, NovaBase):
numa_topology = orm.deferred(Column(Text))
pci_requests = orm.deferred(Column(Text))
flavor = orm.deferred(Column(Text))
vcpu_model = orm.deferred(Column(Text))
instance = orm.relationship(Instance,
backref=orm.backref('extra',
uselist=False),

View File

@ -47,7 +47,8 @@ class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject,
# Version 1.5: Instance version 1.17
# Version 1.6: Instance version 1.18
# Version 1.7: Add update_or_create method
VERSION = '1.7'
# Version 1.8: Instance version 1.19
VERSION = '1.8'
fields = {
'id': fields.IntegerField(),
@ -71,7 +72,8 @@ class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject,
obj_relationships = {
'instance': [('1.0', '1.13'), ('1.2', '1.14'), ('1.3', '1.15'),
('1.4', '1.16'), ('1.5', '1.17'), ('1.6', '1.18')],
('1.4', '1.16'), ('1.5', '1.17'), ('1.6', '1.18'),
('1.8', '1.19')],
}
@staticmethod
@ -236,7 +238,8 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject):
# Version 1.6: BlockDeviceMapping <= version 1.5
# Version 1.7: BlockDeviceMapping <= version 1.6
# Version 1.8: BlockDeviceMapping <= version 1.7
VERSION = '1.8'
# Version 1.9: BlockDeviceMapping <= version 1.8
VERSION = '1.9'
fields = {
'objects': fields.ListOfObjectsField('BlockDeviceMapping'),
@ -251,6 +254,7 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject):
'1.6': '1.5',
'1.7': '1.6',
'1.8': '1.7',
'1.9': '1.8',
}
@base.remotable_classmethod

View File

@ -38,7 +38,8 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject,
# Version 1.6: Instance 1.16
# Version 1.7: Instance 1.17
# Version 1.8: Instance 1.18
VERSION = '1.8'
# Version 1.8: Instance 1.19
VERSION = '1.9'
fields = {
'id': fields.IntegerField(),
@ -63,7 +64,8 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject,
obj_relationships = {
'instance': [('1.0', '1.13'), ('1.2', '1.14'), ('1.3', '1.15'),
('1.6', '1.16'), ('1.7', '1.17'), ('1.8', '1.18')],
('1.6', '1.16'), ('1.7', '1.17'), ('1.8', '1.18'),
('1.9', '1.19')],
'network': [('1.0', '1.2')],
'virtual_interface': [('1.1', '1.0')],
'floating_ips': [('1.5', '1.7')],
@ -210,7 +212,8 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
# Version 1.6: FixedIP <= version 1.6
# Version 1.7: FixedIP <= version 1.7
# Version 1.8: FixedIP <= version 1.8
VERSION = '1.8'
# Version 1.8: FixedIP <= version 1.9
VERSION = '1.9'
fields = {
'objects': fields.ListOfObjectsField('FixedIP'),
@ -225,6 +228,7 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
'1.6': '1.6',
'1.7': '1.7',
'1.8': '1.8',
'1.9': '1.9',
}
@obj_base.remotable_classmethod

View File

@ -45,7 +45,8 @@ _INSTANCE_OPTIONAL_JOINED_FIELDS = ['metadata', 'system_metadata',
_INSTANCE_OPTIONAL_NON_COLUMN_FIELDS = ['fault', 'flavor', 'old_flavor',
'new_flavor']
# These are fields that are optional and in instance_extra
_INSTANCE_EXTRA_FIELDS = ['numa_topology', 'pci_requests', 'flavor']
_INSTANCE_EXTRA_FIELDS = ['numa_topology', 'pci_requests',
'flavor', 'vcpu_model']
# These are fields that can be specified as expected_attrs
INSTANCE_OPTIONAL_ATTRS = (_INSTANCE_OPTIONAL_JOINED_FIELDS +
@ -151,7 +152,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
# Version 1.16: Added pci_requests
# Version 1.17: Added tags
# Version 1.18: Added flavor, old_flavor, new_flavor
VERSION = '1.18'
# Version 1.19: Added vcpu_model
VERSION = '1.19'
fields = {
'id': fields.IntegerField(),
@ -245,6 +247,7 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
'flavor': fields.ObjectField('Flavor'),
'old_flavor': fields.ObjectField('Flavor', nullable=True),
'new_flavor': fields.ObjectField('Flavor', nullable=True),
'vcpu_model': fields.ObjectField('VirtCPUModel', nullable=True),
}
obj_extra_fields = ['name']
@ -260,6 +263,7 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
'flavor': [('1.18', '1.1')],
'old_flavor': [('1.18', '1.1')],
'new_flavor': [('1.18', '1.1')],
'vcpu_model': [('1.19', '1.0')],
}
def __init__(self, *args, **kwargs):
@ -503,7 +507,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
if 'pci_requests' in expected_attrs:
instance._load_pci_requests(
db_inst.get('extra').get('pci_requests'))
if 'vcpu_model' in expected_attrs:
instance._load_vcpu_model(db_inst.get('extra').get('vcpu_model'))
if 'info_cache' in expected_attrs:
if db_inst['info_cache'] is None:
instance.info_cache = None
@ -610,6 +615,11 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
'new': new,
}
updates['extra']['flavor'] = jsonutils.dumps(flavor_info)
vcpu_model = updates.pop('vcpu_model', None)
if vcpu_model:
expected_attrs.append('vcpu_model')
updates['extra']['vcpu_model'] = (
jsonutils.dumps(vcpu_model.obj_to_primitive()))
db_inst = db.instance_create(context, updates)
self._from_db_object(context, self, db_inst, expected_attrs)
@ -691,6 +701,18 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
if 'new_flavor' in self.obj_what_changed():
self._save_flavor(context)
def _save_vcpu_model(self, context):
# TODO(yjiang5): should merge the db accesses for all the extra
# fields
if 'vcpu_model' in self.obj_what_changed():
if self.vcpu_model:
update = jsonutils.dumps(self.vcpu_model.obj_to_primitive())
else:
update = None
db.instance_extra_update_by_uuid(
context, self.uuid,
{'vcpu_model': update})
def _maybe_upgrade_flavor(self):
# NOTE(danms): We may have regressed to flavors stored in sysmeta,
# so we have to merge back in here. That could happen if we pass
@ -926,6 +948,15 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
instance.system_metadata.update(self.get('system_metadata', {}))
self.system_metadata = instance.system_metadata
def _load_vcpu_model(self, db_vcpu_model=None):
if db_vcpu_model is None:
self.vcpu_model = objects.VirtCPUModel.get_by_instance_uuid(
self._context, self.uuid)
else:
db_vcpu_model = jsonutils.loads(db_vcpu_model)
self.vcpu_model = objects.VirtCPUModel.obj_from_primitive(
db_vcpu_model)
def obj_load_attr(self, attrname):
if attrname not in INSTANCE_OPTIONAL_ATTRS:
raise exception.ObjectActionError(
@ -960,6 +991,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
self._load_numa_topology()
elif attrname == 'pci_requests':
self._load_pci_requests()
elif attrname == 'vcpu_model':
self._load_vcpu_model()
elif 'flavor' in attrname:
self._load_flavor()
else:
@ -1057,7 +1090,8 @@ class InstanceList(base.ObjectListBase, base.NovaObject):
# Version 1.12: Pass expected_attrs to instance_get_active_by_window_joined
# Version 1.13: Instance <= version 1.17
# Version 1.14: Instance <= version 1.18
VERSION = '1.14'
# Version 1.15: Instance <= version 1.19
VERSION = '1.15'
fields = {
'objects': fields.ListOfObjectsField('Instance'),
@ -1078,6 +1112,7 @@ class InstanceList(base.ObjectListBase, base.NovaObject):
'1.12': '1.16',
'1.13': '1.17',
'1.14': '1.18',
'1.15': '1.19',
}
@base.remotable_classmethod

View File

@ -2647,9 +2647,10 @@ class InstanceExtraTestCase(test.TestCase):
def test_instance_extra_get_with_columns(self):
extra = db.instance_extra_get_by_instance_uuid(
self.ctxt, self.instance['uuid'],
columns=['numa_topology'])
columns=['numa_topology', 'vcpu_model'])
self.assertNotIn('pci_requests', extra)
self.assertIn('numa_topology', extra)
self.assertIn('vcpu_model', extra)
class ServiceTestCase(test.TestCase, ModelsObjectComparatorMixin):

View File

@ -787,6 +787,23 @@ class NovaMigrationsCheckers(test_migrations.ModelsMigrationsSync,
self.assertColumnNotExists(engine, 'key_pairs', 'type')
self.assertColumnNotExists(engine, 'shadow_key_pairs', 'type')
def _check_276(self, engine, data):
self.assertColumnExists(engine, 'instance_extra', 'vcpu_model')
self.assertColumnExists(engine, 'shadow_instance_extra', 'vcpu_model')
instance_extra = oslodbutils.get_table(engine, 'instance_extra')
shadow_instance_extra = oslodbutils.get_table(
engine, 'shadow_instance_extra')
self.assertIsInstance(instance_extra.c.vcpu_model.type,
sqlalchemy.types.Text)
self.assertIsInstance(shadow_instance_extra.c.vcpu_model.type,
sqlalchemy.types.Text)
def _post_downgrade_276(self, engine):
self.assertColumnNotExists(engine, 'instance_extra', 'vcpu_model')
self.assertColumnNotExists(engine, 'shadow_instance_extra',
'vcpu_model')
class TestNovaMigrationsSQLite(NovaMigrationsCheckers,
test.TestCase,

View File

@ -71,7 +71,9 @@ def fake_db_instance(**updates):
'ephemeral_gb': 0,
'extra': {'pci_requests': None,
'flavor': flavorinfo,
'numa_topology': None},
'numa_topology': None,
'vcpu_model': None,
},
'tags': []
}

View File

@ -41,6 +41,7 @@ from nova.tests.unit.objects import test_instance_numa_topology
from nova.tests.unit.objects import test_instance_pci_requests
from nova.tests.unit.objects import test_objects
from nova.tests.unit.objects import test_security_group
from nova.tests.unit.objects import test_vcpu_model
class _TestInstanceObject(object):
@ -74,7 +75,7 @@ class _TestInstanceObject(object):
primitive = inst.obj_to_primitive()
expected = {'nova_object.name': 'Instance',
'nova_object.namespace': 'nova',
'nova_object.version': '1.18',
'nova_object.version': '1.19',
'nova_object.data':
{'uuid': 'fake-uuid',
'launched_at': '1955-11-05T00:00:00Z'},
@ -90,7 +91,7 @@ class _TestInstanceObject(object):
primitive = inst.obj_to_primitive()
expected = {'nova_object.name': 'Instance',
'nova_object.namespace': 'nova',
'nova_object.version': '1.18',
'nova_object.version': '1.19',
'nova_object.data':
{'uuid': 'fake-uuid',
'access_ip_v4': '1.2.3.4',
@ -127,9 +128,10 @@ class _TestInstanceObject(object):
exp_cols.remove('fault')
exp_cols.remove('numa_topology')
exp_cols.remove('pci_requests')
exp_cols.remove('vcpu_model')
exp_cols = filter(lambda x: 'flavor' not in x, exp_cols)
exp_cols.extend(['extra', 'extra.numa_topology', 'extra.pci_requests',
'extra.flavor'])
'extra.flavor', 'extra.vcpu_model'])
fake_topology = (test_instance_numa_topology.
fake_db_topology['numa_topology'])
@ -138,11 +140,14 @@ class _TestInstanceObject(object):
fake_flavor = jsonutils.dumps(
{'cur': objects.Flavor().obj_to_primitive(),
'old': None, 'new': None})
fake_vcpu_model = jsonutils.dumps(
test_vcpu_model.fake_vcpumodel.obj_to_primitive())
fake_instance = dict(self.fake_instance,
extra={
'numa_topology': fake_topology,
'pci_requests': fake_requests,
'flavor': fake_flavor,
'vcpu_model': fake_vcpu_model,
})
db.instance_get_by_uuid(
self.context, 'uuid',
@ -460,6 +465,21 @@ class _TestInstanceObject(object):
mock_extra_update.assert_called_once_with(
self.context, inst.uuid, {'numa_topology': None})
@mock.patch('nova.db.instance_extra_update_by_uuid')
def test_save_vcpu_model(self, mock_update):
inst = fake_instance.fake_instance_obj(self.context)
inst.vcpu_model = test_vcpu_model.fake_vcpumodel
inst.save()
mock_update.assert_called_once_with(
self.context, inst.uuid,
{'vcpu_model': jsonutils.dumps(
test_vcpu_model.fake_vcpumodel.obj_to_primitive())})
mock_update.reset_mock()
inst.vcpu_model = None
inst.save()
mock_update.assert_called_once_with(
self.context, inst.uuid, {'vcpu_model': None})
def test_get_deleted(self):
fake_inst = dict(self.fake_instance, id=123, deleted=123)
fake_uuid = fake_inst['uuid']
@ -740,10 +760,13 @@ class _TestInstanceObject(object):
pci_requests=objects.InstancePCIRequests(
requests=[
objects.InstancePCIRequest(count=123,
spec=[])]))
spec=[])]),
vcpu_model=test_vcpu_model.fake_vcpumodel,
)
inst.create()
self.assertIsNotNone(inst.numa_topology)
self.assertIsNotNone(inst.pci_requests)
self.assertIsNotNone(inst.vcpu_model)
got_numa_topo = objects.InstanceNUMATopology.get_by_instance_uuid(
self.context, inst.uuid)
self.assertEqual(inst.numa_topology.instance_uuid,
@ -751,6 +774,9 @@ class _TestInstanceObject(object):
got_pci_requests = objects.InstancePCIRequests.get_by_instance_uuid(
self.context, inst.uuid)
self.assertEqual(123, got_pci_requests.requests[0].count)
vcpu_model = objects.VirtCPUModel.get_by_instance_uuid(
self.context, inst.uuid)
self.assertEqual('fake-model', vcpu_model.model)
def test_recreate_fails(self):
inst = instance.Instance(context=self.context,

View File

@ -1144,8 +1144,8 @@ object_data = {
'AggregateList': '1.2-4b02a285b8612bfb86a96ff80052fb0a',
'BandwidthUsage': '1.2-a9d7c2ba54995e48ce38688c51c9416d',
'BandwidthUsageList': '1.2-5b564cbfd5ae6e106443c086938e7602',
'BlockDeviceMapping': '1.7-c53f09c7f969e0222d9f6d67a950a08e',
'BlockDeviceMappingList': '1.8-15ab98892f8fd26faa49f45f3cffaef0',
'BlockDeviceMapping': '1.8-c53f09c7f969e0222d9f6d67a950a08e',
'BlockDeviceMappingList': '1.9-0faaeebdca213010c791bc37a22546e3',
'ComputeNode': '1.10-70202a38b858977837b313d94475a26b',
'ComputeNodeList': '1.10-4ae1f844c247029fbcdb5fdccbe9e619',
'DNSDomain': '1.0-5bdc288d7c3b723ce86ede998fd5c9ba',
@ -1153,14 +1153,14 @@ object_data = {
'EC2InstanceMapping': '1.0-627baaf4b12c9067200979bdc4558a99',
'EC2SnapshotMapping': '1.0-26cf315be1f8abab4289d4147671c836',
'EC2VolumeMapping': '1.0-2f8c3bf077c65a425294ec2b361c9143',
'FixedIP': '1.8-2472964d39e50da67202109eb85cd173',
'FixedIPList': '1.8-6cfaa5b6dd27e9eb8fcf8462dea06077',
'FixedIP': '1.9-2472964d39e50da67202109eb85cd173',
'FixedIPList': '1.9-68ade91cf8d97053c1ef401d87eb6ecd',
'Flavor': '1.1-096cfd023c35d07542cf732fb29b45e4',
'FlavorList': '1.1-a3d5551267cb8f62ff38ded125900721',
'FloatingIP': '1.6-27eb68b7c9c620dd5f0561b5a3be0e82',
'FloatingIPList': '1.7-f376f63ed99243f9d90841b7f6732bbf',
'HVSpec': '1.0-c4d8377cc4fe519930e60c1d8265a142',
'Instance': '1.18-7827a9e9846a75f3038bd556e6f530d3',
'Instance': '1.19-e5b80cc3b734b418d5c4b9140a4d2a25',
'InstanceAction': '1.1-6b1d0a6dbd522b5a83c20757ec659663',
'InstanceActionEvent': '1.1-42dbdba74bd06e0619ca75cd3397cd1b',
'InstanceActionEventList': '1.0-1d5cc958171d6ce07383c2ad6208318e',
@ -1171,7 +1171,7 @@ object_data = {
'InstanceGroup': '1.9-95ece99f092e8f4f88327cdbb44162c9',
'InstanceGroupList': '1.6-c6b78f3c9d9080d33c08667e80589817',
'InstanceInfoCache': '1.5-ef64b604498bfa505a8c93747a9d8b2f',
'InstanceList': '1.14-fe7f3266de1475454b939dee36a2ebcc',
'InstanceList': '1.15-20d822b2abb0051d72f81ed686a79e5b',
'InstanceNUMACell': '1.2-5d2dfa36e9ecca9b63f24bf3bc958ea4',
'InstanceNUMATopology': '1.1-86b95d263c4c68411d44c6741b8d2bb0',
'InstancePCIRequest': '1.1-e082d174f4643e5756ba098c47c1510f',
@ -1214,12 +1214,12 @@ object_data = {
object_relationships = {
'BlockDeviceMapping': {'Instance': '1.18'},
'BlockDeviceMapping': {'Instance': '1.19'},
'ComputeNode': {'PciDevicePoolList': '1.0'},
'FixedIP': {'Instance': '1.18', 'Network': '1.2',
'FixedIP': {'Instance': '1.19', 'Network': '1.2',
'VirtualInterface': '1.0',
'FloatingIPList': '1.7'},
'FloatingIP': {'FixedIP': '1.8'},
'FloatingIP': {'FixedIP': '1.9'},
'Instance': {'InstanceFault': '1.2',
'InstanceInfoCache': '1.5',
'InstanceNUMATopology': '1.1',
@ -1227,7 +1227,9 @@ object_relationships = {
'TagList': '1.0',
'SecurityGroupList': '1.0',
'Flavor': '1.1',
'InstancePCIRequests': '1.1'},
'InstancePCIRequests': '1.1',
'VirtCPUModel': '1.0',
},
'InstanceNUMACell': {'VirtCPUTopology': '1.0'},
'MyObj': {'MyOwnedObject': '1.0'},
'SecurityGroupRule': {'SecurityGroup': '1.1'},