Manila REST API Microversion Support for Client
Manila client needs to provide 'X-Openstack-Manila-Api-Version' in the HTTP request header along with the appropriate version. Manila client also needs to provide 'X-OpenStack-Manila-API-Experimental' in the HTTP request header for APIs that are marked as experimental. This is a temporary fix until Manila client can support Nova style microversions. Closes-bug: #1489450 Change-Id: I6344dfa6d272f0e813a4873c015be614ebeb4e7e
This commit is contained in:
parent
80eac1e73c
commit
36384fa190
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 manilaclient
|
||||
from manilaclient.common import constants
|
||||
|
||||
|
||||
def experimental_api(f):
|
||||
"""Adds to HTTP Header to indicate this is an experimental API call."""
|
||||
def _decorator(*args, **kwargs):
|
||||
client = args[0]
|
||||
if isinstance(client, manilaclient.v1.client.Client):
|
||||
dh = client.client.default_headers
|
||||
dh[constants.EXPERIMENTAL_HTTP_HEADER] = 'true'
|
||||
f(*args, **kwargs)
|
||||
return _decorator
|
|
@ -138,6 +138,14 @@ class Manager(utils.HookableMixin):
|
|||
else:
|
||||
return self.resource_class(self, body, loaded=True)
|
||||
|
||||
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)
|
||||
|
||||
def _create(self, url, body, response_key, return_raw=False, **kwargs):
|
||||
self.run_hooks('modify_body_for_create', body, **kwargs)
|
||||
resp, body = self.api.client.post(url, body=body)
|
||||
|
|
|
@ -39,6 +39,10 @@ def get_client_class(version):
|
|||
return importutils.import_class(client_path)
|
||||
|
||||
|
||||
def get_major_version(version):
|
||||
return version.split('.')[0]
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
client_class = get_client_class(version)
|
||||
return client_class(*args, **kwargs)
|
||||
client_class = get_client_class(get_major_version(version))
|
||||
return client_class(version, *args, **kwargs)
|
||||
|
|
|
@ -38,3 +38,6 @@ SNAPSHOT_SORT_KEY_VALUES = (
|
|||
'progress',
|
||||
'name', 'display_name',
|
||||
)
|
||||
|
||||
EXPERIMENTAL_HTTP_HEADER = 'X-OpenStack-Manila-API-Experimental'
|
||||
MAX_API_VERSION = '1.4'
|
||||
|
|
|
@ -33,10 +33,12 @@ except ImportError:
|
|||
|
||||
|
||||
class HTTPClient(object):
|
||||
def __init__(self, base_url, token, user_agent,
|
||||
|
||||
def __init__(self, endpoint_url, token, user_agent, api_version,
|
||||
insecure=False, cacert=None, timeout=None, retries=None,
|
||||
http_log_debug=False):
|
||||
self.base_url = base_url
|
||||
self.endpoint_url = endpoint_url
|
||||
self.base_url = self._get_base_url(self.endpoint_url)
|
||||
self.retries = retries
|
||||
self.http_log_debug = http_log_debug
|
||||
|
||||
|
@ -45,6 +47,7 @@ class HTTPClient(object):
|
|||
|
||||
self.default_headers = {
|
||||
'X-Auth-Token': token,
|
||||
'X-Openstack-Manila-Api-Version': api_version,
|
||||
'User-Agent': user_agent,
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
|
@ -57,6 +60,11 @@ class HTTPClient(object):
|
|||
if hasattr(requests, 'logging'):
|
||||
requests.logging.getLogger(requests.__name__).addHandler(ch)
|
||||
|
||||
def _get_base_url(self, url):
|
||||
"""Truncates url and returns transport, address, and port number."""
|
||||
base_url = '/'.join(url.split('/')[:3]) + '/'
|
||||
return base_url
|
||||
|
||||
def _set_request_options(self, insecure, cacert, timeout=None):
|
||||
options = {'verify': True}
|
||||
|
||||
|
@ -98,13 +106,24 @@ class HTTPClient(object):
|
|||
return resp, body
|
||||
|
||||
def _cs_request(self, url, method, **kwargs):
|
||||
return self._cs_request_with_retries(
|
||||
self.endpoint_url + url,
|
||||
method,
|
||||
**kwargs)
|
||||
|
||||
def _cs_request_base_url(self, url, method, **kwargs):
|
||||
return self._cs_request_with_retries(
|
||||
self.base_url + url,
|
||||
method,
|
||||
**kwargs)
|
||||
|
||||
def _cs_request_with_retries(self, url, method, **kwargs):
|
||||
attempts = 0
|
||||
timeout = 1
|
||||
while True:
|
||||
attempts += 1
|
||||
try:
|
||||
resp, body = self.request(self.base_url + url, method,
|
||||
**kwargs)
|
||||
resp, body = self.request(url, method, **kwargs)
|
||||
return resp, body
|
||||
except (exceptions.BadRequest,
|
||||
requests.exceptions.RequestException,
|
||||
|
@ -124,6 +143,9 @@ class HTTPClient(object):
|
|||
sleep(timeout)
|
||||
timeout *= 2
|
||||
|
||||
def get_with_base_url(self, url, **kwargs):
|
||||
return self._cs_request_base_url(url, 'GET', **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self._cs_request(url, 'GET', **kwargs)
|
||||
|
||||
|
|
|
@ -33,13 +33,14 @@ from oslo_utils import encodeutils
|
|||
import six
|
||||
|
||||
from manilaclient import client
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions as exc
|
||||
import manilaclient.extension
|
||||
from manilaclient.openstack.common import cliutils
|
||||
from manilaclient.v1 import shell as shell_v1
|
||||
# from manilaclient.v2 import shell as shell_v2
|
||||
|
||||
DEFAULT_OS_SHARE_API_VERSION = "1"
|
||||
DEFAULT_OS_SHARE_API_VERSION = constants.MAX_API_VERSION
|
||||
DEFAULT_MANILA_ENDPOINT_TYPE = 'publicURL'
|
||||
DEFAULT_MANILA_SERVICE_TYPE = 'share'
|
||||
|
||||
|
@ -218,11 +219,11 @@ class OpenStackManilaShell(object):
|
|||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-share-api-version',
|
||||
metavar='<compute-api-ver>',
|
||||
metavar='<share-api-ver>',
|
||||
default=cliutils.env(
|
||||
'OS_SHARE_API_VERSION',
|
||||
default=DEFAULT_OS_SHARE_API_VERSION),
|
||||
help='Accepts 1 or 2, defaults '
|
||||
help='Accepts 1.x to override default '
|
||||
'to env[OS_SHARE_API_VERSION].')
|
||||
parser.add_argument('--os_share_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
@ -294,7 +295,7 @@ class OpenStackManilaShell(object):
|
|||
|
||||
def _discover_via_contrib_path(self, version):
|
||||
module_path = os.path.dirname(os.path.abspath(__file__))
|
||||
version_str = "v%s" % version.replace('.', '_')
|
||||
version_str = "v%s" % version.split('.')[0]
|
||||
ext_path = os.path.join(module_path, version_str, 'contrib')
|
||||
ext_glob = os.path.join(ext_path, "*.py")
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
import mock
|
||||
import requests
|
||||
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions
|
||||
from manilaclient import httpclient
|
||||
from manilaclient.tests.unit import utils
|
||||
|
@ -72,7 +73,8 @@ retry_after_non_supporting_mock_request = mock.Mock(
|
|||
|
||||
def get_authed_client(retries=0):
|
||||
cl = httpclient.HTTPClient("http://example.com", "token", fake_user_agent,
|
||||
retries=retries, http_log_debug=True)
|
||||
retries=retries, http_log_debug=True,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
return cl
|
||||
|
||||
|
||||
|
@ -85,9 +87,12 @@ class ClientTest(utils.TestCase):
|
|||
@mock.patch('time.time', mock.Mock(return_value=1234))
|
||||
def test_get_call():
|
||||
resp, body = cl.get("/hi")
|
||||
headers = {"X-Auth-Token": "token",
|
||||
"User-Agent": fake_user_agent,
|
||||
'Accept': 'application/json', }
|
||||
headers = {
|
||||
"X-Auth-Token": "token",
|
||||
"User-Agent": fake_user_agent,
|
||||
"X-Openstack-Manila-Api-Version": constants.MAX_API_VERSION,
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
mock_request.assert_called_with(
|
||||
"GET",
|
||||
"http://example.com/hi",
|
||||
|
@ -176,6 +181,7 @@ class ClientTest(utils.TestCase):
|
|||
"X-Auth-Token": "token",
|
||||
"Content-Type": "application/json",
|
||||
'Accept': 'application/json',
|
||||
"X-Openstack-Manila-Api-Version": constants.MAX_API_VERSION,
|
||||
"User-Agent": fake_user_agent
|
||||
}
|
||||
mock_request.assert_called_with(
|
||||
|
|
|
@ -36,8 +36,15 @@ class FakeHTTPClient(httpclient.HTTPClient):
|
|||
self.password = 'password'
|
||||
self.auth_url = 'auth_url'
|
||||
self.callstack = []
|
||||
self.base_url = 'localhost'
|
||||
|
||||
def _cs_request(self, url, method, **kwargs):
|
||||
return self._cs_request_with_retries(url, method, **kwargs)
|
||||
|
||||
def _cs_request_base_url(self, url, method, **kwargs):
|
||||
return self._cs_request_with_retries(url, method, **kwargs)
|
||||
|
||||
def _cs_request_with_retries(self, url, method, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
if method in ['GET', 'DELETE']:
|
||||
assert 'body' not in kwargs
|
||||
|
|
|
@ -32,6 +32,31 @@ class FakeClient(fakes.FakeClient):
|
|||
|
||||
class FakeHTTPClient(fakes.FakeHTTPClient):
|
||||
|
||||
def get_(self, **kw):
|
||||
body = {
|
||||
"versions": [
|
||||
{
|
||||
"status": "CURRENT",
|
||||
"updated": "2015-07-30T11:33:21Z",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://docs.openstack.org/",
|
||||
"type": "text/html",
|
||||
"rel": "describedby",
|
||||
},
|
||||
{
|
||||
"href": "http://localhost:8786/v1/",
|
||||
"rel": "self",
|
||||
}
|
||||
],
|
||||
"min_version": "1.0",
|
||||
"version": "1.1",
|
||||
"id": "v1.0",
|
||||
}
|
||||
]
|
||||
}
|
||||
return (200, {}, body)
|
||||
|
||||
def get_shares_1234(self, **kw):
|
||||
share = {'share': {'id': 1234, 'name': 'sharename'}}
|
||||
return (200, {}, share)
|
||||
|
|
|
@ -14,6 +14,7 @@ import uuid
|
|||
|
||||
from keystoneclient import session
|
||||
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.v1 import client
|
||||
|
@ -27,21 +28,24 @@ class ClientTest(utils.TestCase):
|
|||
base_url = uuid.uuid4().hex
|
||||
|
||||
s = session.Session()
|
||||
c = client.Client(session=s, service_catalog_url=base_url,
|
||||
retries=retries, input_auth_token='token')
|
||||
c = client.Client(session=s, api_version=constants.MAX_API_VERSION,
|
||||
service_catalog_url=base_url, retries=retries,
|
||||
input_auth_token='token')
|
||||
|
||||
self.assertEqual(base_url, c.client.base_url)
|
||||
self.assertEqual(base_url, c.client.endpoint_url)
|
||||
self.assertEqual(retries, c.client.retries)
|
||||
|
||||
def test_auth_via_token_invalid(self):
|
||||
self.assertRaises(exceptions.ClientException, client.Client,
|
||||
input_auth_token='token')
|
||||
api_version=constants.MAX_API_VERSION,
|
||||
input_auth_token="token")
|
||||
|
||||
def test_auth_via_token_and_session(self):
|
||||
s = session.Session()
|
||||
base_url = uuid.uuid4().hex
|
||||
c = client.Client(input_auth_token='token',
|
||||
service_catalog_url=base_url, session=s)
|
||||
service_catalog_url=base_url, session=s,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
|
||||
self.assertIsNotNone(c.client)
|
||||
self.assertIsNone(c.keystone_client)
|
||||
|
@ -50,7 +54,8 @@ class ClientTest(utils.TestCase):
|
|||
base_url = uuid.uuid4().hex
|
||||
|
||||
c = client.Client(input_auth_token='token',
|
||||
service_catalog_url=base_url)
|
||||
service_catalog_url=base_url,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
|
||||
self.assertIsNotNone(c.client)
|
||||
self.assertIsNone(c.keystone_client)
|
||||
|
|
|
@ -1152,3 +1152,12 @@ class ShellTest(test_utils.TestCase):
|
|||
cliutils.print_list.assert_called_with(
|
||||
mock.ANY,
|
||||
fields=["Name", "Host", "Backend", "Pool"])
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
def test_api_version(self):
|
||||
self.run_command('api-version')
|
||||
self.assert_called('GET', '')
|
||||
cliutils.print_list.assert_called_with(
|
||||
mock.ANY,
|
||||
['ID', 'Status', 'Version', 'Min_version'],
|
||||
field_labels=['ID', 'Status', 'Version', 'Minimum Version'])
|
||||
|
|
|
@ -58,9 +58,9 @@ class Client(object):
|
|||
>>> client.shares.list()
|
||||
...
|
||||
"""
|
||||
def __init__(self, username=None, api_key=None, project_id=None,
|
||||
auth_url=None, insecure=False, timeout=None, tenant_id=None,
|
||||
project_name=None, region_name=None,
|
||||
def __init__(self, api_version, username=None, api_key=None,
|
||||
project_id=None, auth_url=None, insecure=False, timeout=None,
|
||||
tenant_id=None, project_name=None, region_name=None,
|
||||
endpoint_type='publicURL', extensions=None,
|
||||
service_type='share', service_name=None, retries=None,
|
||||
http_log_debug=False, input_auth_token=None, session=None,
|
||||
|
@ -157,7 +157,8 @@ class Client(object):
|
|||
cacert=cacert,
|
||||
timeout=timeout,
|
||||
retries=retries,
|
||||
http_log_debug=http_log_debug)
|
||||
http_log_debug=http_log_debug,
|
||||
api_version=api_version)
|
||||
|
||||
self.limits = limits.LimitsManager(self)
|
||||
self.services = services.ServiceManager(self)
|
||||
|
|
|
@ -31,6 +31,10 @@ class Service(common_base.Resource):
|
|||
def __repr__(self):
|
||||
return "<Service: %s>" % self.id
|
||||
|
||||
def api_version(self):
|
||||
"""Get api version."""
|
||||
return self.manager.api_version(self)
|
||||
|
||||
|
||||
class ServiceManager(base.Manager):
|
||||
"""Manage :class:`Service` resources."""
|
||||
|
@ -48,3 +52,7 @@ class ServiceManager(base.Manager):
|
|||
if query_string:
|
||||
query_string = "?%s" % query_string
|
||||
return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME)
|
||||
|
||||
def api_version(self):
|
||||
"""Get api version."""
|
||||
return self._get_with_base_url("", "versions")
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
@ -149,6 +150,14 @@ def _extract_key_value_options(args, option_name):
|
|||
return result_dict
|
||||
|
||||
|
||||
def do_api_version(cs, args):
|
||||
"""Display the API version information."""
|
||||
columns = ['ID', 'Status', 'Version', 'Min_version']
|
||||
column_labels = ['ID', 'Status', 'Version', 'Minimum Version']
|
||||
versions = cs.services.api_version()
|
||||
cliutils.print_list(versions, columns, field_labels=column_labels)
|
||||
|
||||
|
||||
def do_endpoints(cs, args):
|
||||
"""Discover endpoints that get returned from the authenticate services."""
|
||||
catalog = cs.keystone_client.service_catalog.catalog
|
||||
|
|
Loading…
Reference in New Issue