Virtual device tagging client support

In order to support virtual device role tagging that was introduced in
microversion 2.32, this patch adds:

* The 'tag' key to the --nic flag when booting an instance.
* The 'tag' key to the --block-device flag when booting an instance.

Change-Id: I1866c670994254bc2849b494e318f4ff8cc44eb7
Implements: blueprint virt-device-role-tagging
This commit is contained in:
Artom Lifshitz 2016-03-30 04:58:50 -04:00
parent 423239c975
commit 7fb0bd4f35
7 changed files with 245 additions and 4 deletions

View File

@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.31")
API_MAX_VERSION = api_versions.APIVersion("2.32")

View File

@ -0,0 +1,36 @@
# 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.
import uuid
from novaclient.tests.functional import base
class TestDeviceTaggingCLI(base.ClientTestBase):
COMPUTE_API_VERSION = "2.32"
def test_boot_server_with_tagged_devices(self):
server_info = self.nova('boot', params=(
'%(name)s --flavor %(flavor)s --poll '
'--nic net-id=%(net-uuid)s,tag=foo '
'--block-device '
'source=image,dest=volume,id=%(image)s,size=1,'
'bootindex=0,tag=bar' % {'name': str(uuid.uuid4()),
'flavor': self.flavor.id,
'net-uuid': self.network.id,
'image': self.image.id}))
server_id = self._get_value_from_the_table(server_info, 'id')
self.client.servers.delete(server_id)
self.wait_for_resource_delete(server_id, self.client.servers)

View File

@ -1217,3 +1217,53 @@ class ServersV230Test(ServersV229Test):
{'os-migrateLive': {'host': 'hostname',
'block_migration': 'auto',
'force': True}})
class ServersV232Test(ServersV226Test):
def setUp(self):
super(ServersV232Test, self).setUp()
self.cs.api_version = api_versions.APIVersion("2.32")
def test_create_server_boot_with_tagged_nics(self):
nics = [{'net-id': '11111111-1111-1111-1111-111111111111',
'tag': 'one'},
{'net-id': '22222222-2222-2222-2222-222222222222',
'tag': 'two'}]
self.cs.servers.create(name="Server with tagged nics",
image=1,
flavor=1,
nics=nics)
self.assert_called('POST', '/servers')
def test_create_server_boot_with_tagged_nics_pre232(self):
self.cs.api_version = api_versions.APIVersion("2.31")
nics = [{'net-id': '11111111-1111-1111-1111-111111111111',
'tag': 'one'},
{'net-id': '22222222-2222-2222-2222-222222222222',
'tag': 'two'}]
self.assertRaises(ValueError, self.cs.servers.create,
name="Server with tagged nics", image=1, flavor=1,
nics=nics)
def test_create_server_boot_from_volume_tagged_bdm_v2(self):
bdm = [{"volume_size": "1",
"volume_id": "11111111-1111-1111-1111-111111111111",
"delete_on_termination": "0",
"device_name": "vda", "tag": "foo"}]
s = self.cs.servers.create(name="My server", image=1, flavor=1,
meta={'foo': 'bar'}, userdata="hello moto",
key_name="fakekey",
block_device_mapping_v2=bdm)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/os-volumes_boot')
def test_create_server_boot_from_volume_tagged_bdm_v2_pre232(self):
self.cs.api_version = api_versions.APIVersion("2.31")
bdm = [{"volume_size": "1",
"volume_id": "11111111-1111-1111-1111-111111111111",
"delete_on_termination": "0",
"device_name": "vda", "tag": "foo"}]
self.assertRaises(ValueError, self.cs.servers.create, name="My server",
image=1, flavor=1, meta={'foo': 'bar'},
userdata="hello moto", key_name="fakekey",
block_device_mapping_v2=bdm)

View File

