keystoneauth/keystoneauth1/tests/unit/test_discovery.py
Grzegorz Grasza 5098d45cca Allow passing of version header
Add keyword option to get_version_data() to allow passing
of the version header so that we can get the microversions.
Specifically, this is so that we can re-use this function
in barbican, which recently implemented microversions, but
doesn't return them by default, for backward compatibility
with old clients.

Change-Id: I909750381a559f9dc61650c9f98c88d4481012b7
2022-12-20 15:58:04 +01:00

1377 lines
51 KiB
Python

# 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 json
import re
from unittest import mock
from testtools import matchers
from keystoneauth1 import adapter
from keystoneauth1 import discover
from keystoneauth1 import exceptions
from keystoneauth1 import fixture
from keystoneauth1 import http_basic
from keystoneauth1 import noauth
from keystoneauth1 import session
from keystoneauth1.tests.unit import utils
from keystoneauth1 import token_endpoint
BASE_HOST = 'http://keystone.example.com'
BASE_URL = "%s:5000/" % BASE_HOST
UPDATED = '2013-03-06T00:00:00Z'
TEST_SERVICE_CATALOG = [{
"endpoints": [{
"adminURL": "%s:8774/v1.0" % BASE_HOST,
"region": "RegionOne",
"internalURL": "%s://127.0.0.1:8774/v1.0" % BASE_HOST,
"publicURL": "%s:8774/v1.0/" % BASE_HOST
}],
"type": "nova_compat",
"name": "nova_compat"
}, {
"endpoints": [{
"adminURL": "http://nova/novapi/admin",
"region": "RegionOne",
"internalURL": "http://nova/novapi/internal",
"publicURL": "http://nova/novapi/public"
}],
"type": "compute",
"name": "nova"
}, {
"endpoints": [{
"adminURL": "http://glance/glanceapi/admin",
"region": "RegionOne",
"internalURL": "http://glance/glanceapi/internal",
"publicURL": "http://glance/glanceapi/public"
}],
"type": "image",
"name": "glance"
}, {
"endpoints": [{
"adminURL": "%s:35357/v2.0" % BASE_HOST,
"region": "RegionOne",
"internalURL": "%s:5000/v2.0" % BASE_HOST,
"publicURL": "%s:5000/v2.0" % BASE_HOST
}],
"type": "identity",
"name": "keystone"
}, {
"endpoints": [{
"adminURL": "http://swift/swiftapi/admin",
"region": "RegionOne",
"internalURL": "http://swift/swiftapi/internal",
"publicURL": "http://swift/swiftapi/public"
}],
"type": "object-store",
"name": "swift"
}]
V2_URL = "%sv2.0" % BASE_URL
V2_VERSION = fixture.V2Discovery(V2_URL)
V2_VERSION.updated_str = UPDATED
V2_AUTH_RESPONSE = json.dumps({
"access": {
"token": {
"expires": "2020-01-01T00:00:10.000123Z",
"id": 'fakeToken',
"tenant": {
"id": '1'
},
},
"user": {
"id": 'test'
},
"serviceCatalog": TEST_SERVICE_CATALOG,
},
})
V3_URL = "%sv3" % BASE_URL
V3_VERSION = fixture.V3Discovery(V3_URL)
V3_MEDIA_TYPES = V3_VERSION.media_types
V3_VERSION.updated_str = UPDATED
V3_AUTH_RESPONSE = json.dumps({
"token": {
"methods": [
"token",
"password"
],
"expires_at": "2020-01-01T00:00:10.000123Z",
"project": {
"domain": {
"id": '1',
"name": 'test-domain'
},
"id": '1',
"name": 'test-project'
},
"user": {
"domain": {
"id": '1',
"name": 'test-domain'
},
"id": '1',
"name": 'test-user'
},
"issued_at": "2013-05-29T16:55:21.468960Z",
},
})
CINDER_EXAMPLES = {
"versions": [
{
"status": discover.Status.CURRENT,
"updated": "2012-01-04T11:33:21Z",
"id": "v1.0",
"links": [
{
"href": "%sv1/" % BASE_URL,
"rel": "self"
}
]
},
{
"status": discover.Status.CURRENT,
"updated": "2012-11-21T11:33:21Z",
"id": "v2.0",
"links": [
{
"href": "%sv2/" % BASE_URL,
"rel": "self"
}
]
},
{
"status": discover.Status.CURRENT,
"updated": "2012-11-21T11:33:21Z",
"id": "v3.0",
"version": "3.27",
"min_version": "3.0",
"next_min_version": "3.4",
"not_before": "2019-12-31",
"links": [
{
"href": BASE_URL,
"rel": "collection"
},
{
"href": "%sv3/" % BASE_URL,
"rel": "self"
}
]
}
]
}
GLANCE_EXAMPLES = {
"versions": [
{
"status": discover.Status.CURRENT,
"id": "v2.2",
"links": [
{
"href": "%sv2/" % BASE_URL,
"rel": "self"
}
]
},
{
"status": discover.Status.SUPPORTED,
"id": "v2.1",
"links": [
{
"href": "%sv2/" % BASE_URL,
"rel": "self"
}
]
},
{
"status": discover.Status.SUPPORTED,
"id": "v2.0",
"links": [
{
"href": "%sv2/" % BASE_URL,
"rel": "self"
}
]
},
{
"status": discover.Status.CURRENT,
"id": "v1.1",
"links": [
{
"href": "%sv1/" % BASE_URL,
"rel": "self"
}
]
},
{
"status": discover.Status.SUPPORTED,
"id": "v1.0",
"links": [
{
"href": "%sv1/" % BASE_URL,
"rel": "self"
}
]
}
]
}
def _create_version_list(versions):
return {'versions': {'values': versions}}
def _create_single_version(version):
return {'version': version}
V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION])
V2_VERSION_LIST = _create_version_list([V2_VERSION])
V3_VERSION_ENTRY = _create_single_version(V3_VERSION)
V2_VERSION_ENTRY = _create_single_version(V2_VERSION)
class CatalogHackTests(utils.TestCase):
TEST_URL = 'http://keystone.server:5000/v2.0'
OTHER_URL = 'http://other.server:5000/path'
IDENTITY = 'identity'
BASE_URL = 'http://keystone.server:5000/'
V2_URL = BASE_URL + 'v2.0'
V3_URL = BASE_URL + 'v3'
def setUp(self):
super(CatalogHackTests, self).setUp()
self.hacks = discover._VersionHacks()
self.hacks.add_discover_hack(self.IDENTITY,
re.compile('/v2.0/?$'),
'/')
def test_version_hacks(self):
self.assertEqual(self.BASE_URL,
self.hacks.get_discover_hack(self.IDENTITY,
self.V2_URL))
self.assertEqual(self.BASE_URL,
self.hacks.get_discover_hack(self.IDENTITY,
self.V2_URL + '/'))
self.assertEqual(self.OTHER_URL,
self.hacks.get_discover_hack(self.IDENTITY,
self.OTHER_URL))
def test_ignored_non_service_type(self):
self.assertEqual(self.V2_URL,
self.hacks.get_discover_hack('other', self.V2_URL))
class DiscoverUtils(utils.TestCase):
def test_version_number(self):
def assertVersion(out, inp):
self.assertEqual(out, discover.normalize_version_number(inp))
def versionRaises(inp):
self.assertRaises(TypeError,
discover.normalize_version_number,
inp)
assertVersion((1, 2), 'v1.2')
assertVersion((11, 0), 'v11')
assertVersion((1, 2), '1.2')
assertVersion((1, 5, 1), '1.5.1')
assertVersion((1, 0), '1')
assertVersion((1, 0), 1)
assertVersion((5, 2), 5.2)
assertVersion((3, 20), '3.20')
assertVersion((6, 1), (6, 1))
assertVersion((1, 40), [1, 40])
assertVersion((1, 0), (1,))
assertVersion((1, 0), ['1'])
assertVersion((discover.LATEST, discover.LATEST), 'latest')
assertVersion((discover.LATEST, discover.LATEST), ['latest'])
assertVersion((discover.LATEST, discover.LATEST), discover.LATEST)
assertVersion((discover.LATEST, discover.LATEST), (discover.LATEST,))
assertVersion((10, discover.LATEST), '10.latest')
assertVersion((10, discover.LATEST), (10, 'latest'))
assertVersion((10, discover.LATEST), (10, discover.LATEST))
versionRaises(None)
versionRaises('hello')
versionRaises('1.a')
versionRaises('vacuum')
versionRaises('')
versionRaises(('1', 'a'))
def test_version_args(self):
"""Validate _normalize_version_args."""
def assert_min_max(in_ver, in_min, in_max, in_type, out_min, out_max):
self.assertEqual(
(out_min, out_max),
discover._normalize_version_args(
in_ver, in_min, in_max, service_type=in_type))
def normalize_raises(ver, min, max, in_type):
self.assertRaises(
ValueError,
discover._normalize_version_args,
ver, min, max, service_type=in_type)
assert_min_max(None, None, None, None,
None, None)
assert_min_max(None, None, 'v1.2', None,
None, (1, 2))
assert_min_max(None, 'v1.2', 'latest', None,
(1, 2), (discover.LATEST, discover.LATEST))
assert_min_max(None, 'v1.2', '1.6', None,
(1, 2), (1, 6))
assert_min_max(None, 'v1.2', '1.latest', None,
(1, 2), (1, discover.LATEST))
assert_min_max(None, 'latest', 'latest', None,
(discover.LATEST, discover.LATEST),
(discover.LATEST, discover.LATEST))
assert_min_max(None, 'latest', None, None,
(discover.LATEST, discover.LATEST),
(discover.LATEST, discover.LATEST))
assert_min_max(None, (1, 2), None, None,
(1, 2), (discover.LATEST, discover.LATEST))
assert_min_max('', ('1', '2'), (1, 6), None,
(1, 2), (1, 6))
assert_min_max(None, ('1', '2'), (1, discover.LATEST), None,
(1, 2), (1, discover.LATEST))
assert_min_max('v1.2', '', None, None,
(1, 2), (1, discover.LATEST))
assert_min_max('1.latest', None, '', None,
(1, discover.LATEST), (1, discover.LATEST))
assert_min_max('v1', None, None, None,
(1, 0), (1, discover.LATEST))
assert_min_max('latest', None, None, None,
(discover.LATEST, discover.LATEST),
(discover.LATEST, discover.LATEST))
assert_min_max(None, None, 'latest', 'volumev2',
(2, 0), (2, discover.LATEST))
assert_min_max(None, None, None, 'volumev2',
(2, 0), (2, discover.LATEST))
normalize_raises('v1', 'v2', None, None)
normalize_raises('v1', None, 'v2', None)
normalize_raises(None, 'latest', 'v1', None)
normalize_raises(None, 'v1.2', 'v1.1', None)
normalize_raises(None, 'v1.2', 1, None)
normalize_raises('v2', None, None, 'volumev3')
normalize_raises('v3', None, None, 'volumev2')
normalize_raises(None, 'v2', None, 'volumev3')
normalize_raises(None, None, 'v3', 'volumev2')
def test_version_to_string(self):
def assert_string(out, inp):
self.assertEqual(out, discover.version_to_string(inp))
assert_string('latest', (discover.LATEST,))
assert_string('latest', (discover.LATEST, discover.LATEST))
assert_string('latest',
(discover.LATEST, discover.LATEST, discover.LATEST))
assert_string('1', (1,))
assert_string('1.2', (1, 2))
assert_string('1.latest', (1, discover.LATEST))
def test_version_between(self):
def good(minver, maxver, cand):
self.assertTrue(discover.version_between(minver, maxver, cand))
def bad(minver, maxver, cand):
self.assertFalse(discover.version_between(minver, maxver, cand))
def exc(excls, minver, maxver, cand):
self.assertRaises(excls,
discover.version_between, minver, maxver, cand)
# candidate required
exc(ValueError, (1, 0), (1, 0), None)
exc(ValueError, 'v1.0', '1.0', '')
# malformed candidate
exc(TypeError, None, None, 'bogus')
exc(TypeError, None, None, (1, 'two'))
# malformed min_version
exc(TypeError, 'bogus', None, (1, 0))
exc(TypeError, (1, 'two'), None, (1, 0))
# malformed max_version
exc(TypeError, None, 'bogus', (1, 0))
exc(TypeError, None, (1, 'two'), (1, 0))
# fail on minimum
bad((2, 4), None, (1, 55))
bad('v2.4', '', '2.3')
bad('latest', None, (2, 3000))
bad((2, discover.LATEST), '', 'v2.3000')
bad((2, 3000), '', (1, discover.LATEST))
bad('latest', None, 'v1000.latest')
# fail on maximum
bad(None, (2, 4), (2, 5))
bad('', 'v2.4', '2.5')
bad(None, (2, discover.LATEST), (3, 0))
bad('', '2000.latest', 'latest')
# candidate matches a bound
good((1, 0), (1, 0), (1, 0))
good('1.0', '2.9', '1.0')
good('v1.0', 'v2.9', 'v2.9')
# properly in between
good((1, 0), (1, 10), (1, 2))
good('1', '2', '1.2')
# no lower bound
good(None, (2, 5), (2, 3))
# no upper bound
good('2.5', '', '2.6')
# no bounds at all
good('', '', 'v1')
good(None, None, (999, 999))
good(None, None, 'latest')
# Various good 'latest' scenarios
good((discover.LATEST, discover.LATEST),
(discover.LATEST, discover.LATEST),
(discover.LATEST, discover.LATEST))
good((discover.LATEST, discover.LATEST), None,
(discover.LATEST, discover.LATEST))
good('', 'latest', 'latest')
good('2.latest', '3.latest', '3.0')
good('2.latest', None, (55, 66))
good(None, '3.latest', '3.9999')
class VersionDataTests(utils.TestCase):
def setUp(self):
super(VersionDataTests, self).setUp()
self.session = session.Session()
def test_version_data_basics(self):
examples = {'keystone': V3_VERSION_LIST,
'cinder': CINDER_EXAMPLES,
'glance': GLANCE_EXAMPLES}
for path, data in examples.items():
url = "%s%s" % (BASE_URL, path)
mock = self.requests_mock.get(url, status_code=300, json=data)
disc = discover.Discover(self.session, url)
raw_data = disc.raw_version_data()
clean_data = disc.version_data()
for v in raw_data:
for n in ('id', 'status', 'links'):
msg = '%s missing from %s version data' % (n, path)
self.assertThat(v, matchers.Annotate(msg,
matchers.Contains(n)))
for v in clean_data:
for n in ('version', 'url', 'raw_status'):
msg = '%s missing from %s version data' % (n, path)
self.assertThat(v, matchers.Annotate(msg,
matchers.Contains(n)))
self.assertTrue(mock.called_once)
def test_version_data_override_version_url(self):
# if the request url is versioned already, just return it.
self.requests_mock.get(
V3_URL, status_code=200,
json={'version': fixture.V3Discovery('http://override/identity/v3')
}
)
disc = discover.Discover(self.session, V3_URL)
version_data = disc.version_data()
for v in version_data:
self.assertEqual(v['version'], (3, 0))
self.assertEqual(v['status'], discover.Status.CURRENT)
self.assertEqual(v['raw_status'], 'stable')
self.assertEqual(v['url'], V3_URL)
# if the request url is not versioned, just add version info to it.(
# do not changed the url's netloc or path)
self.requests_mock.get(
BASE_URL, status_code=200,
json={'version': fixture.V3Discovery('http://override/identity/v3')
}
)
disc = discover.Discover(self.session, BASE_URL)
version_data = disc.version_data()
for v in version_data:
self.assertEqual(v['version'], (3, 0))
self.assertEqual(v['status'], discover.Status.CURRENT)
self.assertEqual(v['raw_status'], 'stable')
self.assertEqual(v['url'], V3_URL)
def test_version_data_unknown(self):
discovery_fixture = fixture.V3Discovery(V3_URL)
discovery_fixture.status = 'hungry'
discovery_doc = _create_single_version(discovery_fixture)
self.requests_mock.get(V3_URL, status_code=200, json=discovery_doc)
disc = discover.Discover(self.session, V3_URL)
clean_data = disc.version_data(allow_unknown=True)
self.assertEqual(discover.Status.UNKNOWN, clean_data[0]['status'])
def test_version_data_individual(self):
mock = self.requests_mock.get(V3_URL,
status_code=200,
json=V3_VERSION_ENTRY)
disc = discover.Discover(self.session, V3_URL)
raw_data = disc.raw_version_data()
clean_data = disc.version_data()
for v in raw_data:
self.assertEqual(v['id'], 'v3.0')
self.assertEqual(v['status'], 'stable')
self.assertIn('media-types', v)
self.assertIn('links', v)
for v in clean_data:
self.assertEqual(v['version'], (3, 0))
self.assertEqual(v['status'], discover.Status.CURRENT)
self.assertEqual(v['raw_status'], 'stable')
self.assertEqual(v['url'], V3_URL)
self.assertTrue(mock.called_once)
def test_version_data_legacy_ironic_no_override(self):
"""Validate detection of legacy Ironic microversion ranges."""
ironic_url = 'https://bare-metal.example.com/v1/'
self.requests_mock.get(
ironic_url, status_code=200,
json={
'id': 'v1',
'links': [{
"href": ironic_url,
"rel": "self"}]},
headers={
'X-OpenStack-Ironic-API-Minimum-Version': '1.3',
'X-OpenStack-Ironic-API-Maximum-Version': '1.21',
})
plugin = noauth.NoAuth()
a = adapter.Adapter(
self.session,
auth=plugin,
service_type='baremetal')
self.assertIsNone(a.get_api_major_version())
def test_version_data_ironic_microversions(self):
"""Validate detection of Ironic microversion ranges."""
ironic_url = 'https://bare-metal.example.com/v1/'
self.requests_mock.get(
ironic_url, status_code=200,
json={
'id': 'v1',
'version': {
'id': 'v1',
'links': [{
"href": ironic_url,
"rel": "self"}],
'version': '1.40',
'min_version': '1.10',
'status': 'CURRENT',
},
'links': [{
"href": ironic_url,
"rel": "self"}],
},
# Keep headers so we can verify that body trumps headers
headers={
'X-OpenStack-Ironic-API-Minimum-Version': '1.3',
'X-OpenStack-Ironic-API-Maximum-Version': '1.21',
})
self.assertEqual(
[
{
'collection': None,
'version': (1, 0),
'url': ironic_url,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
'min_microversion': (1, 10),
'max_microversion': (1, 40),
'next_min_version': None,
'not_before': None,
},
],
discover.Discover(self.session, ironic_url).version_data())
def test_version_data_legacy_ironic_microversions(self):
"""Validate detection of legacy Ironic microversion ranges."""
ironic_url = 'https://bare-metal.example.com/v1/'
self.requests_mock.get(
ironic_url, status_code=200,
json={
'id': 'v1',
'links': [{
"href": ironic_url,
"rel": "self"}]},
headers={
'X-OpenStack-Ironic-API-Minimum-Version': '1.3',
'X-OpenStack-Ironic-API-Maximum-Version': '1.21',
})
self.assertEqual(
[
{
'collection': None,
'version': (1, 0),
'url': ironic_url,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
'min_microversion': (1, 3),
'max_microversion': (1, 21),
'next_min_version': None,
'not_before': None,
},
],
discover.Discover(self.session, ironic_url).version_data())
def test_version_data_microversions(self):
"""Validate [min_|max_]version conversion to {min|max}_microversion."""
def setup_mock(versions_in):
# Set up the test data with the input version data
jsondata = {
"versions": [
dict(
{
"status": discover.Status.CURRENT,
"id": "v2.2",
"links": [
{
"href": V3_URL,
"rel": "self"
}
]
},
**versions_in
)
]
}
self.requests_mock.get(
V3_URL, status_code=200, json=jsondata)
def test_ok(versions_in, versions_out):
setup_mock(versions_in)
# Ensure the output contains the expected microversions
self.assertEqual(
[
dict(
{
'collection': None,
'version': (2, 2),
'url': V3_URL,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
},
**versions_out
)
],
discover.Discover(self.session, V3_URL).version_data())
def test_exc(versions_in):
setup_mock(versions_in)
# Ensure TypeError is raised
self.assertRaises(
TypeError,
discover.Discover(self.session, V3_URL).version_data)
# no version info in input
test_ok({},
{'min_microversion': None, 'max_microversion': None,
'next_min_version': None, 'not_before': None})
# version => max_microversion
test_ok({'version': '2.2'},
{'min_microversion': None, 'max_microversion': (2, 2),
'next_min_version': None, 'not_before': None})
# max_version supersedes version (even if malformed). min_version &
# normalization.
test_ok({'min_version': '2', 'version': 'foo', 'max_version': '2.2'},
{'min_microversion': (2, 0), 'max_microversion': (2, 2),
'next_min_version': None, 'not_before': None})
# Edge case: min/max_version ignored if present but "empty"; version
# used for max_microversion.
test_ok({'min_version': '', 'version': '2.1', 'max_version': ''},
{'min_microversion': None, 'max_microversion': (2, 1),
'next_min_version': None, 'not_before': None})
# next_min_version set
test_ok({'min_version': '2', 'max_version': '2.2',
'next_min_version': '2.1', 'not_before': '2019-07-01'},
{'min_microversion': (2, 0), 'max_microversion': (2, 2),
'next_min_version': (2, 1), 'not_before': '2019-07-01'})
# Badly-formatted min_version
test_exc({'min_version': 'foo', 'max_version': '2.1'})
# Badly-formatted max_version
test_exc({'min_version': '2.1', 'max_version': 'foo'})
# Badly-formatted version (when max_version omitted)
test_exc({'min_version': '2.1', 'version': 'foo'})
# Badly-formatted next_min_version
test_exc({'next_min_version': 'bogus', 'not_before': '2019-07-01'})
def test_endpoint_data_noauth_discover(self):
mock = self.requests_mock.get(
BASE_URL, status_code=200, json=V3_VERSION_LIST)
self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = noauth.NoAuth(endpoint=BASE_URL)
data = plugin.get_endpoint_data(self.session)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), BASE_URL)
self.assertTrue(mock.called_once)
def test_endpoint_data_noauth_versioned_discover(self):
self.requests_mock.get(
BASE_URL, status_code=200, json=V3_VERSION_LIST)
self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = noauth.NoAuth(endpoint=V3_URL)
data = plugin.get_endpoint_data(self.session)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
def test_endpoint_data_noauth_no_discover(self):
plugin = noauth.NoAuth(endpoint=V3_URL)
data = plugin.get_endpoint_data(
self.session, discover_versions=False)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
def test_endpoint_data_noauth_override_no_discover(self):
plugin = noauth.NoAuth()
data = plugin.get_endpoint_data(
self.session, endpoint_override=V3_URL, discover_versions=False)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(
plugin.get_endpoint(self.session, endpoint_override=V3_URL),
V3_URL)
def test_endpoint_data_http_basic_discover(self):
self.requests_mock.get(
BASE_URL, status_code=200, json=V3_VERSION_LIST)
self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = http_basic.HTTPBasicAuth(endpoint=V3_URL)
data = plugin.get_endpoint_data(self.session)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
def test_endpoint_data_http_basic_no_discover(self):
plugin = http_basic.HTTPBasicAuth(endpoint=V3_URL)
data = plugin.get_endpoint_data(
self.session, discover_versions=False)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
def test_endpoint_data_http_basic_override_no_discover(self):
plugin = http_basic.HTTPBasicAuth()
data = plugin.get_endpoint_data(
self.session, endpoint_override=V3_URL, discover_versions=False)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(
plugin.get_api_major_version(
self.session, endpoint_override=V3_URL),
(3, 0))
self.assertEqual(
plugin.get_endpoint(self.session, endpoint_override=V3_URL),
V3_URL)
def test_endpoint_data_noauth_adapter(self):
self.requests_mock.get(
BASE_URL, status_code=200, json=V3_VERSION_LIST)
self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
client = adapter.Adapter(
session.Session(noauth.NoAuth()),
endpoint_override=BASE_URL)
data = client.get_endpoint_data()
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(client.get_api_major_version(), (3, 0))
self.assertEqual(client.get_endpoint(), BASE_URL)
def test_endpoint_data_noauth_versioned_adapter(self):
mock = self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
client = adapter.Adapter(
session.Session(noauth.NoAuth()),
endpoint_override=V3_URL)
data = client.get_endpoint_data()
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(client.get_api_major_version(), (3, 0))
self.assertEqual(client.get_endpoint(), V3_URL)
self.assertTrue(mock.called_once)
def test_endpoint_data_token_endpoint_discover(self):
mock = self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = token_endpoint.Token(endpoint=V3_URL, token='bogus')
data = plugin.get_endpoint_data(self.session)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
self.assertTrue(mock.called_once)
def test_endpoint_data_token_endpoint_no_discover(self):
plugin = token_endpoint.Token(endpoint=V3_URL, token='bogus')
data = plugin.get_endpoint_data(self.session, discover_versions=False)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
def test_endpoint_data_token_endpoint_adapter(self):
mock = self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = token_endpoint.Token(endpoint=V3_URL, token='bogus')
client = adapter.Adapter(session.Session(plugin))
data = client.get_endpoint_data()
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(client.get_api_major_version(), (3, 0))
self.assertEqual(client.get_endpoint(), V3_URL)
self.assertTrue(mock.called_once)
def test_data_for_url(self):
mock = self.requests_mock.get(V3_URL,
status_code=200,
json=V3_VERSION_ENTRY)
disc = discover.Discover(self.session, V3_URL)
for url in (V3_URL, V3_URL + '/'):
data = disc.versioned_data_for(url=url)
self.assertEqual(data['version'], (3, 0))
self.assertEqual(data['raw_status'], 'stable')
self.assertEqual(data['url'], V3_URL)
self.assertTrue(mock.called_once)
def test_data_for_no_version(self):
mock = self.requests_mock.get(V3_URL,
status_code=200,
json=V3_VERSION_ENTRY)
disc = discover.Discover(self.session, V3_URL)
data = disc.versioned_data_for()
self.assertEqual(data['version'], (3, 0))
self.assertEqual(data['raw_status'], 'stable')
self.assertEqual(data['url'], V3_URL)
self.assertRaises(TypeError, disc.data_for, version=None)
self.assertTrue(mock.called_once)
def test_keystone_version_data(self):
mock = self.requests_mock.get(BASE_URL,
status_code=300,
json=V3_VERSION_LIST)
disc = discover.Discover(self.session, BASE_URL)
raw_data = disc.raw_version_data()
clean_data = disc.version_data()
self.assertEqual(2, len(raw_data))
self.assertEqual(2, len(clean_data))
for v in raw_data:
self.assertIn(v['id'], ('v2.0', 'v3.0'))
self.assertEqual(v['updated'], UPDATED)
self.assertEqual(v['status'], 'stable')
if v['id'] == 'v3.0':
self.assertEqual(v['media-types'], V3_MEDIA_TYPES)
for v in clean_data:
self.assertIn(v['version'], ((2, 0), (3, 0)))
self.assertEqual(v['raw_status'], 'stable')
valid_v3_versions = (
disc.data_for('v3.0'),
disc.data_for('3.latest'),
disc.data_for('latest'),
disc.versioned_data_for(min_version='v3.0',
max_version='v3.latest'),
disc.versioned_data_for(min_version='3'),
disc.versioned_data_for(min_version='3.latest'),
disc.versioned_data_for(min_version='latest'),
disc.versioned_data_for(min_version='3.latest',
max_version='latest'),
disc.versioned_data_for(min_version='latest',
max_version='latest'),
disc.versioned_data_for(min_version=2),
disc.versioned_data_for(min_version='2.latest'))
for version in valid_v3_versions:
self.assertEqual((3, 0), version['version'])
self.assertEqual('stable', version['raw_status'])
self.assertEqual(V3_URL, version['url'])
valid_v2_versions = (
disc.data_for(2),
disc.data_for('2.latest'),
disc.versioned_data_for(min_version=2,
max_version=(2, discover.LATEST)),
disc.versioned_data_for(min_version='2.latest',
max_version='2.latest'))
for version in valid_v2_versions:
self.assertEqual((2, 0), version['version'])
self.assertEqual('stable', version['raw_status'])
self.assertEqual(V2_URL, version['url'])
self.assertIsNone(disc.url_for('v4'))
self.assertIsNone(disc.versioned_url_for(
min_version='v4', max_version='v4.latest'))
self.assertEqual(V3_URL, disc.url_for('v3'))
self.assertEqual(V3_URL, disc.versioned_url_for(
min_version='v3', max_version='v3.latest'))
self.assertEqual(V2_URL, disc.url_for('v2'))
self.assertEqual(V2_URL, disc.versioned_url_for(
min_version='v2', max_version='v2.latest'))
self.assertTrue(mock.called_once)
def test_cinder_version_data(self):
mock = self.requests_mock.get(BASE_URL,
status_code=300,
json=CINDER_EXAMPLES)
disc = discover.Discover(self.session, BASE_URL)
raw_data = disc.raw_version_data()
clean_data = disc.version_data()
self.assertEqual(3, len(raw_data))
for v in raw_data:
self.assertEqual(v['status'], discover.Status.CURRENT)
if v['id'] == 'v1.0':
self.assertEqual(v['updated'], '2012-01-04T11:33:21Z')
elif v['id'] == 'v2.0':
self.assertEqual(v['updated'], '2012-11-21T11:33:21Z')
elif v['id'] == 'v3.0':
self.assertEqual(v['updated'], '2012-11-21T11:33:21Z')
else:
self.fail("Invalid version found")
v1_url = "%sv1/" % BASE_URL
v2_url = "%sv2/" % BASE_URL
v3_url = "%sv3/" % BASE_URL
self.assertEqual(clean_data, [
{
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (1, 0),
'url': v1_url,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
},
{
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 0),
'url': v2_url,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
},
{
'collection': BASE_URL,
'max_microversion': (3, 27),
'min_microversion': (3, 0),
'next_min_version': (3, 4),
'not_before': u'2019-12-31',
'version': (3, 0),
'url': v3_url,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
},
])
for version in (disc.data_for('v2.0'),
disc.versioned_data_for(min_version='v2.0',
max_version='v2.latest')):
self.assertEqual((2, 0), version['version'])
self.assertEqual(discover.Status.CURRENT, version['raw_status'])
self.assertEqual(v2_url, version['url'])
for version in (disc.data_for(1),
disc.versioned_data_for(
min_version=(1,),
max_version=(1, discover.LATEST))):
self.assertEqual((1, 0), version['version'])
self.assertEqual(discover.Status.CURRENT, version['raw_status'])
self.assertEqual(v1_url, version['url'])
self.assertIsNone(disc.url_for('v4'))
self.assertIsNone(disc.versioned_url_for(min_version='v4',
max_version='v4.latest'))
self.assertEqual(v3_url, disc.url_for('v3'))
self.assertEqual(v3_url, disc.versioned_url_for(
min_version='v3', max_version='v3.latest'))
self.assertEqual(v2_url, disc.url_for('v2'))
self.assertEqual(v2_url, disc.versioned_url_for(
min_version='v2', max_version='v2.latest'))
self.assertEqual(v1_url, disc.url_for('v1'))
self.assertEqual(v1_url, disc.versioned_url_for(
min_version='v1', max_version='v1.latest'))
self.assertTrue(mock.called_once)
def test_glance_version_data(self):
mock = self.requests_mock.get(BASE_URL,
status_code=200,
json=GLANCE_EXAMPLES)
disc = discover.Discover(self.session, BASE_URL)
raw_data = disc.raw_version_data()
clean_data = disc.version_data()
self.assertEqual(5, len(raw_data))
for v in raw_data:
if v['id'] in ('v2.2', 'v1.1'):
self.assertEqual(v['status'], discover.Status.CURRENT)
elif v['id'] in ('v2.1', 'v2.0', 'v1.0'):
self.assertEqual(v['status'], discover.Status.SUPPORTED)
else:
self.fail("Invalid version found")
v1_url = '%sv1/' % BASE_URL
v2_url = '%sv2/' % BASE_URL
self.assertEqual(clean_data, [
{
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (1, 0),
'url': v1_url,
'status': discover.Status.SUPPORTED,
'raw_status': discover.Status.SUPPORTED,
},
{
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (1, 1),
'url': v1_url,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
},
{
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 0),
'url': v2_url,
'status': discover.Status.SUPPORTED,
'raw_status': discover.Status.SUPPORTED,
},
{
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 1),
'url': v2_url,
'status': discover.Status.SUPPORTED,
'raw_status': discover.Status.SUPPORTED,
},
{
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 2),
'url': v2_url,
'status': discover.Status.CURRENT,
'raw_status': discover.Status.CURRENT,
},
])
for ver in (2, 2.1, 2.2):
for version in (disc.data_for(ver),
disc.versioned_data_for(
min_version=ver,
max_version=(2, discover.LATEST))):
self.assertEqual((2, 2), version['version'])
self.assertEqual(
discover.Status.CURRENT, version['raw_status']
)
self.assertEqual(v2_url, version['url'])
self.assertEqual(v2_url, disc.url_for(ver))
for ver in (1, 1.1):
for version in (disc.data_for(ver),
disc.versioned_data_for(
min_version=ver,
max_version=(1, discover.LATEST))):
self.assertEqual((1, 1), version['version'])
self.assertEqual(
discover.Status.CURRENT, version['raw_status']
)
self.assertEqual(v1_url, version['url'])
self.assertEqual(v1_url, disc.url_for(ver))
self.assertIsNone(disc.url_for('v3'))
self.assertIsNone(disc.versioned_url_for(min_version='v3',
max_version='v3.latest'))
self.assertIsNone(disc.url_for('v2.3'))
self.assertIsNone(disc.versioned_url_for(min_version='v2.3',
max_version='v2.latest'))
self.assertTrue(mock.called_once)
def test_allow_deprecated(self):
status = 'deprecated'
version_list = [{'id': 'v3.0',
'links': [{'href': V3_URL, 'rel': 'self'}],
'media-types': V3_MEDIA_TYPES,
'status': status,
'updated': UPDATED}]
self.requests_mock.get(BASE_URL, json={'versions': version_list})
disc = discover.Discover(self.session, BASE_URL)
# deprecated is allowed by default
versions = disc.version_data(allow_deprecated=False)
self.assertEqual(0, len(versions))
versions = disc.version_data(allow_deprecated=True)
self.assertEqual(1, len(versions))
self.assertEqual(status, versions[0]['raw_status'])
self.assertEqual(V3_URL, versions[0]['url'])
self.assertEqual((3, 0), versions[0]['version'])
def test_allow_experimental(self):
status = 'experimental'
version_list = [{'id': 'v3.0',
'links': [{'href': V3_URL, 'rel': 'self'}],
'media-types': V3_MEDIA_TYPES,
'status': status,
'updated': UPDATED}]
self.requests_mock.get(BASE_URL, json={'versions': version_list})
disc = discover.Discover(self.session, BASE_URL)
versions = disc.version_data()
self.assertEqual(0, len(versions))
versions = disc.version_data(allow_experimental=True)
self.assertEqual(1, len(versions))
self.assertEqual(status, versions[0]['raw_status'])
self.assertEqual(V3_URL, versions[0]['url'])
self.assertEqual((3, 0), versions[0]['version'])
def test_allow_unknown(self):
status = 'abcdef'
version_list = fixture.DiscoveryList(BASE_URL,
v2=False,
v3_status=status)
self.requests_mock.get(BASE_URL, json=version_list)
disc = discover.Discover(self.session, BASE_URL)
versions = disc.version_data()
self.assertEqual(0, len(versions))
versions = disc.version_data(allow_unknown=True)
self.assertEqual(1, len(versions))
self.assertEqual(status, versions[0]['raw_status'])
self.assertEqual(V3_URL, versions[0]['url'])
self.assertEqual((3, 0), versions[0]['version'])
def test_ignoring_invalid_links(self):
version_list = [{'id': 'v3.0',
'links': [{'href': V3_URL, 'rel': 'self'}],
'media-types': V3_MEDIA_TYPES,
'status': 'stable',
'updated': UPDATED},
{'id': 'v3.1',
'media-types': V3_MEDIA_TYPES,
'status': 'stable',
'updated': UPDATED},
{'media-types': V3_MEDIA_TYPES,
'status': 'stable',
'updated': UPDATED,
'links': [{'href': V3_URL, 'rel': 'self'}],
}]
self.requests_mock.get(BASE_URL, json={'versions': version_list})
disc = discover.Discover(self.session, BASE_URL)
# raw_version_data will return all choices, even invalid ones
versions = disc.raw_version_data()
self.assertEqual(3, len(versions))
# only the version with both id and links will be actually returned
versions = disc.version_data()
self.assertEqual(1, len(versions))
class EndpointDataTests(utils.TestCase):
def setUp(self):
super(EndpointDataTests, self).setUp()
self.session = session.Session()
@mock.patch('keystoneauth1.discover.get_discovery')
@mock.patch('keystoneauth1.discover.EndpointData.'
'_get_discovery_url_choices')
def test_run_discovery_cache(self, mock_url_choices, mock_get_disc):
# get_discovery raises so we keep looping
mock_get_disc.side_effect = exceptions.DiscoveryFailure()
# Duplicate 'url1' in here to validate the cache behavior
mock_url_choices.return_value = ('url1', 'url2', 'url1', 'url3')
epd = discover.EndpointData()
epd._run_discovery(
session='sess', cache='cache', min_version='min',
max_version='max', project_id='projid',
allow_version_hack='allow_hack', discover_versions='disc_vers')
# Only one call with 'url1'
self.assertEqual(3, mock_get_disc.call_count)
mock_get_disc.assert_has_calls(
[mock.call('sess', url, cache='cache', authenticated=False)
for url in ('url1', 'url2', 'url3')])
def test_run_discovery_auth(self):
url = 'https://example.com'
headers = {'Accept': 'application/json',
'OpenStack-API-Version': 'version header test'}
session = mock.Mock()
session.get.side_effect = [
exceptions.Unauthorized('unauthorized'),
# Throw a different exception the second time so that we can
# catch it in the test and verify the retry.
exceptions.BadRequest('bad request'),
]
try:
discover.get_version_data(
session, url, version_header='version header test')
except exceptions.BadRequest:
pass
# Only one call with 'url'
self.assertEqual(2, session.get.call_count)
session.get.assert_has_calls([
mock.call(url, headers=headers, authenticated=None),
mock.call(url, headers=headers, authenticated=True),
])
def test_endpoint_data_str(self):
"""Validate EndpointData.__str__."""
# Populate a few fields to make sure they come through.
epd = discover.EndpointData(catalog_url='abc', service_type='123',
api_version=(2, 3))
exp = (
'EndpointData{api_version=(2, 3), catalog_url=abc,'
' endpoint_id=None, interface=None, major_version=None,'
' max_microversion=None, min_microversion=None,'
' next_min_version=None, not_before=None, raw_endpoint=None,'
' region_name=None, service_id=None, service_name=None,'
' service_type=123, service_url=None, url=abc}')
# Works with str()
self.assertEqual(exp, str(epd))
# Works with implicit stringification
self.assertEqual(exp, "%s" % epd)
def test_project_id_int_fallback(self):
bad_url = "https://compute.example.com/v2/123456"
epd = discover.EndpointData(catalog_url=bad_url)
self.assertEqual((2, 0), epd.api_version)
def test_url_version_match_project_id_int(self):
self.session = session.Session()
discovery_fixture = fixture.V3Discovery(V3_URL)
discovery_doc = _create_single_version(discovery_fixture)
self.requests_mock.get(V3_URL, status_code=200, json=discovery_doc)
epd = discover.EndpointData(catalog_url=V3_URL).get_versioned_data(
session=self.session, project_id='3')
self.assertEqual(epd.catalog_url, epd.url)