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:
parent
c368a3e802
commit
9ba07fdf9f
@ -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'])
|
||||
|
@ -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())
|
@ -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?
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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():
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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',
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user