Microversion 2.49 - Virt device tagged attach

Change-Id: I65a2d33710f85e9b8c342d6ff4c1e4cc82990b8c
Implements: blueprint virt-device-tagged-attach-detach
This commit is contained in:
Takashi NATSUME 2017-07-05 13:23:33 +09:00
parent bd0a2adefe
commit e11a1266bb
8 changed files with 173 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 # when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some # the client may break due to server side new version may include some
# backward incompatible change. # backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.48") API_MAX_VERSION = api_versions.APIVersion("2.49")

View File

@ -1457,3 +1457,29 @@ class ServersCreateImageBackupV2_45Test(utils.FixturedTestCase):
self.assertEqual('456', sb['image_id']) self.assertEqual('456', sb['image_id'])
self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers/1234/action') 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

View File

@ -2697,6 +2697,20 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/1234/os-interface', self.assert_called('POST', '/servers/1234/os-interface',
{'interfaceAttachment': {'port_id': 'port_id'}}) {'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): def test_interface_detach(self):
self.run_command('interface-detach 1234 port_id') self.run_command('interface-detach 1234 port_id')
self.assert_called('DELETE', '/servers/1234/os-interface/port_id') self.assert_called('DELETE', '/servers/1234/os-interface/port_id')
@ -2718,6 +2732,22 @@ class ShellTest(utils.TestCase):
{'volumeAttachment': {'volumeAttachment':
{'volumeId': 'Work'}}) {'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): def test_volume_update(self):
self.run_command('volume-update sample-server Work Work') self.run_command('volume-update sample-server Work Work')
self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work',

View File

@ -20,9 +20,11 @@ from novaclient.v2 import volumes
class VolumesTest(utils.TestCase): class VolumesTest(utils.TestCase):
api_version = "2.0"
def setUp(self): def setUp(self):
super(VolumesTest, self).setUp() 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): def test_create_server_volume(self):
v = self.cs.volumes.create_server_volume( 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.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST)
self.cs.assert_called('DELETE', self.cs.assert_called('DELETE',
'/servers/1234/os-volume_attachments/Work') '/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)

View File

@ -577,12 +577,21 @@ class Server(base.Resource):
""" """
return self.manager.interface_list(self) return self.manager.interface_list(self)
@api_versions.wraps("2.0", "2.48")
def interface_attach(self, port_id, net_id, fixed_ip): def interface_attach(self, port_id, net_id, fixed_ip):
""" """
Attach a network interface to an instance. Attach a network interface to an instance.
""" """
return self.manager.interface_attach(self, port_id, net_id, fixed_ip) 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): def interface_detach(self, port_id):
""" """
Detach a network interface from an instance. 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), return self._list('/servers/%s/os-interface' % base.getid(server),
'interfaceAttachments', obj_class=NetworkInterface) 'interfaceAttachments', obj_class=NetworkInterface)
@api_versions.wraps("2.0", "2.48")
def interface_attach(self, server, port_id, net_id, fixed_ip): def interface_attach(self, server, port_id, net_id, fixed_ip):
""" """
Attach a network_interface to an instance. 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), return self._create('/servers/%s/os-interface' % base.getid(server),
body, 'interfaceAttachment') 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): def interface_detach(self, server, port_id):
""" """
Detach a network_interface from an instance. Detach a network_interface from an instance.

View File

@ -2339,14 +2339,25 @@ def _translate_volume_attachments_keys(collection):
help=_('Name of the device e.g. /dev/vdb. ' help=_('Name of the device e.g. /dev/vdb. '
'Use "auto" for autoassign (if supported). ' 'Use "auto" for autoassign (if supported). '
'Libvirt driver will use default device name.')) 'Libvirt driver will use default device name.'))
@utils.arg(
'--tag',
metavar='<tag>',
default=None,
help=_('Tag for the attached volume.'),
start_version="2.49")
def do_volume_attach(cs, args): def do_volume_attach(cs, args):
"""Attach a volume to a server.""" """Attach a volume to a server."""
if args.device == 'auto': if args.device == 'auto':
args.device = None 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, volume = cs.volumes.create_server_volume(_find_server(cs, args.server).id,
args.volume, args.volume,
args.device) args.device,
**update_kwargs)
_print_volume(volume) _print_volume(volume)
@ -4376,11 +4387,23 @@ def do_interface_list(cs, args):
metavar='<fixed_ip>', metavar='<fixed_ip>',
help=_('Requested fixed IP.'), help=_('Requested fixed IP.'),
default=None, dest="fixed_ip") default=None, dest="fixed_ip")
@utils.arg(
'--tag',
metavar='<tag>',
default=None,
dest="tag",
help=_('Tag for the attached interface.'),
start_version="2.49")
def do_interface_attach(cs, args): def do_interface_attach(cs, args):
"""Attach a network interface to a server.""" """Attach a network interface to a server."""
server = _find_server(cs, args.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): if isinstance(res, dict):
utils.print_dict(res) utils.print_dict(res)

View File

@ -17,6 +17,7 @@
Volume interface Volume interface
""" """
from novaclient import api_versions
from novaclient import base from novaclient import base
@ -37,6 +38,7 @@ class VolumeManager(base.Manager):
""" """
resource_class = Volume resource_class = Volume
@api_versions.wraps("2.0", "2.48")
def create_server_volume(self, server_id, volume_id, device=None): def create_server_volume(self, server_id, volume_id, device=None):
""" """
Attach a volume identified by the volume ID to the given server ID 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, return self._create("/servers/%s/os-volume_attachments" % server_id,
body, "volumeAttachment") 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): def update_server_volume(self, server_id, src_volid, dest_volid):
""" """
Swaps the existing volume attachment to point to a new volume. Swaps the existing volume attachment to point to a new volume.

View File

@ -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.