Check policies for image import operation in API
This patch enforces policy checks required for importing/copying image data to store in API layer. Partially-Implements: blueprint policy-refactor Change-Id: I18a5187d80bf76c0dc6f22dd8c96a8ffa0f46dc1
This commit is contained in:
parent
3e2474676d
commit
6c87a18d4c
|
@ -32,7 +32,6 @@ from six.moves import http_client as http
|
|||
import six.moves.urllib.parse as urlparse
|
||||
import webob.exc
|
||||
|
||||
from glance.api import authorization
|
||||
from glance.api import common
|
||||
from glance.api import policy
|
||||
from glance.api.v2 import policy as api_policy
|
||||
|
@ -168,8 +167,10 @@ class ImagesController(object):
|
|||
|
||||
def _enforce_import_lock(self, req, image):
|
||||
admin_context = req.context.elevated()
|
||||
admin_image_repo = self.gateway.get_repo(admin_context)
|
||||
admin_task_repo = self.gateway.get_task_repo(admin_context)
|
||||
admin_image_repo = self.gateway.get_repo(
|
||||
admin_context, authorization_layer=False)
|
||||
admin_task_repo = self.gateway.get_task_repo(
|
||||
admin_context, authorization_layer=False)
|
||||
other_task = image.extra_properties['os_glance_import_task']
|
||||
|
||||
expiry = datetime.timedelta(minutes=60)
|
||||
|
@ -313,9 +314,12 @@ class ImagesController(object):
|
|||
@utils.mutating
|
||||
def import_image(self, req, image_id, body):
|
||||
ctxt = req.context
|
||||
image_repo = self.gateway.get_repo(ctxt)
|
||||
task_factory = self.gateway.get_task_factory(ctxt)
|
||||
task_repo = self.gateway.get_task_repo(ctxt)
|
||||
image_repo = self.gateway.get_repo(ctxt,
|
||||
authorization_layer=False)
|
||||
task_factory = self.gateway.get_task_factory(
|
||||
ctxt, authorization_layer=False)
|
||||
task_repo = self.gateway.get_task_repo(
|
||||
ctxt, authorization_layer=False)
|
||||
import_method = body.get('method').get('name')
|
||||
uri = body.get('method').get('uri')
|
||||
all_stores_must_succeed = body.get('all_stores_must_succeed', True)
|
||||
|
@ -355,12 +359,15 @@ class ImagesController(object):
|
|||
# NOTE(danms): For copy-image only, we check policy to decide
|
||||
# if the user should be able to do this. Otherwise, we forbid
|
||||
# the import if the user is not the owner.
|
||||
|
||||
api_pol = api_policy.ImageAPIPolicy(req.context, image,
|
||||
enforcer=self.policy)
|
||||
if import_method == 'copy-image':
|
||||
self.policy.enforce(ctxt, 'copy_image',
|
||||
dict(policy.ImageTarget(image)))
|
||||
elif not authorization.is_image_mutable(ctxt, image):
|
||||
raise webob.exc.HTTPForbidden(
|
||||
explanation=_("Operation not permitted"))
|
||||
api_pol.copy_image()
|
||||
else:
|
||||
# NOTE(abhishekk): We need to perform ownership check on image
|
||||
# so that non-admin or non-owner can not import data to image
|
||||
api_pol.modify_image()
|
||||
|
||||
if 'os_glance_import_task' in image.extra_properties:
|
||||
# NOTE(danms): This will raise exception.Conflict if the
|
||||
|
@ -433,7 +440,7 @@ class ImagesController(object):
|
|||
admin_context = None
|
||||
|
||||
executor_factory = self.gateway.get_task_executor_factory(
|
||||
ctxt, admin_context=admin_context)
|
||||
ctxt, admin_context=admin_context, authorization_layer=False)
|
||||
|
||||
if (import_method == 'web-download' and
|
||||
not utils.validate_import_uri(uri)):
|
||||
|
|
|
@ -265,6 +265,9 @@ class ImageAPIPolicy(APIPolicyBase):
|
|||
if not CONF.enforce_secure_rbac:
|
||||
check_is_image_mutable(self._context, self._image)
|
||||
|
||||
def copy_image(self):
|
||||
self._enforce('copy_image')
|
||||
|
||||
|
||||
class MetadefAPIPolicy(APIPolicyBase):
|
||||
def __init__(self, context, md_resource=None, target=None, enforcer=None):
|
||||
|
|
|
@ -1710,7 +1710,7 @@ class SynchronousAPIBase(test_utils.BaseTestCase):
|
|||
return self.api_request('PATCH', url, headers=headers,
|
||||
json=list(patches))
|
||||
|
||||
def _import_copy(self, image_id, stores):
|
||||
def _import_copy(self, image_id, stores, headers=None):
|
||||
"""Do an import of image_id to the given stores."""
|
||||
body = {'method': {'name': 'copy-image'},
|
||||
'stores': stores,
|
||||
|
@ -1718,9 +1718,10 @@ class SynchronousAPIBase(test_utils.BaseTestCase):
|
|||
|
||||
return self.api_post(
|
||||
'/v2/images/%s/import' % image_id,
|
||||
headers=headers,
|
||||
json=body)
|
||||
|
||||
def _import_direct(self, image_id, stores):
|
||||
def _import_direct(self, image_id, stores, headers=None):
|
||||
"""Do an import of image_id to the given stores."""
|
||||
body = {'method': {'name': 'glance-direct'},
|
||||
'stores': stores,
|
||||
|
@ -1728,9 +1729,10 @@ class SynchronousAPIBase(test_utils.BaseTestCase):
|
|||
|
||||
return self.api_post(
|
||||
'/v2/images/%s/import' % image_id,
|
||||
headers=headers,
|
||||
json=body)
|
||||
|
||||
def _import_web_download(self, image_id, stores, url):
|
||||
def _import_web_download(self, image_id, stores, url, headers=None):
|
||||
"""Do an import of image_id to the given stores."""
|
||||
body = {'method': {'name': 'web-download',
|
||||
'uri': url},
|
||||
|
@ -1739,6 +1741,7 @@ class SynchronousAPIBase(test_utils.BaseTestCase):
|
|||
|
||||
return self.api_post(
|
||||
'/v2/images/%s/import' % image_id,
|
||||
headers=headers,
|
||||
json=body)
|
||||
|
||||
def _create_and_upload(self, data_iter=None, expected_code=204,
|
||||
|
@ -1770,11 +1773,18 @@ class SynchronousAPIBase(test_utils.BaseTestCase):
|
|||
|
||||
return image['id']
|
||||
|
||||
def _create_and_stage(self, data_iter=None, expected_code=204):
|
||||
def _create_and_stage(self, data_iter=None, expected_code=204,
|
||||
visibility=None):
|
||||
data = {
|
||||
'name': 'foo',
|
||||
'container_format': 'bare',
|
||||
'disk_format': 'raw',
|
||||
}
|
||||
if visibility:
|
||||
data['visibility'] = visibility
|
||||
|
||||
resp = self.api_post('/v2/images',
|
||||
json={'name': 'foo',
|
||||
'container_format': 'bare',
|
||||
'disk_format': 'raw'})
|
||||
json=data)
|
||||
image = jsonutils.loads(resp.text)
|
||||
|
||||
if data_iter:
|
||||
|
@ -1805,12 +1815,14 @@ class SynchronousAPIBase(test_utils.BaseTestCase):
|
|||
|
||||
return image
|
||||
|
||||
def _create_and_import(self, stores=[], data_iter=None, expected_code=202):
|
||||
def _create_and_import(self, stores=[], data_iter=None, expected_code=202,
|
||||
visibility=None):
|
||||
"""Create an image, stage data, and import into the given stores.
|
||||
|
||||
:returns: image_id
|
||||
"""
|
||||
image_id = self._create_and_stage(data_iter=data_iter)
|
||||
image_id = self._create_and_stage(data_iter=data_iter,
|
||||
visibility=visibility)
|
||||
|
||||
resp = self._import_direct(image_id, stores)
|
||||
self.assertEqual(expected_code, resp.status_code)
|
||||
|
|
|
@ -724,3 +724,124 @@ class TestImagesPolicy(functional.SynchronousAPIBase):
|
|||
path = "/v2/stores/store2/%s" % image_id
|
||||
response = self.api_delete(path, headers=headers)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
def test_copy_image(self):
|
||||
self.start_server()
|
||||
# create image using import
|
||||
image_id = self._create_and_import(
|
||||
stores=['store1'], visibility='public')
|
||||
|
||||
# Make sure you can copy image to another store
|
||||
self.set_policy_rules({
|
||||
'copy_image': 'role:admin',
|
||||
'get_image': '',
|
||||
'modify_image': ''
|
||||
})
|
||||
store_to_copy = ["store2"]
|
||||
response = self._import_copy(image_id, store_to_copy)
|
||||
self.assertEqual(202, response.status_code)
|
||||
self._wait_for_import(image_id)
|
||||
self.assertEqual('success', self._get_latest_task(image_id)['status'])
|
||||
|
||||
# Now disable copy image and see you will get 403 Forbidden
|
||||
store_to_copy = ["store3"]
|
||||
self.set_policy_rules({
|
||||
'copy_image': '!',
|
||||
'get_image': '',
|
||||
'modify_image': ''
|
||||
})
|
||||
response = self._import_copy(image_id, store_to_copy)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
# Verify that non-admin but member of same project can not copy image
|
||||
self.set_policy_rules({
|
||||
'copy_image': 'role:admin',
|
||||
'get_image': '',
|
||||
'modify_image': ''
|
||||
})
|
||||
headers = self._headers({'X-Roles': 'member'})
|
||||
response = self._import_copy(image_id, store_to_copy,
|
||||
headers=headers)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
# Verify that non-owner can not copy image
|
||||
self.set_policy_rules({
|
||||
'copy_image': 'role:admin',
|
||||
'get_image': '',
|
||||
'modify_image': ''
|
||||
})
|
||||
headers = self._headers({
|
||||
'X-Roles': 'member',
|
||||
'X-Project-Id': 'fake-project-id'
|
||||
})
|
||||
response = self._import_copy(image_id, store_to_copy,
|
||||
headers=headers)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
# Now disable copy image and get_image and see you will get
|
||||
# 404 NotFound
|
||||
self.set_policy_rules({
|
||||
'copy_image': '!',
|
||||
'get_image': '!',
|
||||
'modify_image': ''
|
||||
})
|
||||
store_to_copy = ["store3"]
|
||||
print(self.policy.rules.items())
|
||||
response = self._import_copy(image_id, store_to_copy)
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
||||
def test_import_glance_direct(self):
|
||||
self.start_server()
|
||||
# create image and stage data to it
|
||||
image_id = self._create_and_stage(visibility='public')
|
||||
|
||||
# Make sure you can import using glance-direct
|
||||
self.set_policy_rules({
|
||||
'get_image': '',
|
||||
'communitize_image': '',
|
||||
'add_image': '',
|
||||
'modify_image': ''
|
||||
})
|
||||
store_to_import = ['store1']
|
||||
response = self._import_direct(image_id, store_to_import)
|
||||
self.assertEqual(202, response.status_code)
|
||||
self._wait_for_import(image_id)
|
||||
self.assertEqual('success', self._get_latest_task(image_id)['status'])
|
||||
|
||||
# Make sure you can import data to image using non-admin role
|
||||
image_id = self._create_and_stage(visibility='community')
|
||||
headers = self._headers({'X-Roles': 'member'})
|
||||
response = self._import_direct(image_id, store_to_import,
|
||||
headers=headers)
|
||||
self.assertEqual(202, response.status_code)
|
||||
self._wait_for_import(image_id)
|
||||
self.assertEqual('success', self._get_latest_task(image_id)['status'])
|
||||
|
||||
# Make sure you can not import data to image using non-admin role of
|
||||
# different project
|
||||
image_id = self._create_and_stage(visibility='community')
|
||||
# Make sure you will get 403 Forbidden
|
||||
self.set_policy_rules({
|
||||
'get_image': '',
|
||||
'modify_image': '!'
|
||||
})
|
||||
headers = self._headers({
|
||||
'X-Roles': 'member',
|
||||
'X-Project-Id': 'fake-project-id'
|
||||
})
|
||||
response = self._import_direct(image_id, store_to_import,
|
||||
headers=headers)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
# disabling both get_image and modify_image should return 404 NotFound
|
||||
self.set_policy_rules({
|
||||
'get_image': '!',
|
||||
'modify_image': '!'
|
||||
})
|
||||
headers = self._headers({
|
||||
'X-Roles': 'member',
|
||||
'X-Project-Id': 'fake-project-id'
|
||||
})
|
||||
response = self._import_direct(image_id, store_to_import,
|
||||
headers=headers)
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
|
|
@ -47,7 +47,8 @@ class TestImageImportLocking(functional.SynchronousAPIBase):
|
|||
|
||||
# Set up a fake data pipeline that will stall until we are ready
|
||||
# to unblock it
|
||||
def slow_fake_set_data(data_iter, backend=None, set_active=True):
|
||||
def slow_fake_set_data(data_iter, size=None, backend=None,
|
||||
set_active=True):
|
||||
me = str(uuid.uuid4())
|
||||
while state['want_run'] == True:
|
||||
LOG.info('fake_set_data running %s' % me)
|
||||
|
@ -61,7 +62,7 @@ class TestImageImportLocking(functional.SynchronousAPIBase):
|
|||
|
||||
# Turn on the delayed data pipeline and start a copy-image
|
||||
# import which will hang out for a while
|
||||
with mock.patch('glance.domain.proxy.Image.set_data') as mock_sd:
|
||||
with mock.patch('glance.location.ImageProxy.set_data') as mock_sd:
|
||||
mock_sd.side_effect = slow_fake_set_data
|
||||
|
||||
resp = self._import_copy(image_id, ['store2'])
|
||||
|
|
|
@ -841,7 +841,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(container_format=None)
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -851,7 +851,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(disk_format=None)
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -861,7 +861,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='queued')
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -871,7 +871,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage()
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -881,7 +881,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage()
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -892,7 +892,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
def test_image_import_raises_bad_request(self, mock_gpt, mock_spa):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='uploading')
|
||||
# NOTE(abhishekk): Due to
|
||||
# https://bugs.launchpad.net/glance/+bug/1712463 taskflow is not
|
||||
|
@ -907,7 +907,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
def test_image_import_invalid_uri_filtering(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='queued')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -922,7 +922,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request(
|
||||
'/v2/images/%s/import' % UUID4)
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='uploading')
|
||||
mock_get.return_value.extra_properties['os_glance_stage_host'] = (
|
||||
'https://glance-worker1.openstack.org')
|
||||
|
@ -986,7 +986,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request(
|
||||
'/v2/images/%s/import' % UUID4)
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='uploading')
|
||||
mock_get.return_value.extra_properties['os_glance_stage_host'] = (
|
||||
'https://glance-worker1.openstack.org')
|
||||
|
@ -1078,7 +1078,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request(
|
||||
'/v2/images/%s/import' % UUID4)
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='queued')
|
||||
mock_get.return_value.extra_properties['os_glance_stage_host'] = (
|
||||
'https://glance-worker1.openstack.org')
|
||||
|
@ -3339,7 +3339,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
self.assertIsNone(pos)
|
||||
|
||||
@mock.patch('glance.db.simple.api.image_set_property_atomic')
|
||||
@mock.patch.object(glance.api.authorization.TaskFactoryProxy, 'new_task')
|
||||
@mock.patch.object(glance.notifier.TaskFactoryProxy, 'new_task')
|
||||
@mock.patch.object(glance.domain.TaskExecutorFactory, 'new_task_executor')
|
||||
@mock.patch('glance.api.common.get_thread_pool')
|
||||
@mock.patch('glance.quota.keystone.enforce_image_size_total')
|
||||
|
@ -3348,7 +3348,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
image = FakeImage(status='uploading')
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = image
|
||||
output = self.controller.import_image(
|
||||
request, UUID4, {'method': {'name': 'glance-direct'}})
|
||||
|
@ -3370,7 +3370,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
mock_nt.return_value.run, mock_nte.return_value)
|
||||
|
||||
@mock.patch.object(glance.domain.TaskFactory, 'new_task')
|
||||
@mock.patch.object(glance.api.authorization.ImageRepoProxy, 'get')
|
||||
@mock.patch.object(glance.notifier.ImageRepoProxy, 'get')
|
||||
def test_image_import_not_allowed(self, mock_get, mock_new_task):
|
||||
# NOTE(danms): FakeImage is owned by utils.TENANT1. Try to do the
|
||||
# import as TENANT2 and we should get an HTTPForbidden
|
||||
|
@ -3384,7 +3384,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
# a task
|
||||
mock_new_task.assert_not_called()
|
||||
|
||||
@mock.patch.object(glance.api.authorization.ImageRepoProxy, 'get')
|
||||
@mock.patch.object(glance.notifier.ImageRepoProxy, 'get')
|
||||
@mock.patch('glance.quota.keystone.enforce_image_size_total')
|
||||
def test_image_import_quota_fail(self, mock_enforce, mock_get):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
|
@ -3398,7 +3398,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
@mock.patch('glance.db.simple.api.image_set_property_atomic')
|
||||
@mock.patch('glance.context.RequestContext.elevated')
|
||||
@mock.patch.object(glance.domain.TaskFactory, 'new_task')
|
||||
@mock.patch.object(glance.api.authorization.ImageRepoProxy, 'get')
|
||||
@mock.patch.object(glance.notifier.ImageRepoProxy, 'get')
|
||||
def test_image_import_copy_allowed_by_policy(self, mock_get,
|
||||
mock_new_task,
|
||||
mock_elevated,
|
||||
|
@ -3423,7 +3423,8 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
# Make sure we passed an admin context to our task executor factory
|
||||
mock_tef.assert_called_once_with(
|
||||
request.context,
|
||||
admin_context=mock_elevated.return_value)
|
||||
admin_context=mock_elevated.return_value,
|
||||
authorization_layer=False)
|
||||
|
||||
expected_input = {'image_id': UUID4,
|
||||
'import_req': mock.ANY,
|
||||
|
@ -3442,7 +3443,7 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
self.test_image_import_copy_allowed_by_policy,
|
||||
allowed=False)
|
||||
|
||||
@mock.patch.object(glance.api.authorization.ImageRepoProxy, 'get')
|
||||
@mock.patch.object(glance.notifier.ImageRepoProxy, 'get')
|
||||
def test_image_import_locked(self, mock_get):
|
||||
task = test_tasks_resource._db_fixture(test_tasks_resource.UUID1,
|
||||
status='pending')
|
||||
|
@ -3463,8 +3464,8 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
|
||||
@mock.patch('glance.db.simple.api.image_set_property_atomic')
|
||||
@mock.patch('glance.db.simple.api.image_delete_property_atomic')
|
||||
@mock.patch.object(glance.api.authorization.TaskFactoryProxy, 'new_task')
|
||||
@mock.patch.object(glance.api.authorization.ImageRepoProxy, 'get')
|
||||
@mock.patch.object(glance.notifier.TaskFactoryProxy, 'new_task')
|
||||
@mock.patch.object(glance.notifier.ImageRepoProxy, 'get')
|
||||
def test_image_import_locked_by_reaped_task(self, mock_get, mock_nt,
|
||||
mock_dpi, mock_spi):
|
||||
image = FakeImage(status='uploading')
|
||||
|
@ -3485,11 +3486,11 @@ class TestImagesController(base.IsolatedUnitTest):
|
|||
mock_spi.assert_called_once_with(image.id, 'os_glance_import_task',
|
||||
'mytask')
|
||||
|
||||
@mock.patch.object(glance.api.authorization.ImageRepoProxy, 'save')
|
||||
@mock.patch.object(glance.notifier.ImageRepoProxy, 'save')
|
||||
@mock.patch('glance.db.simple.api.image_set_property_atomic')
|
||||
@mock.patch('glance.db.simple.api.image_delete_property_atomic')
|
||||
@mock.patch.object(glance.api.authorization.TaskFactoryProxy, 'new_task')
|
||||
@mock.patch.object(glance.api.authorization.ImageRepoProxy, 'get')
|
||||
@mock.patch.object(glance.notifier.TaskFactoryProxy, 'new_task')
|
||||
@mock.patch.object(glance.notifier.ImageRepoProxy, 'get')
|
||||
def test_image_import_locked_by_bustable_task(self, mock_get, mock_nt,
|
||||
mock_dpi, mock_spi,
|
||||
mock_save,
|
||||
|
@ -6057,7 +6058,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
request.headers['x-image-meta-store'] = 'dummy'
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='uploading')
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image,
|
||||
|
@ -6068,7 +6069,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(disk_format=None)
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -6078,7 +6079,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='queued')
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -6088,7 +6089,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage()
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID4,
|
||||
|
@ -6099,7 +6100,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
request.headers['x-image-meta-store'] = 'fast'
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage()
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.import_image, request, UUID7,
|
||||
|
@ -6110,7 +6111,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.side_effect = exception.NotFound
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.import_image, request, UUID1,
|
||||
|
@ -6125,7 +6126,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
'status': 'active'},
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
with mock.patch.object(self.store,
|
||||
'get_store_from_store_identifier'):
|
||||
mock_get.return_value = FakeImage(id=UUID7, status='active',
|
||||
|
@ -6138,7 +6139,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
request = unit_test_utils.get_fake_request()
|
||||
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(status='uploading')
|
||||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.import_image, request, UUID1,
|
||||
|
@ -6159,7 +6160,7 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
|
|||
'metadata': {'store': 'fast'},
|
||||
'status': 'active'},
|
||||
with mock.patch.object(
|
||||
glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
|
||||
glance.notifier.ImageRepoProxy, 'get') as mock_get:
|
||||
mock_get.return_value = FakeImage(id=UUID7, status='active',
|
||||
locations=locations)
|
||||
|
||||
|
|
|
@ -471,6 +471,12 @@ class APIImagePolicy(APIPolicyBase):
|
|||
self.policy.reactivate_image()
|
||||
self.assertFalse(m.called)
|
||||
|
||||
def test_copy_image(self):
|
||||
self.policy.copy_image()
|
||||
self.enforcer.enforce.assert_called_once_with(self.context,
|
||||
'copy_image',
|
||||
mock.ANY)
|
||||
|
||||
|
||||
class TestMetadefAPIPolicy(APIPolicyBase):
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue