Make location API compatible with multiple store

In case of Multiple store while adding location to image if backend store is
not provided it returns 400 BadRequest saying 'Invalid location'.

Made changes to loaction API to findout the store from location URI
and add it as a backend store to the location metadata.

DocImpact
Closes-Bug: #1802587
Co-Authored-By: Victor Coutellier <victor.coutellier@gmail.com>
Change-Id: If6d0348346d2086a2500b0012a0e81e80cea7395
This commit is contained in:
Abhishek Kekane 2019-05-24 07:53:16 +00:00
parent 0e5984edc7
commit 4e070fd6e0
3 changed files with 217 additions and 0 deletions

View File

@ -31,6 +31,7 @@ from glance.api import common
from glance.api import policy
from glance.common import exception
from glance.common import location_strategy
from glance.common import store_utils
from glance.common import timeutils
from glance.common import utils
from glance.common import wsgi
@ -475,6 +476,9 @@ class ImagesController(object):
raise webob.exc.HTTPConflict(explanation=msg)
val_data = self._validate_validation_data(image, value)
# NOTE(abhishekk): get glance store based on location uri
if CONF.enabled_backends:
store_utils.update_store_in_locations(value, image.image_id)
try:
# NOTE(flwang): _locations_proxy's setattr method will check if
@ -502,6 +506,9 @@ class ImagesController(object):
raise webob.exc.HTTPConflict(explanation=msg)
val_data = self._validate_validation_data(image, [value])
# NOTE(abhishekk): get glance store based on location uri
if CONF.enabled_backends:
store_utils.update_store_in_locations([value], image.image_id)
pos = self._get_locations_op_pos(path_pos,
len(image.locations), True)

View File

@ -23,10 +23,38 @@ import six
import webob
from glance.common import exception
from glance.common import store_utils
from glance.common import utils
from glance.tests import utils as test_utils
class TestStoreUtils(test_utils.BaseTestCase):
"""Test glance.common.store_utils module"""
def _test_update_store_in_location(self, metadata, store_id, expected):
locations = [{
'url': 'rbd://aaaaaaaa/images/id',
'metadata': metadata
}]
with mock.patch.object(
store_utils, '_get_store_id_from_uri') as mock_get_store_id:
mock_get_store_id.return_value = store_id
store_utils.update_store_in_locations(locations, mock.Mock())
self.assertEqual(locations[0]['metadata'].get('store'), expected)
def test_update_store_location_with_no_store(self):
self._test_update_store_in_location({}, 'rbd1', 'rbd1')
def test_update_store_location_with_different_store(self):
self._test_update_store_in_location({'store': 'rbd2'}, 'rbd1', 'rbd1')
def test_update_store_location_with_same_store(self):
self._test_update_store_in_location({'store': 'rbd1'}, 'rbd1', 'rbd1')
def test_update_store_location_with_store_none(self):
self._test_update_store_in_location({}, None, None)
class TestUtils(test_utils.BaseTestCase):
"""Test routines in glance.utils"""

View File

