"Fossies" - the Fresh Open Source Software Archive

Member "glance-19.0.0/glance/tests/unit/v2/test_image_data_resource.py" (16 Oct 2019, 44635 Bytes) of package /linux/misc/openstack/glance-19.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "test_image_data_resource.py": 16.0.1_vs_18.0.0.

    1 # Copyright 2012 OpenStack Foundation.
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 import uuid
   16 
   17 from cursive import exception as cursive_exception
   18 import glance_store
   19 from glance_store._drivers import filesystem
   20 import mock
   21 import six
   22 from six.moves import http_client as http
   23 import webob
   24 
   25 import glance.api.policy
   26 import glance.api.v2.image_data
   27 from glance.common import exception
   28 from glance.common import wsgi
   29 from glance.tests.unit import base
   30 import glance.tests.unit.utils as unit_test_utils
   31 import glance.tests.utils as test_utils
   32 
   33 
   34 class Raise(object):
   35 
   36     def __init__(self, exc):
   37         self.exc = exc
   38 
   39     def __call__(self, *args, **kwargs):
   40         raise self.exc
   41 
   42 
   43 class FakeImage(object):
   44 
   45     def __init__(self, image_id=None, data=None, checksum=None, size=0,
   46                  virtual_size=0, locations=None, container_format='bear',
   47                  disk_format='rawr', status=None):
   48         self.image_id = image_id
   49         self.data = data
   50         self.checksum = checksum
   51         self.size = size
   52         self.virtual_size = virtual_size
   53         self.locations = locations
   54         self.container_format = container_format
   55         self.disk_format = disk_format
   56         self._status = status
   57 
   58     @property
   59     def status(self):
   60         return self._status
   61 
   62     @status.setter
   63     def status(self, value):
   64         if isinstance(self._status, BaseException):
   65             raise self._status
   66         else:
   67             self._status = value
   68 
   69     def get_data(self, offset=0, chunk_size=None):
   70         if chunk_size:
   71             return self.data[offset:offset + chunk_size]
   72         return self.data[offset:]
   73 
   74     def set_data(self, data, size=None, backend=None):
   75         self.data = ''.join(data)
   76         self.size = size
   77         self.status = 'modified-by-fake'
   78 
   79 
   80 class FakeImageRepo(object):
   81 
   82     def __init__(self, result=None):
   83         self.result = result
   84 
   85     def get(self, image_id):
   86         if isinstance(self.result, BaseException):
   87             raise self.result
   88         else:
   89             return self.result
   90 
   91     def save(self, image, from_state=None):
   92         self.saved_image = image
   93 
   94 
   95 class FakeGateway(object):
   96 
   97     def __init__(self, db=None, store=None, notifier=None,
   98                  policy=None, repo=None):
   99         self.db = db
  100         self.store = store
  101         self.notifier = notifier
  102         self.policy = policy
  103         self.repo = repo
  104 
  105     def get_repo(self, context):
  106         return self.repo
  107 
  108 
  109 class TestImagesController(base.StoreClearingUnitTest):
  110 
  111     def setUp(self):
  112         super(TestImagesController, self).setUp()
  113 
  114         self.config(debug=True)
  115         self.image_repo = FakeImageRepo()
  116         db = unit_test_utils.FakeDB()
  117         policy = unit_test_utils.FakePolicyEnforcer()
  118         notifier = unit_test_utils.FakeNotifier()
  119         store = unit_test_utils.FakeStoreAPI()
  120         self.controller = glance.api.v2.image_data.ImageDataController()
  121         self.controller.gateway = FakeGateway(db, store, notifier, policy,
  122                                               self.image_repo)
  123 
  124     def test_download(self):
  125         request = unit_test_utils.get_fake_request()
  126         image = FakeImage('abcd',
  127                           locations=[{'url': 'http://example.com/image',
  128                                       'metadata': {}, 'status': 'active'}])
  129         self.image_repo.result = image
  130         image = self.controller.download(request, unit_test_utils.UUID1)
  131         self.assertEqual('abcd', image.image_id)
  132 
  133     def test_download_deactivated(self):
  134         request = unit_test_utils.get_fake_request()
  135         image = FakeImage('abcd',
  136                           status='deactivated',
  137                           locations=[{'url': 'http://example.com/image',
  138                                       'metadata': {}, 'status': 'active'}])
  139         self.image_repo.result = image
  140         self.assertRaises(webob.exc.HTTPForbidden, self.controller.download,
  141                           request, str(uuid.uuid4()))
  142 
  143     def test_download_no_location(self):
  144         # NOTE(mclaren): NoContent will be raised by the ResponseSerializer
  145         # That's tested below.
  146         request = unit_test_utils.get_fake_request()
  147         self.image_repo.result = FakeImage('abcd')
  148         image = self.controller.download(request, unit_test_utils.UUID2)
  149         self.assertEqual('abcd', image.image_id)
  150 
  151     def test_download_non_existent_image(self):
  152         request = unit_test_utils.get_fake_request()
  153         self.image_repo.result = exception.NotFound()
  154         self.assertRaises(webob.exc.HTTPNotFound, self.controller.download,
  155                           request, str(uuid.uuid4()))
  156 
  157     def test_download_forbidden(self):
  158         request = unit_test_utils.get_fake_request()
  159         self.image_repo.result = exception.Forbidden()
  160         self.assertRaises(webob.exc.HTTPForbidden, self.controller.download,
  161                           request, str(uuid.uuid4()))
  162 
  163     def test_download_ok_when_get_image_location_forbidden(self):
  164         class ImageLocations(object):
  165 
  166             def __len__(self):
  167                 raise exception.Forbidden()
  168 
  169         request = unit_test_utils.get_fake_request()
  170         image = FakeImage('abcd')
  171         self.image_repo.result = image
  172         image.locations = ImageLocations()
  173         image = self.controller.download(request, unit_test_utils.UUID1)
  174         self.assertEqual('abcd', image.image_id)
  175 
  176     def test_upload(self):
  177         request = unit_test_utils.get_fake_request()
  178         image = FakeImage('abcd')
  179         self.image_repo.result = image
  180         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
  181         self.assertEqual('YYYY', image.data)
  182         self.assertEqual(4, image.size)
  183 
  184     def test_upload_status(self):
  185         request = unit_test_utils.get_fake_request()
  186         image = FakeImage('abcd')
  187         self.image_repo.result = image
  188         insurance = {'called': False}
  189 
  190         def read_data():
  191             insurance['called'] = True
  192             self.assertEqual('saving', self.image_repo.saved_image.status)
  193             yield 'YYYY'
  194 
  195         self.controller.upload(request, unit_test_utils.UUID2,
  196                                read_data(), None)
  197         self.assertTrue(insurance['called'])
  198         self.assertEqual('modified-by-fake',
  199                          self.image_repo.saved_image.status)
  200 
  201     def test_upload_no_size(self):
  202         request = unit_test_utils.get_fake_request()
  203         image = FakeImage('abcd')
  204         self.image_repo.result = image
  205         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', None)
  206         self.assertEqual('YYYY', image.data)
  207         self.assertIsNone(image.size)
  208 
  209     @mock.patch.object(glance.api.policy.Enforcer, 'enforce')
  210     def test_upload_image_forbidden(self, mock_enforce):
  211         request = unit_test_utils.get_fake_request()
  212         mock_enforce.side_effect = exception.Forbidden
  213         self.assertRaises(webob.exc.HTTPForbidden, self.controller.upload,
  214                           request, unit_test_utils.UUID2, 'YYYY', 4)
  215         mock_enforce.assert_called_once_with(request.context,
  216                                              "upload_image",
  217                                              {})
  218 
  219     def test_upload_invalid(self):
  220         request = unit_test_utils.get_fake_request()
  221         image = FakeImage('abcd')
  222         image.status = ValueError()
  223         self.image_repo.result = image
  224         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
  225                           request, unit_test_utils.UUID1, 'YYYY', 4)
  226 
  227     def test_upload_with_expired_token(self):
  228         def side_effect(image, from_state=None):
  229             if from_state == 'saving':
  230                 raise exception.NotAuthenticated()
  231 
  232         mocked_save = mock.Mock(side_effect=side_effect)
  233         mocked_delete = mock.Mock()
  234         request = unit_test_utils.get_fake_request()
  235         image = FakeImage('abcd')
  236         image.delete = mocked_delete
  237         self.image_repo.result = image
  238         self.image_repo.save = mocked_save
  239         self.assertRaises(webob.exc.HTTPUnauthorized, self.controller.upload,
  240                           request, unit_test_utils.UUID1, 'YYYY', 4)
  241         self.assertEqual(3, mocked_save.call_count)
  242         mocked_delete.assert_called_once_with()
  243 
  244     def test_upload_non_existent_image_during_save_initiates_deletion(self):
  245         def fake_save_not_found(self, from_state=None):
  246             raise exception.ImageNotFound()
  247 
  248         def fake_save_conflict(self, from_state=None):
  249             raise exception.Conflict()
  250 
  251         for fun in [fake_save_not_found, fake_save_conflict]:
  252             request = unit_test_utils.get_fake_request()
  253             image = FakeImage('abcd', locations=['http://example.com/image'])
  254             self.image_repo.result = image
  255             self.image_repo.save = fun
  256             image.delete = mock.Mock()
  257             self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
  258                               request, str(uuid.uuid4()), 'ABC', 3)
  259             self.assertTrue(image.delete.called)
  260 
  261     def test_upload_non_existent_image_raises_image_not_found_exception(self):
  262         def fake_save(self, from_state=None):
  263             raise exception.ImageNotFound()
  264 
  265         def fake_delete():
  266             raise exception.ImageNotFound()
  267 
  268         request = unit_test_utils.get_fake_request()
  269         image = FakeImage('abcd', locations=['http://example.com/image'])
  270         self.image_repo.result = image
  271         self.image_repo.save = fake_save
  272         image.delete = fake_delete
  273         self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
  274                           request, str(uuid.uuid4()), 'ABC', 3)
  275 
  276     def test_upload_non_existent_image_raises_store_not_found_exception(self):
  277         def fake_save(self, from_state=None):
  278             raise glance_store.NotFound()
  279 
  280         def fake_delete():
  281             raise exception.ImageNotFound()
  282 
  283         request = unit_test_utils.get_fake_request()
  284         image = FakeImage('abcd', locations=['http://example.com/image'])
  285         self.image_repo.result = image
  286         self.image_repo.save = fake_save
  287         image.delete = fake_delete
  288         self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
  289                           request, str(uuid.uuid4()), 'ABC', 3)
  290 
  291     def test_upload_non_existent_image_before_save(self):
  292         request = unit_test_utils.get_fake_request()
  293         self.image_repo.result = exception.NotFound()
  294         self.assertRaises(webob.exc.HTTPNotFound, self.controller.upload,
  295                           request, str(uuid.uuid4()), 'ABC', 3)
  296 
  297     def test_upload_data_exists(self):
  298         request = unit_test_utils.get_fake_request()
  299         image = FakeImage()
  300         exc = exception.InvalidImageStatusTransition(cur_status='active',
  301                                                      new_status='queued')
  302         image.set_data = Raise(exc)
  303         self.image_repo.result = image
  304         self.assertRaises(webob.exc.HTTPConflict, self.controller.upload,
  305                           request, unit_test_utils.UUID1, 'YYYY', 4)
  306 
  307     def test_upload_storage_full(self):
  308         request = unit_test_utils.get_fake_request()
  309         image = FakeImage()
  310         image.set_data = Raise(glance_store.StorageFull)
  311         self.image_repo.result = image
  312         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  313                           self.controller.upload,
  314                           request, unit_test_utils.UUID2, 'YYYYYYY', 7)
  315 
  316     def test_upload_signature_verification_fails(self):
  317         request = unit_test_utils.get_fake_request()
  318         image = FakeImage()
  319         image.set_data = Raise(cursive_exception.SignatureVerificationError)
  320         self.image_repo.result = image
  321         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
  322                           request, unit_test_utils.UUID1, 'YYYY', 4)
  323         self.assertEqual('killed', self.image_repo.saved_image.status)
  324 
  325     def test_image_size_limit_exceeded(self):
  326         request = unit_test_utils.get_fake_request()
  327         image = FakeImage()
  328         image.set_data = Raise(exception.ImageSizeLimitExceeded)
  329         self.image_repo.result = image
  330         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  331                           self.controller.upload,
  332                           request, unit_test_utils.UUID1, 'YYYYYYY', 7)
  333 
  334     def test_upload_storage_quota_full(self):
  335         request = unit_test_utils.get_fake_request()
  336         self.image_repo.result = exception.StorageQuotaFull("message")
  337         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  338                           self.controller.upload,
  339                           request, unit_test_utils.UUID1, 'YYYYYYY', 7)
  340 
  341     def test_upload_storage_forbidden(self):
  342         request = unit_test_utils.get_fake_request(user=unit_test_utils.USER2)
  343         image = FakeImage()
  344         image.set_data = Raise(exception.Forbidden)
  345         self.image_repo.result = image
  346         self.assertRaises(webob.exc.HTTPForbidden, self.controller.upload,
  347                           request, unit_test_utils.UUID2, 'YY', 2)
  348 
  349     def test_upload_storage_internal_error(self):
  350         request = unit_test_utils.get_fake_request()
  351         self.image_repo.result = exception.ServerError()
  352         self.assertRaises(exception.ServerError,
  353                           self.controller.upload,
  354                           request, unit_test_utils.UUID1, 'ABC', 3)
  355 
  356     def test_upload_storage_write_denied(self):
  357         request = unit_test_utils.get_fake_request(user=unit_test_utils.USER3)
  358         image = FakeImage()
  359         image.set_data = Raise(glance_store.StorageWriteDenied)
  360         self.image_repo.result = image
  361         self.assertRaises(webob.exc.HTTPServiceUnavailable,
  362                           self.controller.upload,
  363                           request, unit_test_utils.UUID2, 'YY', 2)
  364 
  365     def test_upload_storage_store_disabled(self):
  366         """Test that uploading an image file raises StoreDisabled exception"""
  367         request = unit_test_utils.get_fake_request(user=unit_test_utils.USER3)
  368         image = FakeImage()
  369         image.set_data = Raise(glance_store.StoreAddDisabled)
  370         self.image_repo.result = image
  371         self.assertRaises(webob.exc.HTTPGone,
  372                           self.controller.upload,
  373                           request, unit_test_utils.UUID2, 'YY', 2)
  374 
  375     @mock.patch("glance.common.trust_auth.TokenRefresher")
  376     def test_upload_with_trusts(self, mock_refresher):
  377         """Test that uploading with registry correctly uses trusts"""
  378         # initialize trust environment
  379         self.config(data_api='glance.db.registry.api')
  380         refresher = mock.MagicMock()
  381         mock_refresher.return_value = refresher
  382         refresher.refresh_token.return_value = "fake_token"
  383         # request an image upload
  384         request = unit_test_utils.get_fake_request()
  385         request.environ['keystone.token_auth'] = mock.MagicMock()
  386         request.environ['keystone.token_info'] = {
  387             'token': {
  388                 'roles': [{'name': 'FakeRole', 'id': 'FakeID'}]
  389             }
  390         }
  391         image = FakeImage('abcd')
  392         self.image_repo.result = image
  393         mock_fake_save = mock.Mock()
  394         mock_fake_save.side_effect = [None, exception.NotAuthenticated, None]
  395         temp_save = FakeImageRepo.save
  396         # mocking save to raise NotAuthenticated on the second call
  397         FakeImageRepo.save = mock_fake_save
  398         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
  399         # check image data
  400         self.assertEqual('YYYY', image.data)
  401         self.assertEqual(4, image.size)
  402         FakeImageRepo.save = temp_save
  403         # check that token has been correctly acquired and deleted
  404         mock_refresher.assert_called_once_with(
  405             request.environ['keystone.token_auth'],
  406             request.context.project_id, ['FakeRole'])
  407         refresher.refresh_token.assert_called_once_with()
  408         refresher.release_resources.assert_called_once_with()
  409         self.assertEqual("fake_token", request.context.auth_token)
  410 
  411     @mock.patch("glance.common.trust_auth.TokenRefresher")
  412     def test_upload_with_trusts_fails(self, mock_refresher):
  413         """Test upload with registry if trust was not successfully created"""
  414         # initialize trust environment
  415         self.config(data_api='glance.db.registry.api')
  416         mock_refresher().side_effect = Exception()
  417         # request an image upload
  418         request = unit_test_utils.get_fake_request()
  419         image = FakeImage('abcd')
  420         self.image_repo.result = image
  421         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
  422         # check image data
  423         self.assertEqual('YYYY', image.data)
  424         self.assertEqual(4, image.size)
  425         # check that the token has not been updated
  426         self.assertEqual(0, mock_refresher().refresh_token.call_count)
  427 
  428     def _test_upload_download_prepare_notification(self):
  429         request = unit_test_utils.get_fake_request()
  430         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
  431         output = self.controller.download(request, unit_test_utils.UUID2)
  432         output_log = self.notifier.get_logs()
  433         prepare_payload = output['meta'].copy()
  434         prepare_payload['checksum'] = None
  435         prepare_payload['size'] = None
  436         prepare_payload['virtual_size'] = None
  437         prepare_payload['location'] = None
  438         prepare_payload['status'] = 'queued'
  439         del prepare_payload['updated_at']
  440         prepare_log = {
  441             'notification_type': "INFO",
  442             'event_type': "image.prepare",
  443             'payload': prepare_payload,
  444         }
  445         self.assertEqual(3, len(output_log))
  446         prepare_updated_at = output_log[0]['payload']['updated_at']
  447         del output_log[0]['payload']['updated_at']
  448         self.assertLessEqual(prepare_updated_at, output['meta']['updated_at'])
  449         self.assertEqual(prepare_log, output_log[0])
  450 
  451     def _test_upload_download_upload_notification(self):
  452         request = unit_test_utils.get_fake_request()
  453         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
  454         output = self.controller.download(request, unit_test_utils.UUID2)
  455         output_log = self.notifier.get_logs()
  456         upload_payload = output['meta'].copy()
  457         upload_log = {
  458             'notification_type': "INFO",
  459             'event_type': "image.upload",
  460             'payload': upload_payload,
  461         }
  462         self.assertEqual(3, len(output_log))
  463         self.assertEqual(upload_log, output_log[1])
  464 
  465     def _test_upload_download_activate_notification(self):
  466         request = unit_test_utils.get_fake_request()
  467         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
  468         output = self.controller.download(request, unit_test_utils.UUID2)
  469         output_log = self.notifier.get_logs()
  470         activate_payload = output['meta'].copy()
  471         activate_log = {
  472             'notification_type': "INFO",
  473             'event_type': "image.activate",
  474             'payload': activate_payload,
  475         }
  476         self.assertEqual(3, len(output_log))
  477         self.assertEqual(activate_log, output_log[2])
  478 
  479     def test_restore_image_when_upload_failed(self):
  480         request = unit_test_utils.get_fake_request()
  481         image = FakeImage('fake')
  482         image.set_data = Raise(glance_store.StorageWriteDenied)
  483         self.image_repo.result = image
  484         self.assertRaises(webob.exc.HTTPServiceUnavailable,
  485                           self.controller.upload,
  486                           request, unit_test_utils.UUID2, 'ZZZ', 3)
  487         self.assertEqual('queued', self.image_repo.saved_image.status)
  488 
  489     @mock.patch.object(filesystem.Store, 'add')
  490     def test_restore_image_when_staging_failed(self, mock_store_add):
  491         mock_store_add.side_effect = glance_store.StorageWriteDenied()
  492         request = unit_test_utils.get_fake_request()
  493         image_id = str(uuid.uuid4())
  494         image = FakeImage('fake')
  495         self.image_repo.result = image
  496         self.assertRaises(webob.exc.HTTPServiceUnavailable,
  497                           self.controller.stage,
  498                           request, image_id, 'YYYYYYY', 7)
  499         self.assertEqual('queued', self.image_repo.saved_image.status)
  500 
  501     def test_stage(self):
  502         image_id = str(uuid.uuid4())
  503         request = unit_test_utils.get_fake_request()
  504         image = FakeImage(image_id=image_id)
  505         self.image_repo.result = image
  506         with mock.patch.object(filesystem.Store, 'add'):
  507             self.controller.stage(request, image_id, 'YYYY', 4)
  508         self.assertEqual('uploading', image.status)
  509         self.assertEqual(0, image.size)
  510 
  511     def test_image_already_on_staging(self):
  512         image_id = str(uuid.uuid4())
  513         request = unit_test_utils.get_fake_request()
  514         image = FakeImage(image_id=image_id)
  515         self.image_repo.result = image
  516         with mock.patch.object(filesystem.Store, 'add') as mock_store_add:
  517             self.controller.stage(request, image_id, 'YYYY', 4)
  518             self.assertEqual('uploading', image.status)
  519             mock_store_add.side_effect = glance_store.Duplicate()
  520             self.assertEqual(0, image.size)
  521             self.assertRaises(webob.exc.HTTPConflict, self.controller.stage,
  522                               request, image_id, 'YYYY', 4)
  523 
  524     @mock.patch.object(glance_store.driver.Store, 'configure')
  525     def test_image_stage_raises_bad_store_uri(self, mock_store_configure):
  526         mock_store_configure.side_effect = AttributeError()
  527         image_id = str(uuid.uuid4())
  528         request = unit_test_utils.get_fake_request()
  529         self.assertRaises(exception.BadStoreUri, self.controller.stage,
  530                           request, image_id, 'YYYY', 4)
  531 
  532     @mock.patch.object(filesystem.Store, 'add')
  533     def test_image_stage_raises_storage_full(self, mock_store_add):
  534         mock_store_add.side_effect = glance_store.StorageFull()
  535         image_id = str(uuid.uuid4())
  536         request = unit_test_utils.get_fake_request()
  537         image = FakeImage(image_id=image_id)
  538         self.image_repo.result = image
  539         with mock.patch.object(self.controller, "_unstage"):
  540             self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  541                               self.controller.stage,
  542                               request, image_id, 'YYYYYYY', 7)
  543 
  544     @mock.patch.object(filesystem.Store, 'add')
  545     def test_image_stage_raises_storage_quota_full(self, mock_store_add):
  546         mock_store_add.side_effect = exception.StorageQuotaFull("message")
  547         image_id = str(uuid.uuid4())
  548         request = unit_test_utils.get_fake_request()
  549         image = FakeImage(image_id=image_id)
  550         self.image_repo.result = image
  551         with mock.patch.object(self.controller, "_unstage"):
  552             self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  553                               self.controller.stage,
  554                               request, image_id, 'YYYYYYY', 7)
  555 
  556     @mock.patch.object(filesystem.Store, 'add')
  557     def test_image_stage_raises_storage_write_denied(self, mock_store_add):
  558         mock_store_add.side_effect = glance_store.StorageWriteDenied()
  559         image_id = str(uuid.uuid4())
  560         request = unit_test_utils.get_fake_request()
  561         image = FakeImage(image_id=image_id)
  562         self.image_repo.result = image
  563         with mock.patch.object(self.controller, "_unstage"):
  564             self.assertRaises(webob.exc.HTTPServiceUnavailable,
  565                               self.controller.stage,
  566                               request, image_id, 'YYYYYYY', 7)
  567 
  568     def test_image_stage_raises_internal_error(self):
  569         image_id = str(uuid.uuid4())
  570         request = unit_test_utils.get_fake_request()
  571         self.image_repo.result = exception.ServerError()
  572         self.assertRaises(exception.ServerError,
  573                           self.controller.stage,
  574                           request, image_id, 'YYYYYYY', 7)
  575 
  576     def test_image_stage_non_existent_image(self):
  577         request = unit_test_utils.get_fake_request()
  578         self.image_repo.result = exception.NotFound()
  579         self.assertRaises(webob.exc.HTTPNotFound, self.controller.stage,
  580                           request, str(uuid.uuid4()), 'ABC', 3)
  581 
  582     @mock.patch.object(filesystem.Store, 'add')
  583     def test_image_stage_raises_image_size_exceeded(self, mock_store_add):
  584         mock_store_add.side_effect = exception.ImageSizeLimitExceeded()
  585         image_id = str(uuid.uuid4())
  586         request = unit_test_utils.get_fake_request()
  587         image = FakeImage(image_id=image_id)
  588         self.image_repo.result = image
  589         with mock.patch.object(self.controller, "_unstage"):
  590             self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  591                               self.controller.stage,
  592                               request, image_id, 'YYYYYYY', 7)
  593 
  594     @mock.patch.object(filesystem.Store, 'add')
  595     def test_image_stage_invalid_image_transition(self, mock_store_add):
  596         image_id = str(uuid.uuid4())
  597         request = unit_test_utils.get_fake_request()
  598         image = FakeImage(image_id=image_id)
  599         self.image_repo.result = image
  600         self.controller.stage(request, image_id, 'YYYY', 4)
  601         self.assertEqual('uploading', image.status)
  602         self.assertEqual(0, image.size)
  603         # try staging again
  604         mock_store_add.side_effect = exception.InvalidImageStatusTransition(
  605             cur_status='uploading', new_status='uploading')
  606         self.assertRaises(webob.exc.HTTPConflict, self.controller.stage,
  607                           request, image_id, 'YYYY', 4)
  608 
  609 
  610 class TestImageDataDeserializer(test_utils.BaseTestCase):
  611 
  612     def setUp(self):
  613         super(TestImageDataDeserializer, self).setUp()
  614         self.deserializer = glance.api.v2.image_data.RequestDeserializer()
  615 
  616     def test_upload(self):
  617         request = unit_test_utils.get_fake_request()
  618         request.headers['Content-Type'] = 'application/octet-stream'
  619         request.body = b'YYY'
  620         request.headers['Content-Length'] = 3
  621         output = self.deserializer.upload(request)
  622         data = output.pop('data')
  623         self.assertEqual(b'YYY', data.read())
  624         expected = {'size': 3}
  625         self.assertEqual(expected, output)
  626 
  627     def test_upload_chunked(self):
  628         request = unit_test_utils.get_fake_request()
  629         request.headers['Content-Type'] = 'application/octet-stream'
  630         # If we use body_file, webob assumes we want to do a chunked upload,
  631         # ignoring the Content-Length header
  632         request.body_file = six.StringIO('YYY')
  633         output = self.deserializer.upload(request)
  634         data = output.pop('data')
  635         self.assertEqual('YYY', data.read())
  636         expected = {'size': None}
  637         self.assertEqual(expected, output)
  638 
  639     def test_upload_chunked_with_content_length(self):
  640         request = unit_test_utils.get_fake_request()
  641         request.headers['Content-Type'] = 'application/octet-stream'
  642         request.body_file = six.BytesIO(b'YYY')
  643         # The deserializer shouldn't care if the Content-Length is
  644         # set when the user is attempting to send chunked data.
  645         request.headers['Content-Length'] = 3
  646         output = self.deserializer.upload(request)
  647         data = output.pop('data')
  648         self.assertEqual(b'YYY', data.read())
  649         expected = {'size': 3}
  650         self.assertEqual(expected, output)
  651 
  652     def test_upload_with_incorrect_content_length(self):
  653         request = unit_test_utils.get_fake_request()
  654         request.headers['Content-Type'] = 'application/octet-stream'
  655         # The deserializer shouldn't care if the Content-Length and
  656         # actual request body length differ. That job is left up
  657         # to the controller
  658         request.body = b'YYY'
  659         request.headers['Content-Length'] = 4
  660         output = self.deserializer.upload(request)
  661         data = output.pop('data')
  662         self.assertEqual(b'YYY', data.read())
  663         expected = {'size': 4}
  664         self.assertEqual(expected, output)
  665 
  666     def test_upload_wrong_content_type(self):
  667         request = unit_test_utils.get_fake_request()
  668         request.headers['Content-Type'] = 'application/json'
  669         request.body = b'YYYYY'
  670         self.assertRaises(webob.exc.HTTPUnsupportedMediaType,
  671                           self.deserializer.upload, request)
  672 
  673         request = unit_test_utils.get_fake_request()
  674         request.headers['Content-Type'] = 'application/octet-st'
  675         request.body = b'YYYYY'
  676         self.assertRaises(webob.exc.HTTPUnsupportedMediaType,
  677                           self.deserializer.upload, request)
  678 
  679     def test_stage(self):
  680         req = unit_test_utils.get_fake_request()
  681         req.headers['Content-Type'] = 'application/octet-stream'
  682         req.headers['Content-Length'] = 4
  683         req.body_file = six.BytesIO(b'YYYY')
  684         output = self.deserializer.stage(req)
  685         data = output.pop('data')
  686         self.assertEqual(b'YYYY', data.read())
  687 
  688     def test_stage_without_glance_direct(self):
  689         self.config(enabled_import_methods=['web-download'])
  690         req = unit_test_utils.get_fake_request()
  691         self.assertRaises(webob.exc.HTTPNotFound,
  692                           self.deserializer.stage,
  693                           req)
  694 
  695     def test_stage_raises_invalid_content_type(self):
  696         # TODO(abhishekk): change this when import methods are
  697         # listed in the config file
  698         req = unit_test_utils.get_fake_request()
  699         req.headers['Content-Type'] = 'application/json'
  700         self.assertRaises(webob.exc.HTTPUnsupportedMediaType,
  701                           self.deserializer.stage,
  702                           req)
  703 
  704 
  705 class TestImageDataSerializer(test_utils.BaseTestCase):
  706 
  707     def setUp(self):
  708         super(TestImageDataSerializer, self).setUp()
  709         self.serializer = glance.api.v2.image_data.ResponseSerializer()
  710 
  711     def test_download(self):
  712         request = wsgi.Request.blank('/')
  713         request.environ = {}
  714         response = webob.Response()
  715         response.request = request
  716         image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
  717         self.serializer.download(response, image)
  718         self.assertEqual(b'ZZZ', response.body)
  719         self.assertEqual('3', response.headers['Content-Length'])
  720         self.assertNotIn('Content-MD5', response.headers)
  721         self.assertEqual('application/octet-stream',
  722                          response.headers['Content-Type'])
  723 
  724     def test_range_requests_for_image_downloads(self):
  725         """
  726         Test partial download 'Range' requests for images (random image access)
  727         """
  728         def download_successful_Range(d_range):
  729             request = wsgi.Request.blank('/')
  730             request.environ = {}
  731             request.headers['Range'] = d_range
  732             response = webob.Response()
  733             response.request = request
  734             image = FakeImage(size=3, data=[b'X', b'Y', b'Z'])
  735             self.serializer.download(response, image)
  736             self.assertEqual(206, response.status_code)
  737             self.assertEqual('2', response.headers['Content-Length'])
  738             self.assertEqual('bytes 1-2/3', response.headers['Content-Range'])
  739             self.assertEqual(b'YZ', response.body)
  740 
  741         download_successful_Range('bytes=1-2')
  742         download_successful_Range('bytes=1-')
  743         download_successful_Range('bytes=1-3')
  744         download_successful_Range('bytes=-2')
  745         download_successful_Range('bytes=1-100')
  746 
  747         def full_image_download_w_range(d_range):
  748             request = wsgi.Request.blank('/')
  749             request.environ = {}
  750             request.headers['Range'] = d_range
  751             response = webob.Response()
  752             response.request = request
  753             image = FakeImage(size=3, data=[b'X', b'Y', b'Z'])
  754             self.serializer.download(response, image)
  755             self.assertEqual(206, response.status_code)
  756             self.assertEqual('3', response.headers['Content-Length'])
  757             self.assertEqual('bytes 0-2/3', response.headers['Content-Range'])
  758             self.assertEqual(b'XYZ', response.body)
  759 
  760         full_image_download_w_range('bytes=0-')
  761         full_image_download_w_range('bytes=0-2')
  762         full_image_download_w_range('bytes=0-3')
  763         full_image_download_w_range('bytes=-3')
  764         full_image_download_w_range('bytes=-4')
  765         full_image_download_w_range('bytes=0-100')
  766         full_image_download_w_range('bytes=-100')
  767 
  768         def download_failures_Range(d_range):
  769             request = wsgi.Request.blank('/')
  770             request.environ = {}
  771             request.headers['Range'] = d_range
  772             response = webob.Response()
  773             response.request = request
  774             image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
  775             self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
  776                               self.serializer.download,
  777                               response, image)
  778             return
  779 
  780         download_failures_Range('bytes=4-1')
  781         download_failures_Range('bytes=4-')
  782         download_failures_Range('bytes=3-')
  783         download_failures_Range('bytes=1')
  784         download_failures_Range('bytes=100')
  785         download_failures_Range('bytes=100-')
  786         download_failures_Range('bytes=')
  787 
  788     def test_multi_range_requests_raises_bad_request_error(self):
  789         request = wsgi.Request.blank('/')
  790         request.environ = {}
  791         request.headers['Range'] = 'bytes=0-0,-1'
  792         response = webob.Response()
  793         response.request = request
  794         image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
  795         self.assertRaises(webob.exc.HTTPBadRequest,
  796                           self.serializer.download,
  797                           response, image)
  798 
  799     def test_download_failure_with_valid_range(self):
  800         with mock.patch.object(glance.api.policy.ImageProxy,
  801                                'get_data') as mock_get_data:
  802             mock_get_data.side_effect = glance_store.NotFound(image="image")
  803         request = wsgi.Request.blank('/')
  804         request.environ = {}
  805         request.headers['Range'] = 'bytes=1-2'
  806         response = webob.Response()
  807         response.request = request
  808         image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
  809         image.get_data = mock_get_data
  810         self.assertRaises(webob.exc.HTTPNoContent,
  811                           self.serializer.download,
  812                           response, image)
  813 
  814     def test_content_range_requests_for_image_downloads(self):
  815         """
  816         Even though Content-Range is incorrect on requests, we support it
  817         for backward compatibility with clients written for pre-Pike
  818         Glance.
  819         The following test is for 'Content-Range' requests, which we have
  820         to ensure that we prevent regression.
  821         """
  822         def download_successful_ContentRange(d_range):
  823             request = wsgi.Request.blank('/')
  824             request.environ = {}
  825             request.headers['Content-Range'] = d_range
  826             response = webob.Response()
  827             response.request = request
  828             image = FakeImage(size=3, data=[b'X', b'Y', b'Z'])
  829             self.serializer.download(response, image)
  830             self.assertEqual(206, response.status_code)
  831             self.assertEqual('2', response.headers['Content-Length'])
  832             self.assertEqual('bytes 1-2/3', response.headers['Content-Range'])
  833             self.assertEqual(b'YZ', response.body)
  834 
  835         download_successful_ContentRange('bytes 1-2/3')
  836         download_successful_ContentRange('bytes 1-2/*')
  837 
  838         def download_failures_ContentRange(d_range):
  839             request = wsgi.Request.blank('/')
  840             request.environ = {}
  841             request.headers['Content-Range'] = d_range
  842             response = webob.Response()
  843             response.request = request
  844             image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
  845             self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
  846                               self.serializer.download,
  847                               response, image)
  848             return
  849 
  850         download_failures_ContentRange('bytes -3/3')
  851         download_failures_ContentRange('bytes 1-/3')
  852         download_failures_ContentRange('bytes 1-3/3')
  853         download_failures_ContentRange('bytes 1-4/3')
  854         download_failures_ContentRange('bytes 1-4/*')
  855         download_failures_ContentRange('bytes 4-1/3')
  856         download_failures_ContentRange('bytes 4-1/*')
  857         download_failures_ContentRange('bytes 4-8/*')
  858         download_failures_ContentRange('bytes 4-8/10')
  859         download_failures_ContentRange('bytes 4-8/3')
  860 
  861     def test_download_failure_with_valid_content_range(self):
  862         with mock.patch.object(glance.api.policy.ImageProxy,
  863                                'get_data') as mock_get_data:
  864             mock_get_data.side_effect = glance_store.NotFound(image="image")
  865         request = wsgi.Request.blank('/')
  866         request.environ = {}
  867         request.headers['Content-Range'] = 'bytes %s-%s/3' % (1, 2)
  868         response = webob.Response()
  869         response.request = request
  870         image = FakeImage(size=3, data=[b'Z', b'Z', b'Z'])
  871         image.get_data = mock_get_data
  872         self.assertRaises(webob.exc.HTTPNoContent,
  873                           self.serializer.download,
  874                           response, image)
  875 
  876     def test_download_with_checksum(self):
  877         request = wsgi.Request.blank('/')
  878         request.environ = {}
  879         response = webob.Response()
  880         response.request = request
  881         checksum = '0745064918b49693cca64d6b6a13d28a'
  882         image = FakeImage(size=3, checksum=checksum, data=[b'Z', b'Z', b'Z'])
  883         self.serializer.download(response, image)
  884         self.assertEqual(b'ZZZ', response.body)
  885         self.assertEqual('3', response.headers['Content-Length'])
  886         self.assertEqual(checksum, response.headers['Content-MD5'])
  887         self.assertEqual('application/octet-stream',
  888                          response.headers['Content-Type'])
  889 
  890     def test_download_forbidden(self):
  891         """Make sure the serializer can return 403 forbidden error instead of
  892         500 internal server error.
  893         """
  894         def get_data(*args, **kwargs):
  895             raise exception.Forbidden()
  896 
  897         self.mock_object(glance.api.policy.ImageProxy,
  898                          'get_data',
  899                          get_data)
  900         request = wsgi.Request.blank('/')
  901         request.environ = {}
  902         response = webob.Response()
  903         response.request = request
  904         image = FakeImage(size=3, data=iter('ZZZ'))
  905         image.get_data = get_data
  906         self.assertRaises(webob.exc.HTTPForbidden,
  907                           self.serializer.download,
  908                           response, image)
  909 
  910     def test_download_no_content(self):
  911         """Test image download returns HTTPNoContent
  912 
  913         Make sure that serializer returns 204 no content error in case of
  914         image data is not available at specified location.
  915         """
  916         with mock.patch.object(glance.api.policy.ImageProxy,
  917                                'get_data') as mock_get_data:
  918             mock_get_data.side_effect = glance_store.NotFound(image="image")
  919 
  920             request = wsgi.Request.blank('/')
  921             response = webob.Response()
  922             response.request = request
  923             image = FakeImage(size=3, data=iter('ZZZ'))
  924             image.get_data = mock_get_data
  925             self.assertRaises(webob.exc.HTTPNoContent,
  926                               self.serializer.download,
  927                               response, image)
  928 
  929     def test_download_service_unavailable(self):
  930         """Test image download returns HTTPServiceUnavailable."""
  931         with mock.patch.object(glance.api.policy.ImageProxy,
  932                                'get_data') as mock_get_data:
  933             mock_get_data.side_effect = glance_store.RemoteServiceUnavailable()
  934 
  935             request = wsgi.Request.blank('/')
  936             response = webob.Response()
  937             response.request = request
  938             image = FakeImage(size=3, data=iter('ZZZ'))
  939             image.get_data = mock_get_data
  940             self.assertRaises(webob.exc.HTTPServiceUnavailable,
  941                               self.serializer.download,
  942                               response, image)
  943 
  944     def test_download_store_get_not_support(self):
  945         """Test image download returns HTTPBadRequest.
  946 
  947         Make sure that serializer returns 400 bad request error in case of
  948         getting images from this store is not supported at specified location.
  949         """
  950         with mock.patch.object(glance.api.policy.ImageProxy,
  951                                'get_data') as mock_get_data:
  952             mock_get_data.side_effect = glance_store.StoreGetNotSupported()
  953 
  954             request = wsgi.Request.blank('/')
  955             response = webob.Response()
  956             response.request = request
  957             image = FakeImage(size=3, data=iter('ZZZ'))
  958             image.get_data = mock_get_data
  959             self.assertRaises(webob.exc.HTTPBadRequest,
  960                               self.serializer.download,
  961                               response, image)
  962 
  963     def test_download_store_random_get_not_support(self):
  964         """Test image download returns HTTPBadRequest.
  965 
  966         Make sure that serializer returns 400 bad request error in case of
  967         getting randomly images from this store is not supported at
  968         specified location.
  969         """
  970         with mock.patch.object(glance.api.policy.ImageProxy,
  971                                'get_data') as m_get_data:
  972             err = glance_store.StoreRandomGetNotSupported(offset=0,
  973                                                           chunk_size=0)
  974             m_get_data.side_effect = err
  975 
  976             request = wsgi.Request.blank('/')
  977             response = webob.Response()
  978             response.request = request
  979             image = FakeImage(size=3, data=iter('ZZZ'))
  980             image.get_data = m_get_data
  981             self.assertRaises(webob.exc.HTTPBadRequest,
  982                               self.serializer.download,
  983                               response, image)
  984 
  985     def test_upload(self):
  986         request = webob.Request.blank('/')
  987         request.environ = {}
  988         response = webob.Response()
  989         response.request = request
  990         self.serializer.upload(response, {})
  991         self.assertEqual(http.NO_CONTENT, response.status_int)
  992         self.assertEqual('0', response.headers['Content-Length'])
  993 
  994     def test_stage(self):
  995         request = webob.Request.blank('/')
  996         request.environ = {}
  997         response = webob.Response()
  998         response.request = request
  999         self.serializer.stage(response, {})
 1000         self.assertEqual(http.NO_CONTENT, response.status_int)
 1001         self.assertEqual('0', response.headers['Content-Length'])
 1002 
 1003 
 1004 class TestMultiBackendImagesController(base.MultiStoreClearingUnitTest):
 1005 
 1006     def setUp(self):
 1007         super(TestMultiBackendImagesController, self).setUp()
 1008 
 1009         self.config(debug=True)
 1010         self.image_repo = FakeImageRepo()
 1011         db = unit_test_utils.FakeDB()
 1012         policy = unit_test_utils.FakePolicyEnforcer()
 1013         notifier = unit_test_utils.FakeNotifier()
 1014         store = unit_test_utils.FakeStoreAPI()
 1015         self.controller = glance.api.v2.image_data.ImageDataController()
 1016         self.controller.gateway = FakeGateway(db, store, notifier, policy,
 1017                                               self.image_repo)
 1018 
 1019     def test_upload(self):
 1020         request = unit_test_utils.get_fake_request()
 1021         image = FakeImage('abcd')
 1022         self.image_repo.result = image
 1023         self.controller.upload(request, unit_test_utils.UUID2, 'YYYY', 4)
 1024         self.assertEqual('YYYY', image.data)
 1025         self.assertEqual(4, image.size)
 1026 
 1027     def test_upload_invalid_backend_in_request_header(self):
 1028         request = unit_test_utils.get_fake_request()
 1029         request.headers['x-image-meta-store'] = 'dummy'
 1030         image = FakeImage('abcd')
 1031         self.image_repo.result = image
 1032         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
 1033                           request, unit_test_utils.UUID2, 'YYYY', 4)