From e11a1266bb9d0b904045575a2c59b894a3d6666f Mon Sep 17 00:00:00 2001 From: Takashi NATSUME Date: Wed, 5 Jul 2017 13:23:33 +0900 Subject: [PATCH] Microversion 2.49 - Virt device tagged attach Change-Id: I65a2d33710f85e9b8c342d6ff4c1e4cc82990b8c Implements: blueprint virt-device-tagged-attach-detach --- novaclient/__init__.py | 2 +- novaclient/tests/unit/v2/test_servers.py | 26 +++++++++++++ novaclient/tests/unit/v2/test_shell.py | 30 ++++++++++++++ novaclient/tests/unit/v2/test_volumes.py | 24 +++++++++++- novaclient/v2/servers.py | 39 +++++++++++++++++++ novaclient/v2/shell.py | 27 ++++++++++++- novaclient/v2/volumes.py | 22 +++++++++++ .../microversion-v2_49-56bde596ee13366d.yaml | 7 ++++ 8 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/microversion-v2_49-56bde596ee13366d.yaml diff --git a/novaclient/__init__.py b/novaclient/__init__.py index d4aeb9862..768ad119a 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.48") +API_MAX_VERSION = api_versions.APIVersion("2.49") diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 348091d06..04772306b 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -1457,3 +1457,29 @@ class ServersCreateImageBackupV2_45Test(utils.FixturedTestCase): self.assertEqual('456', sb['image_id']) self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') + + +class ServersV249Test(ServersV2_37Test): + + api_version = "2.49" + + def test_interface_attach_with_tag(self): + s = self.cs.servers.get(1234) + ret = s.interface_attach('7f42712e-63fe-484c-a6df-30ae4867ff66', + None, None, 'test_tag') + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', '/servers/1234/os-interface', + {'interfaceAttachment': + {'port_id': '7f42712e-63fe-484c-a6df-30ae4867ff66', + 'tag': 'test_tag'}}) + + def test_add_fixed_ip(self): + # novaclient.v2.servers.Server.add_fixed_ip() + # is not available after 2.44 + pass + + def test_remove_fixed_ip(self): + # novaclient.v2.servers.Server.remove_fixed_ip() + # is not available after 2.44 + pass diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index fa4140cb5..64b7c931b 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -2697,6 +2697,20 @@ class ShellTest(utils.TestCase): self.assert_called('POST', '/servers/1234/os-interface', {'interfaceAttachment': {'port_id': 'port_id'}}) + def test_interface_attach_with_tag_pre_v2_49(self): + self.assertRaises( + SystemExit, self.run_command, + 'interface-attach --port-id port_id --tag test_tag 1234', + api_version='2.48') + + def test_interface_attach_with_tag(self): + self.run_command( + 'interface-attach --port-id port_id --tag test_tag 1234', + api_version='2.49') + self.assert_called('POST', '/servers/1234/os-interface', + {'interfaceAttachment': {'port_id': 'port_id', + 'tag': 'test_tag'}}) + def test_interface_detach(self): self.run_command('interface-detach 1234 port_id') self.assert_called('DELETE', '/servers/1234/os-interface/port_id') @@ -2718,6 +2732,22 @@ class ShellTest(utils.TestCase): {'volumeAttachment': {'volumeId': 'Work'}}) + def test_volume_attach_with_tag_pre_v2_49(self): + self.assertRaises( + SystemExit, self.run_command, + 'volume-attach --tag test_tag sample-server Work /dev/vdb', + api_version='2.48') + + def test_volume_attach_with_tag(self): + self.run_command( + 'volume-attach --tag test_tag sample-server Work /dev/vdb', + api_version='2.49') + self.assert_called('POST', '/servers/1234/os-volume_attachments', + {'volumeAttachment': + {'device': '/dev/vdb', + 'volumeId': 'Work', + 'tag': 'test_tag'}}) + def test_volume_update(self): self.run_command('volume-update sample-server Work Work') self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', diff --git a/novaclient/tests/unit/v2/test_volumes.py b/novaclient/tests/unit/v2/test_volumes.py index eb0fe71fb..60178cb02 100644 --- a/novaclient/tests/unit/v2/test_volumes.py +++ b/novaclient/tests/unit/v2/test_volumes.py @@ -20,9 +20,11 @@ from novaclient.v2 import volumes class VolumesTest(utils.TestCase): + api_version = "2.0" + def setUp(self): super(VolumesTest, self).setUp() - self.cs = fakes.FakeClient(api_versions.APIVersion("2.0")) + self.cs = fakes.FakeClient(api_versions.APIVersion(self.api_version)) def test_create_server_volume(self): v = self.cs.volumes.create_server_volume( @@ -66,3 +68,23 @@ class VolumesTest(utils.TestCase): self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('DELETE', '/servers/1234/os-volume_attachments/Work') + + +class VolumesV249Test(VolumesTest): + api_version = "2.49" + + def test_create_server_volume_with_tag(self): + v = self.cs.volumes.create_server_volume( + server_id=1234, + volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983', + device='/dev/vdb', + tag='test_tag' + ) + self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) + self.cs.assert_called( + 'POST', '/servers/1234/os-volume_attachments', + {'volumeAttachment': { + 'volumeId': '15e59938-07d5-11e1-90e3-e3dffe0c5983', + 'device': '/dev/vdb', + 'tag': 'test_tag'}}) + self.assertIsInstance(v, volumes.Volume) diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index d16396ff6..b60b0b6de 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -577,12 +577,21 @@ class Server(base.Resource): """ return self.manager.interface_list(self) + @api_versions.wraps("2.0", "2.48") def interface_attach(self, port_id, net_id, fixed_ip): """ Attach a network interface to an instance. """ return self.manager.interface_attach(self, port_id, net_id, fixed_ip) + @api_versions.wraps("2.49") + def interface_attach(self, port_id, net_id, fixed_ip, tag=None): + """ + Attach a network interface to an instance with an optional tag. + """ + return self.manager.interface_attach(self, port_id, net_id, fixed_ip, + tag) + def interface_detach(self, port_id): """ Detach a network interface from an instance. @@ -1839,6 +1848,7 @@ class ServerManager(base.BootingManagerWithFind): return self._list('/servers/%s/os-interface' % base.getid(server), 'interfaceAttachments', obj_class=NetworkInterface) + @api_versions.wraps("2.0", "2.48") def interface_attach(self, server, port_id, net_id, fixed_ip): """ Attach a network_interface to an instance. @@ -1859,6 +1869,35 @@ class ServerManager(base.BootingManagerWithFind): return self._create('/servers/%s/os-interface' % base.getid(server), body, 'interfaceAttachment') + @api_versions.wraps("2.49") + def interface_attach(self, server, port_id, net_id, fixed_ip, tag=None): + """ + Attach a network_interface to an instance. + + :param server: The :class:`Server` (or its ID) to attach to. + :param port_id: The port to attach. + The port_id and net_id parameters are mutually + exclusive. + :param net_id: The ID of the network to attach. + :param fixed_ip: The fixed IP addresses. If the fixed_ip is specified, + the net_id has to be specified at the same time. + :param tag: The tag. + """ + + body = {'interfaceAttachment': {}} + if port_id: + body['interfaceAttachment']['port_id'] = port_id + if net_id: + body['interfaceAttachment']['net_id'] = net_id + if fixed_ip: + body['interfaceAttachment']['fixed_ips'] = [ + {'ip_address': fixed_ip}] + if tag: + body['interfaceAttachment']['tag'] = tag + + return self._create('/servers/%s/os-interface' % base.getid(server), + body, 'interfaceAttachment') + def interface_detach(self, server, port_id): """ Detach a network_interface from an instance. diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 7896b7c7f..72d71e2b1 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -2339,14 +2339,25 @@ def _translate_volume_attachments_keys(collection): help=_('Name of the device e.g. /dev/vdb. ' 'Use "auto" for autoassign (if supported). ' 'Libvirt driver will use default device name.')) +@utils.arg( + '--tag', + metavar='', + default=None, + help=_('Tag for the attached volume.'), + start_version="2.49") def do_volume_attach(cs, args): """Attach a volume to a server.""" if args.device == 'auto': args.device = None + update_kwargs = {} + if 'tag' in args and args.tag: + update_kwargs['tag'] = args.tag + volume = cs.volumes.create_server_volume(_find_server(cs, args.server).id, args.volume, - args.device) + args.device, + **update_kwargs) _print_volume(volume) @@ -4376,11 +4387,23 @@ def do_interface_list(cs, args): metavar='', help=_('Requested fixed IP.'), default=None, dest="fixed_ip") +@utils.arg( + '--tag', + metavar='', + default=None, + dest="tag", + help=_('Tag for the attached interface.'), + start_version="2.49") def do_interface_attach(cs, args): """Attach a network interface to a server.""" server = _find_server(cs, args.server) - res = server.interface_attach(args.port_id, args.net_id, args.fixed_ip) + update_kwargs = {} + if 'tag' in args and args.tag: + update_kwargs['tag'] = args.tag + + res = server.interface_attach(args.port_id, args.net_id, args.fixed_ip, + **update_kwargs) if isinstance(res, dict): utils.print_dict(res) diff --git a/novaclient/v2/volumes.py b/novaclient/v2/volumes.py index b0f66f32b..ebccf8a09 100644 --- a/novaclient/v2/volumes.py +++ b/novaclient/v2/volumes.py @@ -17,6 +17,7 @@ Volume interface """ +from novaclient import api_versions from novaclient import base @@ -37,6 +38,7 @@ class VolumeManager(base.Manager): """ resource_class = Volume + @api_versions.wraps("2.0", "2.48") def create_server_volume(self, server_id, volume_id, device=None): """ Attach a volume identified by the volume ID to the given server ID @@ -52,6 +54,26 @@ class VolumeManager(base.Manager): return self._create("/servers/%s/os-volume_attachments" % server_id, body, "volumeAttachment") + @api_versions.wraps("2.49") + def create_server_volume(self, server_id, volume_id, device=None, + tag=None): + """ + Attach a volume identified by the volume ID to the given server ID + + :param server_id: The ID of the server + :param volume_id: The ID of the volume to attach. + :param device: The device name (optional) + :param tag: The tag (optional) + :rtype: :class:`Volume` + """ + body = {'volumeAttachment': {'volumeId': volume_id}} + if device is not None: + body['volumeAttachment']['device'] = device + if tag is not None: + body['volumeAttachment']['tag'] = tag + return self._create("/servers/%s/os-volume_attachments" % server_id, + body, "volumeAttachment") + def update_server_volume(self, server_id, src_volid, dest_volid): """ Swaps the existing volume attachment to point to a new volume. diff --git a/releasenotes/notes/microversion-v2_49-56bde596ee13366d.yaml b/releasenotes/notes/microversion-v2_49-56bde596ee13366d.yaml new file mode 100644 index 000000000..86120be48 --- /dev/null +++ b/releasenotes/notes/microversion-v2_49-56bde596ee13366d.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added support for microversion 2.49 that enables users to + attach tagged interfaces and volumes. + A new ``--tag`` option is added to ``nova volume-attach`` and + ``nova interface-attach`` commands.