Add tag column to vifs and bdm

This patch series adds the ability for a user to specify a tag to be
applied to a network interface or a disk when booting an instance.
This tag is then exposed through the metadata API. For example, a user
boots a VM with two network interfaces. One is connected to a private
network, the other to the public Internet. There is currently no
direct way to tell which interface is which. Specifying tags allows
the user to distinguish between the two interfaces.

This patch contains the database migration to add a 'tag' column to
the virtual_interfaces and block_device_mapping tables as well as the
corresponding change to the VirtualInterface and BlockDeviceMapping
objects.

Implements: blueprint virt-device-role-tagging
Co-authored-by: Vladik Romanovsky <vromanso@redhat.com>
Change-Id: Ic8be3de4e970116772f9b6ce01c55e26b829e6cb
This commit is contained in:
Artom Lifshitz 2016-01-05 22:57:53 +00:00 committed by Dan Smith
parent c368a3e802
commit 9ba07fdf9f
13 changed files with 121 additions and 17 deletions

View File

@ -45,7 +45,7 @@ bdm_new_fields = set(['source_type', 'destination_type',
'guest_format', 'device_type', 'disk_bus', 'boot_index',
'device_name', 'delete_on_termination', 'snapshot_id',
'volume_id', 'volume_size', 'image_id', 'no_device',
'connection_info'])
'connection_info', 'tag'])
bdm_db_only_fields = set(['id', 'instance_uuid'])

View File

@ -0,0 +1,47 @@
# Copyright (C) 2016, Red Hat, Inc.
#
# 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 String
from nova.db.sqlalchemy import api
from oslo_db.sqlalchemy import utils
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
shadow_prefix = api._SHADOW_TABLE_PREFIX
tag = Column('tag', String(255))
vifs = utils.get_table(migrate_engine, 'virtual_interfaces')
if not hasattr(vifs.c, 'tag'):
vifs.create_column(tag.copy())
shadow_vifs = utils.get_table(migrate_engine,
'%svirtual_interfaces' % shadow_prefix)
if not hasattr(shadow_vifs.c, 'tag'):
shadow_vifs.create_column(tag.copy())
bdm = utils.get_table(migrate_engine, 'block_device_mapping')
if not hasattr(bdm.c, 'tag'):
bdm.create_column(tag.copy())
shadow_bdm = utils.get_table(migrate_engine,
'%sblock_device_mapping' % shadow_prefix)
if not hasattr(shadow_bdm.c, 'tag'):
shadow_bdm.create_column(tag.copy())

View File

@ -614,6 +614,8 @@ class BlockDeviceMapping(BASE, NovaBase, models.SoftDeleteMixin):
connection_info = Column(MediumText())
tag = Column(String(255))
class SecurityGroupInstanceAssociation(BASE, NovaBase, models.SoftDeleteMixin):
__tablename__ = 'security_group_instance_association'
@ -834,6 +836,7 @@ class VirtualInterface(BASE, NovaBase, models.SoftDeleteMixin):
network_id = Column(Integer)
instance_uuid = Column(String(36), ForeignKey('instances.uuid'))
uuid = Column(String(36))
tag = Column(String(255))
# TODO(vish): can these both come from the same baseclass?

View File

@ -13,6 +13,7 @@
# under the License.
from oslo_log import log as logging
from oslo_utils import versionutils
from nova import block_device
from nova.cells import opts as cells_opts
@ -60,7 +61,8 @@ class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject,
# Version 1.15: Instance version 1.23
# Version 1.16: Deprecate get_by_volume_id(), add
# get_by_volume() and get_by_volume_and_instance()
VERSION = '1.16'
# Version 1.17: Added tag field
VERSION = '1.17'
fields = {
'id': fields.IntegerField(),
@ -81,8 +83,14 @@ class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject,
'image_id': fields.StringField(nullable=True),
'no_device': fields.BooleanField(default=False),
'connection_info': fields.SensitiveStringField(nullable=True),
'tag': fields.StringField(nullable=True),
}
def obj_make_compatible(self, primitive, target_version):
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 17) and 'tag' in primitive:
del primitive['tag']
@staticmethod
def _from_db_object(context, block_device_obj,
db_block_device, expected_attrs=None):

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import versionutils
from nova import db
from nova import exception
from nova import objects
@ -22,7 +24,8 @@ from nova.objects import fields
@base.NovaObjectRegistry.register
class VirtualInterface(base.NovaPersistentObject, base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Add tag field
VERSION = '1.1'
fields = {
'id': fields.IntegerField(),
@ -30,8 +33,14 @@ class VirtualInterface(base.NovaPersistentObject, base.NovaObject):
'network_id': fields.IntegerField(),
'instance_uuid': fields.UUIDField(),
'uuid': fields.UUIDField(),
'tag': fields.StringField(nullable=True),
}
def obj_make_compatible(self, primitive, target_version):
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 1) and 'tag' in primitive:
del primitive['tag']
@staticmethod
def _from_db_object(context, vif, db_vif):
for field in vif.fields:

View File

@ -2314,7 +2314,8 @@ class _ComputeAPIUnitTestMixIn(object):
bdm = fake_block_device.FakeDbBlockDeviceDict(
{'no_device': False, 'volume_id': '1', 'boot_index': 0,
'connection_info': 'inf', 'device_name': '/dev/vda',
'source_type': 'volume', 'destination_type': 'volume'})
'source_type': 'volume', 'destination_type': 'volume',
'tag': None})
instance_bdms.append(bdm)
expect_meta['properties']['bdm_v2'] = True
@ -2325,7 +2326,8 @@ class _ComputeAPIUnitTestMixIn(object):
'volume_size': None, 'source_type': 'snapshot',
'device_type': None, 'snapshot_id': '1-snapshot',
'device_name': '/dev/vda',
'destination_type': 'volume', 'delete_on_termination': False})
'destination_type': 'volume', 'delete_on_termination': False,
'tag': None})
# All the db_only fields and the volume ones are removed
self.compute_api.snapshot_volume_backed(
@ -2352,7 +2354,8 @@ class _ComputeAPIUnitTestMixIn(object):
{'no_device': False, 'volume_id': None, 'boot_index': -1,
'connection_info': 'inf', 'device_name': '/dev/vdh',
'source_type': 'blank', 'destination_type': 'local',
'guest_format': 'swap', 'delete_on_termination': True})
'guest_format': 'swap', 'delete_on_termination': True,
'tag': None})
instance_bdms.append(bdm)
expect_meta['properties']['block_device_mapping'].append(
{'guest_format': 'swap', 'boot_index': -1, 'no_device': False,
@ -2360,7 +2363,8 @@ class _ComputeAPIUnitTestMixIn(object):
'volume_size': None, 'source_type': 'blank',
'device_type': None, 'snapshot_id': None,
'device_name': '/dev/vdh',
'destination_type': 'local', 'delete_on_termination': True})
'destination_type': 'local', 'delete_on_termination': True,
'tag': None})
quiesced = [False, False]

View File

@ -6369,7 +6369,8 @@ class VirtualInterfaceTestCase(test.TestCase, ModelsObjectComparatorMixin):
'instance_uuid': self.instance_uuid,
'address': 'fake_address',
'network_id': self.network['id'],
'uuid': str(stdlib_uuid.uuid4())
'uuid': str(stdlib_uuid.uuid4()),
'tag': 'fake-tag',
}
def mock_db_query_first_to_raise_data_error_exception(self):

View File

@ -891,6 +891,10 @@ class NovaMigrationsCheckers(test_migrations.ModelsMigrationsSync,
# Just a sanity-check migration
pass
def _check_331(self, engine, data):
self.assertColumnExists(engine, 'virtual_interfaces', 'tag')
self.assertColumnExists(engine, 'block_device_mapping', 'tag')
class TestNovaMigrationsSQLite(NovaMigrationsCheckers,
test_base.DbTestCase,

View File

@ -60,7 +60,8 @@ class FakeNetworkManager(network_manager.NetworkManager):
'instance_uuid': uuids.instance_1,
'network_id': 1,
'uuid': uuids.vifs_1,
'address': 'DC:AD:BE:FF:EF:01'},
'address': 'DC:AD:BE:FF:EF:01',
'tag': 'fake-tag1'},
{'id': 1,
'created_at': None,
'updated_at': None,
@ -69,7 +70,8 @@ class FakeNetworkManager(network_manager.NetworkManager):
'instance_uuid': uuids.instance_2,
'network_id': 21,
'uuid': uuids.vifs_2,
'address': 'DC:AD:BE:FF:EF:02'},
'address': 'DC:AD:BE:FF:EF:02',
'tag': 'fake-tag2'},
{'id': 2,
'created_at': None,
'updated_at': None,
@ -78,7 +80,8 @@ class FakeNetworkManager(network_manager.NetworkManager):
'instance_uuid': uuids.instance_1,
'network_id': 31,
'uuid': uuids.vifs_3,
'address': 'DC:AD:BE:FF:EF:03'}]
'address': 'DC:AD:BE:FF:EF:03',
'tag': None}]
floating_ips = [dict(address='172.16.1.1',
fixed_ip_id=100),
@ -223,7 +226,8 @@ def fake_vif(x):
'address': 'DE:AD:BE:EF:00:%02x' % x,
'uuid': getattr(uuids, 'vif%i' % x),
'network_id': x,
'instance_uuid': uuids.vifs_1}
'instance_uuid': uuids.vifs_1,
'tag': 'fake-tag'}
def floating_ip_ids():

