Added objects Tag and TagList

Also field 'tags' was added to the instance object.
These objects will be used in new instance-tags API extension.

Implements: blueprint tag-instances
Change-Id: I34ec32f0b3704b01aaca9c0cb2b34eb6d3107513
This commit is contained in:
Sergey Nikitin 2014-06-02 13:05:46 +04:00
parent 9ccad00922
commit 5783c04cd2
9 changed files with 222 additions and 18 deletions

View File

@ -49,6 +49,7 @@ def register_all():
__import__('nova.objects.network_request')
__import__('nova.objects.numa')
__import__('nova.objects.pci_device')
__import__('nova.objects.tag')
__import__('nova.objects.quotas')
__import__('nova.objects.security_group')
__import__('nova.objects.security_group_rule')

View File

@ -43,7 +43,8 @@ class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject):
# Version 1.2: Instance version 1.14
# Version 1.3: Instance version 1.15
# Version 1.4: Instance version 1.16
VERSION = '1.4'
# Version 1.5: Instance version 1.17
VERSION = '1.5'
fields = {
'id': fields.IntegerField(),
@ -211,7 +212,8 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject):
# Version 1.3: BlockDeviceMapping <= version 1.2
# Version 1.4: BlockDeviceMapping <= version 1.3
# Version 1.5: BlockDeviceMapping <= version 1.4
VERSION = '1.5'
# Version 1.6: BlockDeviceMapping <= version 1.5
VERSION = '1.6'
fields = {
'objects': fields.ListOfObjectsField('BlockDeviceMapping'),
@ -223,6 +225,7 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject):
'1.3': '1.2',
'1.4': '1.3',
'1.5': '1.4',
'1.6': '1.5',
}
@base.remotable_classmethod

View File

@ -34,7 +34,8 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
# Version 1.4: Added default_route field
# Version 1.5: Added floating_ips field
# Version 1.6: Instance 1.16
VERSION = '1.6'
# Version 1.7: Instance 1.17
VERSION = '1.7'
fields = {
'id': fields.IntegerField(),
@ -209,7 +210,8 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
# Version 1.4: FixedIP <= version 1.4
# Version 1.5: FixedIP <= version 1.5, added expected attrs to gets
# Version 1.6: FixedIP <= version 1.6
VERSION = '1.6'
# Version 1.7: FixedIP <= version 1.7
VERSION = '1.7'
fields = {
'objects': fields.ListOfObjectsField('FixedIP'),
@ -222,6 +224,7 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
'1.4': '1.4',
'1.5': '1.5',
'1.6': '1.6',
'1.7': '1.7',
}
@obj_base.remotable_classmethod

View File

@ -37,7 +37,7 @@ LOG = logging.getLogger(__name__)
# List of fields that can be joined in DB layer.
_INSTANCE_OPTIONAL_JOINED_FIELDS = ['metadata', 'system_metadata',
'info_cache', 'security_groups',
'pci_devices']
'pci_devices', 'tags']
# These are fields that are optional but don't translate to db columns
_INSTANCE_OPTIONAL_NON_COLUMN_FIELDS = ['fault']
# These are fields that are optional and in instance_extra
@ -86,7 +86,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject):
# Version 1.14: Added numa_topology
# Version 1.15: PciDeviceList 1.1
# Version 1.16: Added pci_requests
VERSION = '1.16'
# Version 1.17: Added tags
VERSION = '1.17'
fields = {
'id': fields.IntegerField(),
@ -176,6 +177,7 @@ class Instance(base.NovaPersistentObject, base.NovaObject):
nullable=True),
'pci_requests': fields.ObjectField('InstancePCIRequests',
nullable=True),
'tags': fields.ObjectField('TagList'),
}
obj_extra_fields = ['name']
@ -248,6 +250,10 @@ class Instance(base.NovaPersistentObject, base.NovaObject):
del primitive['pci_devices']
if target_version < (1, 16) and 'pci_requests' in primitive:
del primitive['pci_requests']
if target_version < (1, 17):
# NOTE(snikitin): Before 1.17 there was no tags list
if 'tags' in primitive:
del primitive['tags']
@property
def name(self):
@ -333,6 +339,12 @@ class Instance(base.NovaPersistentObject, base.NovaObject):
objects.SecurityGroup, db_inst['security_groups'])
instance['security_groups'] = sec_groups
if 'tags' in expected_attrs:
tags = base.obj_make_list(
context, objects.TagList(context),
objects.Tag, db_inst['tags'])
instance['tags'] = tags
instance.obj_reset_changes()
return instance
@ -715,7 +727,8 @@ class InstanceList(base.ObjectListBase, base.NovaObject):
# Version 1.10: Instance <= version 1.16
# Version 1.11: Added sort_keys and sort_dirs to get_by_filters
# Version 1.12: Pass expected_attrs to instance_get_active_by_window_joined
VERSION = '1.12'
# Version 1.13: Instance <= version 1.17
VERSION = '1.13'
fields = {
'objects': fields.ListOfObjectsField('Instance'),
@ -734,6 +747,7 @@ class InstanceList(base.ObjectListBase, base.NovaObject):
'1.10': '1.16',
'1.11': '1.16',
'1.12': '1.16',
'1.13': '1.17',
}
@base.remotable_classmethod

69
nova/objects/tag.py Normal file
View File

@ -0,0 +1,69 @@
# 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 nova import db
from nova import objects
from nova.objects import base
from nova.objects import fields
class Tag(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'resource_id': fields.StringField(),
'tag': fields.StringField(),
}
@staticmethod
def _from_db_object(context, tag, db_tag):
for key in tag.fields:
tag[key] = db_tag[key]
tag.obj_reset_changes()
tag._context = context
return tag
@base.remotable
def create(self, context):
db_tag = db.instance_tag_add(context, self.resource_id, self.tag)
self._from_db_object(context, self, db_tag)
@base.remotable_classmethod
def destroy(cls, context, resource_id, name):
db.instance_tag_delete(context, resource_id, name)
class TagList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('Tag'),
}
child_versions = {
'1.0': '1.0',
}
@base.remotable_classmethod
def get_by_resource_id(cls, context, resource_id):
db_tags = db.instance_tag_get_by_instance_uuid(context, resource_id)
return base.obj_make_list(context, cls(), objects.Tag, db_tags)
@base.remotable_classmethod
def create(cls, context, resource_id, tags):
db_tags = db.instance_tag_set(context, resource_id, tags)
return base.obj_make_list(context, cls(), objects.Tag, db_tags)
@base.remotable_classmethod
def destroy(cls, context, resource_id):
db.instance_tag_delete_all(context, resource_id)

View File

@ -57,6 +57,7 @@ def fake_db_instance(**updates):
'ephemeral_gb': 0,
'extra': {'pci_requests': None,
'numa_topology': None},
'tags': []
}
for name, field in objects.Instance.fields.items():

View File

