Add a fixture for Keystone version discovery

As we push out the session and expect people to test with proper URL
responses we should provide a means to create a correct Keystone version
list to test with.

Change-Id: I8309ab6cbc47ad159f3c6e018b60ff8c15c6d917
This commit is contained in:
Jamie Lennox
2014-06-13 15:33:03 +10:00
parent 548c15f7f7
commit 05f362dcb4
4 changed files with 309 additions and 84 deletions

View File

@@ -11,8 +11,8 @@
# under the License. # under the License.
""" """
The generators in this directory produce keystone compliant tokens for use in The generators in this directory produce keystone compliant structures for use
testing. in testing.
They should be considered part of the public API because they may be relied They should be considered part of the public API because they may be relied
upon to generate test tokens for other clients. However they should never be upon to generate test tokens for other clients. However they should never be
@@ -21,8 +21,15 @@ may be dependencies from this module on libraries that are only available in
testing. testing.
""" """
from keystoneclient.fixture.discovery import * # noqa
from keystoneclient.fixture.exception import FixtureValidationError # noqa from keystoneclient.fixture.exception import FixtureValidationError # noqa
from keystoneclient.fixture.v2 import Token as V2Token # noqa from keystoneclient.fixture.v2 import Token as V2Token # noqa
from keystoneclient.fixture.v3 import Token as V3Token # noqa from keystoneclient.fixture.v3 import Token as V3Token # noqa
__all__ = ['V2Token', 'V3Token', 'FixtureValidationError'] __all__ = ['DiscoveryList',
'FixtureValidationError',
'V2Discovery',
'V3Discovery',
'V2Token',
'V3Token',
]

View File