@ -1780,6 +1780,112 @@ class TestImagesController(base.IsolatedUnitTest):
self.assertEqual('sha512', output.os_hash_algo)
self.assertEqual(MULTIHASH1, output.os_hash_value)
@mock.patch.object(glance.quota, '_calc_required_size')
@mock.patch.object(glance.location, '_check_image_location')
@mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
@mock.patch.object(store, 'get_size_from_uri_and_backend')
@mock.patch.object(store, 'get_size_from_backend')
def test_replace_locations_identify_associated_store(
self, mock_get_size, mock_get_size_uri, mock_set_acls,
mock_check_loc, mock_calc):
mock_calc.return_value = 1
mock_get_size.return_value = 1
mock_get_size_uri.return_value = 1
self.config(show_multiple_locations=True)
self.config(enabled_backends={'fake-store': 'http'})
image_id = str(uuid.uuid4())
self.images = [
_db_fixture(image_id, owner=TENANT1,
name='1',
disk_format='raw',
container_format='bare',
status='queued',
checksum=None,
os_hash_algo=None,
os_hash_value=None),
]
self.db.image_create(None, self.images[0])
request = unit_test_utils.get_fake_request()
new_location1 = {'url': '%s/fake_location_1' % BASE_URI,
'metadata': {},
'validation_data': {'checksum': CHKSUM,
'os_hash_algo': 'sha512',
'os_hash_value': MULTIHASH1}}
new_location2 = {'url': '%s/fake_location_2' % BASE_URI,
'metadata': {},
'validation_data': {'checksum': CHKSUM,
'os_hash_algo': 'sha512',
'os_hash_value': MULTIHASH1}}
changes = [{'op': 'replace', 'path': ['locations'],
'value': [new_location1, new_location2]}]
with mock.patch.object(store_utils,
'_get_store_id_from_uri') as mock_store:
mock_store.return_value = 'fake-store'
# ensure location metadata is updated
new_location1['metadata']['store'] = 'fake-store'
new_location1['metadata']['store'] = 'fake-store'
output = self.controller.update(request, image_id, changes)
self.assertEqual(2, len(output.locations))
self.assertEqual(image_id, output.image_id)
self.assertEqual(new_location1, output.locations[0])
self.assertEqual(new_location2, output.locations[1])
self.assertEqual('active', output.status)
self.assertEqual(CHKSUM, output.checksum)
self.assertEqual('sha512', output.os_hash_algo)
self.assertEqual(MULTIHASH1, output.os_hash_value)
@mock.patch.object(glance.quota, '_calc_required_size')
@mock.patch.object(glance.location, '_check_image_location')
@mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
@mock.patch.object(store, 'get_size_from_uri_and_backend')
@mock.patch.object(store, 'get_size_from_backend')
def test_replace_locations_unknon_locations(
self, mock_get_size, mock_get_size_uri, mock_set_acls,
mock_check_loc, mock_calc):
mock_calc.return_value = 1
mock_get_size.return_value = 1
mock_get_size_uri.return_value = 1
self.config(show_multiple_locations=True)
self.config(enabled_backends={'fake-store': 'http'})
image_id = str(uuid.uuid4())
self.images = [
_db_fixture(image_id, owner=TENANT1,
name='1',
disk_format='raw',
container_format='bare',
status='queued',
checksum=None,
os_hash_algo=None,
os_hash_value=None),
]
self.db.image_create(None, self.images[0])
request = unit_test_utils.get_fake_request()
new_location1 = {'url': 'unknown://whocares',
'metadata': {},
'validation_data': {'checksum': CHKSUM,
'os_hash_algo': 'sha512',
'os_hash_value': MULTIHASH1}}
new_location2 = {'url': 'unknown://whatever',
'metadata': {'store': 'unkstore'},
'validation_data': {'checksum': CHKSUM,
'os_hash_algo': 'sha512',
'os_hash_value': MULTIHASH1}}
changes = [{'op': 'replace', 'path': ['locations'],
'value': [new_location1, new_location2]}]
output = self.controller.update(request, image_id, changes)
self.assertEqual(2, len(output.locations))
self.assertEqual(image_id, output.image_id)
self.assertEqual('active', output.status)
self.assertEqual(CHKSUM, output.checksum)
self.assertEqual('sha512', output.os_hash_algo)
self.assertEqual(MULTIHASH1, output.os_hash_value)
# ensure location metadata is same
self.assertEqual(new_location1, output.locations[0])
self.assertEqual(new_location2, output.locations[1])
@mock.patch.object(glance.quota, '_calc_required_size')
@mock.patch.object(glance.location, '_check_image_location')
@mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
@ -1898,6 +2004,82 @@ class TestImagesController(base.IsolatedUnitTest):
self.assertEqual(new_location, output.locations[0])
self.assertEqual('active', output.status)
@mock.patch.object(glance.quota, '_calc_required_size')
@mock.patch.object(glance.location, '_check_image_location')
@mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
@mock.patch.object(store, 'get_size_from_uri_and_backend')
@mock.patch.object(store, 'get_size_from_backend')
def test_add_location_identify_associated_store(
self, mock_get_size, mock_get_size_uri, mock_set_acls,
mock_check_loc, mock_calc):
mock_calc.return_value = 1
mock_get_size.return_value = 1
mock_get_size_uri.return_value = 1
self.config(show_multiple_locations=True)
self.config(enabled_backends={'fake-store': 'http'})
image_id = str(uuid.uuid4())
self.images = [
_db_fixture(image_id, owner=TENANT1, checksum=CHKSUM,
name='1',
disk_format='raw',
container_format='bare',
status='queued'),
]
self.db.image_create(None, self.images[0])
request = unit_test_utils.get_fake_request()
new_location = {'url': '%s/fake_location_1' % BASE_URI,
'metadata': {}}
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
with mock.patch.object(store_utils,
'_get_store_id_from_uri') as mock_store:
mock_store.return_value = 'fake-store'
output = self.controller.update(request, image_id, changes)
self.assertEqual(image_id, output.image_id)
self.assertEqual(1, len(output.locations))
self.assertEqual('active', output.status)
# ensure location metadata is updated
new_location['metadata']['store'] = 'fake-store'
self.assertEqual(new_location, output.locations[0])
@mock.patch.object(glance.quota, '_calc_required_size')
@mock.patch.object(glance.location, '_check_image_location')
@mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
@mock.patch.object(store, 'get_size_from_uri_and_backend')
@mock.patch.object(store, 'get_size_from_backend')
def test_add_location_unknown_locations(
self, mock_get_size, mock_get_size_uri, mock_set_acls,
mock_check_loc, mock_calc):
mock_calc.return_value = 1
mock_get_size.return_value = 1
mock_get_size_uri.return_value = 1
self.config(show_multiple_locations=True)
self.config(enabled_backends={'fake-store': 'http'})
image_id = str(uuid.uuid4())
self.images = [
_db_fixture(image_id, owner=TENANT1, checksum=CHKSUM,
name='1',
disk_format='raw',
container_format='bare',
status='queued'),
]
self.db.image_create(None, self.images[0])
new_location = {'url': 'unknown://whocares', 'metadata': {}}
request = unit_test_utils.get_fake_request()
changes = [{'op': 'add', 'path': ['locations', '-'],
'value': new_location}]
output = self.controller.update(request, image_id, changes)
self.assertEqual(image_id, output.image_id)
self.assertEqual('active', output.status)
self.assertEqual(1, len(output.locations))
# ensure location metadata is same
self.assertEqual(new_location, output.locations[0])
@mock.patch.object(glance.quota, '_calc_required_size')
@mock.patch.object(glance.location, '_check_image_location')
@mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')