diff --git a/openstack/config/cloud_region.py b/openstack/config/cloud_region.py index 73d05ae3d..b44c89afc 100644 --- a/openstack/config/cloud_region.py +++ b/openstack/config/cloud_region.py @@ -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 += '/' diff --git a/openstack/service_description.py b/openstack/service_description.py index c6cd11e3f..2aad61a6d 100644 --- a/openstack/service_description.py +++ b/openstack/service_description.py @@ -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) diff --git a/openstack/tests/unit/base.py b/openstack/tests/unit/base.py index 9bb7a4d03..dca787917 100644 --- a/openstack/tests/unit/base.py +++ b/openstack/tests/unit/base.py @@ -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" diff --git a/openstack/tests/unit/fixtures/bad-placement.json b/openstack/tests/unit/fixtures/bad-placement.json new file mode 100644 index 000000000..72f7fd716 --- /dev/null +++ b/openstack/tests/unit/fixtures/bad-placement.json @@ -0,0 +1,10 @@ +{ + "versions": [ + { + "id": "v1.0", + "links": [{"href": "", "rel": "self"}], + "max_version": "1.17", + "min_version": "1.0" + } + ] +} diff --git a/openstack/tests/unit/test_placement_rest.py b/openstack/tests/unit/test_placement_rest.py index b82b31d52..cd8104997 100644 --- a/openstack/tests/unit/test_placement_rest.py +++ b/openstack/tests/unit/test_placement_rest.py @@ -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) diff --git a/releasenotes/notes/old-placement-4b3c34abb8fe7b81.yaml b/releasenotes/notes/old-placement-4b3c34abb8fe7b81.yaml new file mode 100644 index 000000000..402e87343 --- /dev/null +++ b/releasenotes/notes/old-placement-4b3c34abb8fe7b81.yaml @@ -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.