@@ -0,0 +1,267 @@
# 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 datetime
from keystoneclient.openstack.common import timeutils
from keystoneclient import utils
__all__ = ['DiscoveryList',
'V2Discovery',
'V3Discovery',
]
_DEFAULT_DAYS_AGO = 30
class DiscoveryBase(dict):
"""The basic version discovery structure.
All version discovery elements should have access to these values.
"""
@utils.positional()
def __init__(self, id, status=None, updated=None):
"""Create a new structure.
:param string id: The version id for this version entry.
:param string status: The status of this entry.
:param DateTime updated: When the API was last updated.
"""
super(DiscoveryBase, self).__init__()
self.id = id
self.status = status or 'stable'
self.updated = updated or (timeutils.utcnow() -
datetime.timedelta(days=_DEFAULT_DAYS_AGO))
@property
def id(self):
return self.get('id')
@id.setter
def id(self, value):
self['id'] = value
@property
def status(self):
return self.get('status')
@status.setter
def status(self, value):
self['status'] = value
@property
def links(self):
return self.setdefault('links', [])
@property
def updated_str(self):
return self.get('updated')
@updated_str.setter
def updated_str(self, value):
self['updated'] = value
@property
def updated(self):
return timeutils.parse_isotime(self.updated_str)
@updated.setter
def updated(self, value):
self.updated_str = timeutils.isotime(value)
@utils.positional()
def add_link(self, href, rel='self', type=None):
link = {'href': href, 'rel': rel}
if type:
link['type'] = type
self.links.append(link)
return link
@property
def media_types(self):
return self.setdefault('media-types', [])
@utils.positional(1)
def add_media_type(self, base, type):
mt = {'base': base, 'type': type}
self.media_types.append(mt)
return mt
class V2Discovery(DiscoveryBase):
"""A Version element for a V2 identity service endpoint.
Provides some default values and helper methods for creating a v2.0
endpoint version structure. Clients should use this instead of creating
their own structures.
"""
_DESC_URL = 'http://docs.openstack.org/api/openstack-identity-service/2.0/'
@utils.positional()
def __init__(self, href, id=None, html=True, pdf=True, **kwargs):
"""Create a new structure.
:param string href: The url that this entry should point to.
:param string id: The version id that should be reported. (optional)
Defaults to 'v2.0'.
:param bool html: Add HTML describedby links to the structure.
:param bool pdf: Add PDF describedby links to the structure.
"""
super(V2Discovery, self).__init__(id or 'v2.0', **kwargs)
self.add_link(href)
if html:
self.add_html_description()
if pdf:
self.add_pdf_description()
def add_html_description(self):
"""Add the HTML described by links.
The standard structure includes a link to a HTML document with the
API specification. Add it to this entry.
"""
self.add_link(href=self._DESC_URL + 'content',
rel='describedby',
type='text/html')
def add_pdf_description(self):
"""Add the PDF described by links.
The standard structure includes a link to a PDF document with the
API specification. Add it to this entry.
"""
self.add_link(href=self._DESC_URL + 'identity-dev-guide-2.0.pdf',
rel='describedby',
type='application/pdf')
class V3Discovery(DiscoveryBase):
"""A Version element for a V3 identity service endpoint.
Provides some default values and helper methods for creating a v3
endpoint version structure. Clients should use this instead of creating
there own structures.
"""
@utils.positional()
def __init__(self, href, id=None, json=True, xml=True, **kwargs):
"""Create a new structure.
:param href: The url that this entry should point to.
:param string id: The version id that should be reported. (optional)
Defaults to 'v3.0'.
:param bool json: Add JSON media-type elements to the structure.
:param bool xml: Add XML media-type elements to the structure.
"""
super(V3Discovery, self).__init__(id or 'v3.0', **kwargs)
self.add_link(href)
if json:
self.add_json_media_type()
if xml:
self.add_xml_media_type()
def add_json_media_type(self):
"""Add the JSON media-type links.
The standard structure includes a list of media-types that the endpoint
supports. Add JSON to the list.
"""
self.add_media_type(base='application/json',
type='application/vnd.openstack.identity-v3+json')
def add_xml_media_type(self):
"""Add the XML media-type links.
The standard structure includes a list of media-types that the endpoint
supports. Add XML to the list.
"""
self.add_media_type(base='application/xml',
type='application/vnd.openstack.identity-v3+xml')
class DiscoveryList(dict):
"""A List of version elements.
Creates a correctly structured list of identity service endpoints for
use in testing with discovery.
"""
TEST_URL = 'http://keystone.host:5000/'
@utils.positional(2)
def __init__(self, href=None, v2=True, v3=True, v2_id=None, v3_id=None,
v2_status=None, v2_updated=None, v2_html=True, v2_pdf=True,
v3_status=None, v3_updated=None, v3_json=True, v3_xml=True):
"""Create a new structure.
:param string href: The url that this should be based at.
:param bool v2: Add a v2 element.
:param bool v3: Add a v3 element.
:param string v2_status: The status to use for the v2 element.
:param DateTime v2_updated: The update time to use for the v2 element.
:param bool v2_html: True to add a html link to the v2 element.
:param bool v2_pdf: True to add a pdf link to the v2 element.
:param string v3_status: The status to use for the v3 element.
:param DateTime v3_updated: The update time to use for the v3 element.
:param bool v3_json: True to add a html link to the v2 element.
:param bool v3_xml: True to add a pdf link to the v2 element.
"""
super(DiscoveryList, self).__init__(versions={'values': []})
href = href or self.TEST_URL
if v2:
v2_href = href.rstrip('/') + '/v2.0'
self.add_v2(v2_href, id=v2_id, status=v2_status,
updated=v2_updated, html=v2_html, pdf=v2_pdf)
if v3:
v3_href = href.rstrip('/') + '/v3'
self.add_v3(v3_href, id=v3_id, status=v3_status,
updated=v3_updated, json=v3_json, xml=v3_xml)
@property
def versions(self):
return self['versions']['values']
def add_version(self, version):
"""Add a new version structure to the list.
:param dict version: A new version structure to add to the list.
"""
self.versions.append(version)
def add_v2(self, href, **kwargs):
"""Add a v2 version to the list.
The parameters are the same as V2Discovery.
"""
obj = V2Discovery(href, **kwargs)
self.add_version(obj)
return obj
def add_v3(self, href, **kwargs):
"""Add a v3 version to the list.
The parameters are the same as V3Discovery.
"""
obj = V3Discovery(href, **kwargs)
self.add_version(obj)
return obj

View File