@ -65,6 +65,7 @@ class _TestInstanceObject(object):
fake_instance['pci_devices'] = []
fake_instance['user_id'] = self.context.user_id
fake_instance['project_id'] = self.context.project_id
fake_instance['tags'] = []
return fake_instance
def test_datetime_deserialization(self):
@ -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.16',
'nova_object.version': '1.17',
'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.16',
'nova_object.version': '1.17',
'nova_object.data':
{'uuid': 'fake-uuid',
'access_ip_v4': '1.2.3.4',

View File

@ -1075,8 +1075,8 @@ object_data = {
'AggregateList': '1.2-4b02a285b8612bfb86a96ff80052fb0a',
'BandwidthUsage': '1.2-a9d7c2ba54995e48ce38688c51c9416d',
'BandwidthUsageList': '1.2-5b564cbfd5ae6e106443c086938e7602',
'BlockDeviceMapping': '1.4-9968ffe513e7672484b0f528b034cd0f',
'BlockDeviceMappingList': '1.5-83767968de6e91e9705bddaae02bc649',
'BlockDeviceMapping': '1.5-9968ffe513e7672484b0f528b034cd0f',
'BlockDeviceMappingList': '1.6-ee2ed2eb3f3f2f54d573ccea0ff2eeaa',
'ComputeNode': '1.6-d2ea9b8f4a6e95ff6a683266eebddbff',
'ComputeNodeList': '1.6-205aa2ea08d49f6ce87df1fcd2407b4e',
'DNSDomain': '1.0-5bdc288d7c3b723ce86ede998fd5c9ba',
@ -1084,14 +1084,14 @@ object_data = {
'EC2InstanceMapping': '1.0-627baaf4b12c9067200979bdc4558a99',
'EC2SnapshotMapping': '1.0-26cf315be1f8abab4289d4147671c836',
'EC2VolumeMapping': '1.0-2f8c3bf077c65a425294ec2b361c9143',
'FixedIP': '1.6-2472964d39e50da67202109eb85cd173',
'FixedIPList': '1.6-f2f740de66bc2d90627004bd311690ad',
'FixedIP': '1.7-2472964d39e50da67202109eb85cd173',
'FixedIPList': '1.7-125de790b58cfb8c84ffc8c34db4a81e',
'Flavor': '1.1-096cfd023c35d07542cf732fb29b45e4',
'FlavorList': '1.1-a3d5551267cb8f62ff38ded125900721',
'FloatingIP': '1.6-27eb68b7c9c620dd5f0561b5a3be0e82',
'FloatingIPList': '1.7-f376f63ed99243f9d90841b7f6732bbf',
'HVSpec': '1.0-c4d8377cc4fe519930e60c1d8265a142',
'Instance': '1.16-b00c09fb92ae80b393943f56e84abd9c',
'Instance': '1.17-972cae223db35e88bb184bdf8c197229',
'InstanceAction': '1.1-6b1d0a6dbd522b5a83c20757ec659663',
'InstanceActionEvent': '1.1-42dbdba74bd06e0619ca75cd3397cd1b',
'InstanceActionEventList': '1.0-1d5cc958171d6ce07383c2ad6208318e',
@ -1102,7 +1102,7 @@ object_data = {
'InstanceGroup': '1.9-95ece99f092e8f4f88327cdbb44162c9',
'InstanceGroupList': '1.6-c6b78f3c9d9080d33c08667e80589817',
'InstanceInfoCache': '1.5-ef64b604498bfa505a8c93747a9d8b2f',
'InstanceList': '1.12-f044d117e39478498e0d3124c5c4ef54',
'InstanceList': '1.13-179093360c48747a41694cc2f326d75d',
'InstanceNUMACell': '1.1-8d2a13c8360cc9ea1b68c9c6c4476857',
'InstanceNUMATopology': '1.1-86b95d263c4c68411d44c6741b8d2bb0',
'InstancePCIRequest': '1.1-e082d174f4643e5756ba098c47c1510f',
@ -1131,6 +1131,8 @@ object_data = {
'Service': '1.5-82bbfd46a744a9c89bc44b47a1b81683',
'ServiceList': '1.3-4a1a5822dea268d0d7f892f5106bb2e1',
'TestSubclassedObject': '1.6-b9be83b5587fbca3c8570aab67cb3d02',
'Tag': '1.0-a11531f4e4e3166eef6243d6d58a18bd',
'TagList': '1.0-e89bf8c8055f1f1d654fb44f0abf1f53',
'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2',
'VirtualInterfaceList': '1.0-accbf02628a8063c1d885077a2bf49b6',
'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563',
@ -1138,15 +1140,16 @@ object_data = {
object_relationships = {
'BlockDeviceMapping': {'Instance': '1.16'},
'FixedIP': {'Instance': '1.16', 'Network': '1.2',
'BlockDeviceMapping': {'Instance': '1.17'},
'FixedIP': {'Instance': '1.17', 'Network': '1.2',
'VirtualInterface': '1.0',
'FloatingIPList': '1.7'},
'FloatingIP': {'FixedIP': '1.6'},
'FloatingIP': {'FixedIP': '1.7'},
'Instance': {'InstanceFault': '1.2',
'InstanceInfoCache': '1.5',
'InstanceNUMATopology': '1.1',
'PciDeviceList': '1.1',
'TagList': '1.0',
'SecurityGroupList': '1.0',
'InstancePCIRequests': '1.1'},
'MyObj': {'MyOwnedObject': '1.0'},

View File

@ -0,0 +1,109 @@
# 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.
import mock
from nova.objects import tag
from nova.tests.unit.objects import test_objects
RESOURCE_ID = '123'
TAG_NAME1 = 'fake-tag1'
TAG_NAME2 = 'fake-tag2'
fake_tag1 = {
'resource_id': RESOURCE_ID,
'tag': TAG_NAME1,
}
fake_tag2 = {
'resource_id': RESOURCE_ID,
'tag': TAG_NAME1,
}
fake_tag_list = [fake_tag1, fake_tag2]
def _get_tag(resource_id, tag_name):
t = tag.Tag()
t.resource_id = resource_id
t.tag = tag_name
return t
class _TestTagObject(object):
@mock.patch('nova.db.instance_tag_add')
def test_create(self, tag_add):
tag_add.return_value = fake_tag1
tag_obj = _get_tag(RESOURCE_ID, TAG_NAME1)
tag_obj.create(self.context)
tag_add.assert_called_once_with(self.context, RESOURCE_ID, TAG_NAME1)
self.compare_obj(tag_obj, fake_tag1)
@mock.patch('nova.db.instance_tag_delete')
def test_destroy(self, tag_delete):
tag.Tag.destroy(self.context, RESOURCE_ID, TAG_NAME1)
tag_delete.assert_called_once_with(self.context,
RESOURCE_ID, TAG_NAME1)
class TestMigrationObject(test_objects._LocalTest,
_TestTagObject):
pass
class TestRemoteMigrationObject(test_objects._RemoteTest,
_TestTagObject):
pass
class _TestTagList(object):
def _compare_tag_list(self, tag_list, tag_list_obj):
self.assertEqual(len(tag_list), len(tag_list_obj))
for obj, fake in zip(tag_list_obj, tag_list):
self.assertIsInstance(obj, tag.Tag)
self.assertEqual(obj['tag'], fake['tag'])
self.assertEqual(obj['resource_id'], fake['resource_id'])
@mock.patch('nova.db.instance_tag_get_by_instance_uuid')
def test_get_by_resource_id(self, get_by_inst):
get_by_inst.return_value = fake_tag_list
tag_list_obj = tag.TagList.get_by_resource_id(
self.context, RESOURCE_ID)
get_by_inst.assert_called_once_with(self.context, RESOURCE_ID)
self._compare_tag_list(fake_tag_list, tag_list_obj)
@mock.patch('nova.db.instance_tag_set')
def test_create(self, tag_set):
tag_set.return_value = fake_tag_list
tag_list_obj = tag.TagList.create(
self.context, RESOURCE_ID, [TAG_NAME1, TAG_NAME2])
tag_set.assert_called_once_with(self.context,
RESOURCE_ID, [TAG_NAME1, TAG_NAME2])
self._compare_tag_list(fake_tag_list, tag_list_obj)
@mock.patch('nova.db.instance_tag_delete_all')
def test_destroy(self, tag_delete_all):
tag.TagList.destroy(self.context, RESOURCE_ID)
tag_delete_all.assert_called_once_with(self.context, RESOURCE_ID)
class TestTagList(test_objects._LocalTest, _TestTagList):
pass
class TestTagListRemote(test_objects._RemoteTest, _TestTagList):
pass