Add support for new location APIs
Related blueprint new-location-apis Change-Id: Id378cd5b7d20775b5117ee3f509bd6bdd61702e3
This commit is contained in:
@@ -117,6 +117,15 @@ class BaseController(testtools.TestCase):
|
|||||||
resp = self.controller.image_import(*args, **kwargs)
|
resp = self.controller.image_import(*args, **kwargs)
|
||||||
self._assertRequestId(resp)
|
self._assertRequestId(resp)
|
||||||
|
|
||||||
|
def add_image_location(self, *args):
|
||||||
|
resp = self.controller.add_image_location(*args)
|
||||||
|
self._assertRequestId(resp)
|
||||||
|
|
||||||
|
def get_image_locations(self, *args):
|
||||||
|
resource = self.controller.get_image_locations(*args)
|
||||||
|
self._assertRequestId(resource)
|
||||||
|
return resource
|
||||||
|
|
||||||
|
|
||||||
class BaseResourceTypeController(BaseController):
|
class BaseResourceTypeController(BaseController):
|
||||||
def __init__(self, api, schema_api, controller_class):
|
def __init__(self, api, schema_api, controller_class):
|
||||||
|
@@ -688,6 +688,18 @@ data_fixtures = {
|
|||||||
]},
|
]},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
'/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1/locations': {
|
||||||
|
'POST': ({}, '')
|
||||||
|
},
|
||||||
|
'/v2/images/a2b83adc-888e-11e3-8872-78acc0b951d8/locations': {
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
[{'url': 'http://foo.com/',
|
||||||
|
'metadata': {'store': 'cheap'}},
|
||||||
|
{'url': 'http://bar.com/',
|
||||||
|
'metadata': {'store': 'fast'}}], ''
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
schema_fixtures = {
|
schema_fixtures = {
|
||||||
@@ -1503,3 +1515,60 @@ class TestController(testtools.TestCase):
|
|||||||
self.controller.update_location,
|
self.controller.update_location,
|
||||||
image_id, **new_loc)
|
image_id, **new_loc)
|
||||||
self.assertIn(err_str, str(err))
|
self.assertIn(err_str, str(err))
|
||||||
|
|
||||||
|
def test_add_image_location(self):
|
||||||
|
location_url = 'http://spam.com/'
|
||||||
|
image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
||||||
|
expect = [
|
||||||
|
('POST',
|
||||||
|
'/v2/images/%s/locations' % (image_id),
|
||||||
|
{},
|
||||||
|
[('url', location_url), ('validation_data', {})]),
|
||||||
|
('GET', '/v2/images/%s' % (image_id), {}, None)]
|
||||||
|
with mock.patch.object(common_utils,
|
||||||
|
'has_version') as mock_has_version:
|
||||||
|
mock_has_version.return_value = True
|
||||||
|
self.controller.add_image_location(image_id, location_url, {})
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
|
||||||
|
def test_add_image_location_with_validation_data(self):
|
||||||
|
location_url = 'http://spam.com/'
|
||||||
|
image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
||||||
|
expect = [
|
||||||
|
('POST',
|
||||||
|
'/v2/images/%s/locations' % (image_id),
|
||||||
|
{},
|
||||||
|
[('url', location_url), ('validation_data',
|
||||||
|
{'os_hash_algo': 'sha512'})]),
|
||||||
|
('GET', '/v2/images/%s' % (image_id), {}, None)]
|
||||||
|
with mock.patch.object(common_utils,
|
||||||
|
'has_version') as mock_has_version:
|
||||||
|
mock_has_version.return_value = True
|
||||||
|
self.controller.add_image_location(image_id, location_url,
|
||||||
|
{'os_hash_algo': 'sha512'})
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
|
||||||
|
def test_add_image_location_not_supported(self):
|
||||||
|
with mock.patch.object(common_utils,
|
||||||
|
'has_version') as mock_has_version:
|
||||||
|
mock_has_version.return_value = False
|
||||||
|
self.assertRaises(exc.HTTPNotImplemented,
|
||||||
|
self.controller.add_image_location,
|
||||||
|
'3a4560a1-e585-443e-9b39-553b46ec92d1',
|
||||||
|
'http://spam.com/')
|
||||||
|
|
||||||
|
def test_get_image_locations(self):
|
||||||
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
||||||
|
with mock.patch.object(common_utils,
|
||||||
|
'has_version') as mock_has_version:
|
||||||
|
mock_has_version.return_value = True
|
||||||
|
locations = self.controller.get_image_locations(image_id)
|
||||||
|
self.assertEqual(2, len(locations))
|
||||||
|
|
||||||
|
def test_get_image_location_not_supported(self):
|
||||||
|
with mock.patch.object(common_utils,
|
||||||
|
'has_version') as mock_has_version:
|
||||||
|
mock_has_version.return_value = False
|
||||||
|
self.assertRaises(exc.HTTPNotImplemented,
|
||||||
|
self.controller.get_image_locations,
|
||||||
|
'3a4560a1-e585-443e-9b39-553b46ec92d1')
|
||||||
|
@@ -1989,6 +1989,68 @@ class ShellV2Test(testtools.TestCase):
|
|||||||
loc['metadata'])
|
loc['metadata'])
|
||||||
utils.print_dict.assert_called_once_with(expect_image)
|
utils.print_dict.assert_called_once_with(expect_image)
|
||||||
|
|
||||||
|
def test_do_add_location(self):
|
||||||
|
gc = self.gc
|
||||||
|
url = 'http://foo.com/',
|
||||||
|
validation_data = {'os_hash_algo': 'sha512',
|
||||||
|
'os_hash_value': 'value'}
|
||||||
|
args = {'id': 'IMG-01',
|
||||||
|
'url': url,
|
||||||
|
'validation_data': json.dumps(validation_data)}
|
||||||
|
with mock.patch.object(gc.images,
|
||||||
|
'add_image_location') as mock_addloc:
|
||||||
|
expect_image = {'id': 'pass'}
|
||||||
|
mock_addloc.return_value = expect_image
|
||||||
|
|
||||||
|
test_shell.do_add_location(self.gc, self._make_args(args))
|
||||||
|
mock_addloc.assert_called_once_with(
|
||||||
|
'IMG-01', url, validation_data=validation_data)
|
||||||
|
utils.print_dict.assert_called_once_with(expect_image)
|
||||||
|
|
||||||
|
@mock.patch('glanceclient.common.utils.exit')
|
||||||
|
def test_do_add_location_with_checksum_in_validation_data(self,
|
||||||
|
mock_exit):
|
||||||
|
validation_data = {'checksum': 'value',
|
||||||
|
'os_hash_algo': 'sha512',
|
||||||
|
'os_hash_value': 'value'}
|
||||||
|
|
||||||
|
args = self._make_args(
|
||||||
|
{'id': 'IMG-01', 'url': 'http://foo.com/',
|
||||||
|
'validation_data': json.dumps(validation_data)})
|
||||||
|
expected_msg = ('Validation Data should contain only os_hash_algo'
|
||||||
|
' and os_hash_value. `checksum` is not allowed')
|
||||||
|
mock_exit.side_effect = self._mock_utils_exit
|
||||||
|
try:
|
||||||
|
test_shell.do_add_location(self.gc, args)
|
||||||
|
self.fail("utils.exit should have been called")
|
||||||
|
except SystemExit:
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_exit.assert_called_once_with(expected_msg)
|
||||||
|
|
||||||
|
@mock.patch('glanceclient.common.utils.exit')
|
||||||
|
def test_do_add_location_with_invalid_algo_in_validation_data(self,
|
||||||
|
mock_exit):
|
||||||
|
validation_data = {'os_hash_algo': 'algo',
|
||||||
|
'os_hash_value': 'value'}
|
||||||
|
|
||||||
|
args = self._make_args(
|
||||||
|
{'id': 'IMG-01', 'url': 'http://foo.com/',
|
||||||
|
'validation_data': json.dumps(validation_data)})
|
||||||
|
allowed_hash_algo = ['sha512', 'sha256', 'sha1', 'md5']
|
||||||
|
expected_msg = ('os_hash_algo: `%s` is incorrect, '
|
||||||
|
'allowed hashing algorithms: %s' %
|
||||||
|
(validation_data['os_hash_algo'],
|
||||||
|
allowed_hash_algo))
|
||||||
|
mock_exit.side_effect = self._mock_utils_exit
|
||||||
|
try:
|
||||||
|
test_shell.do_add_location(self.gc, args)
|
||||||
|
self.fail("utils.exit should have been called")
|
||||||
|
except SystemExit:
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_exit.assert_called_once_with(expected_msg)
|
||||||
|
|
||||||
def test_image_upload(self):
|
def test_image_upload(self):
|
||||||
args = self._make_args(
|
args = self._make_args(
|
||||||
{'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False})
|
{'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False})
|
||||||
|
@@ -560,3 +560,33 @@ class Controller(object):
|
|||||||
req_id_hdr = {'x-openstack-request-id': response.request_ids[0]}
|
req_id_hdr = {'x-openstack-request-id': response.request_ids[0]}
|
||||||
|
|
||||||
return self._get(image_id, req_id_hdr)
|
return self._get(image_id, req_id_hdr)
|
||||||
|
|
||||||
|
def add_image_location(self, image_id, location_url, validation_data={}):
|
||||||
|
"""Add a new location to an image.
|
||||||
|
|
||||||
|
:param image_id: ID of image to which the location is to be added.
|
||||||
|
:param location_url: URL of the location to add.
|
||||||
|
:param validation_data: Validation data for the image.
|
||||||
|
"""
|
||||||
|
if not utils.has_version(self.http_client, 'v2.17'):
|
||||||
|
raise exc.HTTPNotImplemented(
|
||||||
|
'This operation is not supported by Glance.')
|
||||||
|
|
||||||
|
url = '/v2/images/%s/locations' % image_id
|
||||||
|
data = {'url': location_url,
|
||||||
|
'validation_data': validation_data}
|
||||||
|
resp, body = self.http_client.post(url, data=data)
|
||||||
|
return self._get(image_id)
|
||||||
|
|
||||||
|
@utils.add_req_id_to_object()
|
||||||
|
def get_image_locations(self, image_id):
|
||||||
|
"""Fetch list of locations associated to the Image.
|
||||||
|
|
||||||
|
:param image_id: ID of image to which the location is to be fetched.
|
||||||
|
"""
|
||||||
|
if not utils.has_version(self.http_client, 'v2.17'):
|
||||||
|
raise exc.HTTPNotImplemented(
|
||||||
|
'This operation is not supported by Glance.')
|
||||||
|
url = '/v2/images/%s/locations' % (image_id)
|
||||||
|
resp, locations = self.http_client.get(url)
|
||||||
|
return locations, resp
|
||||||
|
@@ -1012,6 +1012,43 @@ def do_location_update(gc, args):
|
|||||||
utils.print_dict(image)
|
utils.print_dict(image)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('--url', metavar='<URL>', required=True,
|
||||||
|
help=_('URL of location to add.'))
|
||||||
|
@utils.arg('--validation-data', metavar='<STRING>', default='{}',
|
||||||
|
help=_('Validation data containing os_hash_algo and os_hash_value '
|
||||||
|
'only associated to the image. Must be a valid JSON object '
|
||||||
|
'(default: %(default)s)'))
|
||||||
|
@utils.arg('id', metavar='<IMAGE_ID>',
|
||||||
|
help=_('ID of image whose location is to be added.'))
|
||||||
|
def do_add_location(gc, args):
|
||||||
|
"""Add location to an image which is in `queued` state only. """
|
||||||
|
try:
|
||||||
|
invalid_val_data = None
|
||||||
|
validation_data = json.loads(args.validation_data)
|
||||||
|
accepted_values = ['os_hash_algo', 'os_hash_value']
|
||||||
|
invalid_val_data = list(set(validation_data.keys()).difference(
|
||||||
|
accepted_values))
|
||||||
|
if invalid_val_data:
|
||||||
|
utils.exit('Validation Data should contain only os_hash_algo '
|
||||||
|
'and os_hash_value. `%s` is not allowed' %
|
||||||
|
(*invalid_val_data,))
|
||||||
|
|
||||||
|
allowed_hash_algo = ['sha512', 'sha256', 'sha1', 'md5']
|
||||||
|
if validation_data and \
|
||||||
|
validation_data['os_hash_algo'] not in allowed_hash_algo:
|
||||||
|
raise utils.exit('os_hash_algo: `%s` is incorrect, '
|
||||||
|
'allowed hashing algorithms: %s' %
|
||||||
|
(validation_data['os_hash_algo'],
|
||||||
|
allowed_hash_algo))
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
utils.exit('validation-data is not a valid JSON object.')
|
||||||
|
else:
|
||||||
|
image = gc.images.add_image_location(args.id, args.url,
|
||||||
|
validation_data=validation_data)
|
||||||
|
utils.print_image(image)
|
||||||
|
|
||||||
|
|
||||||
# Metadata - catalog
|
# Metadata - catalog
|
||||||
NAMESPACE_SCHEMA = None
|
NAMESPACE_SCHEMA = None
|
||||||
|
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add support for the new Glance ``Locations`` APIs
|
||||||
|
|
||||||
|
- Add client support for newly added API,
|
||||||
|
``POST /v2/images/{image_id}/locations`` in Glance.
|
||||||
|
New add location operation is allowed for service to service
|
||||||
|
interaction, end users only when `http` store is enabled in
|
||||||
|
deployment and images which are in ``queued`` state only.
|
||||||
|
This api replaces the image-update (old location-add) mechanism
|
||||||
|
for consumers like cinder and nova to address `OSSN-0090`_ and
|
||||||
|
`OSSN-0065`_. This client change adds support of new shell command
|
||||||
|
``add-location`` and new client method ``add_image_location``.
|
||||||
|
- Add support for newly added API,
|
||||||
|
``GET /v2/images/{image_id}/locations`` in Glance to fetch the
|
||||||
|
locations associated to an image. This change adds new client method
|
||||||
|
``get_image_locations`` since this new get locations api is meant for
|
||||||
|
service user only hence it is not exposed to the end user as a shell
|
||||||
|
command.
|
||||||
|
|
||||||
|
.. _OSSN-0090: https://wiki.openstack.org/wiki/OSSN/OSSN-0090
|
||||||
|
.. _OSSN-0065: https://wiki.openstack.org/wiki/OSSN/OSSN-0065
|
Reference in New Issue
Block a user