Add support for microversion 2.70 - expose device tags

This adds support for microversion 2.70 which exposes
the 'tag' field in the following APIs:

* GET /servers/{server_id}/os-volume_attachments
* GET /servers/{server_id}/os-volume_attachments/{volume_id}
* POST /servers/{server_id}/os-volume_attachments
* GET /servers/{server_id}/os-interface
* GET /servers/{server_id}/os-interface/{port_id}
* POST /servers/{server_id}/os-interface

Which corresponds to showing the tag in the output of
the following commands:

* nova volume-attachments
* nova volume-attach
* nova interface-list
* nova interface-attach

Depends-On: https://review.openstack.org/631948/

Part of blueprint expose-virtual-device-tags-in-rest-api

Change-Id: I5e9d7e0219605503a56d2cf745b95c6e05d01101
This commit is contained in:
Matt Riedemann 2019-02-13 18:29:39 -05:00
parent d64677701c
commit de22cdd3c8
5 changed files with 96 additions and 22 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.69") API_MAX_VERSION = api_versions.APIVersion("2.70")

View File

@ -2004,7 +2004,7 @@ class FakeSessionClient(base_client.SessionClient):
"hosts": None}]}) "hosts": None}]})
def get_servers_1234_os_interface(self, **kw): def get_servers_1234_os_interface(self, **kw):
return (200, {}, { attachments = {
"interfaceAttachments": [ "interfaceAttachments": [
{"port_state": "ACTIVE", {"port_state": "ACTIVE",
"net_id": "net-id-1", "net_id": "net-id-1",
@ -2017,27 +2017,38 @@ class FakeSessionClient(base_client.SessionClient):
"port_id": "port-id-1", "port_id": "port-id-1",
"mac_address": "aa:bb:cc:dd:ee:ff", "mac_address": "aa:bb:cc:dd:ee:ff",
"fixed_ips": [{"ip_address": "1.2.3.4"}], "fixed_ips": [{"ip_address": "1.2.3.4"}],
}] }
}) ]
}
if self.api_version >= api_versions.APIVersion('2.70'):
# Include the "tag" field in each attachment.
for attachment in attachments['interfaceAttachments']:
attachment['tag'] = 'test-tag'
return (200, {}, attachments)
def post_servers_1234_os_interface(self, **kw): def post_servers_1234_os_interface(self, **kw):
return (200, {}, {'interfaceAttachment': {}}) attachment = {}
if self.api_version >= api_versions.APIVersion('2.70'):
# Include the "tag" field in the response.
attachment['tag'] = 'test-tag'
return (200, {}, {'interfaceAttachment': attachment})
def delete_servers_1234_os_interface_port_id(self, **kw): def delete_servers_1234_os_interface_port_id(self, **kw):
return (200, {}, None) return (200, {}, None)
def post_servers_1234_os_volume_attachments(self, **kw): def post_servers_1234_os_volume_attachments(self, **kw):
return (200, FAKE_RESPONSE_HEADERS, { attachment = {"device": "/dev/vdb", "volumeId": 2}
"volumeAttachment": if self.api_version >= api_versions.APIVersion('2.70'):
{"device": "/dev/vdb", # Include the "tag" field in the response.
"volumeId": 2}}) attachment['tag'] = 'test-tag'
return (200, FAKE_RESPONSE_HEADERS, {"volumeAttachment": attachment})
def put_servers_1234_os_volume_attachments_Work(self, **kw): def put_servers_1234_os_volume_attachments_Work(self, **kw):
return (200, FAKE_RESPONSE_HEADERS, return (200, FAKE_RESPONSE_HEADERS,
{"volumeAttachment": {"volumeId": 2}}) {"volumeAttachment": {"volumeId": 2}})
def get_servers_1234_os_volume_attachments(self, **kw): def get_servers_1234_os_volume_attachments(self, **kw):
return (200, FAKE_RESPONSE_HEADERS, { attachments = {
"volumeAttachments": [ "volumeAttachments": [
{"display_name": "Work", {"display_name": "Work",
"display_description": "volume for work", "display_description": "volume for work",
@ -2047,7 +2058,14 @@ class FakeSessionClient(base_client.SessionClient):
"attached": "2011-11-11T00:00:00Z", "attached": "2011-11-11T00:00:00Z",
"size": 1024, "size": 1024,
"attachments": [{"id": "3333", "links": ''}], "attachments": [{"id": "3333", "links": ''}],
"metadata": {}}]}) "metadata": {}}
]
}
if self.api_version >= api_versions.APIVersion('2.70'):
# Include the "tag" field in each attachment.
for attachment in attachments['volumeAttachments']:
attachment['tag'] = 'test-tag'
return (200, FAKE_RESPONSE_HEADERS, attachments)
def get_servers_1234_os_volume_attachments_Work(self, **kw): def get_servers_1234_os_volume_attachments_Work(self, **kw):
return (200, FAKE_RESPONSE_HEADERS, { return (200, FAKE_RESPONSE_HEADERS, {

View File

@ -3518,8 +3518,14 @@ class ShellTest(utils.TestCase):
self.assert_called('GET', '/servers/1234/os-security-groups') self.assert_called('GET', '/servers/1234/os-security-groups')
def test_interface_list(self): def test_interface_list(self):
self.run_command('interface-list 1234') out = self.run_command('interface-list 1234')[0]
self.assert_called('GET', '/servers/1234/os-interface') self.assert_called('GET', '/servers/1234/os-interface')
self.assertNotIn('Tag', out)
def test_interface_list_v2_70(self):
out = self.run_command('interface-list 1234', api_version='2.70')[0]
self.assert_called('GET', '/servers/1234/os-interface')
self.assertIn('test-tag', out)
def test_interface_attach(self): def test_interface_attach(self):
self.run_command('interface-attach --port-id port_id 1234') self.run_command('interface-attach --port-id port_id 1234')
@ -3533,20 +3539,37 @@ class ShellTest(utils.TestCase):
api_version='2.48') api_version='2.48')
def test_interface_attach_with_tag(self): def test_interface_attach_with_tag(self):
self.run_command( out = self.run_command(
'interface-attach --port-id port_id --tag test_tag 1234', 'interface-attach --port-id port_id --tag test-tag 1234',
api_version='2.49') api_version='2.49')[0]
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',
'tag': 'test_tag'}}) 'tag': 'test-tag'}})
self.assertNotIn('test-tag', out)
def test_interface_attach_v2_70(self):
out = self.run_command(
'interface-attach --port-id port_id --tag test-tag 1234',
api_version='2.70')[0]
self.assert_called('POST', '/servers/1234/os-interface',
{'interfaceAttachment': {'port_id': 'port_id',
'tag': 'test-tag'}})
self.assertIn('test-tag', out)
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')
def test_volume_attachments(self): def test_volume_attachments(self):
self.run_command('volume-attachments 1234') out = self.run_command('volume-attachments 1234')[0]
self.assert_called('GET', '/servers/1234/os-volume_attachments') self.assert_called('GET', '/servers/1234/os-volume_attachments')
self.assertNotIn('test-tag', out)
def test_volume_attachments_v2_70(self):
out = self.run_command(
'volume-attachments 1234', api_version='2.70')[0]
self.assert_called('GET', '/servers/1234/os-volume_attachments')
self.assertIn('test-tag', out)
def test_volume_attach(self): def test_volume_attach(self):
self.run_command('volume-attach sample-server Work /dev/vdb') self.run_command('volume-attach sample-server Work /dev/vdb')
@ -3568,14 +3591,26 @@ class ShellTest(utils.TestCase):
api_version='2.48') api_version='2.48')
def test_volume_attach_with_tag(self): def test_volume_attach_with_tag(self):
self.run_command( out = self.run_command(
'volume-attach --tag test_tag sample-server Work /dev/vdb', 'volume-attach --tag test_tag sample-server Work /dev/vdb',
api_version='2.49') api_version='2.49')[0]
self.assert_called('POST', '/servers/1234/os-volume_attachments', self.assert_called('POST', '/servers/1234/os-volume_attachments',
{'volumeAttachment': {'volumeAttachment':
{'device': '/dev/vdb', {'device': '/dev/vdb',
'volumeId': 'Work', 'volumeId': 'Work',
'tag': 'test_tag'}}) 'tag': 'test_tag'}})
self.assertNotIn('test-tag', out)
def test_volume_attach_with_tag_v2_70(self):
out = self.run_command(
'volume-attach --tag test-tag sample-server Work /dev/vdb',
api_version='2.70')[0]
self.assert_called('POST', '/servers/1234/os-volume_attachments',
{'volumeAttachment':
{'device': '/dev/vdb',
'volumeId': 'Work',
'tag': 'test-tag'}})
self.assertIn('test-tag', out)
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')
@ -4045,6 +4080,7 @@ class ShellTest(utils.TestCase):
# cell, they will be handled on the client side by being # cell, they will be handled on the client side by being
# skipped when forming the detailed lists for embedded # skipped when forming the detailed lists for embedded
# flavor information. # flavor information.
70, # There are no version-wrapped shell method changes for this.
]) ])
versions_supported = set(range(0, versions_supported = set(range(0,
novaclient.API_MAX_VERSION.ver_minor + 1)) novaclient.API_MAX_VERSION.ver_minor + 1))

View File

@ -2617,7 +2617,11 @@ def do_volume_attachments(cs, args):
"""List all the volumes attached to a server.""" """List all the volumes attached to a server."""
volumes = cs.volumes.get_server_volumes(_find_server(cs, args.server).id) volumes = cs.volumes.get_server_volumes(_find_server(cs, args.server).id)
_translate_volume_attachments_keys(volumes) _translate_volume_attachments_keys(volumes)
utils.print_list(volumes, ['ID', 'DEVICE', 'SERVER ID', 'VOLUME ID']) # Microversion >= 2.70 returns the tag value.
fields = ['ID', 'DEVICE', 'SERVER ID', 'VOLUME ID']
if cs.api_version >= api_versions.APIVersion('2.70'):
fields.append('TAG')
utils.print_list(volumes, fields)
@api_versions.wraps('2.0', '2.5') @api_versions.wraps('2.0', '2.5')
@ -4497,9 +4501,11 @@ def do_evacuate(cs, args):
utils.print_dict(res) utils.print_dict(res)
def _print_interfaces(interfaces): def _print_interfaces(interfaces, show_tag=False):
columns = ['Port State', 'Port ID', 'Net ID', 'IP addresses', columns = ['Port State', 'Port ID', 'Net ID', 'IP addresses',
'MAC Addr'] 'MAC Addr']
if show_tag:
columns.append('Tag')
class FormattedInterface(object): class FormattedInterface(object):
def __init__(self, interface): def __init__(self, interface):
@ -4519,7 +4525,9 @@ def do_interface_list(cs, args):
res = server.interface_list() res = server.interface_list()
if isinstance(res, list): if isinstance(res, list):
_print_interfaces(res) # The "tag" field is in the response starting with microversion 2.70.
show_tag = cs.api_version >= api_versions.APIVersion('2.70')
_print_interfaces(res, show_tag=show_tag)
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.')) @utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))

View File

@ -0,0 +1,12 @@
---
features:
- |
Added support for `microversion 2.70`_ which outputs the `Tag` field in
the following commands:
* ``nova interface-list``
* ``nova interface-attach``
* ``nova volume-attachments``
* ``nova volume-attach``
.. _microversion 2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id63