diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index e56a40f22..b1f2bdb8b 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -394,66 +394,73 @@ class OpenStackCloud(_normalize.Normalizer): self._raw_clients['raw-image'] = image_client return self._raw_clients['raw-image'] + def _discover_image_endpoint(self, config_version, image_client): + try: + # Version discovery + versions = image_client.get('/') + api_version = None + if config_version.startswith('1'): + api_version = [ + version for version in versions + if version['id'] in ('v1.0', 'v1.1')] + if api_version: + api_version = api_version[0] + if not api_version: + api_version = [ + version for version in versions + if version['status'] == 'CURRENT'][0] + + image_url = api_version['links'][0]['href'] + # If we detect a different version that was configured, + # set the version in occ because we have logic elsewhere + # that is different depending on which version we're using + warning_msg = None + if (config_version.startswith('2') + and api_version['id'].startswith('v1')): + self.cloud_config.config['image_api_version'] = '1' + warning_msg = ( + 'image_api_version is 2 but only 1 is available.') + elif (config_version.startswith('1') + and api_version['id'].startswith('v2')): + self.cloud_config.config['image_api_version'] = '2' + warning_msg = ( + 'image_api_version is 1 but only 2 is available.') + if warning_msg: + self.log.debug(warning_msg) + warnings.warn(warning_msg) + except (keystoneauth1.exceptions.connection.ConnectFailure, + OpenStackCloudURINotFound) as e: + # A 404 or a connection error is a likely thing to get + # either with a misconfgured glance. or we've already + # gotten a versioned endpoint from the catalog + self.log.debug( + "Glance version discovery failed, assuming endpoint in" + " the catalog is already versioned. {e}".format(e=str(e))) + image_url = image_client.get_endpoint() + + service_url = image_client.get_endpoint() + parsed_image_url = urllib.parse.urlparse(image_url) + parsed_service_url = urllib.parse.urlparse(service_url) + + image_url = urllib.parse.ParseResult( + parsed_service_url.scheme, + parsed_image_url.netloc, + parsed_image_url.path, + parsed_image_url.params, + parsed_image_url.query, + parsed_image_url.fragment).geturl() + return image_url + @property def _image_client(self): if 'image' not in self._raw_clients: # Get configured api version for downgrades config_version = self.cloud_config.get_api_version('image') image_client = self._get_raw_client('image') - try: - # Version discovery - versions = image_client.get('/') - api_version = None - if config_version.startswith('1'): - api_version = [ - version for version in versions - if version['id'] in ('v1.0', 'v1.1')] - if api_version: - api_version = api_version[0] - if not api_version: - api_version = [ - version for version in versions - if version['status'] == 'CURRENT'][0] - - image_url = api_version['links'][0]['href'] - # If we detect a different version that was configured, - # set the version in occ because we have logic elsewhere - # that is different depending on which version we're using - warning_msg = None - if (config_version.startswith('2') - and api_version['id'].startswith('v1')): - self.cloud_config.config['image_api_version'] = '1' - warning_msg = ( - 'image_api_version is 2 but only 1 is available.') - elif (config_version.startswith('1') - and api_version['id'].startswith('v2')): - self.cloud_config.config['image_api_version'] = '2' - warning_msg = ( - 'image_api_version is 1 but only 2 is available.') - if warning_msg: - self.log.debug(warning_msg) - warnings.warn(warning_msg) - except (keystoneauth1.exceptions.connection.ConnectFailure, - OpenStackCloudURINotFound) as e: - # A 404 or a connection error is a likely thing to get - # either with a misconfgured glance. or we've already - # gotten a versioned endpoint from the catalog - self.log.debug( - "Glance version discovery failed, assuming endpoint in" - " the catalog is already versioned. {e}".format(e=str(e))) - image_url = image_client.get_endpoint() - - service_url = image_client.get_endpoint() - parsed_image_url = urllib.parse.urlparse(image_url) - parsed_service_url = urllib.parse.urlparse(service_url) - - image_url = urllib.parse.ParseResult( - parsed_service_url.scheme, - parsed_image_url.netloc, - parsed_image_url.path, - parsed_image_url.params, - parsed_image_url.query, - parsed_image_url.fragment).geturl() + image_url = self.cloud_config.config.get('image_endpoint_override') + if not image_url: + image_url = self._discover_image_endpoint( + config_version, image_client) image_client.endpoint_override = image_url self._raw_clients['image'] = image_client return self._raw_clients['image'] diff --git a/shade/tests/unit/test_image.py b/shade/tests/unit/test_image.py index 0b6ea254a..cd0ae847e 100644 --- a/shade/tests/unit/test_image.py +++ b/shade/tests/unit/test_image.py @@ -31,10 +31,10 @@ NO_MD5 = '93b885adfe0da089cdf634904fd59f71' NO_SHA256 = '6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d' -class TestImage(base.RequestsMockTestCase): +class BaseTestImage(base.RequestsMockTestCase): def setUp(self): - super(TestImage, self).setUp() + super(BaseTestImage, self).setUp() self.image_id = str(uuid.uuid4()) self.imagefile = tempfile.NamedTemporaryFile(delete=False) self.imagefile.write(b'\0') @@ -68,6 +68,12 @@ class TestImage(base.RequestsMockTestCase): u'protected': False} self.fake_search_return = {'images': [self.fake_image_dict]} self.output = uuid.uuid4().bytes + + +class TestImage(BaseTestImage): + + def setUp(self): + super(TestImage, self).setUp() self.use_glance() def test_config_v1(self): @@ -733,3 +739,24 @@ class TestImageV2Only(base.RequestsMockTestCase): self.cloud._image_client.get_endpoint()) self.assertEqual( '2', self.cloud_config.get_api_version('image')) + + +class TestImageVersionDiscovery(BaseTestImage): + + def test_version_discovery_skip(self): + self.cloud.cloud_config.config['image_endpoint_override'] = \ + 'https://image.example.com/v2/override' + + self.adapter.register_uri( + 'GET', 'https://image.example.com/v2/override/images', + json={'images': []}) + self.assertEqual([], self.cloud.list_images()) + self.assertEqual( + self.cloud._image_client.endpoint_override, + 'https://image.example.com/v2/override') + self.calls += [ + dict( + method='GET', + url='https://image.example.com/v2/override/images'), + ] + self.assert_calls()