Clean up API
* Merge api.KeystoneSession into api.BaseAPI * Beef up testing coverage Change-Id: I304871c95c1dc82cd8ce2afbbda2320643ff4d27
This commit is contained in:
parent
de84ddacca
commit
26ff8c98d7
@ -96,3 +96,14 @@ Shell
|
||||
* Break up ``OpenStackShell.initialize_app()``
|
||||
* leave all plugin initialization in OSC in ``_load_plugins()``
|
||||
* leave all command loading in OSC in ``_load_commands()``
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
The API base layer is the common point for all API subclasses. It is a
|
||||
wrapper around ``keystoneauth1.session.Session`` that fixes the ``request()``
|
||||
interface and provides simple endpoint handling that is useful when a Service
|
||||
Catalog is either not available or is insufficient. It also adds simple
|
||||
implementations of the common API CRUD operations: create(), delete(), etc.
|
||||
|
||||
* ``KeystoneSession`` -> merged into ``BaseAPI``
|
||||
|
@ -21,34 +21,47 @@ from keystoneauth1 import session as ks_session
|
||||
from osc_lib import exceptions
|
||||
|
||||
|
||||
class KeystoneSession(object):
|
||||
"""Wrapper for the Keystone Session
|
||||
|
||||
Restore some requests.session.Session compatibility;
|
||||
keystoneauth1.session.Session.request() has the method and url
|
||||
arguments swapped from the rest of the requests-using world.
|
||||
class BaseAPI(object):
|
||||
"""Base API wrapper for keystoneauth1.session.Session
|
||||
|
||||
Encapsulate the translation between keystoneauth1.session.Session
|
||||
and requests.Session in a single layer:
|
||||
* Restore some requests.session.Session compatibility;
|
||||
keystoneauth1.session.Session.request() has the method and url
|
||||
arguments swapped from the rest of the requests-using world.
|
||||
* Provide basic endpoint handling when a Service Catalog is not available.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session=None,
|
||||
service_type=None,
|
||||
endpoint=None,
|
||||
**kwargs
|
||||
):
|
||||
"""Base object that contains some common API objects and methods
|
||||
|
||||
:param Session session:
|
||||
The default session to be used for making the HTTP API calls.
|
||||
:param keystoneauth1.session.Session session:
|
||||
The session to be used for making the HTTP API calls. If None,
|
||||
a default keystoneauth1.session.Session will be created.
|
||||
:param string service_type:
|
||||
API name, i.e. ``identity`` or ``compute``
|
||||
:param string endpoint:
|
||||
The URL from the Service Catalog to be used as the base for API
|
||||
requests on this API.
|
||||
An optional URL to be used as the base for API requests on
|
||||
this API.
|
||||
:param kwargs:
|
||||
Keyword arguments passed to keystoneauth1.session.Session().
|
||||
"""
|
||||
|
||||
super(KeystoneSession, self).__init__()
|
||||
super(BaseAPI, self).__init__()
|
||||
|
||||
# a requests.Session-style interface
|
||||
self.session = session
|
||||
# Create a keystoneauth1.session.Session if one is not supplied
|
||||
if not session:
|
||||
self.session = ks_session.Session(**kwargs)
|
||||
else:
|
||||
self.session = session
|
||||
|
||||
self.service_type = service_type
|
||||
self.endpoint = endpoint
|
||||
|
||||
def _request(self, method, url, session=None, **kwargs):
|
||||
@ -60,54 +73,34 @@ class KeystoneSession(object):
|
||||
:param string method:
|
||||
The HTTP method name, i.e. ``GET``, ``PUT``, etc
|
||||
:param string url:
|
||||
The API-specific portion of the URL path
|
||||
:param Session session:
|
||||
HTTP client session
|
||||
The API-specific portion of the URL path, or a full URL if
|
||||
``endpoint`` was not supplied at initialization.
|
||||
:param keystoneauth1.session.Session session:
|
||||
An initialized session to override the one created at
|
||||
initialization.
|
||||
:param kwargs:
|
||||
keyword arguments passed to requests.request().
|
||||
Keyword arguments passed to requests.request().
|
||||
:return: the requests.Response object
|
||||
"""
|
||||
|
||||
# If session arg is supplied, use it this time, but don't save it
|
||||
if not session:
|
||||
session = self.session
|
||||
if not session:
|
||||
session = ks_session.Session()
|
||||
|
||||
# Do the auto-endpoint magic
|
||||
if self.endpoint:
|
||||
if url:
|
||||
url = '/'.join([self.endpoint.rstrip('/'), url.lstrip('/')])
|
||||
else:
|
||||
url = self.endpoint.rstrip('/')
|
||||
else:
|
||||
# Pass on the lack of URL unmolested to maintain the same error
|
||||
# handling from keystoneauth: raise EndpointNotFound
|
||||
pass
|
||||
|
||||
# Why is ksc session backwards???
|
||||
return session.request(url, method, **kwargs)
|
||||
|
||||
|
||||
class BaseAPI(KeystoneSession):
|
||||
"""Base API"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session=None,
|
||||
service_type=None,
|
||||
endpoint=None,
|
||||
**kwargs
|
||||
):
|
||||
"""Base object that contains some common API objects and methods
|
||||
|
||||
:param Session session:
|
||||
The default session to be used for making the HTTP API calls.
|
||||
:param string service_type:
|
||||
API name, i.e. ``identity`` or ``compute``
|
||||
:param string endpoint:
|
||||
The URL from the Service Catalog to be used as the base for API
|
||||
requests on this API.
|
||||
"""
|
||||
|
||||
super(BaseAPI, self).__init__(session=session, endpoint=endpoint)
|
||||
|
||||
self.service_type = service_type
|
||||
|
||||
# The basic action methods all take a Session and return dict/lists
|
||||
|
||||
def create(
|
||||
|
@ -48,7 +48,7 @@ LIST_BODY = {
|
||||
|
||||
class TestSession(utils.TestCase):
|
||||
|
||||
BASE_URL = 'https://api.example.com:1234/vX'
|
||||
BASE_URL = 'https://api.example.com:1234/test'
|
||||
|
||||
def setUp(self):
|
||||
super(TestSession, self).setUp()
|
||||
|
@ -13,21 +13,75 @@
|
||||
|
||||
"""Base API Library Tests"""
|
||||
|
||||
from keystoneauth1 import exceptions as ks_exceptions
|
||||
from keystoneauth1 import session
|
||||
|
||||
from osc_lib.api import api
|
||||
from osc_lib import exceptions
|
||||
from osc_lib.tests.api import fakes as api_fakes
|
||||
|
||||
|
||||
class TestKeystoneSession(api_fakes.TestSession):
|
||||
class TestBaseAPIDefault(api_fakes.TestSession):
|
||||
|
||||
def setUp(self):
|
||||
super(TestKeystoneSession, self).setUp()
|
||||
self.api = api.KeystoneSession(
|
||||
super(TestBaseAPIDefault, self).setUp()
|
||||
self.api = api.BaseAPI()
|
||||
|
||||
def test_baseapi_request_url(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
json=api_fakes.RESP_ITEM_1,
|
||||
status_code=200,
|
||||
)
|
||||
ret = self.api._request('GET', self.BASE_URL + '/qaz')
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret.json())
|
||||
self.assertIsNotNone(self.api.session)
|
||||
self.assertNotEqual(self.sess, self.api.session)
|
||||
|
||||
def test_baseapi_request_url_path(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
json=api_fakes.RESP_ITEM_1,
|
||||
status_code=200,
|
||||
)
|
||||
self.assertRaises(
|
||||
ks_exceptions.EndpointNotFound,
|
||||
self.api._request,
|
||||
'GET',
|
||||
'/qaz',
|
||||
)
|
||||
self.assertIsNotNone(self.api.session)
|
||||
self.assertNotEqual(self.sess, self.api.session)
|
||||
|
||||
def test_baseapi_request_session(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
json=api_fakes.RESP_ITEM_1,
|
||||
status_code=200,
|
||||
)
|
||||
ret = self.api._request(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
session=self.sess,
|
||||
)
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret.json())
|
||||
self.assertIsNotNone(self.api.session)
|
||||
self.assertNotEqual(self.sess, self.api.session)
|
||||
|
||||
|
||||
class TestBaseAPIArgs(api_fakes.TestSession):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaseAPIArgs, self).setUp()
|
||||
self.api = api.BaseAPI(
|
||||
session=self.sess,
|
||||
endpoint=self.BASE_URL,
|
||||
)
|
||||
|
||||
def test_session_request(self):
|
||||
def test_baseapi_request_url_path(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -36,18 +90,33 @@ class TestKeystoneSession(api_fakes.TestSession):
|
||||
)
|
||||
ret = self.api._request('GET', '/qaz')
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret.json())
|
||||
self.assertIsNotNone(self.api.session)
|
||||
self.assertEqual(self.sess, self.api.session)
|
||||
|
||||
def test_baseapi_request_session(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
json=api_fakes.RESP_ITEM_1,
|
||||
status_code=200,
|
||||
)
|
||||
new_session = session.Session()
|
||||
ret = self.api._request('GET', '/qaz', session=new_session)
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret.json())
|
||||
self.assertIsNotNone(self.api.session)
|
||||
self.assertNotEqual(new_session, self.api.session)
|
||||
|
||||
|
||||
class TestBaseAPI(api_fakes.TestSession):
|
||||
class TestBaseAPICreate(api_fakes.TestSession):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaseAPI, self).setUp()
|
||||
super(TestBaseAPICreate, self).setUp()
|
||||
self.api = api.BaseAPI(
|
||||
session=self.sess,
|
||||
endpoint=self.BASE_URL,
|
||||
)
|
||||
|
||||
def test_create_post(self):
|
||||
def test_baseapi_create_post(self):
|
||||
self.requests_mock.register_uri(
|
||||
'POST',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -57,7 +126,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.create('qaz')
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret)
|
||||
|
||||
def test_create_put(self):
|
||||
def test_baseapi_create_put(self):
|
||||
self.requests_mock.register_uri(
|
||||
'PUT',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -67,7 +136,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.create('qaz', method='PUT')
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret)
|
||||
|
||||
def test_delete(self):
|
||||
def test_baseapi_delete(self):
|
||||
self.requests_mock.register_uri(
|
||||
'DELETE',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -76,9 +145,17 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.delete('qaz')
|
||||
self.assertEqual(204, ret.status_code)
|
||||
|
||||
# find tests
|
||||
|
||||
def test_find_attr_by_id(self):
|
||||
class TestBaseAPIFind(api_fakes.TestSession):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaseAPIFind, self).setUp()
|
||||
self.api = api.BaseAPI(
|
||||
session=self.sess,
|
||||
endpoint=self.BASE_URL,
|
||||
)
|
||||
|
||||
def test_baseapi_find_attr_by_id(self):
|
||||
|
||||
# All first requests (by name) will fail in this test
|
||||
self.requests_mock.register_uri(
|
||||
@ -128,7 +205,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.find_attr('qaz', value='UP', attr='status')
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret)
|
||||
|
||||
def test_find_attr_by_name(self):
|
||||
def test_baseapi_find_attr_by_name(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz?name=alpha',
|
||||
@ -170,7 +247,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.find_attr('qaz', value='UP', attr='status')
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret)
|
||||
|
||||
def test_find_attr_path_resource(self):
|
||||
def test_baseapi_find_attr_path_resource(self):
|
||||
|
||||
# Test resource different than path
|
||||
self.requests_mock.register_uri(
|
||||
@ -188,7 +265,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.find_attr('wsx', '1', resource='qaz')
|
||||
self.assertEqual(api_fakes.RESP_ITEM_1, ret)
|
||||
|
||||
def test_find_bulk_none(self):
|
||||
def test_baseapi_find_bulk_none(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -198,7 +275,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.find_bulk('qaz')
|
||||
self.assertEqual(api_fakes.LIST_RESP, ret)
|
||||
|
||||
def test_find_bulk_one(self):
|
||||
def test_baseapi_find_bulk_one(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -217,7 +294,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.find_bulk('qaz', error='bogus')
|
||||
self.assertEqual([], ret)
|
||||
|
||||
def test_find_bulk_two(self):
|
||||
def test_baseapi_find_bulk_two(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -233,7 +310,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.find_bulk('qaz', id='1', error='beta')
|
||||
self.assertEqual([], ret)
|
||||
|
||||
def test_find_bulk_dict(self):
|
||||
def test_baseapi_find_bulk_dict(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -243,28 +320,27 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.find_bulk('qaz', id='1')
|
||||
self.assertEqual([api_fakes.LIST_RESP[0]], ret)
|
||||
|
||||
# list tests
|
||||
|
||||
def test_list_no_body(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL,
|
||||
json=api_fakes.LIST_RESP,
|
||||
status_code=200,
|
||||
class TestBaseAPIList(api_fakes.TestSession):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaseAPIList, self).setUp()
|
||||
self.api = api.BaseAPI(
|
||||
session=self.sess,
|
||||
endpoint=self.BASE_URL,
|
||||
)
|
||||
ret = self.api.list('')
|
||||
self.assertEqual(api_fakes.LIST_RESP, ret)
|
||||
|
||||
def test_baseapi_list_no_args(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz',
|
||||
json=api_fakes.LIST_RESP,
|
||||
status_code=200,
|
||||
status_code=204,
|
||||
)
|
||||
ret = self.api.list('qaz')
|
||||
ret = self.api.list('/qaz')
|
||||
self.assertEqual(api_fakes.LIST_RESP, ret)
|
||||
|
||||
def test_list_params(self):
|
||||
def test_baseapi_list_params(self):
|
||||
params = {'format': 'json'}
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
@ -284,7 +360,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.list('qaz', **params)
|
||||
self.assertEqual(api_fakes.LIST_RESP, ret)
|
||||
|
||||
def test_list_body(self):
|
||||
def test_baseapi_list_body(self):
|
||||
self.requests_mock.register_uri(
|
||||
'POST',
|
||||
self.BASE_URL + '/qaz',
|
||||
@ -294,7 +370,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.list('qaz', body=api_fakes.LIST_BODY)
|
||||
self.assertEqual(api_fakes.LIST_RESP, ret)
|
||||
|
||||
def test_list_detailed(self):
|
||||
def test_baseapi_list_detailed(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz/details',
|
||||
@ -304,7 +380,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.list('qaz', detailed=True)
|
||||
self.assertEqual(api_fakes.LIST_RESP, ret)
|
||||
|
||||
def test_list_filtered(self):
|
||||
def test_baseapi_list_filtered(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz?attr=value',
|
||||
@ -314,7 +390,7 @@ class TestBaseAPI(api_fakes.TestSession):
|
||||
ret = self.api.list('qaz', attr='value')
|
||||
self.assertEqual(api_fakes.LIST_RESP, ret)
|
||||
|
||||
def test_list_wrapped(self):
|
||||
def test_baseapi_list_wrapped(self):
|
||||
self.requests_mock.register_uri(
|
||||
'GET',
|
||||
self.BASE_URL + '/qaz?attr=value',
|
||||
|
Loading…
x
Reference in New Issue
Block a user