View File

@ -156,7 +156,8 @@ vifs = [{'id': 0,
'address': 'DE:AD:BE:EF:00:00',
'uuid': '00000000-0000-0000-0000-0000000000000000',
'network_id': 0,
'instance_uuid': 0},
'instance_uuid': 0,
'tag': 'fake-tag1'},
{'id': 1,
'created_at': None,
'updated_at': None,
@ -165,7 +166,8 @@ vifs = [{'id': 0,
'address': 'DE:AD:BE:EF:00:01',
'uuid': '00000000-0000-0000-0000-0000000000000001',
'network_id': 1,
'instance_uuid': 0},
'instance_uuid': 0,
'tag': 'fake-tag2'},
{'id': 2,
'created_at': None,
'updated_at': None,
@ -174,7 +176,8 @@ vifs = [{'id': 0,
'address': 'DE:AD:BE:EF:00:02',
'uuid': '00000000-0000-0000-0000-0000000000000002',
'network_id': 2,
'instance_uuid': 0}]
'instance_uuid': 0,
'tag': 'fake-tag3'}]
class FlatNetworkTestCase(test.TestCase):

View File

@ -362,6 +362,14 @@ class _TestBlockDeviceMappingObject(object):
destination_type='local')
self.assertFalse(bdm.is_volume)
def test_obj_make_compatible_pre_1_17(self):
values = {'source_type': 'volume', 'volume_id': 'fake-vol-id',
'destination_type': 'volume',
'instance_uuid': 'fake-instance'}
bdm = objects.BlockDeviceMapping(context=self.context, **values)
primitive = bdm.obj_to_primitive(target_version='1.16')
self.assertNotIn('tag', primitive)
class TestBlockDeviceMappingObject(test_objects._LocalTest,
_TestBlockDeviceMappingObject):

View File

@ -1103,7 +1103,7 @@ object_data = {
'AggregateList': '1.2-fb6e19f3c3a3186b04eceb98b5dadbfa',
'BandwidthUsage': '1.2-c6e4c779c7f40f2407e3d70022e3cd1c',
'BandwidthUsageList': '1.2-5fe7475ada6fe62413cbfcc06ec70746',
'BlockDeviceMapping': '1.16-f0c172e902bc62f1cac05b17d7be7688',
'BlockDeviceMapping': '1.17-5e094927f1251770dcada6ab05adfcdb',
'BlockDeviceMappingList': '1.17-1e568eecb91d06d4112db9fd656de235',
'BuildRequest': '1.0-e4ca475cabb07f73d8176f661afe8c55',
'CellMapping': '1.0-7f1a7e85a22bbb7559fc730ab658b9bd',
@ -1191,7 +1191,7 @@ object_data = {
'VirtCPUFeature': '1.0-3310718d8c72309259a6e39bdefe83ee',
'VirtCPUModel': '1.0-6a5cc9f322729fc70ddc6733bacd57d3',
'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563',
'VirtualInterface': '1.0-19921e38cba320f355d56ecbf8f29587',
'VirtualInterface': '1.1-422f46c1eaa24a1f63d3360c199cc7c0',
'VirtualInterfaceList': '1.0-9750e2074437b3077e46359102779fc6',
'VolumeUsage': '1.0-6c8190c46ce1469bb3286a1f21c2e475',
'XenapiLiveMigrateData': '1.0-5f982bec68f066e194cd9ce53a24ac4c',

View File

@ -29,6 +29,7 @@ fake_vif = {
'network_id': 123,
'instance_uuid': 'fake-uuid',
'uuid': 'fake-uuid-2',
'tag': 'fake-tag',
}
@ -72,6 +73,7 @@ class _TestVirtualInterface(object):
vif.network_id = 123
vif.instance_uuid = 'fake-uuid'
vif.uuid = 'fake-uuid-2'
vif.tag = 'fake-tag'
with mock.patch.object(db, 'virtual_interface_create') as create:
create.return_value = fake_vif
@ -88,6 +90,17 @@ class _TestVirtualInterface(object):
'fake-uuid')
delete.assert_called_with(self.context, 'fake-uuid')
def test_obj_make_compatible_pre_1_1(self):
vif = vif_obj.VirtualInterface(context=self.context)
vif.address = '00:00:00:00:00:00'
vif.network_id = 123
vif.instance_uuid = 'fake-uuid'
vif.uuid = 'fake-uuid-2'
vif.tag = 'fake-tag'
primitive = vif.obj_to_primitive(target_version='1.0')
self.assertNotIn('tag', primitive)
class TestVirtualInterfaceObject(test_objects._LocalTest,
_TestVirtualInterface):