From 7fb0bd4f357a7f9e24c864584b398a25171580cf Mon Sep 17 00:00:00 2001 From: Artom Lifshitz Date: Wed, 30 Mar 2016 04:58:50 -0400 Subject: [PATCH] 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 --- novaclient/__init__.py | 2 +- .../functional/v2/test_device_tagging.py | 36 +++++++++++ novaclient/tests/unit/v2/test_servers.py | 50 ++++++++++++++++ novaclient/tests/unit/v2/test_shell.py | 60 +++++++++++++++++++ novaclient/v2/servers.py | 18 ++++++ novaclient/v2/shell.py | 58 +++++++++++++++++- .../microversion-v2_32-7947430cc2415597.yaml | 25 ++++++++ 7 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 novaclient/tests/functional/v2/test_device_tagging.py create mode 100644 releasenotes/notes/microversion-v2_32-7947430cc2415597.yaml diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 55c0acec8..e13263741 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -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") diff --git a/novaclient/tests/functional/v2/test_device_tagging.py b/novaclient/tests/functional/v2/test_device_tagging.py new file mode 100644 index 000000000..d9a5850d2 --- /dev/null +++ b/novaclient/tests/functional/v2/test_device_tagging.py @@ -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) diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 38bf1a4ba..38a6bd367 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -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) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index f0593e443..587b6338f 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -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)) diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index 1ddbc721b..ebeaaee30 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -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, diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 7394316cd..6252aa3bc 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -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 , 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="", @@ -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="", + 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="", diff --git a/releasenotes/notes/microversion-v2_32-7947430cc2415597.yaml b/releasenotes/notes/microversion-v2_32-7947430cc2415597.yaml new file mode 100644 index 000000000..69d6f5e1f --- /dev/null +++ b/releasenotes/notes/microversion-v2_32-7947430cc2415597.yaml @@ -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.