Remove Glance v1 API support

The Glance v1 API is about to be removed. Cinder support for using the
current v2 API was added in Pike, but we allowed selection of which
version to use through the glance_api_version config option. That
option was also deprecated in Pike.

This removes the config option to allow selection and switches things
over to only use Glance API v2.

Change-Id: Ice379db9ae83420bacf9e96e242c7515930eae86
This commit is contained in:
Sean McGinnis 2017-09-08 17:13:14 -05:00
parent bfcef9cb6b
commit d76fef6bf4
11 changed files with 64 additions and 209 deletions

View File

@ -279,12 +279,6 @@ class VolumeActionsController(wsgi.Controller):
if image_metadata['visibility'] == 'public':
authorize(context, 'upload_public')
if CONF.glance_api_version != 2:
# Replace visibility with is_public for Glance V1
image_metadata['is_public'] = (
image_metadata['visibility'] == 'public')
image_metadata.pop('visibility', None)
image_metadata['protected'] = (
utils.get_bool_param('protected', image_metadata))

View File

@ -53,12 +53,6 @@ global_opts = [
help='A list of the URLs of glance API servers available to '
'cinder ([http[s]://][hostname|ip]:port). If protocol '
'is not specified it defaults to http.'),
cfg.IntOpt('glance_api_version',
default=2,
deprecated_for_removal=True,
deprecated_since="11.0.0",
deprecated_reason='Glance v1 support will be removed in Queens',
help='Version of the glance API to use'),
cfg.IntOpt('glance_num_retries',
min=0,
default=0,

View File

@ -62,7 +62,6 @@ glance_core_properties_opts = [
CONF = cfg.CONF
CONF.register_opts(glance_opts)
CONF.register_opts(glance_core_properties_opts)
CONF.import_opt('glance_api_version', 'cinder.common.config')
LOG = logging.getLogger(__name__)
@ -82,10 +81,8 @@ def _parse_image_ref(image_href):
return (image_id, netloc, use_ssl)
def _create_glance_client(context, netloc, use_ssl, version=None):
def _create_glance_client(context, netloc, use_ssl):
"""Instantiate a new glanceclient.Client object."""
if version is None:
version = CONF.glance_api_version
params = {}
if use_ssl:
scheme = 'https'
@ -101,7 +98,7 @@ def _create_glance_client(context, netloc, use_ssl, version=None):
params['timeout'] = CONF.glance_request_timeout
endpoint = '%s://%s' % (scheme, netloc)
params['global_request_id'] = context.global_id
return glanceclient.Client(str(version), endpoint, **params)
return glanceclient.Client('2', endpoint, **params)
def get_api_servers(context):
@ -147,34 +144,31 @@ def get_api_servers(context):
class GlanceClientWrapper(object):
"""Glance client wrapper class that implements retries."""
def __init__(self, context=None, netloc=None, use_ssl=False,
version=None):
def __init__(self, context=None, netloc=None, use_ssl=False):
if netloc is not None:
self.client = self._create_static_client(context,
netloc,
use_ssl, version)
use_ssl)
else:
self.client = None
self.api_servers = None
self.version = version
def _create_static_client(self, context, netloc, use_ssl, version):
def _create_static_client(self, context, netloc, use_ssl):
"""Create a client that we'll use for every call."""
self.netloc = netloc
self.use_ssl = use_ssl
self.version = version
return _create_glance_client(context,
self.netloc,
self.use_ssl, self.version)
self.use_ssl)
def _create_onetime_client(self, context, version):
def _create_onetime_client(self, context):
"""Create a client that will be used for one call."""
if self.api_servers is None:
self.api_servers = get_api_servers(context)
self.netloc, self.use_ssl = next(self.api_servers)
return _create_glance_client(context,
self.netloc,
self.use_ssl, version)
self.use_ssl)
def call(self, context, method, *args, **kwargs):
"""Call a glance client method.
@ -182,7 +176,6 @@ class GlanceClientWrapper(object):
If we get a connection error,
retry the request according to CONF.glance_num_retries.
"""
version = kwargs.pop('version', self.version)
retry_excs = (glanceclient.exc.ServiceUnavailable,
glanceclient.exc.InvalidEndpoint,
@ -190,8 +183,7 @@ class GlanceClientWrapper(object):
num_attempts = 1 + CONF.glance_num_retries
for attempt in range(1, num_attempts + 1):
client = self.client or self._create_onetime_client(context,
version)
client = self.client or self._create_onetime_client(context)
try:
controller = getattr(client,
kwargs.pop('controller', 'images'))
@ -248,16 +240,6 @@ class GlanceImageService(object):
if param in params:
_params[param] = params.get(param)
# NOTE(geguileo): We set is_public default value for v1 because we want
# to retrieve all images by default. We don't need to send v2
# equivalent - "visible" - because its default value when omitted is
# "public, private, shared", which will return all.
if CONF.glance_api_version <= 1:
# ensure filters is a dict
_params.setdefault('filters', {})
# NOTE(vish): don't filter out private images
_params['filters'].setdefault('is_public', 'none')
return _params
def show(self, context, image_id):
@ -280,12 +262,9 @@ class GlanceImageService(object):
the backend storage location, or (None, None) if these attributes are
not shown by Glance.
"""
if CONF.glance_api_version == 1:
# image location not available in v1
return (None, None)
try:
# direct_url is returned by v2 api
client = GlanceClientWrapper(version=2)
client = GlanceClientWrapper()
image_meta = client.call(context, 'get', image_id)
except Exception:
_reraise_translated_image_exception(image_id)
@ -304,9 +283,7 @@ class GlanceImageService(object):
Returns a dict containing image metadata on success.
"""
if CONF.glance_api_version != 2:
raise exception.Invalid("Image API version 2 is disabled.")
client = GlanceClientWrapper(version=2)
client = GlanceClientWrapper()
try:
return client.call(context, 'add_location',
image_id, url, metadata)
@ -360,39 +337,30 @@ class GlanceImageService(object):
# directly. We need the custom properties to identify properties to
# remove if purge_props is True. Save the custom properties before
# translate.
if CONF.glance_api_version > 1 and purge_props:
if purge_props:
props_to_update = image_meta.get('properties', {}).keys()
image_meta = self._translate_to_glance(image_meta)
# NOTE(dosaboy): see comment in bug 1210467
if CONF.glance_api_version == 1:
image_meta['purge_props'] = purge_props
# NOTE(bcwaldon): id is not an editable field, but it is likely to be
# passed in by calling code. Let's be nice and ignore it.
image_meta.pop('id', None)
try:
# NOTE(dosaboy): the v2 api separates update from upload
if CONF.glance_api_version > 1:
if data:
self._client.call(context, 'upload', image_id, data)
if image_meta:
if purge_props:
# Properties to remove are those not specified in
# input properties.
cur_image_meta = self.show(context, image_id)
cur_props = cur_image_meta['properties'].keys()
remove_props = list(set(cur_props) -
set(props_to_update))
image_meta['remove_props'] = remove_props
image_meta = self._client.call(context, 'update', image_id,
**image_meta)
else:
image_meta = self._client.call(context, 'get', image_id)
else:
if data:
image_meta['data'] = data
if data:
self._client.call(context, 'upload', image_id, data)
if image_meta:
if purge_props:
# Properties to remove are those not specified in
# input properties.
cur_image_meta = self.show(context, image_id)
cur_props = cur_image_meta['properties'].keys()
remove_props = list(set(cur_props) -
set(props_to_update))
image_meta['remove_props'] = remove_props
image_meta = self._client.call(context, 'update', image_id,
**image_meta)
else:
image_meta = self._client.call(context, 'get', image_id)
except Exception:
_reraise_translated_image_exception(image_id)
else:
@ -420,27 +388,23 @@ class GlanceImageService(object):
:param image: glance image object
:return: image metadata dictionary
"""
if CONF.glance_api_version == 2:
if self._image_schema is None:
self._image_schema = self._client.call(context, 'get',
controller='schemas',
schema_name='image',
version=2)
# NOTE(aarefiev): get base image property, store image 'schema'
# is redundant, so ignore it.
image_meta = {key: getattr(image, key)
for key in image.keys()
if self._image_schema.is_base_property(key) is True
and key != 'schema'}
if self._image_schema is None:
self._image_schema = self._client.call(context, 'get',
controller='schemas',
schema_name='image')
# NOTE(aarefiev): get base image property, store image 'schema'
# is redundant, so ignore it.
image_meta = {key: getattr(image, key)
for key in image.keys()
if self._image_schema.is_base_property(key) is True
and key != 'schema'}
# NOTE(aarefiev): nova is expected that all image properties
# (custom or defined in schema-image.json) stores in
# 'properties' key.
image_meta['properties'] = {
key: getattr(image, key) for key in image.keys()
if self._image_schema.is_base_property(key) is False}
else:
image_meta = _extract_attributes(image)
# NOTE(aarefiev): nova is expected that all image properties
# (custom or defined in schema-image.json) stores in
# 'properties' key.
image_meta['properties'] = {
key: getattr(image, key) for key in image.keys()
if self._image_schema.is_base_property(key) is False}
image_meta = _convert_timestamps_to_datetimes(image_meta)
image_meta = _convert_from_string(image_meta)
@ -453,11 +417,10 @@ class GlanceImageService(object):
# NOTE(tsekiyama): From the Image API v2, custom properties must
# be stored in image_meta directly, instead of the 'properties' key.
if CONF.glance_api_version >= 2:
properties = image_meta.get('properties')
if properties:
image_meta.update(properties)
del image_meta['properties']
properties = image_meta.get('properties')
if properties:
image_meta.update(properties)
del image_meta['properties']
return image_meta
@ -545,11 +508,8 @@ def _extract_attributes(image):
'container_format', 'status', 'id',
'name', 'created_at', 'updated_at',
'deleted', 'deleted_at', 'checksum',
'min_disk', 'min_ram', 'protected']
if CONF.glance_api_version == 2:
IMAGE_ATTRIBUTES.append('visibility')
else:
IMAGE_ATTRIBUTES.append('is_public')
'min_disk', 'min_ram', 'protected',
'visibility']
output = {}

View File

@ -1327,8 +1327,6 @@ class VolumeImageActionsTest(test.TestCase):
mock_copy_volume_to_image.side_effect = \
self.fake_rpc_copy_volume_to_image
self.override_config('glance_api_version', 2)
req = fakes.HTTPRequest.blank(
'/v3/%s/volumes/%s/action' % (fake.PROJECT_ID, volume.id),
use_admin_context=self.context.is_admin)

View File

@ -108,7 +108,7 @@ class TestGlanceImageService(test.TestCase):
self.mock_object(glance.time, 'sleep', return_value=None)
def _create_image_service(self, client):
def _fake_create_glance_client(context, netloc, use_ssl, version):
def _fake_create_glance_client(context, netloc, use_ssl):
return client
self.mock_object(glance, '_create_glance_client',
@ -251,19 +251,8 @@ class TestGlanceImageService(test.TestCase):
self.assertEqual('test image', image_metas[0]['name'])
self.assertEqual('private', image_metas[0]['visibility'])
def test_detail_v1(self):
"""Confirm we send is_public = None as default when using Glance v1."""
self.override_config('glance_api_version', 1)
with mock.patch.object(self.service, '_client') as client_mock:
client_mock.return_value = []
result = self.service.detail(self.context)
self.assertListEqual([], result)
client_mock.call.assert_called_once_with(self.context, 'list',
filters={'is_public': 'none'})
def test_detail_v2(self):
"""Check we don't send is_public key by default with Glance v2."""
self.override_config('glance_api_version', 2)
with mock.patch.object(self.service, '_client') as client_mock:
client_mock.return_value = []
result = self.service.detail(self.context)
@ -381,10 +370,6 @@ class TestGlanceImageService(test.TestCase):
new_image_data = self.service.show(self.context, image_id)
self.assertEqual('new image name', new_image_data['name'])
def test_update_v2(self):
self.flags(glance_api_version=2)
self.test_update()
def test_update_with_data(self):
fixture = self._make_fixture(name='test image')
image = self.service.create(self.context, fixture)
@ -397,37 +382,21 @@ class TestGlanceImageService(test.TestCase):
self.assertEqual(256, new_image_data['size'])
self.assertEqual('new image name', new_image_data['name'])
def test_update_with_data_v2(self):
self.flags(glance_api_version=2)
self.test_update_with_data()
@mock.patch.object(glance.GlanceImageService, '_translate_from_glance')
@mock.patch.object(glance.GlanceImageService, 'show')
@ddt.data(1, 2)
def test_update_purge_props(self, ver, show, translate_from_glance):
self.flags(glance_api_version=ver)
def test_update_purge_props(self, show, translate_from_glance):
image_id = mock.sentinel.image_id
client = mock.Mock(call=mock.Mock())
service = glance.GlanceImageService(client=client)
image_meta = {'properties': {'k1': 'v1'}}
client.call.return_value = {'k1': 'v1'}
if ver == 2:
show.return_value = {'properties': {'k2': 'v2'}}
show.return_value = {'properties': {'k2': 'v2'}}
translate_from_glance.return_value = image_meta.copy()
ret = service.update(self.context, image_id, image_meta)
self.assertDictEqual(image_meta, ret)
if ver == 2:
client.call.assert_called_once_with(
self.context, 'update', image_id, k1='v1', remove_props=['k2'])
else:
client.call.assert_called_once_with(
self.context, 'update', image_id, properties={'k1': 'v1'},
purge_props=True)
translate_from_glance.assert_called_once_with(self.context,
{'k1': 'v1'})
client.call.assert_called_once_with(
self.context, 'update', image_id, k1='v1', remove_props=['k2'])
def test_delete(self):
fixture1 = self._make_fixture(name='test image 1')
@ -619,7 +588,6 @@ class TestGlanceImageService(test.TestCase):
image_id = self.service.create(self.context, fixture)['id']
writer = NullWriter()
self.flags(allowed_direct_url_schemes=['file'])
self.flags(glance_api_version=2)
self.service.download(self.context, image_id, writer)
mock_copyfileobj.assert_called_once_with(mock.ANY, writer)
@ -634,7 +602,6 @@ class TestGlanceImageService(test.TestCase):
image_id = self.service.create(self.context, fixture)['id']
writer = NullWriter()
self.flags(allowed_direct_url_schemes=['file'])
self.flags(glance_api_version=2)
self.service.download(self.context, image_id, writer)
self.assertIsNone(mock_copyfileobj.call_args)
@ -709,7 +676,6 @@ class TestGlanceImageService(test.TestCase):
@mock.patch('cinder.image.glance.CONF')
def test_v2_passes_visibility_param(self, config):
config.glance_api_version = 2
config.glance_num_retries = 0
metadata = {
@ -749,7 +715,6 @@ class TestGlanceImageService(test.TestCase):
@mock.patch('cinder.image.glance.CONF')
def test_extracting_v2_boot_properties(self, config):
config.glance_api_version = 2
config.glance_num_retries = 0
metadata = {
@ -791,26 +756,6 @@ class TestGlanceImageService(test.TestCase):
self.assertEqual(expected, actual)
def test_translate_to_glance(self):
self.flags(glance_api_version=1)
client = glance_stubs.StubGlanceClient()
service = self._create_image_service(client)
metadata = {
'id': 1,
'size': 2,
'min_disk': 2,
'min_ram': 2,
'properties': {'kernel_id': 'foo',
'ramdisk_id': 'bar',
'x_billinginfo': '123'},
}
actual = service._translate_to_glance(metadata)
expected = metadata
self.assertEqual(expected, actual)
def test_translate_to_glance_v2(self):
self.flags(glance_api_version=2)
client = glance_stubs.StubGlanceClient()
service = self._create_image_service(client)
@ -836,41 +781,6 @@ class TestGlanceImageService(test.TestCase):
}
self.assertEqual(expected, actual)
class TestGlanceClientVersion(test.TestCase):
"""Tests the version of the glance client generated."""
@mock.patch('cinder.image.glance.glanceclient.Client')
def test_glance_version_by_flag(self, _mockglanceclient):
"""Test glance version set by flag is honoured."""
ctx = mock.MagicMock()
glance.GlanceClientWrapper(ctx, 'fake_host', 9292)
self.assertEqual('2', _mockglanceclient.call_args[0][0])
self.flags(glance_api_version=1)
glance.GlanceClientWrapper(ctx, 'fake_host', 9292)
self.assertEqual('1', _mockglanceclient.call_args[0][0])
CONF.reset()
@mock.patch('cinder.image.glance.glanceclient.Client')
def test_glance_version_by_arg(self, _mockglanceclient):
"""Test glance version set by arg to GlanceClientWrapper"""
ctx = mock.MagicMock()
glance.GlanceClientWrapper(ctx, 'fake_host', 9292, version=1)
self.assertEqual('1', _mockglanceclient.call_args[0][0])
glance.GlanceClientWrapper(ctx, 'fake_host', 9292, version=2)
self.assertEqual('2', _mockglanceclient.call_args[0][0])
@mock.patch('cinder.image.glance.glanceclient.Client')
@mock.patch('cinder.image.glance.get_api_servers',
return_value=itertools.cycle([(False, 'localhost:9292')]))
def test_call_glance_version_by_arg(self, api_servers, _mockglanceclient):
"""Test glance version set by arg to GlanceClientWrapper"""
glance_wrapper = glance.GlanceClientWrapper()
ctx = mock.MagicMock()
glance_wrapper.call(ctx, 'method', version=2)
self.assertEqual('2', _mockglanceclient.call_args[0][0])
@mock.patch('cinder.image.glance.glanceclient.Client')
@mock.patch('cinder.image.glance.get_api_servers',
return_value=itertools.cycle([(False, 'localhost:9292')]))
@ -882,8 +792,7 @@ class TestGlanceClientVersion(test.TestCase):
side_effect=glanceclient.exc.HTTPOverLimit)
self.mock_object(glance_wrapper, 'client', fake_client)
self.assertRaises(exception.ImageLimitExceeded,
glance_wrapper.call, 'fake_context', 'method',
version=2)
glance_wrapper.call, 'fake_context', 'method')
def _create_failing_glance_client(info):
@ -943,7 +852,7 @@ class TestGlanceImageServiceClient(test.TestCase):
class MyGlanceStubClient(object):
def __init__(inst, version, *args, **kwargs):
self.assertEqual("2", version)
self.assertEqual('2', version)
self.assertEqual("http://fake_host:9292", args[0])
self.assertTrue(kwargs['token'])
self.assertNotIn('timeout', kwargs)