@ -343,6 +343,44 @@ class ShellTest(utils.TestCase):
}},
)
def test_boot_image_bdms_v2_with_tag(self):
self.run_command(
'boot --flavor 1 --image 1 --block-device id=fake-id,'
'source=volume,dest=volume,device=vda,size=1,format=ext4,'
'type=disk,shutdown=preserve,tag=foo some-server',
api_version='2.32'
)
self.assert_called_anytime(
'POST', '/os-volumes_boot',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'block_device_mapping_v2': [
{
'uuid': 1,
'source_type': 'image',
'destination_type': 'local',
'boot_index': 0,
'delete_on_termination': True,
},
{
'uuid': 'fake-id',
'source_type': 'volume',
'destination_type': 'volume',
'device_name': 'vda',
'volume_size': '1',
'guest_format': 'ext4',
'device_type': 'disk',
'delete_on_termination': False,
'tag': 'foo',
},
],
'imageRef': '1',
'min_count': 1,
'max_count': 1,
}},
)
def test_boot_no_image_bdms_v2(self):
self.run_command(
'boot --flavor 1 --block-device id=fake-id,source=volume,'
@ -523,6 +561,26 @@ class ShellTest(utils.TestCase):
},
)
def test_boot_nics_with_tag(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,tag=foo some-server')
self.run_command(cmd, api_version='2.32')
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'min_count': 1,
'max_count': 1,
'networks': [
{'uuid': 'a=c', 'fixed_ip': '10.0.0.1', 'tag': 'foo'},
],
},
},
)
def test_boot_nics_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=2001:db9:0:1::10 some-server')
@ -2864,6 +2922,8 @@ class ShellTest(utils.TestCase):
# not explicitly tested via wraps and _SUBSTITUTIONS.
28, # doesn't require any changes in novaclient
31, # doesn't require any changes in novaclient
32, # doesn't require separate version-wrapped methods in
# novaclient
])
versions_supported = set(range(0,
novaclient.API_MAX_VERSION.ver_minor + 1))

View File

@ -727,6 +727,8 @@ class ServerManager(base.BootingManagerWithFind):
net_data['fixed_ip'] = nic_info['v6-fixed-ip']
if nic_info.get('port-id'):
net_data['port'] = nic_info['port-id']
if nic_info.get('tag'):
net_data['tag'] = nic_info['tag']
all_net_data.append(net_data)
body['server']['networks'] = all_net_data
@ -1287,6 +1289,22 @@ class ServerManager(base.BootingManagerWithFind):
if "description" in kwargs and self.api_version < descr_microversion:
raise exceptions.UnsupportedAttribute("description", "2.19")
tags_microversion = api_versions.APIVersion("2.32")
if self.api_version < tags_microversion:
if nics:
for nic_info in nics:
if nic_info.get("tag"):
raise ValueError("Setting interface tags is "
"unsupported before microversion "
"2.32")
if block_device_mapping_v2:
for bdm in block_device_mapping_v2:
if bdm.get("tag"):
raise ValueError("Setting block device tags is "
"unsupported before microversion "
"2.32")
boot_kwargs = dict(
meta=meta, files=files, userdata=userdata,
reservation_id=reservation_id, min_count=min_count,

View File

@ -63,6 +63,7 @@ CLIENT_BDM2_KEYS = {
'bootindex': 'boot_index',
'type': 'device_type',
'shutdown': 'delete_on_termination',
'tag': 'tag',
}
@ -269,10 +270,10 @@ def _boot(cs, args):
err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of "
"the form --nic <net-id=net-uuid,net-name=network-name,"
"v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
"port-id=port-uuid>, with only one of net-id, net-name "
"or port-id specified.") % nic_str)
"port-id=port-uuid,tag=tag>, with only one of net-id, "
"net-name or port-id specified.") % nic_str)
nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "",
"port-id": "", "net-name": ""}
"port-id": "", "net-name": "", "tag": ""}
for kv_str in nic_str.split(","):
try:
@ -438,6 +439,8 @@ def _boot(cs, args):
metavar="key1=value1[,key2=value2...]",
action='append',
default=[],
start_version='2.0',
end_version='2.31',
help=_("Block device mapping with the keys: "
"id=UUID (image_id, snapshot_id or volume_id only if using source "
"image, snapshot or volume) "
@ -460,6 +463,35 @@ def _boot(cs, args):
"for others need to be specified) and "
"shutdown=shutdown behaviour (either preserve or remove, "
"for local destination set to remove)."))
@utils.arg(
'--block-device',
metavar="key1=value1[,key2=value2...]",
action='append',
default=[],
start_version='2.32',
help=_("Block device mapping with the keys: "
"id=UUID (image_id, snapshot_id or volume_id only if using source "
"image, snapshot or volume) "
"source=source type (image, snapshot, volume or blank), "
"dest=destination type of the block device (volume or local), "
"bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, "
"hypervisor driver chooses a suitable default, "
"honoured only if device type is supplied) "
"type=device type (e.g. disk, cdrom, ...; defaults to 'disk') "
"device=name of the device (e.g. vda, xda, ...; "
"tag=device metadata tag (optional) "
"if omitted, hypervisor driver chooses suitable device "
"depending on selected bus; note the libvirt driver always "
"uses default device names), "
"size=size of the block device in MB(for swap) and in "
"GB(for other formats) "
"(if omitted, hypervisor driver calculates size), "
"format=device will be formatted (e.g. swap, ntfs, ...; optional), "
"bootindex=integer used for ordering the boot disks "
"(for image backed instances it is equal to 0, "
"for others need to be specified) and "
"shutdown=shutdown behaviour (either preserve or remove, "
"for local destination set to remove)."))
@utils.arg(
'--swap',
metavar="<swap_size>",
@ -487,6 +519,8 @@ def _boot(cs, args):
action='append',
dest='nics',
default=[],
start_version='2.0',
end_version='2.31',
help=_("Create a NIC on the server. "
"Specify option multiple times to create multiple NICs. "
"net-id: attach NIC to network with this UUID "
@ -496,6 +530,24 @@ def _boot(cs, args):
"v6-fixed-ip: IPv6 fixed address for NIC (optional), "
"port-id: attach NIC to port with this UUID "
"(either port-id or net-id must be provided)."))
@utils.arg(
'--nic',
metavar="<net-id=net-uuid,net-name=network-name,v4-fixed-ip=ip-addr,"
"v6-fixed-ip=ip-addr,port-id=port-uuid>",
action='append',
dest='nics',
default=[],
start_version='2.32',
help=_("Create a NIC on the server. "
"Specify option multiple times to create multiple nics. "
"net-id: attach NIC to network with this UUID "
"net-name: attach NIC to network with this name "
"(either port-id or net-id or net-name must be provided), "
"v4-fixed-ip: IPv4 fixed address for NIC (optional), "
"v6-fixed-ip: IPv6 fixed address for NIC (optional), "
"port-id: attach NIC to port with this UUID "
"tag: interface metadata tag (optional) "
"(either port-id or net-id must be provided)."))
@utils.arg(
'--config-drive',
metavar="<value>",

View File

@ -0,0 +1,25 @@
---
features:
- |
The 2.32 microverison adds support for virtual device
role tagging. Device role tagging is an answer to the
question 'Which device is which?' from inside the guest.
When booting an instance, an optional arbitrary 'tag'
parameter can be set on virtual network interfaces
and/or block device mappings. This tag is exposed to the
instance through the metadata API and on the config
drive. Each tagged virtual network interface is listed
along with information about the virtual hardware, such
as bus type (ex: PCI), bus address (ex: 0000:00:02.0),
and MAC address. For tagged block devices, the exposed
hardware metadata includes the bus (ex: SCSI), bus
address (ex: 1:0:2:0) and serial number.
In the client, device tagging is exposed with the 'tag'
key in the --block-device and --nic boot arguments.
issues:
- |
While not an issue with the client itself, it should be
noted that if a tagged network interface or volume is
detached from a guest, its metadata will continue to
appear in the config drive, even after instance reboot.