diff --git a/nova/image/glance.py b/nova/image/glance.py index 2de3228fb0..1a8d532afb 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -22,6 +22,7 @@ import inspect import itertools import os import random +import re import stat import sys import time @@ -122,7 +123,14 @@ def get_api_servers(context): nova.conf.glance.DEFAULT_SERVICE_TYPE, ksa_auth=auth, ksa_session=sess, min_version='2.0', max_version='2.latest') - api_servers = [utils.get_endpoint(ksa_adap)] + endpoint = utils.get_endpoint(ksa_adap) + if endpoint: + # NOTE(mriedem): Due to python-glanceclient bug 1707995 we have + # to massage the endpoint URL otherwise it won't work properly. + # We can't use glanceclient.common.utils.strip_version because + # of bug 1748009. + endpoint = re.sub(r'/v\d+(\.\d+)?/?$', '/', endpoint) + api_servers = [endpoint] return itertools.cycle(api_servers) diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index b511a00ef6..e775e67aad 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -3960,6 +3960,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase): mock_setup_networks, mock_setup_intance_network, mock_get_bdms, mock_mutate_migration, mock_appy_migration, mock_drop_migration, mock_context_elevated): + self.flags(api_servers=['http://localhost/image/v2'], group='glance') instance = fake_instance.fake_instance_obj(self.context) instance.info_cache = None elevated_context = mock.Mock() diff --git a/nova/tests/unit/image/test_glance.py b/nova/tests/unit/image/test_glance.py index bae2fbc356..d1507827b4 100644 --- a/nova/tests/unit/image/test_glance.py +++ b/nova/tests/unit/image/test_glance.py @@ -19,6 +19,7 @@ import datetime import cryptography from cursive import exception as cursive_exception +import ddt import glanceclient.exc from glanceclient.v1 import images import glanceclient.v2.schemas as schemas @@ -1601,6 +1602,7 @@ class TestDelete(test.NoDBTestCase): mock.sentinel.image_id) +@ddt.ddt class TestGlanceApiServers(test.NoDBTestCase): def test_get_api_servers_multiple(self): @@ -1615,18 +1617,36 @@ class TestGlanceApiServers(test.NoDBTestCase): self.assertEqual(expected_servers, {next(api_servers) for _ in expected_servers}) - @mock.patch('keystoneauth1.adapter.Adapter.get_endpoint_data') - def test_get_api_servers_get_ksa_adapter(self, mock_epd): + @ddt.data(['http://158.69.92.100/image/v2/', + 'http://158.69.92.100/image/'], + ['http://158.69.92.100/image/v2', + 'http://158.69.92.100/image/'], + ['http://158.69.92.100/image/v2.0/', + 'http://158.69.92.100/image/'], + ['http://158.69.92.100/image/', + 'http://158.69.92.100/image/'], + ['http://158.69.92.100/image', + 'http://158.69.92.100/image'], + ['http://158.69.92.100/v2', + 'http://158.69.92.100/'], + ['http://thing.novav2.0oh.v2.foo/image/v2/', + 'http://thing.novav2.0oh.v2.foo/image/']) + @ddt.unpack + def test_get_api_servers_get_ksa_adapter(self, catalog_url, stripped): """Test get_api_servers via nova.utils.get_ksa_adapter().""" self.flags(api_servers=None, group='glance') - api_servers = glance.get_api_servers(mock.Mock()) - self.assertEqual(mock_epd.return_value.catalog_url, next(api_servers)) - # Still get itertools.cycle behavior - self.assertEqual(mock_epd.return_value.catalog_url, next(api_servers)) - mock_epd.assert_called_once_with() + with mock.patch('keystoneauth1.adapter.Adapter.' + 'get_endpoint_data') as mock_epd: + mock_epd.return_value.catalog_url = catalog_url + api_servers = glance.get_api_servers(mock.Mock()) + self.assertEqual(stripped, next(api_servers)) + # Still get itertools.cycle behavior + self.assertEqual(stripped, next(api_servers)) + mock_epd.assert_called_once_with() - # Now test with endpoint_override - get_endpoint_data is not called. - mock_epd.reset_mock() + @mock.patch('keystoneauth1.adapter.Adapter.get_endpoint_data') + def test_get_api_servers_get_ksa_adapter_endpoint_override(self, + mock_epd): self.flags(endpoint_override='foo', group='glance') api_servers = glance.get_api_servers(mock.Mock()) self.assertEqual('foo', next(api_servers)) diff --git a/releasenotes/notes/bug-1747511-glance-api-servers-1e17757b901a76d8.yaml b/releasenotes/notes/bug-1747511-glance-api-servers-1e17757b901a76d8.yaml new file mode 100644 index 0000000000..e81a40d6c9 --- /dev/null +++ b/releasenotes/notes/bug-1747511-glance-api-servers-1e17757b901a76d8.yaml @@ -0,0 +1,11 @@ +--- +issues: + - | + Due to a bug in python-glanceclient: + + https://bugs.launchpad.net/python-glanceclient/+bug/1707995 + + If ``[glance]/api_servers`` is not set in nova.conf, and there is a + versioned endpoint URL in the service catalog, nova makes a best attempt + at parsing and stripping the version from the URL in order to make + API requests to the image service.