@@ -64,37 +64,9 @@ FAKE_ADMIN_TOKEN = jsonutils.dumps(
'expires': '2022-10-03T16:58:01Z'}}}) 'expires': '2022-10-03T16:58:01Z'}}})
VERSION_LIST_v3 = jsonutils.dumps({ VERSION_LIST_v2 = jsonutils.dumps(fixture.DiscoveryList(href=BASE_URI,
"versions": { v3=False))
"values": [ VERSION_LIST_v3 = jsonutils.dumps(fixture.DiscoveryList(href=BASE_URI))
{
"id": "v3.0",
"status": "stable",
"updated": "2013-03-06T00:00:00Z",
"links": [{'href': '%s/v3' % BASE_URI, 'rel': 'self'}]
},
{
"id": "v2.0",
"status": "stable",
"updated": "2011-11-19T00:00:00Z",
"links": [{'href': '%s/v2.0' % BASE_URI, 'rel': 'self'}]
}
]
}
})
VERSION_LIST_v2 = jsonutils.dumps({
"versions": {
"values": [
{
"id": "v2.0",
"status": "stable",
"updated": "2011-11-19T00:00:00Z",
"links": []
}
]
}
})
ERROR_TOKEN = '7ae290c2a06244c4b41692eb4e9225f2' ERROR_TOKEN = '7ae290c2a06244c4b41692eb4e9225f2'
MEMCACHED_SERVERS = ['localhost:11211'] MEMCACHED_SERVERS = ['localhost:11211']

View File

