Handle old status-less placement service

Older versions of placement didn't include the status field
in their version discovery documents. This was fixed in
placement in https://review.opendev.org/575117, but there
are clouds out there in the wild running this version
and SDK bombs out trying to talk to them.

Change-Id: I152f0c626b358328cedd404c8fc8d0bee46d2991
This commit is contained in:
Monty Taylor 2020-03-04 14:10:27 -06:00
parent f34d399f52
commit 6f80554807
6 changed files with 89 additions and 7 deletions

View File

@ -321,6 +321,10 @@ class CloudRegion(object):
else:
return 'unknown'
def set_service_value(self, key, service_type, value):
key = _make_key(key, service_type)
self.config[key] = value
def set_session_constructor(self, session_constructor):
"""Sets the Session constructor."""
self._session_constructor = session_constructor
@ -645,7 +649,7 @@ class CloudRegion(object):
self.get_interface(service_type), {})
return interface_versions.get(service_type, [])
def _get_hardcoded_endpoint(self, service_type, constructor):
def _get_endpoint_from_catalog(self, service_type, constructor):
adapter = constructor(
session=self.get_session(),
service_type=self.get_service_type(service_type),
@ -653,7 +657,11 @@ class CloudRegion(object):
interface=self.get_interface(service_type),
region_name=self.get_region_name(service_type),
)
endpoint = adapter.get_endpoint()
return adapter.get_endpoint()
def _get_hardcoded_endpoint(self, service_type, constructor):
endpoint = self._get_endpoint_from_catalog(
service_type, constructor)
if not endpoint.rstrip().rsplit('/')[-1] == 'v2.0':
if not endpoint.endswith('/'):
endpoint += '/'

View File

@ -89,13 +89,38 @@ class ServiceDescription(object):
# The keystone proxy has a method called get_endpoint
# that is about managing keystone endpoints. This is
# unfortunate.
endpoint = proxy_mod.Proxy.get_endpoint(proxy)
try:
endpoint = proxy_mod.Proxy.get_endpoint(proxy)
except IndexError:
# It's best not to look to closely here. This is
# to support old placement.
# There was a time when it had no status entry
# in its version discovery doc (OY) In this case,
# no endpoints get through version discovery
# filtering. In order to deal with that, catch
# the IndexError thrown by keystoneauth and
# set an endpoint_override for the user to the
# url in the catalog and try again.
self._set_override_from_catalog(instance.config)
proxy = self._make_proxy(instance)
endpoint = proxy_mod.Proxy.get_endpoint(proxy)
if instance._strict_proxies:
self._validate_proxy(proxy, endpoint)
proxy._connection = instance
instance._proxies[self.service_type] = proxy
return instance._proxies[self.service_type]
def _set_override_from_catalog(self, config):
override = config._get_endpoint_from_catalog(
self.service_type,
proxy_mod.Proxy,
)
config.set_service_value(
'endpoint_override',
self.service_type,
override,
)
def _validate_proxy(self, proxy, endpoint):
exc = None
service_url = getattr(proxy, 'skip_discovery', None)

View File

@ -532,9 +532,10 @@ class TestCase(base.TestCase):
uri=compute_discovery_url,
text=open(discovery_fixture, 'r').read())
def get_placement_discovery_mock_dict(self):
def get_placement_discovery_mock_dict(
self, discovery_fixture='placement.json'):
discovery_fixture = os.path.join(
self.fixtures_directory, "placement.json")
self.fixtures_directory, discovery_fixture)
return dict(method='GET', uri="https://placement.example.com/",
text=open(discovery_fixture, 'r').read())
@ -580,9 +581,9 @@ class TestCase(base.TestCase):
self.__do_register_uris([
self.get_cinder_discovery_mock_dict()])
def use_placement(self):
def use_placement(self, **kwargs):
self.__do_register_uris([
self.get_placement_discovery_mock_dict()])
self.get_placement_discovery_mock_dict(**kwargs)])
def use_designate(self):
# NOTE(slaweq): This method is only meant to be used in "setUp"

View File

@ -0,0 +1,10 @@
{
"versions": [
{
"id": "v1.0",
"links": [{"href": "", "rel": "self"}],
"max_version": "1.17",
"min_version": "1.0"
}
]
}

View File

@ -69,3 +69,35 @@ class TestPlacementRest(base.TestCase):
(1, 17),
self.cloud.placement.get_endpoint_data().max_microversion)
self.assert_calls()
class TestBadPlacementRest(base.TestCase):
def setUp(self):
super(TestBadPlacementRest, self).setUp()
# The bad-placement.json is for older placement that was
# missing the status field from its discovery doc. This
# lets us show that we can talk to such a placement.
self.use_placement(discovery_fixture='bad-placement.json')
def _register_uris(self, status_code=None):
uri = dict(
method='GET',
uri=self.get_mock_url(
'placement', 'public', append=['allocation_candidates']),
json={})
if status_code is not None:
uri['status_code'] = status_code
self.register_uris([uri])
def _validate_resp(self, resp, status_code):
self.assertEqual(status_code, resp.status_code)
self.assertEqual(
'https://placement.example.com/allocation_candidates',
resp.url)
self.assert_calls()
def test_discovery(self):
self._register_uris()
rs = self.cloud.placement.get('/allocation_candidates')
self._validate_resp(rs, 200)

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Workaround an issue using openstacksdk with older versions of
the placement service that are missing a status field in
their version discovery doc.