From 09b51a294ecbe8898580fa75c124a569a386d29f Mon Sep 17 00:00:00 2001 From: scottda Date: Fri, 8 Apr 2016 15:35:41 -0600 Subject: [PATCH] Add api-version to get server versions Mitaka Cinder added an API to return Versions from the base endpoint URL: http://:8776/ This patch exposes that API for /v3 endpoint microversions 3.0 and above with the command: cinder api-version Implements: blueprint add-get-server-versions Change-Id: Ieb1a56b28188ec17946fe5564b28c165833ffc24 --- cinderclient/base.py | 8 +++ cinderclient/client.py | 15 +++++ cinderclient/tests/unit/test_client.py | 7 +++ cinderclient/tests/unit/v2/fakes.py | 68 ++++++++++++++++++++- cinderclient/tests/unit/v2/test_services.py | 5 ++ cinderclient/v3/services.py | 14 +++++ cinderclient/v3/shell.py | 10 +++ 7 files changed, 126 insertions(+), 1 deletion(-) diff --git a/cinderclient/base.py b/cinderclient/base.py index 528489a..9cf3300 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -335,6 +335,14 @@ class Manager(common_base.HookableMixin): body = body or {} return common_base.DictWithMeta(body, resp) + def _get_with_base_url(self, url, response_key=None): + resp, body = self.api.client.get_with_base_url(url) + if response_key: + return [self.resource_class(self, res, loaded=True) + for res in body[response_key] if res] + else: + return self.resource_class(self, body, loaded=True) + class ManagerWithFind(six.with_metaclass(abc.ABCMeta, Manager)): """ diff --git a/cinderclient/client.py b/cinderclient/client.py index 557a0a1..8d43176 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -151,6 +151,11 @@ class SessionClient(adapter.LegacyJsonAdapter): def delete(self, url, **kwargs): return self._cs_request(url, 'DELETE', **kwargs) + def _get_base_url(self): + endpoint = self.get_endpoint() + base_url = '/'.join(endpoint.split('/')[:3]) + '/' + return base_url + def get_volume_api_version_from_endpoint(self): try: version = get_volume_api_from_url(self.get_endpoint()) @@ -176,6 +181,16 @@ class SessionClient(adapter.LegacyJsonAdapter): raise AttributeError('There is no service catalog for this type of ' 'auth plugin.') + def _cs_request_base_url(self, url, method, **kwargs): + base_url = self._get_base_url(**kwargs) + return self._cs_request( + base_url + url, + method, + **kwargs) + + def get_with_base_url(self, url, **kwargs): + return self._cs_request_base_url(url, 'GET', **kwargs) + class HTTPClient(object): diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index 253b9d2..2962818 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -79,6 +79,13 @@ class ClientTest(utils.TestCase): cinderclient.client.get_volume_api_from_url, unknown_url) + @mock.patch('cinderclient.client.SessionClient.get_endpoint') + def test_get_base_url(self, mock_get_endpoint): + url = 'http://192.168.122.104:8776/v3/de50d1f33a38415fadfd3e1dea28f4d3' + mock_get_endpoint.return_value = url + cs = cinderclient.client.SessionClient(self, api_version='3.0') + self.assertEqual('http://192.168.122.104:8776/', cs._get_base_url()) + @mock.patch.object(cinderclient.client, '_log_request_id') @mock.patch.object(adapter.Adapter, 'request') @mock.patch.object(exceptions, 'from_response') diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index af44155..ff54c6a 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -249,6 +249,65 @@ def _stub_extend(id, new_size): return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'} +def _stub_server_versions(): + return [ + { + "status": "SUPPORTED", + "updated": "2015-07-30T11:33:21Z", + "links": [ + { + "href": "http://docs.openstack.org/", + "type": "text/html", + "rel": "describedby", + }, + { + "href": "http://localhost:8776/v1/", + "rel": "self", + } + ], + "min_version": "", + "version": "", + "id": "v1.0", + }, + { + "status": "SUPPORTED", + "updated": "2015-09-30T11:33:21Z", + "links": [ + { + "href": "http://docs.openstack.org/", + "type": "text/html", + "rel": "describedby", + }, + { + "href": "http://localhost:8776/v2/", + "rel": "self", + } + ], + "min_version": "", + "version": "", + "id": "v2.0", + }, + { + "status": "CURRENT", + "updated": "2016-04-01T11:33:21Z", + "links": [ + { + "href": "http://docs.openstack.org/", + "type": "text/html", + "rel": "describedby", + }, + { + "href": "http://localhost:8776/v3/", + "rel": "self", + } + ], + "min_version": "3.0", + "version": "3.1", + "id": "v3.0", + } + ] + + class FakeClient(fakes.FakeClient, client.Client): def __init__(self, api_version=None, *args, **kwargs): @@ -264,7 +323,7 @@ class FakeClient(fakes.FakeClient, client.Client): class FakeHTTPClient(base_client.HTTPClient): - def __init__(self, **kwargs): + def __init__(self, version_header=None, **kwargs): self.username = 'username' self.password = 'password' self.auth_url = 'auth_url' @@ -272,6 +331,7 @@ class FakeHTTPClient(base_client.HTTPClient): self.management_url = 'http://10.0.2.15:8776/v2/fake' self.osapi_max_limit = 1000 self.marker = None + self.version_header = version_header def _cs_request(self, url, method, **kwargs): # Check that certain things are called correctly @@ -308,6 +368,8 @@ class FakeHTTPClient(base_client.HTTPClient): status, headers, body = getattr(self, callback)(**kwargs) # add fake request-id header headers['x-openstack-request-id'] = REQUEST_ID + if self.version_header: + headers['OpenStack-API-version'] = version_header r = utils.TestResponse({ "status_code": status, "text": body, @@ -969,6 +1031,10 @@ class FakeHTTPClient(base_client.HTTPClient): return (200, {}, {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) + def get_with_base_url(self, url, **kw): + server_versions = _stub_server_versions() + return (200, {'versions': server_versions}) + # # Services # diff --git a/cinderclient/tests/unit/v2/test_services.py b/cinderclient/tests/unit/v2/test_services.py index 4e08e69..d3133b6 100644 --- a/cinderclient/tests/unit/v2/test_services.py +++ b/cinderclient/tests/unit/v2/test_services.py @@ -80,3 +80,8 @@ class ServicesTest(utils.TestCase): self.assertIsInstance(s, services.Service) self.assertEqual('disabled', s.status) self._assert_request_id(s) + + def test_api_version(self): + client = fakes.FakeClient(version_header='3.0') + svs = client.services.server_api_version() + [self.assertIsInstance(s, services.Service) for s in svs] diff --git a/cinderclient/v3/services.py b/cinderclient/v3/services.py index 8cefc34..beaee44 100644 --- a/cinderclient/v3/services.py +++ b/cinderclient/v3/services.py @@ -16,6 +16,7 @@ """ service interface """ +from cinderclient import api_versions from cinderclient import base @@ -77,3 +78,16 @@ class ServiceManager(base.ManagerWithFind): """Failover a replicated backend by hostname.""" body = {"host": host, "backend_id": backend_id} return self._update("/os-services/failover_host", body) + + @api_versions.wraps("3.0") + def server_api_version(self, url_append=""): + """Returns the API Version supported by the server. + + :param url_append: String to append to url to obtain specific version + :return: Returns response obj for a server that supports microversions. + Returns an empty list for Liberty and prior Cinder servers. + """ + try: + return self._get_with_base_url(url_append, response_key='versions') + except LookupError: + return [] diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index fefe5a0..ebd71e6 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -24,6 +24,7 @@ import time import six +from cinderclient import api_versions from cinderclient import base from cinderclient import exceptions from cinderclient import utils @@ -2680,3 +2681,12 @@ def do_thaw_host(cs, args): def do_failover_host(cs, args): """Failover a replicating cinder-volume host.""" cs.services.failover_host(args.host, args.backend_id) + + +@utils.service_type('volumev3') +@api_versions.wraps("3.0") +def do_api_version(cs, args): + """Display the API version information.""" + columns = ['ID', 'Status', 'Version', 'Min_version'] + response = cs.services.server_api_version() + utils.print_list(response, columns)