@@ -18,6 +18,7 @@ from keystoneclient import _discover
from keystoneclient import client from keystoneclient import client
from keystoneclient import discover from keystoneclient import discover
from keystoneclient import exceptions from keystoneclient import exceptions
from keystoneclient import fixture
from keystoneclient.openstack.common import jsonutils from keystoneclient.openstack.common import jsonutils
from keystoneclient.tests import utils from keystoneclient.tests import utils
from keystoneclient.v2_0 import client as v2_client from keystoneclient.v2_0 import client as v2_client
@@ -76,20 +77,8 @@ TEST_SERVICE_CATALOG = [{
}] }]
V2_URL = "%sv2.0" % BASE_URL V2_URL = "%sv2.0" % BASE_URL
V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/' V2_VERSION = fixture.V2Discovery(V2_URL)
'openstack-identity-service/2.0/content/', V2_VERSION.updated_str = UPDATED
'rel': 'describedby',
'type': 'text/html'}
V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident'
'ity-service/2.0/identity-dev-guide-2.0.pdf',
'rel': 'describedby',
'type': 'application/pdf'}
V2_VERSION = {'id': 'v2.0',
'links': [{'href': V2_URL, 'rel': 'self'},
V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF],
'status': 'stable',
'updated': UPDATED}
V2_AUTH_RESPONSE = jsonutils.dumps({ V2_AUTH_RESPONSE = jsonutils.dumps({
"access": { "access": {
@@ -108,16 +97,9 @@ V2_AUTH_RESPONSE = jsonutils.dumps({
}) })
V3_URL = "%sv3" % BASE_URL V3_URL = "%sv3" % BASE_URL
V3_MEDIA_TYPES = [{'base': 'application/json', V3_VERSION = fixture.V3Discovery(V3_URL)
'type': 'application/vnd.openstack.identity-v3+json'}, V3_MEDIA_TYPES = V3_VERSION.media_types
{'base': 'application/xml', V3_VERSION.updated_str = UPDATED
'type': 'application/vnd.openstack.identity-v3+xml'}]
V3_VERSION = {'id': 'v3.0',
'links': [{'href': V3_URL, 'rel': 'self'}],
'media-types': V3_MEDIA_TYPES,
'status': 'stable',
'updated': UPDATED}
V3_TOKEN = six.u('3e2813b7ba0b4006840c3825860b86ed'), V3_TOKEN = six.u('3e2813b7ba0b4006840c3825860b86ed'),
V3_AUTH_RESPONSE = jsonutils.dumps({ V3_AUTH_RESPONSE = jsonutils.dumps({
@@ -434,12 +416,9 @@ class ClientDiscoveryTests(utils.TestCase):
self.assertVersionNotAvailable(auth_url=V3_URL, version=2) self.assertVersionNotAvailable(auth_url=V3_URL, version=2)
def test_discover_unstable_versions(self): def test_discover_unstable_versions(self):
v3_unstable_version = V3_VERSION.copy() version_list = fixture.DiscoveryList(BASE_URL, v3_status='beta')
v3_unstable_version['status'] = 'beta'
version_list = _create_version_list([v3_unstable_version, V2_VERSION])
httpretty.register_uri(httpretty.GET, BASE_URL, status=300, httpretty.register_uri(httpretty.GET, BASE_URL, status=300,
body=version_list) body=jsonutils.dumps(version_list))
self.assertCreatesV2(auth_url=BASE_URL) self.assertCreatesV2(auth_url=BASE_URL)
self.assertVersionNotAvailable(auth_url=BASE_URL, version=3) self.assertVersionNotAvailable(auth_url=BASE_URL, version=3)
@@ -489,23 +468,15 @@ class ClientDiscoveryTests(utils.TestCase):
self.assertCreatesV2(auth_url=BASE_URL) self.assertCreatesV2(auth_url=BASE_URL)
def test_greater_version_than_required(self): def test_greater_version_than_required(self):
resp = [{'id': 'v3.6', versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.6')
'links': [{'href': V3_URL, 'rel': 'self'}],
'media-types': V3_MEDIA_TYPES,
'status': 'stable',
'updated': UPDATED}]
httpretty.register_uri(httpretty.GET, BASE_URL, status=200, httpretty.register_uri(httpretty.GET, BASE_URL, status=200,
body=_create_version_list(resp)) body=jsonutils.dumps(versions))
self.assertCreatesV3(auth_url=BASE_URL, version=(3, 4)) self.assertCreatesV3(auth_url=BASE_URL, version=(3, 4))
def test_lesser_version_than_required(self): def test_lesser_version_than_required(self):
resp = [{'id': 'v3.4', versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.4')
'links': [{'href': V3_URL, 'rel': 'self'}],
'media-types': V3_MEDIA_TYPES,
'status': 'stable',
'updated': UPDATED}]
httpretty.register_uri(httpretty.GET, BASE_URL, status=200, httpretty.register_uri(httpretty.GET, BASE_URL, status=200,
body=_create_version_list(resp)) body=jsonutils.dumps(versions))
self.assertVersionNotAvailable(auth_url=BASE_URL, version=(3, 6)) self.assertVersionNotAvailable(auth_url=BASE_URL, version=(3, 6))
def test_bad_response(self): def test_bad_response(self):
@@ -557,13 +528,24 @@ class ClientDiscoveryTests(utils.TestCase):
'media-types': V3_MEDIA_TYPES, 'media-types': V3_MEDIA_TYPES,
'status': 'stable', 'status': 'stable',
'updated': UPDATED} 'updated': UPDATED}
body = _create_version_list([V4_VERSION, V3_VERSION, V2_VERSION]) versions = fixture.DiscoveryList()
httpretty.register_uri(httpretty.GET, BASE_URL, status=300, body=body) versions.add_version(V4_VERSION)
httpretty.register_uri(httpretty.GET, BASE_URL, status=300,
body=jsonutils.dumps(versions))
disc = discover.Discover(auth_url=BASE_URL) disc = discover.Discover(auth_url=BASE_URL)
self.assertRaises(exceptions.DiscoveryFailure, self.assertRaises(exceptions.DiscoveryFailure,
disc.create_client, version=4) disc.create_client, version=4)
def test_discovery_fail_for_missing_v3(self):
versions = fixture.DiscoveryList(v2=True, v3=False)
httpretty.register_uri(httpretty.GET, BASE_URL, status=300,
body=jsonutils.dumps(versions))
disc = discover.Discover(auth_url=BASE_URL)
self.assertRaises(exceptions.DiscoveryFailure,
disc.create_client, version=(3, 0))
@httpretty.activate @httpretty.activate
class DiscoverQueryTests(utils.TestCase): class DiscoverQueryTests(utils.TestCase):
@@ -715,13 +697,10 @@ class DiscoverQueryTests(utils.TestCase):
def test_allow_unknown(self): def test_allow_unknown(self):
status = 'abcdef' status = 'abcdef'
version_list = [{'id': 'v3.0', version_list = fixture.DiscoveryList(BASE_URL, v2=False,
'links': [{'href': V3_URL, 'rel': 'self'}], v3_status=status)
'media-types': V3_MEDIA_TYPES, httpretty.register_uri(httpretty.GET, BASE_URL, status=200,
'status': status, body=jsonutils.dumps(version_list))
'updated': UPDATED}]
body = jsonutils.dumps({'versions': version_list})
httpretty.register_uri(httpretty.GET, BASE_URL, status=200, body=body)
disc = discover.Discover(auth_url=BASE_URL) disc = discover.Discover(auth_url=BASE_URL)