View File

@ -269,7 +269,6 @@ class CopyVolumeToImageTestCase(base.BaseVolumeTestCase):
def _test_copy_volume_to_image_with_image_volume(
self, mock_copy, mock_create, mock_quota_commit,
mock_quota_reserve):
self.flags(glance_api_version=2)
self.volume.driver.configuration.image_upload_use_cinder_backend = True
self.addCleanup(fake_image.FakeImageService_reset)
image_service = fake_image.FakeImageService()

View File

@ -225,8 +225,7 @@ volume_opts = [
'create a cloned volume and register its location to '
'the image service, instead of uploading the volume '
'content. The cinder backend and locations support '
'must be enabled in the image service, and '
'glance_api_version must be set to 2.'),
'must be enabled in the image service.'),
cfg.BoolOpt('image_upload_use_internal_tenant',
default=False,
help='If set to True, the image volume created by '

View File

@ -38,7 +38,6 @@ Volume, set the following options in the ``DEFAULT`` section of the
.. code-block:: ini
glance_api_version = 2
allowed_direct_url_schemes = cinder
To enable the :command:`openstack image create --volume <volume>` command to

View File

@ -213,8 +213,6 @@ To use this feature, you must configure the Block Storage service, as follows:
- Set the ``netapp_copyoffload_tool_path`` configuration option to the path to
the NetApp Copy Offload binary.
- Set the ``glance_api_version`` configuration option to ``2``.
.. important::
This feature requires that:

View File

@ -26,8 +26,6 @@
- (List) A list of the URLs of glance API servers available to cinder ([http[s]://][hostname|ip]:port). If protocol is not specified it defaults to http.
* - ``glance_api_ssl_compression`` = ``False``
- (Boolean) Enables or disables negotiation of SSL layer compression. In some cases disabling compression can improve data throughput, such as when high network bandwidth is available and you use compressed image formats like qcow2.
* - ``glance_api_version`` = ``1``
- (Integer) Version of the glance API to use
* - ``glance_ca_certificates_file`` = ``None``
- (String) Location of ca certificates file to use for glance client requests.
* - ``glance_catalog_info`` = ``image:glance:publicURL``
@ -41,7 +39,7 @@
* - ``image_conversion_dir`` = ``$state_path/conversion``
- (String) Directory used for temporary storage during image conversion
* - ``image_upload_use_cinder_backend`` = ``False``
- (Boolean) If set to True, upload-to-image in raw format will create a cloned volume and register its location to the image service, instead of uploading the volume content. The cinder backend and locations support must be enabled in the image service, and glance_api_version must be set to 2.
- (Boolean) If set to True, upload-to-image in raw format will create a cloned volume and register its location to the image service, instead of uploading the volume content. The cinder backend and locations support must be enabled in the image service.
* - ``image_upload_use_internal_tenant`` = ``False``
- (Boolean) If set to True, the image volume created by upload-to-image will be placed in the internal tenant. Otherwise, the image volume is created in the current context's tenant.
* - ``image_volume_cache_enabled`` = ``False``

View File

@ -0,0 +1,7 @@
---
upgrade:
- |
The Glance v1 API has been deprecated and will soon be removed. Cinder
support for using the v1 API was deprecated in the Pike release and
is now no longer available. The ``glance_api_version`` configuration
option to support version selection has now been removed.