Extend the domain model to v2 image data
This completes blueprint glance-domain-logic-layer Change-Id: I5d0e9f54cfc745f484a8db8f29f64caeb832ec98
This commit is contained in:
parent
8e43e39025
commit
a12e81d5b4
|
@ -245,6 +245,13 @@ class ImmutableImageProxy(object):
|
|||
member_repo = self.base.get_member_repo()
|
||||
return ImageMembershipRepoProxy(member_repo, self.context)
|
||||
|
||||
def get_data(self):
|
||||
return self.base.get_data()
|
||||
|
||||
def set_data(self, *args, **kwargs):
|
||||
message = _("You are not permitted to upload data for this image.")
|
||||
raise exception.Forbidden(message)
|
||||
|
||||
|
||||
class ImmutableMemberProxy(object):
|
||||
def __init__(self, base):
|
||||
|
|
|
@ -193,6 +193,10 @@ class ImageProxy(glance.domain.ImageProxy):
|
|||
self._policy.enforce(self._context, 'delete_image', {})
|
||||
return self._image.delete()
|
||||
|
||||
def get_data(self, *args, **kwargs):
|
||||
self._policy.enforce(self._context, 'download_image', {})
|
||||
return self._image.get_data(*args, **kwargs)
|
||||
|
||||
|
||||
class ImageFactoryProxy(object):
|
||||
|
||||
|
|
|
@ -16,12 +16,14 @@
|
|||
import webob.exc
|
||||
|
||||
from glance.api import common
|
||||
from glance.api import policy
|
||||
import glance.api.policy
|
||||
import glance.api.v2 as v2
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
from glance.common import wsgi
|
||||
import glance.db
|
||||
import glance.domain
|
||||
import glance.gateway
|
||||
import glance.notifier
|
||||
import glance.openstack.common.log as logging
|
||||
import glance.store
|
||||
|
@ -31,34 +33,25 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
class ImageDataController(object):
|
||||
def __init__(self, db_api=None, store_api=None,
|
||||
policy_enforcer=None, notifier=None):
|
||||
self.db_api = db_api or glance.db.get_api()
|
||||
self.db_api.setup_db_env()
|
||||
self.store_api = store_api or glance.store
|
||||
self.policy = policy_enforcer or policy.Enforcer()
|
||||
self.notifier = notifier or glance.notifier.Notifier()
|
||||
|
||||
def _get_image(self, context, image_id):
|
||||
try:
|
||||
return self.db_api.image_get(context, image_id)
|
||||
except exception.NotFound:
|
||||
raise webob.exc.HTTPNotFound(_("Image does not exist"))
|
||||
|
||||
def _enforce(self, req, action):
|
||||
"""Authorize an action against our policies"""
|
||||
try:
|
||||
self.policy.enforce(req.context, action, {})
|
||||
except exception.Forbidden:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
policy_enforcer=None, notifier=None,
|
||||
gateway=None):
|
||||
if gateway is None:
|
||||
db_api = db_api or glance.db.get_api()
|
||||
db_api.setup_db_env()
|
||||
store_api = store_api or glance.store
|
||||
policy = policy_enforcer or glance.api.policy.Enforcer()
|
||||
notifier = notifier or glance.notifier.Notifier()
|
||||
gateway = glance.gateway.Gateway(db_api, store_api,
|
||||
notifier, policy)
|
||||
self.gateway = gateway
|
||||
|
||||
@utils.mutating
|
||||
def upload(self, req, image_id, data, size):
|
||||
image_repo = self.gateway.get_repo(req.context)
|
||||
try:
|
||||
image = self._get_image(req.context, image_id)
|
||||
self.notifier.info("image.prepare", image)
|
||||
location, size, checksum = self.store_api.add_to_backend(
|
||||
req.context, 'file', image_id, data, size)
|
||||
|
||||
image = image_repo.get(image_id)
|
||||
image.set_data(data, size)
|
||||
image_repo.save(image)
|
||||
except exception.Duplicate, e:
|
||||
msg = _("Unable to upload duplicate image data for image: %s")
|
||||
LOG.debug(msg % image_id)
|
||||
|
@ -69,17 +62,18 @@ class ImageDataController(object):
|
|||
LOG.debug(msg % image_id)
|
||||
raise webob.exc.HTTPForbidden(explanation=msg, request=req)
|
||||
|
||||
except exception.NotFound, e:
|
||||
raise webob.exc.HTTPNotFound(explanation=unicode(e))
|
||||
|
||||
except exception.StorageFull, e:
|
||||
msg = _("Image storage media is full: %s") % e
|
||||
LOG.error(msg)
|
||||
self.notifier.error("image.upload", msg)
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
|
||||
request=req)
|
||||
|
||||
except exception.StorageWriteDenied, e:
|
||||
msg = _("Insufficient permissions on image storage media: %s") % e
|
||||
LOG.error(msg)
|
||||
self.notifier.error("image.upload", msg)
|
||||
raise webob.exc.HTTPServiceUnavailable(explanation=msg,
|
||||
request=req)
|
||||
|
||||
|
@ -92,32 +86,19 @@ class ImageDataController(object):
|
|||
"internal error"))
|
||||
raise
|
||||
|
||||
else:
|
||||
v2.update_image_read_acl(req, self.store_api, self.db_api, image)
|
||||
values = {'location': location, 'size': size, 'checksum': checksum,
|
||||
'status': 'active'}
|
||||
self.db_api.image_update(req.context, image_id, values)
|
||||
updated_image = self._get_image(req.context, image_id)
|
||||
self.notifier.info('image.upload', updated_image)
|
||||
self.notifier.info('image.activate', updated_image)
|
||||
|
||||
def download(self, req, image_id):
|
||||
self._enforce(req, 'download_image')
|
||||
ctx = req.context
|
||||
image = self._get_image(ctx, image_id)
|
||||
location = image['location']
|
||||
if location:
|
||||
image_data, image_size = self.store_api.get_from_backend(ctx,
|
||||
location)
|
||||
#NOTE(bcwaldon): This is done to match the behavior of the v1 API.
|
||||
# The store should always return a size that matches what we have
|
||||
# in the database. If the store says otherwise, that's a security
|
||||
# risk.
|
||||
if image_size is not None:
|
||||
image['size'] = int(image_size)
|
||||
return {'data': image_data, 'meta': image}
|
||||
else:
|
||||
raise webob.exc.HTTPNotFound(_("No image data could be found"))
|
||||
image_repo = self.gateway.get_repo(req.context)
|
||||
try:
|
||||
image = image_repo.get(image_id)
|
||||
except exception.NotFound as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=unicode(e))
|
||||
except exception.Forbidden as e:
|
||||
raise webob.exc.HTTPForbidden(explanation=unicode(e))
|
||||
|
||||
if not image.location:
|
||||
reason = _("No image data could be found")
|
||||
raise webob.exc.HTTPNotFound(reason)
|
||||
return image
|
||||
|
||||
|
||||
class RequestDeserializer(wsgi.JSONRequestDeserializer):
|
||||
|
@ -132,24 +113,20 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
|
|||
|
||||
|
||||
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
||||
def __init__(self, notifier=None):
|
||||
super(ResponseSerializer, self).__init__()
|
||||
self.notifier = notifier or glance.notifier.Notifier()
|
||||
|
||||
def download(self, response, result):
|
||||
size = result['meta']['size']
|
||||
checksum = result['meta']['checksum']
|
||||
def download(self, response, image):
|
||||
response.headers['Content-Type'] = 'application/octet-stream'
|
||||
response.app_iter = common.size_checked_iter(
|
||||
response, result['meta'], size, result['data'], self.notifier)
|
||||
# NOTE(markwash): filesystem store (and maybe others?) cause a problem
|
||||
# with the caching middleware if they are not wrapped in an iterator
|
||||
# very strange
|
||||
response.app_iter = iter(image.get_data())
|
||||
#NOTE(saschpe): "response.app_iter = ..." currently resets Content-MD5
|
||||
# (https://github.com/Pylons/webob/issues/86), so it should be set
|
||||
# afterwards for the time being.
|
||||
if checksum:
|
||||
response.headers['Content-MD5'] = checksum
|
||||
if image.checksum:
|
||||
response.headers['Content-MD5'] = image.checksum
|
||||
#NOTE(markwash): "response.app_iter = ..." also erroneously resets the
|
||||
# content-length
|
||||
response.headers['Content-Length'] = str(size)
|
||||
response.headers['Content-Length'] = str(image.size)
|
||||
|
||||
def upload(self, response, result):
|
||||
response.status_int = 201
|
||||
|
|
|
@ -111,6 +111,12 @@ class Image(object):
|
|||
raise exception.ProtectedImageDelete(image_id=self.image_id)
|
||||
self.status = 'deleted'
|
||||
|
||||
def get_data(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_data(self, data, size=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _proxy(target, attr):
|
||||
|
||||
|
@ -172,6 +178,12 @@ class ImageProxy(object):
|
|||
def delete(self):
|
||||
self.base.delete()
|
||||
|
||||
def set_data(self, data, size=None):
|
||||
self.base.set_data(data, size)
|
||||
|
||||
def get_data(self):
|
||||
return self.base.get_data()
|
||||
|
||||
def get_member_repo(self):
|
||||
return self.base.get_member_repo()
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ from glance.api import authorization
|
|||
from glance.api import policy
|
||||
import glance.db
|
||||
import glance.domain
|
||||
import glance.notifier
|
||||
import glance.store
|
||||
|
||||
|
||||
|
@ -50,7 +51,7 @@ class Gateway(object):
|
|||
policy_image_repo = policy.ImageRepoProxy(
|
||||
context, self.policy, store_image_repo)
|
||||
notifier_image_repo = glance.notifier.ImageRepoProxy(
|
||||
policy_image_repo, self.notifier)
|
||||
policy_image_repo, context, self.notifier)
|
||||
authorized_image_repo = authorization.ImageRepoProxy(
|
||||
notifier_image_repo, context)
|
||||
return authorized_image_repo
|
||||
|
|
|
@ -119,8 +119,9 @@ def format_image_notification(image):
|
|||
|
||||
class ImageRepoProxy(glance.domain.ImageRepoProxy):
|
||||
|
||||
def __init__(self, image_repo, notifier):
|
||||
def __init__(self, image_repo, context, notifier):
|
||||
self.image_repo = image_repo
|
||||
self.context = context
|
||||
self.notifier = notifier
|
||||
super(ImageRepoProxy, self).__init__(image_repo)
|
||||
|
||||
|
@ -138,3 +139,63 @@ class ImageRepoProxy(glance.domain.ImageRepoProxy):
|
|||
payload['deleted'] = True
|
||||
payload['deleted_at'] = timeutils.isotime()
|
||||
self.notifier.info('image.delete', payload)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
image = self.image_repo.get(*args, **kwargs)
|
||||
return ImageProxy(image, self.context, self.notifier)
|
||||
|
||||
def list(self, *args, **kwargs):
|
||||
images = self.image_repo.list(*args, **kwargs)
|
||||
return [ImageProxy(i, self.context, self.notifier) for i in images]
|
||||
|
||||
|
||||
class ImageProxy(glance.domain.ImageProxy):
|
||||
|
||||
def __init__(self, image, context, notifier):
|
||||
self.image = image
|
||||
self.context = context
|
||||
self.notifier = notifier
|
||||
super(ImageProxy, self).__init__(image)
|
||||
|
||||
def _format_image_send(self, bytes_sent):
|
||||
return {
|
||||
'bytes_sent': bytes_sent,
|
||||
'image_id': self.image.image_id,
|
||||
'owner_id': self.image.owner,
|
||||
'receiver_tenant_id': self.context.tenant,
|
||||
'receiver_user_id': self.context.user,
|
||||
}
|
||||
|
||||
def get_data(self):
|
||||
sent = 0
|
||||
for chunk in self.image.get_data():
|
||||
yield chunk
|
||||
sent += len(chunk)
|
||||
|
||||
if sent != self.image.size:
|
||||
notify = self.notifier.error
|
||||
else:
|
||||
notify = self.notifier.info
|
||||
|
||||
try:
|
||||
notify('image.send', self._format_image_send(sent))
|
||||
except Exception, err:
|
||||
msg = _("An error occurred during image.send"
|
||||
" notification: %(err)s") % locals()
|
||||
LOG.error(msg)
|
||||
|
||||
def set_data(self, data, size=None):
|
||||
payload = format_image_notification(self.image)
|
||||
self.notifier.info('image.prepare', payload)
|
||||
try:
|
||||
self.image.set_data(data, size)
|
||||
except exception.StorageFull, e:
|
||||
msg = _("Image storage media is full: %s") % e
|
||||
self.notifier.error('image.upload', msg)
|
||||
except exception.StorageWriteDenied, e:
|
||||
msg = _("Insufficient permissions on image storage media: %s") % e
|
||||
self.notifier.error('image.upload', msg)
|
||||
else:
|
||||
payload = format_image_notification(self.image)
|
||||
self.notifier.info('image.upload', payload)
|
||||
self.notifier.info('image.activate', payload)
|
||||
|
|
|
@ -341,3 +341,20 @@ class ImageProxy(glance.domain.ImageProxy):
|
|||
self.store_api.safe_delete_from_backend(self.image.location,
|
||||
self.context,
|
||||
self.image.image_id)
|
||||
|
||||
def set_data(self, data, size=None):
|
||||
if size is None:
|
||||
size = 0 # NOTE(markwash): zero -> unknown size
|
||||
location, size, checksum = self.store_api.add_to_backend(
|
||||
self.context, CONF.default_store,
|
||||
self.image.image_id, data, size)
|
||||
self.image.location = location
|
||||
self.image.size = size
|
||||
self.image.checksum = checksum
|
||||
|
||||
def get_data(self):
|
||||
if not self.image.location:
|
||||
raise exception.NotFound(_("No image data could be found"))
|
||||
data, size = self.store_api.get_from_backend(self.context,
|
||||
self.image.location)
|
||||
return data
|
||||
|
|
|
@ -684,6 +684,22 @@ class TestImmutableImage(utils.BaseTestCase):
|
|||
self.assertRaises(exception.Forbidden,
|
||||
self.image.extra_properties.update, {})
|
||||
|
||||
def test_delete(self):
|
||||
self.assertRaises(exception.Forbidden, self.image.delete)
|
||||
|
||||
def test_set_data(self):
|
||||
self.assertRaises(exception.Forbidden,
|
||||
self.image.set_data, 'blah', 4)
|
||||
|
||||
def test_get_data(self):
|
||||
class FakeImage(object):
|
||||
def get_data(self):
|
||||
return 'tiddlywinks'
|
||||
|
||||
image = glance.api.authorization.ImmutableImageProxy(
|
||||
FakeImage(), self.context)
|
||||
self.assertEquals(image.get_data(), 'tiddlywinks')
|
||||
|
||||
|
||||
class TestImageFactoryProxy(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -26,6 +26,7 @@ except ImportError:
|
|||
import stubout
|
||||
|
||||
from glance.common import exception
|
||||
import glance.context
|
||||
from glance import notifier
|
||||
import glance.notifier.notify_kombu
|
||||
from glance.openstack.common import importutils
|
||||
|
@ -38,7 +39,18 @@ DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
|
|||
|
||||
|
||||
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
|
||||
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
|
||||
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
|
||||
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
|
||||
|
||||
|
||||
class ImageStub(glance.domain.Image):
|
||||
def get_data(self):
|
||||
return ['01234', '56789']
|
||||
|
||||
def set_data(self, data, size=None):
|
||||
for chunk in data:
|
||||
pass
|
||||
|
||||
|
||||
class ImageRepoStub(object):
|
||||
|
@ -51,6 +63,12 @@ class ImageRepoStub(object):
|
|||
def add(self, *args, **kwargs):
|
||||
return 'image_from_add'
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return 'image_from_get'
|
||||
|
||||
def list(self, *args, **kwargs):
|
||||
return ['images_from_list']
|
||||
|
||||
|
||||
class TestNotifier(utils.BaseTestCase):
|
||||
|
||||
|
@ -425,16 +443,20 @@ class TestImageNotifications(utils.BaseTestCase):
|
|||
"""Test Image Notifications work"""
|
||||
|
||||
def setUp(self):
|
||||
self.image = glance.domain.Image(
|
||||
self.image = ImageStub(
|
||||
image_id=UUID1, name='image-1', status='active', size=1024,
|
||||
created_at=DATETIME, updated_at=DATETIME, owner=TENANT1,
|
||||
visibility='public', container_format='ami',
|
||||
tags=['one', 'two'], disk_format='ami', min_ram=128,
|
||||
min_disk=10, checksum='ca425b88f047ce8ec45ee90e813ada91')
|
||||
self.context = glance.context.RequestContext(tenant=TENANT2,
|
||||
user=USER1)
|
||||
self.image_repo_stub = ImageRepoStub()
|
||||
self.notifier = unit_test_utils.FakeNotifier()
|
||||
self.image_repo_proxy = glance.notifier.ImageRepoProxy(
|
||||
self.image_repo_stub, self.notifier)
|
||||
self.image_repo_stub, self.context, self.notifier)
|
||||
self.image_proxy = glance.notifier.ImageProxy(
|
||||
self.image, self.context, self.notifier)
|
||||
super(TestImageNotifications, self).setUp()
|
||||
|
||||
def test_image_save_notification(self):
|
||||
|
@ -464,3 +486,110 @@ class TestImageNotifications(utils.BaseTestCase):
|
|||
self.assertEqual(output_log['event_type'], 'image.delete')
|
||||
self.assertEqual(output_log['payload']['id'], self.image.image_id)
|
||||
self.assertTrue(output_log['payload']['deleted'])
|
||||
|
||||
def test_image_get(self):
|
||||
image = self.image_repo_proxy.get(UUID1)
|
||||
self.assertTrue(isinstance(image, glance.notifier.ImageProxy))
|
||||
self.assertEqual(image.image, 'image_from_get')
|
||||
|
||||
def test_image_list(self):
|
||||
images = self.image_repo_proxy.list()
|
||||
self.assertTrue(isinstance(images[0], glance.notifier.ImageProxy))
|
||||
self.assertEqual(images[0].image, 'images_from_list')
|
||||
|
||||
def test_image_get_data_notification(self):
|
||||
self.image_proxy.size = 10
|
||||
data = ''.join(self.image_proxy.get_data())
|
||||
self.assertEqual(data, '0123456789')
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(len(output_logs), 1)
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual(output_log['notification_type'], 'INFO')
|
||||
self.assertEqual(output_log['event_type'], 'image.send')
|
||||
self.assertEqual(output_log['payload']['image_id'],
|
||||
self.image.image_id)
|
||||
self.assertEqual(output_log['payload']['receiver_tenant_id'], TENANT2)
|
||||
self.assertEqual(output_log['payload']['receiver_user_id'], USER1)
|
||||
self.assertEqual(output_log['payload']['bytes_sent'], 10)
|
||||
self.assertEqual(output_log['payload']['owner_id'], TENANT1)
|
||||
|
||||
def test_image_get_data_size_mismatch(self):
|
||||
self.image_proxy.size = 11
|
||||
list(self.image_proxy.get_data())
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(len(output_logs), 1)
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual(output_log['notification_type'], 'ERROR')
|
||||
self.assertEqual(output_log['event_type'], 'image.send')
|
||||
self.assertEqual(output_log['payload']['image_id'],
|
||||
self.image.image_id)
|
||||
|
||||
def test_image_set_data_prepare_notification(self):
|
||||
insurance = {'called': False}
|
||||
|
||||
def data_iterator():
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(len(output_logs), 1)
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual(output_log['notification_type'], 'INFO')
|
||||
self.assertEqual(output_log['event_type'], 'image.prepare')
|
||||
self.assertEqual(output_log['payload']['id'], self.image.image_id)
|
||||
yield 'abcd'
|
||||
yield 'efgh'
|
||||
insurance['called'] = True
|
||||
|
||||
self.image_proxy.set_data(data_iterator(), 8)
|
||||
self.assertTrue(insurance['called'])
|
||||
|
||||
def test_image_set_data_upload_and_activate_notification(self):
|
||||
def data_iterator():
|
||||
self.notifier.log = []
|
||||
yield 'abcde'
|
||||
yield 'fghij'
|
||||
|
||||
self.image_proxy.set_data(data_iterator(), 10)
|
||||
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(len(output_logs), 2)
|
||||
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual(output_log['notification_type'], 'INFO')
|
||||
self.assertEqual(output_log['event_type'], 'image.upload')
|
||||
self.assertEqual(output_log['payload']['id'], self.image.image_id)
|
||||
|
||||
output_log = output_logs[1]
|
||||
self.assertEqual(output_log['notification_type'], 'INFO')
|
||||
self.assertEqual(output_log['event_type'], 'image.activate')
|
||||
self.assertEqual(output_log['payload']['id'], self.image.image_id)
|
||||
|
||||
def test_image_set_data_storage_full(self):
|
||||
def data_iterator():
|
||||
self.notifier.log = []
|
||||
yield 'abcde'
|
||||
raise exception.StorageFull('Modern Major General')
|
||||
|
||||
self.image_proxy.set_data(data_iterator(), 10)
|
||||
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(len(output_logs), 1)
|
||||
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual(output_log['notification_type'], 'ERROR')
|
||||
self.assertEqual(output_log['event_type'], 'image.upload')
|
||||
self.assertTrue('Modern Major General' in output_log['payload'])
|
||||
|
||||
def test_image_set_data_storage_full(self):
|
||||
def data_iterator():
|
||||
self.notifier.log = []
|
||||
yield 'abcde'
|
||||
raise exception.StorageWriteDenied('The Very Model')
|
||||
|
||||
self.image_proxy.set_data(data_iterator(), 10)
|
||||
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(len(output_logs), 1)
|
||||
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual(output_log['notification_type'], 'ERROR')
|
||||
self.assertEqual(output_log['event_type'], 'image.upload')
|
||||
self.assertTrue('The Very Model' in output_log['payload'])
|
||||
|
|
|
@ -249,3 +249,10 @@ class TestImagePolicy(test_utils.BaseTestCase):
|
|||
image_factory = glance.api.policy.ImageFactoryProxy(
|
||||
self.image_factory_stub, {}, self.policy)
|
||||
image_factory.new_image(visibility='public')
|
||||
|
||||
def test_image_get_data(self):
|
||||
rules = {'download_image': False}
|
||||
self.policy.set_rules(rules)
|
||||
|
||||
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
|
||||
self.assertRaises(exception.Forbidden, image.get_data)
|
||||
|
|
|
@ -21,6 +21,8 @@ from glance.tests import utils
|
|||
|
||||
BASE_URI = 'swift+http://storeurl.com/container'
|
||||
UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
|
||||
UUID2 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
|
||||
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
|
||||
|
||||
|
||||
class ImageRepoStub(object):
|
||||
|
@ -66,6 +68,20 @@ class TestStoreImage(utils.BaseTestCase):
|
|||
self.assertEquals(image.status, 'pending_delete')
|
||||
self.store_api.get_from_backend({}, image.location) # no exception
|
||||
|
||||
def test_image_get_data(self):
|
||||
image = glance.store.ImageProxy(self.image_stub, {}, self.store_api)
|
||||
self.assertEquals(image.get_data(), 'XXX')
|
||||
|
||||
def test_image_set_data(self):
|
||||
context = glance.context.RequestContext(user=USER1)
|
||||
image_stub = ImageStub(UUID2, status='queued', location=None)
|
||||
image = glance.store.ImageProxy(image_stub, context, self.store_api)
|
||||
image.set_data('YYYY', 4)
|
||||
self.assertEquals(image.size, 4)
|
||||
#NOTE(markwash): FakeStore returns image_id for location
|
||||
self.assertEquals(image.location, UUID2)
|
||||
self.assertEquals(image.checksum, 'Z')
|
||||
|
||||
def test_image_repo_get(self):
|
||||
image_repo = glance.store.ImageRepoProxy({}, self.store_api,
|
||||
self.image_repo_stub)
|
||||
|
|
|
@ -18,51 +18,152 @@ import StringIO
|
|||
import webob
|
||||
|
||||
import glance.api.v2.image_data
|
||||
from glance.common import exception
|
||||
from glance.openstack.common import uuidutils
|
||||
from glance.tests.unit import base
|
||||
import glance.tests.unit.utils as unit_test_utils
|
||||
import glance.tests.utils as test_utils
|
||||
|
||||
|
||||
class Raise(object):
|
||||
def __init__(self, exc):
|
||||
self.exc = exc
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
raise self.exc
|
||||
|
||||
|
||||
class FakeImage(object):
|
||||
def __init__(self, image_id=None, data=None, checksum=None, size=0,
|
||||
location=None):
|
||||
self.image_id = image_id
|
||||
self.data = data
|
||||
self.checksum = checksum
|
||||
self.size = size
|
||||
self.location = location
|
||||
|
||||
def get_data(self):
|
||||
return self.data
|
||||
|
||||
def set_data(self, data, size=None):
|
||||
self.data = data
|
||||
self.size = size
|
||||
|
||||
|
||||
class FakeImageRepo(object):
|
||||
def __init__(self, result=None):
|
||||
self.result = result
|
||||
|
||||
def get(self, image_id):
|
||||
if isinstance(self.result, BaseException):
|
||||
raise self.result
|
||||
else:
|
||||
return self.result
|
||||
|
||||
def save(self, image):
|
||||
self.saved_image = image
|
||||
|
||||
|
||||
class FakeGateway(object):
|
||||
def __init__(self, repo):
|
||||
self.repo = repo
|
||||
|
||||
def get_repo(self, context):
|
||||
return self.repo
|
||||
|
||||
|
||||
class TestImagesController(base.StoreClearingUnitTest):
|
||||
def setUp(self):
|
||||
super(TestImagesController, self).setUp()
|
||||
|
||||
self.config(verbose=True, debug=True)
|
||||
self.notifier = unit_test_utils.FakeNotifier()
|
||||
self.image_repo = FakeImageRepo()
|
||||
self.gateway = FakeGateway(self.image_repo)
|
||||
self.controller = glance.api.v2.image_data.ImageDataController(
|
||||
db_api=unit_test_utils.FakeDB(),
|
||||
store_api=unit_test_utils.FakeStoreAPI(),
|
||||
policy_enforcer=unit_test_utils.FakePolicyEnforcer(),
|
||||
notifier=self.notifier)
|
||||
gateway=self.gateway)
|
||||
|
||||
def test_download(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
output = self.controller.download(request, unit_test_utils.UUID1)
|
||||
self.assertEqual(set(['data', 'meta']), set(output.keys()))
|
||||
self.assertEqual(3, output['meta']['size'])
|
||||
self.assertEqual('XXX', output['data'])
|
||||
image = FakeImage('abcd', location='http://example.com/image')
|
||||
self.image_repo.result = image
|
||||
image = self.controller.download(request, unit_test_utils.UUID1)
|
||||
self.assertEqual(image.image_id, 'abcd')
|
||||
|
||||
def test_download_no_data(self):
|
||||
def test_download_no_location(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.image_repo.result = FakeImage('abcd')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.download,
|
||||
request, unit_test_utils.UUID2)
|
||||
|
||||
def test_download_non_existent_image(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.image_repo.result = exception.NotFound()
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.download,
|
||||
request, uuidutils.generate_uuid())
|
||||
|
||||
def test_upload_download(self):
|
||||
def test_download_forbidden(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
|
||||
output = self.controller.download(request, unit_test_utils.UUID2)
|
||||
self.assertEqual(set(['data', 'meta']), set(output.keys()))
|
||||
self.assertEqual(4, output['meta']['size'])
|
||||
self.assertEqual('YYYY', output['data'])
|
||||
self.assertEqual(output['meta']['status'], 'active')
|
||||
self.image_repo.result = exception.Forbidden()
|
||||
self.assertRaises(webob.exc.HTTPForbidden, self.controller.download,
|
||||
request, uuidutils.generate_uuid())
|
||||
|
||||
def test_upload_download_prepare_notification(self):
|
||||
def test_upload(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
image = FakeImage('abcd')
|
||||
self.image_repo.result = image
|
||||
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
|
||||
self.assertEqual(image.data, 'YYYY')
|
||||
self.assertEqual(image.size, 4)
|
||||
|
||||
def test_upload_no_size(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
image = FakeImage('abcd')
|
||||
self.image_repo.result = image
|
||||
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', None)
|
||||
self.assertEqual(image.data, 'YYYY')
|
||||
self.assertEqual(image.size, None)
|
||||
|
||||
def test_upload_non_existent_image(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.image_repo.result = exception.NotFound()
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.upload,
|
||||
request, uuidutils.generate_uuid(), 'ABC', 3)
|
||||
|
||||
def test_upload_data_exists(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
image = FakeImage()
|
||||
image.set_data = Raise(exception.Duplicate)
|
||||
self.image_repo.result = image
|
||||
self.assertRaises(webob.exc.HTTPConflict, self.controller.upload,
|
||||
request, unit_test_utils.UUID1, 'YYYY', 4)
|
||||
|
||||
def test_upload_storage_full(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
image = FakeImage()
|
||||
image.set_data = Raise(exception.StorageFull)
|
||||
self.image_repo.result = image
|
||||
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
|
||||
self.controller.upload,
|
||||
request, unit_test_utils.UUID2, 'YYYYYYY', 7)
|
||||
|
||||
def test_upload_storage_forbidden(self):
|
||||
request = unit_test_utils.get_fake_request(user=unit_test_utils.USER2)
|
||||
image = FakeImage()
|
||||
image.set_data = Raise(exception.Forbidden)
|
||||
self.image_repo.result = image
|
||||
self.assertRaises(webob.exc.HTTPForbidden, self.controller.upload,
|
||||
request, unit_test_utils.UUID2, 'YY', 2)
|
||||
|
||||
def test_upload_storage_write_denied(self):
|
||||
request = unit_test_utils.get_fake_request(user=unit_test_utils.USER3)
|
||||
image = FakeImage()
|
||||
image.set_data = Raise(exception.StorageWriteDenied)
|
||||
self.image_repo.result = image
|
||||
self.assertRaises(webob.exc.HTTPServiceUnavailable,
|
||||
self.controller.upload,
|
||||
request, unit_test_utils.UUID2, 'YY', 2)
|
||||
|
||||
def _test_upload_download_prepare_notification(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
|
||||
output = self.controller.download(request, unit_test_utils.UUID2)
|
||||
|
@ -84,7 +185,7 @@ class TestImagesController(base.StoreClearingUnitTest):
|
|||
self.assertTrue(prepare_updated_at <= output['meta']['updated_at'])
|
||||
self.assertEqual(output_log[0], prepare_log)
|
||||
|
||||
def test_upload_download_upload_notification(self):
|
||||
def _test_upload_download_upload_notification(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
|
||||
output = self.controller.download(request, unit_test_utils.UUID2)
|
||||
|
@ -98,7 +199,7 @@ class TestImagesController(base.StoreClearingUnitTest):
|
|||
self.assertEqual(len(output_log), 3)
|
||||
self.assertEqual(output_log[1], upload_log)
|
||||
|
||||
def test_upload_download_activate_notification(self):
|
||||
def _test_upload_download_activate_notification(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
|
||||
output = self.controller.download(request, unit_test_utils.UUID2)
|
||||
|
@ -112,59 +213,6 @@ class TestImagesController(base.StoreClearingUnitTest):
|
|||
self.assertEqual(len(output_log), 3)
|
||||
self.assertEqual(output_log[2], activate_log)
|
||||
|
||||
def test_upload_non_existent_image(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.upload,
|
||||
request, uuidutils.generate_uuid(), 'YYYY', 4)
|
||||
|
||||
def test_upload_data_exists(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.assertRaises(webob.exc.HTTPConflict, self.controller.upload,
|
||||
request, unit_test_utils.UUID1, 'YYYY', 4)
|
||||
|
||||
def test_upload_storage_full(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
|
||||
self.controller.upload,
|
||||
request, unit_test_utils.UUID2, 'YYYYYYY', 7)
|
||||
|
||||
def test_upload_storage_forbidden(self):
|
||||
request = unit_test_utils.get_fake_request(user=unit_test_utils.USER2)
|
||||
self.assertRaises(webob.exc.HTTPForbidden, self.controller.upload,
|
||||
request, unit_test_utils.UUID2, 'YY', 2)
|
||||
|
||||
def test_upload_storage_write_denied(self):
|
||||
request = unit_test_utils.get_fake_request(user=unit_test_utils.USER3)
|
||||
self.assertRaises(webob.exc.HTTPServiceUnavailable,
|
||||
self.controller.upload,
|
||||
request, unit_test_utils.UUID2, 'YY', 2)
|
||||
|
||||
def test_upload_download_no_size(self):
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', None)
|
||||
output = self.controller.download(request, unit_test_utils.UUID2)
|
||||
self.assertEqual(set(['data', 'meta']), set(output.keys()))
|
||||
self.assertEqual(4, output['meta']['size'])
|
||||
self.assertEqual('YYYY', output['data'])
|
||||
|
||||
|
||||
class TestImageDataControllerPolicies(base.IsolatedUnitTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestImageDataControllerPolicies, self).setUp()
|
||||
self.db = unit_test_utils.FakeDB()
|
||||
self.policy = unit_test_utils.FakePolicyEnforcer()
|
||||
self.controller = glance.api.v2.image_data.ImageDataController(
|
||||
self.db,
|
||||
policy_enforcer=self.policy)
|
||||
|
||||
def test_download_unauthorized(self):
|
||||
rules = {"download_image": False}
|
||||
self.policy.set_rules(rules)
|
||||
request = unit_test_utils.get_fake_request()
|
||||
self.assertRaises(webob.exc.HTTPForbidden, self.controller.download,
|
||||
request, image_id=unit_test_utils.UUID2)
|
||||
|
||||
|
||||
class TestImageDataDeserializer(test_utils.BaseTestCase):
|
||||
|
||||
|
@ -234,19 +282,15 @@ class TestImageDataSerializer(test_utils.BaseTestCase):
|
|||
|
||||
def setUp(self):
|
||||
super(TestImageDataSerializer, self).setUp()
|
||||
self.serializer = glance.api.v2.image_data.ResponseSerializer(
|
||||
notifier=unit_test_utils.FakeNotifier())
|
||||
self.serializer = glance.api.v2.image_data.ResponseSerializer()
|
||||
|
||||
def test_download(self):
|
||||
request = webob.Request.blank('/')
|
||||
request.environ = {}
|
||||
response = webob.Response()
|
||||
response.request = request
|
||||
fixture = {
|
||||
'data': 'ZZZ',
|
||||
'meta': {'size': 3, 'id': 'asdf', 'checksum': None}
|
||||
}
|
||||
self.serializer.download(response, fixture)
|
||||
image = FakeImage(size=3, data=iter('ZZZ'))
|
||||
self.serializer.download(response, image)
|
||||
self.assertEqual('ZZZ', response.body)
|
||||
self.assertEqual('3', response.headers['Content-Length'])
|
||||
self.assertFalse('Content-MD5' in response.headers)
|
||||
|
@ -259,11 +303,8 @@ class TestImageDataSerializer(test_utils.BaseTestCase):
|
|||
response = webob.Response()
|
||||
response.request = request
|
||||
checksum = '0745064918b49693cca64d6b6a13d28a'
|
||||
fixture = {
|
||||
'data': 'ZZZ',
|
||||
'meta': {'size': 3, 'id': 'asdf', 'checksum': checksum}
|
||||
}
|
||||
self.serializer.download(response, fixture)
|
||||
image = FakeImage(size=3, checksum=checksum, data=iter('ZZZ'))
|
||||
self.serializer.download(response, image)
|
||||
self.assertEqual('ZZZ', response.body)
|
||||
self.assertEqual('3', response.headers['Content-Length'])
|
||||
self.assertEqual(checksum, response.headers['Content-MD5'])
|
||||
|
|
Loading…
Reference in New Issue