"Fossies" - the Fresh Open Source Software Archive

Member "glance-20.0.1/glance/tests/unit/v2/test_images_resource.py" (12 Aug 2020, 248580 Bytes) of package /linux/misc/openstack/glance-20.0.1.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 latest Fossies "Diffs" side-by-side code changes report for "test_images_resource.py": 20.0.0_vs_20.0.1.

    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 
   16 import datetime
   17 import eventlet
   18 import hashlib
   19 import os
   20 from unittest import mock
   21 import uuid
   22 
   23 from castellan.common import exception as castellan_exception
   24 import glance_store as store
   25 from oslo_config import cfg
   26 from oslo_serialization import jsonutils
   27 import six
   28 from six.moves import http_client as http
   29 # NOTE(jokke): simplified transition to py3, behaves like py2 xrange
   30 from six.moves import range
   31 import testtools
   32 import webob
   33 
   34 import glance.api.v2.image_actions
   35 import glance.api.v2.images
   36 from glance.common import exception
   37 from glance.common import store_utils
   38 from glance import domain
   39 import glance.schema
   40 from glance.tests.unit import base
   41 from glance.tests.unit.keymgr import fake as fake_keymgr
   42 import glance.tests.unit.utils as unit_test_utils
   43 import glance.tests.utils as test_utils
   44 
   45 CONF = cfg.CONF
   46 
   47 DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
   48 ISOTIME = '2012-05-16T15:27:36Z'
   49 
   50 
   51 BASE_URI = unit_test_utils.BASE_URI
   52 
   53 
   54 UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
   55 UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc'
   56 UUID3 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
   57 UUID4 = '6bbe7cc2-eae7-4c0f-b50d-a7160b0c6a86'
   58 UUID5 = '13c58ac4-210d-41ab-8cdb-1adfe4610019'
   59 UUID6 = '6d33fd0f-2438-4419-acd0-ce1d452c97a0'
   60 UUID7 = '75ddbc84-9427-4f3b-8d7d-b0fd0543d9a8'
   61 
   62 TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
   63 TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
   64 TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
   65 TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
   66 
   67 CHKSUM = '93264c3edf5972c9f1cb309543d38a5c'
   68 CHKSUM1 = '43254c3edf6972c9f1cb309543d38a8c'
   69 
   70 FAKEHASHALGO = 'fake-name-for-sha512'
   71 MULTIHASH1 = hashlib.sha512(b'glance').hexdigest()
   72 MULTIHASH2 = hashlib.sha512(b'image_service').hexdigest()
   73 
   74 
   75 def _db_fixture(id, **kwargs):
   76     obj = {
   77         'id': id,
   78         'name': None,
   79         'visibility': 'shared',
   80         'properties': {},
   81         'checksum': None,
   82         'os_hash_algo': FAKEHASHALGO,
   83         'os_hash_value': None,
   84         'owner': None,
   85         'status': 'queued',
   86         'tags': [],
   87         'size': None,
   88         'virtual_size': None,
   89         'locations': [],
   90         'protected': False,
   91         'disk_format': None,
   92         'container_format': None,
   93         'deleted': False,
   94         'min_ram': None,
   95         'min_disk': None,
   96     }
   97     obj.update(kwargs)
   98     return obj
   99 
  100 
  101 def _domain_fixture(id, **kwargs):
  102     properties = {
  103         'image_id': id,
  104         'name': None,
  105         'visibility': 'private',
  106         'checksum': None,
  107         'os_hash_algo': None,
  108         'os_hash_value': None,
  109         'owner': None,
  110         'status': 'queued',
  111         'size': None,
  112         'virtual_size': None,
  113         'locations': [],
  114         'protected': False,
  115         'disk_format': None,
  116         'container_format': None,
  117         'min_ram': None,
  118         'min_disk': None,
  119         'tags': [],
  120     }
  121     properties.update(kwargs)
  122     return glance.domain.Image(**properties)
  123 
  124 
  125 def _db_image_member_fixture(image_id, member_id, **kwargs):
  126     obj = {
  127         'image_id': image_id,
  128         'member': member_id,
  129     }
  130     obj.update(kwargs)
  131     return obj
  132 
  133 
  134 class FakeImage(object):
  135     def __init__(self, id=None, status='active', container_format='ami',
  136                  disk_format='ami', locations=None):
  137         self.id = id or UUID4
  138         self.status = status
  139         self.container_format = container_format
  140         self.disk_format = disk_format
  141         self.locations = locations
  142         self.owner = unit_test_utils.TENANT1
  143 
  144 
  145 class TestImagesController(base.IsolatedUnitTest):
  146 
  147     def setUp(self):
  148         super(TestImagesController, self).setUp()
  149         self.db = unit_test_utils.FakeDB(initialize=False)
  150         self.policy = unit_test_utils.FakePolicyEnforcer()
  151         self.notifier = unit_test_utils.FakeNotifier()
  152         self.store = unit_test_utils.FakeStoreAPI()
  153         for i in range(1, 4):
  154             self.store.data['%s/fake_location_%i' % (BASE_URI, i)] = ('Z', 1)
  155         self.store_utils = unit_test_utils.FakeStoreUtils(self.store)
  156         self._create_images()
  157         self._create_image_members()
  158         self.controller = glance.api.v2.images.ImagesController(self.db,
  159                                                                 self.policy,
  160                                                                 self.notifier,
  161                                                                 self.store)
  162         self.action_controller = (glance.api.v2.image_actions.
  163                                   ImageActionsController(self.db,
  164                                                          self.policy,
  165                                                          self.notifier,
  166                                                          self.store))
  167         self.controller.gateway.store_utils = self.store_utils
  168         self.controller._key_manager = fake_keymgr.fake_api()
  169         store.create_stores()
  170 
  171     def _create_images(self):
  172         self.images = [
  173             _db_fixture(UUID1, owner=TENANT1, checksum=CHKSUM,
  174                         os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
  175                         name='1', size=256, virtual_size=1024,
  176                         visibility='public',
  177                         locations=[{'url': '%s/%s' % (BASE_URI, UUID1),
  178                                     'metadata': {}, 'status': 'active'}],
  179                         disk_format='raw',
  180                         container_format='bare',
  181                         status='active',
  182                         created_at=DATETIME,
  183                         updated_at=DATETIME),
  184             _db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1,
  185                         os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2,
  186                         name='2', size=512, virtual_size=2048,
  187                         visibility='public',
  188                         disk_format='raw',
  189                         container_format='bare',
  190                         status='active',
  191                         tags=['redhat', '64bit', 'power'],
  192                         properties={'hypervisor_type': 'kvm', 'foo': 'bar',
  193                                     'bar': 'foo'},
  194                         created_at=DATETIME + datetime.timedelta(seconds=1),
  195                         updated_at=DATETIME + datetime.timedelta(seconds=1)),
  196             _db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1,
  197                         os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2,
  198                         name='3', size=512, virtual_size=2048,
  199                         visibility='public', tags=['windows', '64bit', 'x86'],
  200                         created_at=DATETIME + datetime.timedelta(seconds=2),
  201                         updated_at=DATETIME + datetime.timedelta(seconds=2)),
  202             _db_fixture(UUID4, owner=TENANT4, name='4',
  203                         size=1024, virtual_size=3072,
  204                         created_at=DATETIME + datetime.timedelta(seconds=3),
  205                         updated_at=DATETIME + datetime.timedelta(seconds=3)),
  206         ]
  207         [self.db.image_create(None, image) for image in self.images]
  208 
  209         self.db.image_tag_set_all(None, UUID1, ['ping', 'pong'])
  210 
  211     def _create_image_members(self):
  212         self.image_members = [
  213             _db_image_member_fixture(UUID4, TENANT2),
  214             _db_image_member_fixture(UUID4, TENANT3,
  215                                      status='accepted'),
  216         ]
  217         [self.db.image_member_create(None, image_member)
  218             for image_member in self.image_members]
  219 
  220     def test_index(self):
  221         self.config(limit_param_default=1, api_limit_max=3)
  222         request = unit_test_utils.get_fake_request()
  223         output = self.controller.index(request)
  224         self.assertEqual(1, len(output['images']))
  225         actual = set([image.image_id for image in output['images']])
  226         expected = set([UUID3])
  227         self.assertEqual(expected, actual)
  228 
  229     def test_index_member_status_accepted(self):
  230         self.config(limit_param_default=5, api_limit_max=5)
  231         request = unit_test_utils.get_fake_request(tenant=TENANT2)
  232         output = self.controller.index(request)
  233         self.assertEqual(3, len(output['images']))
  234         actual = set([image.image_id for image in output['images']])
  235         expected = set([UUID1, UUID2, UUID3])
  236         # can see only the public image
  237         self.assertEqual(expected, actual)
  238 
  239         request = unit_test_utils.get_fake_request(tenant=TENANT3)
  240         output = self.controller.index(request)
  241         self.assertEqual(4, len(output['images']))
  242         actual = set([image.image_id for image in output['images']])
  243         expected = set([UUID1, UUID2, UUID3, UUID4])
  244         self.assertEqual(expected, actual)
  245 
  246     def test_index_admin(self):
  247         request = unit_test_utils.get_fake_request(is_admin=True)
  248         output = self.controller.index(request)
  249         self.assertEqual(4, len(output['images']))
  250 
  251     def test_index_admin_deleted_images_hidden(self):
  252         request = unit_test_utils.get_fake_request(is_admin=True)
  253         self.controller.delete(request, UUID1)
  254         output = self.controller.index(request)
  255         self.assertEqual(3, len(output['images']))
  256         actual = set([image.image_id for image in output['images']])
  257         expected = set([UUID2, UUID3, UUID4])
  258         self.assertEqual(expected, actual)
  259 
  260     def test_index_return_parameters(self):
  261         self.config(limit_param_default=1, api_limit_max=3)
  262         request = unit_test_utils.get_fake_request()
  263         output = self.controller.index(request, marker=UUID3, limit=1,
  264                                        sort_key=['created_at'],
  265                                        sort_dir=['desc'])
  266         self.assertEqual(1, len(output['images']))
  267         actual = set([image.image_id for image in output['images']])
  268         expected = set([UUID2])
  269         self.assertEqual(actual, expected)
  270         self.assertEqual(UUID2, output['next_marker'])
  271 
  272     def test_index_next_marker(self):
  273         self.config(limit_param_default=1, api_limit_max=3)
  274         request = unit_test_utils.get_fake_request()
  275         output = self.controller.index(request, marker=UUID3, limit=2)
  276         self.assertEqual(2, len(output['images']))
  277         actual = set([image.image_id for image in output['images']])
  278         expected = set([UUID2, UUID1])
  279         self.assertEqual(expected, actual)
  280         self.assertEqual(UUID1, output['next_marker'])
  281 
  282     def test_index_no_next_marker(self):
  283         self.config(limit_param_default=1, api_limit_max=3)
  284         request = unit_test_utils.get_fake_request()
  285         output = self.controller.index(request, marker=UUID1, limit=2)
  286         self.assertEqual(0, len(output['images']))
  287         actual = set([image.image_id for image in output['images']])
  288         expected = set([])
  289         self.assertEqual(expected, actual)
  290         self.assertNotIn('next_marker', output)
  291 
  292     def test_index_with_id_filter(self):
  293         request = unit_test_utils.get_fake_request('/images?id=%s' % UUID1)
  294         output = self.controller.index(request, filters={'id': UUID1})
  295         self.assertEqual(1, len(output['images']))
  296         actual = set([image.image_id for image in output['images']])
  297         expected = set([UUID1])
  298         self.assertEqual(expected, actual)
  299 
  300     def test_index_with_invalid_hidden_filter(self):
  301         request = unit_test_utils.get_fake_request('/images?os_hidden=abcd')
  302         self.assertRaises(webob.exc.HTTPBadRequest,
  303                           self.controller.index, request,
  304                           filters={'os_hidden': 'abcd'})
  305 
  306     def test_index_with_checksum_filter_single_image(self):
  307         req = unit_test_utils.get_fake_request('/images?checksum=%s' % CHKSUM)
  308         output = self.controller.index(req, filters={'checksum': CHKSUM})
  309         self.assertEqual(1, len(output['images']))
  310         actual = list([image.image_id for image in output['images']])
  311         expected = [UUID1]
  312         self.assertEqual(expected, actual)
  313 
  314     def test_index_with_checksum_filter_multiple_images(self):
  315         req = unit_test_utils.get_fake_request('/images?checksum=%s' % CHKSUM1)
  316         output = self.controller.index(req, filters={'checksum': CHKSUM1})
  317         self.assertEqual(2, len(output['images']))
  318         actual = list([image.image_id for image in output['images']])
  319         expected = [UUID3, UUID2]
  320         self.assertEqual(expected, actual)
  321 
  322     def test_index_with_non_existent_checksum(self):
  323         req = unit_test_utils.get_fake_request('/images?checksum=236231827')
  324         output = self.controller.index(req, filters={'checksum': '236231827'})
  325         self.assertEqual(0, len(output['images']))
  326 
  327     def test_index_with_os_hash_value_filter_single_image(self):
  328         req = unit_test_utils.get_fake_request(
  329             '/images?os_hash_value=%s' % MULTIHASH1)
  330         output = self.controller.index(req,
  331                                        filters={'os_hash_value': MULTIHASH1})
  332         self.assertEqual(1, len(output['images']))
  333         actual = list([image.image_id for image in output['images']])
  334         expected = [UUID1]
  335         self.assertEqual(expected, actual)
  336 
  337     def test_index_with_os_hash_value_filter_multiple_images(self):
  338         req = unit_test_utils.get_fake_request(
  339             '/images?os_hash_value=%s' % MULTIHASH2)
  340         output = self.controller.index(req,
  341                                        filters={'os_hash_value': MULTIHASH2})
  342         self.assertEqual(2, len(output['images']))
  343         actual = list([image.image_id for image in output['images']])
  344         expected = [UUID3, UUID2]
  345         self.assertEqual(expected, actual)
  346 
  347     def test_index_with_non_existent_os_hash_value(self):
  348         fake_hash_value = hashlib.sha512(b'not_used_in_fixtures').hexdigest()
  349         req = unit_test_utils.get_fake_request(
  350             '/images?os_hash_value=%s' % fake_hash_value)
  351         output = self.controller.index(req,
  352                                        filters={'checksum': fake_hash_value})
  353         self.assertEqual(0, len(output['images']))
  354 
  355     def test_index_size_max_filter(self):
  356         request = unit_test_utils.get_fake_request('/images?size_max=512')
  357         output = self.controller.index(request, filters={'size_max': 512})
  358         self.assertEqual(3, len(output['images']))
  359         actual = set([image.image_id for image in output['images']])
  360         expected = set([UUID1, UUID2, UUID3])
  361         self.assertEqual(expected, actual)
  362 
  363     def test_index_size_min_filter(self):
  364         request = unit_test_utils.get_fake_request('/images?size_min=512')
  365         output = self.controller.index(request, filters={'size_min': 512})
  366         self.assertEqual(2, len(output['images']))
  367         actual = set([image.image_id for image in output['images']])
  368         expected = set([UUID2, UUID3])
  369         self.assertEqual(expected, actual)
  370 
  371     def test_index_size_range_filter(self):
  372         path = '/images?size_min=512&size_max=512'
  373         request = unit_test_utils.get_fake_request(path)
  374         output = self.controller.index(request,
  375                                        filters={'size_min': 512,
  376                                                 'size_max': 512})
  377         self.assertEqual(2, len(output['images']))
  378         actual = set([image.image_id for image in output['images']])
  379         expected = set([UUID2, UUID3])
  380         self.assertEqual(expected, actual)
  381 
  382     def test_index_virtual_size_max_filter(self):
  383         ref = '/images?virtual_size_max=2048'
  384         request = unit_test_utils.get_fake_request(ref)
  385         output = self.controller.index(request,
  386                                        filters={'virtual_size_max': 2048})
  387         self.assertEqual(3, len(output['images']))
  388         actual = set([image.image_id for image in output['images']])
  389         expected = set([UUID1, UUID2, UUID3])
  390         self.assertEqual(expected, actual)
  391 
  392     def test_index_virtual_size_min_filter(self):
  393         ref = '/images?virtual_size_min=2048'
  394         request = unit_test_utils.get_fake_request(ref)
  395         output = self.controller.index(request,
  396                                        filters={'virtual_size_min': 2048})
  397         self.assertEqual(2, len(output['images']))
  398         actual = set([image.image_id for image in output['images']])
  399         expected = set([UUID2, UUID3])
  400         self.assertEqual(expected, actual)
  401 
  402     def test_index_virtual_size_range_filter(self):
  403         path = '/images?virtual_size_min=512&virtual_size_max=2048'
  404         request = unit_test_utils.get_fake_request(path)
  405         output = self.controller.index(request,
  406                                        filters={'virtual_size_min': 2048,
  407                                                 'virtual_size_max': 2048})
  408         self.assertEqual(2, len(output['images']))
  409         actual = set([image.image_id for image in output['images']])
  410         expected = set([UUID2, UUID3])
  411         self.assertEqual(expected, actual)
  412 
  413     def test_index_with_invalid_max_range_filter_value(self):
  414         request = unit_test_utils.get_fake_request('/images?size_max=blah')
  415         self.assertRaises(webob.exc.HTTPBadRequest,
  416                           self.controller.index,
  417                           request,
  418                           filters={'size_max': 'blah'})
  419 
  420     def test_index_with_filters_return_many(self):
  421         path = '/images?status=queued'
  422         request = unit_test_utils.get_fake_request(path)
  423         output = self.controller.index(request, filters={'status': 'queued'})
  424         self.assertEqual(1, len(output['images']))
  425         actual = set([image.image_id for image in output['images']])
  426         expected = set([UUID3])
  427         self.assertEqual(expected, actual)
  428 
  429     def test_index_with_nonexistent_name_filter(self):
  430         request = unit_test_utils.get_fake_request('/images?name=%s' % 'blah')
  431         images = self.controller.index(request,
  432                                        filters={'name': 'blah'})['images']
  433         self.assertEqual(0, len(images))
  434 
  435     def test_index_with_non_default_is_public_filter(self):
  436         private_uuid = str(uuid.uuid4())
  437         new_image = _db_fixture(private_uuid,
  438                                 visibility='private',
  439                                 owner=TENANT3)
  440         self.db.image_create(None, new_image)
  441 
  442         path = '/images?visibility=private'
  443         request = unit_test_utils.get_fake_request(path, is_admin=True)
  444         output = self.controller.index(request,
  445                                        filters={'visibility': 'private'})
  446         self.assertEqual(1, len(output['images']))
  447         actual = set([image.image_id for image in output['images']])
  448         expected = set([private_uuid])
  449         self.assertEqual(expected, actual)
  450 
  451         path = '/images?visibility=shared'
  452         request = unit_test_utils.get_fake_request(path, is_admin=True)
  453         output = self.controller.index(request,
  454                                        filters={'visibility': 'shared'})
  455         self.assertEqual(1, len(output['images']))
  456         actual = set([image.image_id for image in output['images']])
  457         expected = set([UUID4])
  458         self.assertEqual(expected, actual)
  459 
  460     def test_index_with_many_filters(self):
  461         url = '/images?status=queued&name=3'
  462         request = unit_test_utils.get_fake_request(url)
  463         output = self.controller.index(request,
  464                                        filters={
  465                                            'status': 'queued',
  466                                            'name': '3',
  467                                        })
  468         self.assertEqual(1, len(output['images']))
  469         actual = set([image.image_id for image in output['images']])
  470         expected = set([UUID3])
  471         self.assertEqual(expected, actual)
  472 
  473     def test_index_with_marker(self):
  474         self.config(limit_param_default=1, api_limit_max=3)
  475         path = '/images'
  476         request = unit_test_utils.get_fake_request(path)
  477         output = self.controller.index(request, marker=UUID3)
  478         actual = set([image.image_id for image in output['images']])
  479         self.assertEqual(1, len(actual))
  480         self.assertIn(UUID2, actual)
  481 
  482     def test_index_with_limit(self):
  483         path = '/images'
  484         limit = 2
  485         request = unit_test_utils.get_fake_request(path)
  486         output = self.controller.index(request, limit=limit)
  487         actual = set([image.image_id for image in output['images']])
  488         self.assertEqual(limit, len(actual))
  489         self.assertIn(UUID3, actual)
  490         self.assertIn(UUID2, actual)
  491 
  492     def test_index_greater_than_limit_max(self):
  493         self.config(limit_param_default=1, api_limit_max=3)
  494         path = '/images'
  495         request = unit_test_utils.get_fake_request(path)
  496         output = self.controller.index(request, limit=4)
  497         actual = set([image.image_id for image in output['images']])
  498         self.assertEqual(3, len(actual))
  499         self.assertNotIn(output['next_marker'], output)
  500 
  501     def test_index_default_limit(self):
  502         self.config(limit_param_default=1, api_limit_max=3)
  503         path = '/images'
  504         request = unit_test_utils.get_fake_request(path)
  505         output = self.controller.index(request)
  506         actual = set([image.image_id for image in output['images']])
  507         self.assertEqual(1, len(actual))
  508 
  509     def test_index_with_sort_dir(self):
  510         path = '/images'
  511         request = unit_test_utils.get_fake_request(path)
  512         output = self.controller.index(request, sort_dir=['asc'], limit=3)
  513         actual = [image.image_id for image in output['images']]
  514         self.assertEqual(3, len(actual))
  515         self.assertEqual(UUID1, actual[0])
  516         self.assertEqual(UUID2, actual[1])
  517         self.assertEqual(UUID3, actual[2])
  518 
  519     def test_index_with_sort_key(self):
  520         path = '/images'
  521         request = unit_test_utils.get_fake_request(path)
  522         output = self.controller.index(request, sort_key=['created_at'],
  523                                        limit=3)
  524         actual = [image.image_id for image in output['images']]
  525         self.assertEqual(3, len(actual))
  526         self.assertEqual(UUID3, actual[0])
  527         self.assertEqual(UUID2, actual[1])
  528         self.assertEqual(UUID1, actual[2])
  529 
  530     def test_index_with_multiple_sort_keys(self):
  531         path = '/images'
  532         request = unit_test_utils.get_fake_request(path)
  533         output = self.controller.index(request,
  534                                        sort_key=['created_at', 'name'],
  535                                        limit=3)
  536         actual = [image.image_id for image in output['images']]
  537         self.assertEqual(3, len(actual))
  538         self.assertEqual(UUID3, actual[0])
  539         self.assertEqual(UUID2, actual[1])
  540         self.assertEqual(UUID1, actual[2])
  541 
  542     def test_index_with_marker_not_found(self):
  543         fake_uuid = str(uuid.uuid4())
  544         path = '/images'
  545         request = unit_test_utils.get_fake_request(path)
  546         self.assertRaises(webob.exc.HTTPBadRequest,
  547                           self.controller.index, request, marker=fake_uuid)
  548 
  549     def test_index_invalid_sort_key(self):
  550         path = '/images'
  551         request = unit_test_utils.get_fake_request(path)
  552         self.assertRaises(webob.exc.HTTPBadRequest,
  553                           self.controller.index, request, sort_key=['foo'])
  554 
  555     def test_index_zero_images(self):
  556         self.db.reset()
  557         request = unit_test_utils.get_fake_request()
  558         output = self.controller.index(request)
  559         self.assertEqual([], output['images'])
  560 
  561     def test_index_with_tags(self):
  562         path = '/images?tag=64bit'
  563         request = unit_test_utils.get_fake_request(path)
  564         output = self.controller.index(request, filters={'tags': ['64bit']})
  565         actual = [image.tags for image in output['images']]
  566         self.assertEqual(2, len(actual))
  567         self.assertIn('64bit', actual[0])
  568         self.assertIn('64bit', actual[1])
  569 
  570     def test_index_with_multi_tags(self):
  571         path = '/images?tag=power&tag=64bit'
  572         request = unit_test_utils.get_fake_request(path)
  573         output = self.controller.index(request,
  574                                        filters={'tags': ['power', '64bit']})
  575         actual = [image.tags for image in output['images']]
  576         self.assertEqual(1, len(actual))
  577         self.assertIn('64bit', actual[0])
  578         self.assertIn('power', actual[0])
  579 
  580     def test_index_with_multi_tags_and_nonexistent(self):
  581         path = '/images?tag=power&tag=fake'
  582         request = unit_test_utils.get_fake_request(path)
  583         output = self.controller.index(request,
  584                                        filters={'tags': ['power', 'fake']})
  585         actual = [image.tags for image in output['images']]
  586         self.assertEqual(0, len(actual))
  587 
  588     def test_index_with_tags_and_properties(self):
  589         path = '/images?tag=64bit&hypervisor_type=kvm'
  590         request = unit_test_utils.get_fake_request(path)
  591         output = self.controller.index(request,
  592                                        filters={'tags': ['64bit'],
  593                                                 'hypervisor_type': 'kvm'})
  594         tags = [image.tags for image in output['images']]
  595         properties = [image.extra_properties for image in output['images']]
  596         self.assertEqual(len(tags), len(properties))
  597         self.assertIn('64bit', tags[0])
  598         self.assertEqual('kvm', properties[0]['hypervisor_type'])
  599 
  600     def test_index_with_multiple_properties(self):
  601         path = '/images?foo=bar&hypervisor_type=kvm'
  602         request = unit_test_utils.get_fake_request(path)
  603         output = self.controller.index(request,
  604                                        filters={'foo': 'bar',
  605                                                 'hypervisor_type': 'kvm'})
  606         properties = [image.extra_properties for image in output['images']]
  607         self.assertEqual('kvm', properties[0]['hypervisor_type'])
  608         self.assertEqual('bar', properties[0]['foo'])
  609 
  610     def test_index_with_core_and_extra_property(self):
  611         path = '/images?disk_format=raw&foo=bar'
  612         request = unit_test_utils.get_fake_request(path)
  613         output = self.controller.index(request,
  614                                        filters={'foo': 'bar',
  615                                                 'disk_format': 'raw'})
  616         properties = [image.extra_properties for image in output['images']]
  617         self.assertEqual(1, len(output['images']))
  618         self.assertEqual('raw', output['images'][0].disk_format)
  619         self.assertEqual('bar', properties[0]['foo'])
  620 
  621     def test_index_with_nonexistent_properties(self):
  622         path = '/images?abc=xyz&pudding=banana'
  623         request = unit_test_utils.get_fake_request(path)
  624         output = self.controller.index(request,
  625                                        filters={'abc': 'xyz',
  626                                                 'pudding': 'banana'})
  627         self.assertEqual(0, len(output['images']))
  628 
  629     def test_index_with_non_existent_tags(self):
  630         path = '/images?tag=fake'
  631         request = unit_test_utils.get_fake_request(path)
  632         output = self.controller.index(request,
  633                                        filters={'tags': ['fake']})
  634         actual = [image.tags for image in output['images']]
  635         self.assertEqual(0, len(actual))
  636 
  637     def test_show(self):
  638         request = unit_test_utils.get_fake_request()
  639         output = self.controller.show(request, image_id=UUID2)
  640         self.assertEqual(UUID2, output.image_id)
  641         self.assertEqual('2', output.name)
  642 
  643     def test_show_deleted_properties(self):
  644         """Ensure that the api filters out deleted image properties."""
  645 
  646         # get the image properties into the odd state
  647         image = {
  648             'id': str(uuid.uuid4()),
  649             'status': 'active',
  650             'properties': {'poo': 'bear'},
  651         }
  652         self.db.image_create(None, image)
  653         self.db.image_update(None, image['id'],
  654                              {'properties': {'yin': 'yang'}},
  655                              purge_props=True)
  656 
  657         request = unit_test_utils.get_fake_request()
  658         output = self.controller.show(request, image['id'])
  659         self.assertEqual('yang', output.extra_properties['yin'])
  660 
  661     def test_show_non_existent(self):
  662         request = unit_test_utils.get_fake_request()
  663         image_id = str(uuid.uuid4())
  664         self.assertRaises(webob.exc.HTTPNotFound,
  665                           self.controller.show, request, image_id)
  666 
  667     def test_show_deleted_image_admin(self):
  668         request = unit_test_utils.get_fake_request(is_admin=True)
  669         self.controller.delete(request, UUID1)
  670         self.assertRaises(webob.exc.HTTPNotFound,
  671                           self.controller.show, request, UUID1)
  672 
  673     def test_show_not_allowed(self):
  674         request = unit_test_utils.get_fake_request()
  675         self.assertEqual(TENANT1, request.context.project_id)
  676         self.assertRaises(webob.exc.HTTPNotFound,
  677                           self.controller.show, request, UUID4)
  678 
  679     def test_image_import_raises_conflict_if_container_format_is_none(self):
  680         request = unit_test_utils.get_fake_request()
  681 
  682         with mock.patch.object(
  683                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
  684             mock_get.return_value = FakeImage(container_format=None)
  685             self.assertRaises(webob.exc.HTTPConflict,
  686                               self.controller.import_image, request, UUID4,
  687                               {'method': {'name': 'glance-direct'}})
  688 
  689     def test_image_import_raises_conflict_if_disk_format_is_none(self):
  690         request = unit_test_utils.get_fake_request()
  691 
  692         with mock.patch.object(
  693                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
  694             mock_get.return_value = FakeImage(disk_format=None)
  695             self.assertRaises(webob.exc.HTTPConflict,
  696                               self.controller.import_image, request, UUID4,
  697                               {'method': {'name': 'glance-direct'}})
  698 
  699     def test_image_import_raises_conflict(self):
  700         request = unit_test_utils.get_fake_request()
  701 
  702         with mock.patch.object(
  703                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
  704             mock_get.return_value = FakeImage(status='queued')
  705             self.assertRaises(webob.exc.HTTPConflict,
  706                               self.controller.import_image, request, UUID4,
  707                               {'method': {'name': 'glance-direct'}})
  708 
  709     def test_image_import_raises_conflict_for_web_download(self):
  710         request = unit_test_utils.get_fake_request()
  711 
  712         with mock.patch.object(
  713                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
  714             mock_get.return_value = FakeImage()
  715             self.assertRaises(webob.exc.HTTPConflict,
  716                               self.controller.import_image, request, UUID4,
  717                               {'method': {'name': 'web-download'}})
  718 
  719     def test_image_import_raises_conflict_for_invalid_status_change(self):
  720         request = unit_test_utils.get_fake_request()
  721 
  722         with mock.patch.object(
  723                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
  724             mock_get.return_value = FakeImage()
  725             self.assertRaises(webob.exc.HTTPConflict,
  726                               self.controller.import_image, request, UUID4,
  727                               {'method': {'name': 'glance-direct'}})
  728 
  729     def test_image_import_raises_bad_request(self):
  730         request = unit_test_utils.get_fake_request()
  731         with mock.patch.object(
  732                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
  733             mock_get.return_value = FakeImage(status='uploading')
  734             # NOTE(abhishekk): Due to
  735             # https://bugs.launchpad.net/glance/+bug/1712463 taskflow is not
  736             # executing. Once it is fixed instead of mocking spawn_n method
  737             # we should mock execute method of _ImportToStore task.
  738             with mock.patch.object(eventlet.GreenPool, 'spawn_n',
  739                                    side_effect=ValueError):
  740                 self.assertRaises(webob.exc.HTTPBadRequest,
  741                                   self.controller.import_image, request, UUID4,
  742                                   {'method': {'name': 'glance-direct'}})
  743 
  744     def test_image_import_invalid_uri_filtering(self):
  745         request = unit_test_utils.get_fake_request()
  746         with mock.patch.object(
  747                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
  748             mock_get.return_value = FakeImage(status='queued')
  749             self.assertRaises(webob.exc.HTTPBadRequest,
  750                               self.controller.import_image, request, UUID4,
  751                               {'method': {'name': 'web-download',
  752                                           'uri': 'fake_uri'}})
  753 
  754     def test_create(self):
  755         request = unit_test_utils.get_fake_request()
  756         image = {'name': 'image-1'}
  757         output = self.controller.create(request, image=image,
  758                                         extra_properties={},
  759                                         tags=[])
  760         self.assertEqual('image-1', output.name)
  761         self.assertEqual({}, output.extra_properties)
  762         self.assertEqual(set([]), output.tags)
  763         self.assertEqual('shared', output.visibility)
  764         output_logs = self.notifier.get_logs()
  765         self.assertEqual(1, len(output_logs))
  766         output_log = output_logs[0]
  767         self.assertEqual('INFO', output_log['notification_type'])
  768         self.assertEqual('image.create', output_log['event_type'])
  769         self.assertEqual('image-1', output_log['payload']['name'])
  770 
  771     def test_create_disabled_notification(self):
  772         self.config(disabled_notifications=["image.create"])
  773         request = unit_test_utils.get_fake_request()
  774         image = {'name': 'image-1'}
  775         output = self.controller.create(request, image=image,
  776                                         extra_properties={},
  777                                         tags=[])
  778         self.assertEqual('image-1', output.name)
  779         self.assertEqual({}, output.extra_properties)
  780         self.assertEqual(set([]), output.tags)
  781         self.assertEqual('shared', output.visibility)
  782         output_logs = self.notifier.get_logs()
  783         self.assertEqual(0, len(output_logs))
  784 
  785     def test_create_with_properties(self):
  786         request = unit_test_utils.get_fake_request()
  787         image_properties = {'foo': 'bar'}
  788         image = {'name': 'image-1'}
  789         output = self.controller.create(request, image=image,
  790                                         extra_properties=image_properties,
  791                                         tags=[])
  792         self.assertEqual('image-1', output.name)
  793         self.assertEqual(image_properties, output.extra_properties)
  794         self.assertEqual(set([]), output.tags)
  795         self.assertEqual('shared', output.visibility)
  796         output_logs = self.notifier.get_logs()
  797         self.assertEqual(1, len(output_logs))
  798         output_log = output_logs[0]
  799         self.assertEqual('INFO', output_log['notification_type'])
  800         self.assertEqual('image.create', output_log['event_type'])
  801         self.assertEqual('image-1', output_log['payload']['name'])
  802 
  803     def test_create_with_too_many_properties(self):
  804         self.config(image_property_quota=1)
  805         request = unit_test_utils.get_fake_request()
  806         image_properties = {'foo': 'bar', 'foo2': 'bar'}
  807         image = {'name': 'image-1'}
  808         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  809                           self.controller.create, request,
  810                           image=image,
  811                           extra_properties=image_properties,
  812                           tags=[])
  813 
  814     def test_create_with_bad_min_disk_size(self):
  815         request = unit_test_utils.get_fake_request()
  816         image = {'min_disk': -42, 'name': 'image-1'}
  817         self.assertRaises(webob.exc.HTTPBadRequest,
  818                           self.controller.create, request,
  819                           image=image,
  820                           extra_properties={},
  821                           tags=[])
  822 
  823     def test_create_with_bad_min_ram_size(self):
  824         request = unit_test_utils.get_fake_request()
  825         image = {'min_ram': -42, 'name': 'image-1'}
  826         self.assertRaises(webob.exc.HTTPBadRequest,
  827                           self.controller.create, request,
  828                           image=image,
  829                           extra_properties={},
  830                           tags=[])
  831 
  832     def test_create_public_image_as_admin(self):
  833         request = unit_test_utils.get_fake_request()
  834         image = {'name': 'image-1', 'visibility': 'public'}
  835         output = self.controller.create(request, image=image,
  836                                         extra_properties={}, tags=[])
  837         self.assertEqual('public', output.visibility)
  838         output_logs = self.notifier.get_logs()
  839         self.assertEqual(1, len(output_logs))
  840         output_log = output_logs[0]
  841         self.assertEqual('INFO', output_log['notification_type'])
  842         self.assertEqual('image.create', output_log['event_type'])
  843         self.assertEqual(output.image_id, output_log['payload']['id'])
  844 
  845     def test_create_dup_id(self):
  846         request = unit_test_utils.get_fake_request()
  847         image = {'image_id': UUID4}
  848 
  849         self.assertRaises(webob.exc.HTTPConflict,
  850                           self.controller.create,
  851                           request,
  852                           image=image,
  853                           extra_properties={},
  854                           tags=[])
  855 
  856     def test_create_duplicate_tags(self):
  857         request = unit_test_utils.get_fake_request()
  858         tags = ['ping', 'ping']
  859         output = self.controller.create(request, image={},
  860                                         extra_properties={}, tags=tags)
  861         self.assertEqual(set(['ping']), output.tags)
  862         output_logs = self.notifier.get_logs()
  863         self.assertEqual(1, len(output_logs))
  864         output_log = output_logs[0]
  865         self.assertEqual('INFO', output_log['notification_type'])
  866         self.assertEqual('image.create', output_log['event_type'])
  867         self.assertEqual(output.image_id, output_log['payload']['id'])
  868 
  869     def test_create_with_too_many_tags(self):
  870         self.config(image_tag_quota=1)
  871         request = unit_test_utils.get_fake_request()
  872         tags = ['ping', 'pong']
  873         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  874                           self.controller.create,
  875                           request, image={}, extra_properties={},
  876                           tags=tags)
  877 
  878     def test_create_with_owner_non_admin(self):
  879         request = unit_test_utils.get_fake_request()
  880         request.context.is_admin = False
  881         image = {'owner': '12345'}
  882         self.assertRaises(webob.exc.HTTPForbidden,
  883                           self.controller.create,
  884                           request, image=image, extra_properties={},
  885                           tags=[])
  886 
  887         request = unit_test_utils.get_fake_request()
  888         request.context.is_admin = False
  889         image = {'owner': TENANT1}
  890         output = self.controller.create(request, image=image,
  891                                         extra_properties={}, tags=[])
  892         self.assertEqual(TENANT1, output.owner)
  893 
  894     def test_create_with_owner_admin(self):
  895         request = unit_test_utils.get_fake_request()
  896         request.context.is_admin = True
  897         image = {'owner': '12345'}
  898         output = self.controller.create(request, image=image,
  899                                         extra_properties={}, tags=[])
  900         self.assertEqual('12345', output.owner)
  901 
  902     def test_create_with_duplicate_location(self):
  903         request = unit_test_utils.get_fake_request()
  904         location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
  905         image = {'name': 'image-1', 'locations': [location, location]}
  906         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
  907                           request, image=image, extra_properties={},
  908                           tags=[])
  909 
  910     def test_create_unexpected_property(self):
  911         request = unit_test_utils.get_fake_request()
  912         image_properties = {'unexpected': 'unexpected'}
  913         image = {'name': 'image-1'}
  914         with mock.patch.object(domain.ImageFactory, 'new_image',
  915                                side_effect=TypeError):
  916             self.assertRaises(webob.exc.HTTPBadRequest,
  917                               self.controller.create, request, image=image,
  918                               extra_properties=image_properties, tags=[])
  919 
  920     def test_create_reserved_property(self):
  921         request = unit_test_utils.get_fake_request()
  922         image_properties = {'reserved': 'reserved'}
  923         image = {'name': 'image-1'}
  924         with mock.patch.object(domain.ImageFactory, 'new_image',
  925                                side_effect=exception.ReservedProperty(
  926                                    property='reserved')):
  927             self.assertRaises(webob.exc.HTTPForbidden,
  928                               self.controller.create, request, image=image,
  929                               extra_properties=image_properties, tags=[])
  930 
  931     def test_create_readonly_property(self):
  932         request = unit_test_utils.get_fake_request()
  933         image_properties = {'readonly': 'readonly'}
  934         image = {'name': 'image-1'}
  935         with mock.patch.object(domain.ImageFactory, 'new_image',
  936                                side_effect=exception.ReadonlyProperty(
  937                                    property='readonly')):
  938             self.assertRaises(webob.exc.HTTPForbidden,
  939                               self.controller.create, request, image=image,
  940                               extra_properties=image_properties, tags=[])
  941 
  942     def test_update_no_changes(self):
  943         request = unit_test_utils.get_fake_request()
  944         output = self.controller.update(request, UUID1, changes=[])
  945         self.assertEqual(UUID1, output.image_id)
  946         self.assertEqual(output.created_at, output.updated_at)
  947         self.assertEqual(2, len(output.tags))
  948         self.assertIn('ping', output.tags)
  949         self.assertIn('pong', output.tags)
  950         output_logs = self.notifier.get_logs()
  951         # NOTE(markwash): don't send a notification if nothing is updated
  952         self.assertEqual(0, len(output_logs))
  953 
  954     def test_update_queued_image_with_hidden(self):
  955         request = unit_test_utils.get_fake_request()
  956         changes = [{'op': 'replace', 'path': ['os_hidden'], 'value': 'true'}]
  957         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
  958                           request, UUID3, changes=changes)
  959 
  960     def test_update_with_bad_min_disk(self):
  961         request = unit_test_utils.get_fake_request()
  962         changes = [{'op': 'replace', 'path': ['min_disk'], 'value': -42}]
  963         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  964                           request, UUID1, changes=changes)
  965 
  966     def test_update_with_bad_min_ram(self):
  967         request = unit_test_utils.get_fake_request()
  968         changes = [{'op': 'replace', 'path': ['min_ram'], 'value': -42}]
  969         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  970                           request, UUID1, changes=changes)
  971 
  972     def test_update_image_doesnt_exist(self):
  973         request = unit_test_utils.get_fake_request()
  974         self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
  975                           request, str(uuid.uuid4()), changes=[])
  976 
  977     def test_update_deleted_image_admin(self):
  978         request = unit_test_utils.get_fake_request(is_admin=True)
  979         self.controller.delete(request, UUID1)
  980         self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
  981                           request, UUID1, changes=[])
  982 
  983     def test_update_with_too_many_properties(self):
  984         self.config(show_multiple_locations=True)
  985         self.config(user_storage_quota='1')
  986         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
  987         request = unit_test_utils.get_fake_request()
  988         changes = [{'op': 'add', 'path': ['locations', '-'],
  989                     'value': new_location}]
  990         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
  991                           self.controller.update,
  992                           request, UUID1, changes=changes)
  993 
  994     def test_update_replace_base_attribute(self):
  995         self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
  996         request = unit_test_utils.get_fake_request()
  997         request.context.is_admin = True
  998         changes = [{'op': 'replace', 'path': ['name'], 'value': 'fedora'},
  999                    {'op': 'replace', 'path': ['owner'], 'value': TENANT3}]
 1000         output = self.controller.update(request, UUID1, changes)
 1001         self.assertEqual(UUID1, output.image_id)
 1002         self.assertEqual('fedora', output.name)
 1003         self.assertEqual(TENANT3, output.owner)
 1004         self.assertEqual({'foo': 'bar'}, output.extra_properties)
 1005         self.assertNotEqual(output.created_at, output.updated_at)
 1006 
 1007     def test_update_replace_onwer_non_admin(self):
 1008         request = unit_test_utils.get_fake_request()
 1009         request.context.is_admin = False
 1010         changes = [{'op': 'replace', 'path': ['owner'], 'value': TENANT3}]
 1011         self.assertRaises(webob.exc.HTTPForbidden,
 1012                           self.controller.update, request, UUID1, changes)
 1013 
 1014     def test_update_replace_tags(self):
 1015         request = unit_test_utils.get_fake_request()
 1016         changes = [
 1017             {'op': 'replace', 'path': ['tags'], 'value': ['king', 'kong']},
 1018         ]
 1019         output = self.controller.update(request, UUID1, changes)
 1020         self.assertEqual(UUID1, output.image_id)
 1021         self.assertEqual(2, len(output.tags))
 1022         self.assertIn('king', output.tags)
 1023         self.assertIn('kong', output.tags)
 1024         self.assertNotEqual(output.created_at, output.updated_at)
 1025 
 1026     def test_update_replace_property(self):
 1027         request = unit_test_utils.get_fake_request()
 1028         properties = {'foo': 'bar', 'snitch': 'golden'}
 1029         self.db.image_update(None, UUID1, {'properties': properties})
 1030 
 1031         output = self.controller.show(request, UUID1)
 1032         self.assertEqual('bar', output.extra_properties['foo'])
 1033         self.assertEqual('golden', output.extra_properties['snitch'])
 1034 
 1035         changes = [
 1036             {'op': 'replace', 'path': ['foo'], 'value': 'baz'},
 1037         ]
 1038         output = self.controller.update(request, UUID1, changes)
 1039         self.assertEqual(UUID1, output.image_id)
 1040         self.assertEqual('baz', output.extra_properties['foo'])
 1041         self.assertEqual('golden', output.extra_properties['snitch'])
 1042         self.assertNotEqual(output.created_at, output.updated_at)
 1043 
 1044     def test_update_add_too_many_properties(self):
 1045         self.config(image_property_quota=1)
 1046         request = unit_test_utils.get_fake_request()
 1047 
 1048         changes = [
 1049             {'op': 'add', 'path': ['foo'], 'value': 'baz'},
 1050             {'op': 'add', 'path': ['snitch'], 'value': 'golden'},
 1051         ]
 1052         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
 1053                           self.controller.update, request,
 1054                           UUID1, changes)
 1055 
 1056     def test_update_add_and_remove_too_many_properties(self):
 1057         request = unit_test_utils.get_fake_request()
 1058 
 1059         changes = [
 1060             {'op': 'add', 'path': ['foo'], 'value': 'baz'},
 1061             {'op': 'add', 'path': ['snitch'], 'value': 'golden'},
 1062         ]
 1063         self.controller.update(request, UUID1, changes)
 1064         self.config(image_property_quota=1)
 1065 
 1066         # We must remove two properties to avoid being
 1067         # over the limit of 1 property
 1068         changes = [
 1069             {'op': 'remove', 'path': ['foo']},
 1070             {'op': 'add', 'path': ['fizz'], 'value': 'buzz'},
 1071         ]
 1072         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
 1073                           self.controller.update, request,
 1074                           UUID1, changes)
 1075 
 1076     def test_update_add_unlimited_properties(self):
 1077         self.config(image_property_quota=-1)
 1078         request = unit_test_utils.get_fake_request()
 1079         output = self.controller.show(request, UUID1)
 1080 
 1081         changes = [{'op': 'add',
 1082                     'path': ['foo'],
 1083                     'value': 'bar'}]
 1084         output = self.controller.update(request, UUID1, changes)
 1085         self.assertEqual(UUID1, output.image_id)
 1086         self.assertNotEqual(output.created_at, output.updated_at)
 1087 
 1088     def test_update_format_properties(self):
 1089         statuses_for_immutability = ['active', 'saving', 'killed']
 1090         request = unit_test_utils.get_fake_request(is_admin=True)
 1091         for status in statuses_for_immutability:
 1092             image = {
 1093                 'id': str(uuid.uuid4()),
 1094                 'status': status,
 1095                 'disk_format': 'ari',
 1096                 'container_format': 'ari',
 1097             }
 1098             self.db.image_create(None, image)
 1099             changes = [
 1100                 {'op': 'replace', 'path': ['disk_format'], 'value': 'ami'},
 1101             ]
 1102             self.assertRaises(webob.exc.HTTPForbidden,
 1103                               self.controller.update,
 1104                               request, image['id'], changes)
 1105             changes = [
 1106                 {'op': 'replace',
 1107                  'path': ['container_format'],
 1108                  'value': 'ami'},
 1109             ]
 1110             self.assertRaises(webob.exc.HTTPForbidden,
 1111                               self.controller.update,
 1112                               request, image['id'], changes)
 1113         self.db.image_update(None, image['id'], {'status': 'queued'})
 1114 
 1115         changes = [
 1116             {'op': 'replace', 'path': ['disk_format'], 'value': 'raw'},
 1117             {'op': 'replace', 'path': ['container_format'], 'value': 'bare'},
 1118         ]
 1119         resp = self.controller.update(request, image['id'], changes)
 1120         self.assertEqual('raw', resp.disk_format)
 1121         self.assertEqual('bare', resp.container_format)
 1122 
 1123     def test_update_remove_property_while_over_limit(self):
 1124         """Ensure that image properties can be removed.
 1125 
 1126         Image properties should be able to be removed as long as the image has
 1127         fewer than the limited number of image properties after the
 1128         transaction.
 1129 
 1130         """
 1131         request = unit_test_utils.get_fake_request()
 1132 
 1133         changes = [
 1134             {'op': 'add', 'path': ['foo'], 'value': 'baz'},
 1135             {'op': 'add', 'path': ['snitch'], 'value': 'golden'},
 1136             {'op': 'add', 'path': ['fizz'], 'value': 'buzz'},
 1137         ]
 1138         self.controller.update(request, UUID1, changes)
 1139         self.config(image_property_quota=1)
 1140 
 1141         # We must remove two properties to avoid being
 1142         # over the limit of 1 property
 1143         changes = [
 1144             {'op': 'remove', 'path': ['foo']},
 1145             {'op': 'remove', 'path': ['snitch']},
 1146         ]
 1147         output = self.controller.update(request, UUID1, changes)
 1148         self.assertEqual(UUID1, output.image_id)
 1149         self.assertEqual(1, len(output.extra_properties))
 1150         self.assertEqual('buzz', output.extra_properties['fizz'])
 1151         self.assertNotEqual(output.created_at, output.updated_at)
 1152 
 1153     def test_update_add_and_remove_property_under_limit(self):
 1154         """Ensure that image properties can be removed.
 1155 
 1156         Image properties should be able to be added and removed simultaneously
 1157         as long as the image has fewer than the limited number of image
 1158         properties after the transaction.
 1159 
 1160         """
 1161         request = unit_test_utils.get_fake_request()
 1162 
 1163         changes = [
 1164             {'op': 'add', 'path': ['foo'], 'value': 'baz'},
 1165             {'op': 'add', 'path': ['snitch'], 'value': 'golden'},
 1166         ]
 1167         self.controller.update(request, UUID1, changes)
 1168         self.config(image_property_quota=1)
 1169 
 1170         # We must remove two properties to avoid being
 1171         # over the limit of 1 property
 1172         changes = [
 1173             {'op': 'remove', 'path': ['foo']},
 1174             {'op': 'remove', 'path': ['snitch']},
 1175             {'op': 'add', 'path': ['fizz'], 'value': 'buzz'},
 1176         ]
 1177         output = self.controller.update(request, UUID1, changes)
 1178         self.assertEqual(UUID1, output.image_id)
 1179         self.assertEqual(1, len(output.extra_properties))
 1180         self.assertEqual('buzz', output.extra_properties['fizz'])
 1181         self.assertNotEqual(output.created_at, output.updated_at)
 1182 
 1183     def test_update_replace_missing_property(self):
 1184         request = unit_test_utils.get_fake_request()
 1185 
 1186         changes = [
 1187             {'op': 'replace', 'path': 'foo', 'value': 'baz'},
 1188         ]
 1189         self.assertRaises(webob.exc.HTTPConflict,
 1190                           self.controller.update, request, UUID1, changes)
 1191 
 1192     def test_prop_protection_with_create_and_permitted_role(self):
 1193         enforcer = glance.api.policy.Enforcer()
 1194         self.controller = glance.api.v2.images.ImagesController(self.db,
 1195                                                                 enforcer,
 1196                                                                 self.notifier,
 1197                                                                 self.store)
 1198         self.set_property_protections()
 1199         request = unit_test_utils.get_fake_request(roles=['admin'])
 1200         image = {'name': 'image-1'}
 1201         created_image = self.controller.create(request, image=image,
 1202                                                extra_properties={},
 1203                                                tags=[])
 1204         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1205         changes = [
 1206             {'op': 'add', 'path': ['x_owner_foo'], 'value': 'bar'},
 1207         ]
 1208         output = self.controller.update(another_request,
 1209                                         created_image.image_id, changes)
 1210         self.assertEqual('bar', output.extra_properties['x_owner_foo'])
 1211 
 1212     def test_prop_protection_with_update_and_permitted_policy(self):
 1213         self.set_property_protections(use_policies=True)
 1214         enforcer = glance.api.policy.Enforcer()
 1215         self.controller = glance.api.v2.images.ImagesController(self.db,
 1216                                                                 enforcer,
 1217                                                                 self.notifier,
 1218                                                                 self.store)
 1219         request = unit_test_utils.get_fake_request(roles=['spl_role'])
 1220         image = {'name': 'image-1'}
 1221         extra_props = {'spl_creator_policy': 'bar'}
 1222         created_image = self.controller.create(request, image=image,
 1223                                                extra_properties=extra_props,
 1224                                                tags=[])
 1225         self.assertEqual('bar',
 1226                          created_image.extra_properties['spl_creator_policy'])
 1227 
 1228         another_request = unit_test_utils.get_fake_request(roles=['spl_role'])
 1229         changes = [
 1230             {'op': 'replace', 'path': ['spl_creator_policy'], 'value': 'par'},
 1231         ]
 1232         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 1233                           another_request, created_image.image_id, changes)
 1234         another_request = unit_test_utils.get_fake_request(roles=['admin'])
 1235         output = self.controller.update(another_request,
 1236                                         created_image.image_id, changes)
 1237         self.assertEqual('par',
 1238                          output.extra_properties['spl_creator_policy'])
 1239 
 1240     def test_prop_protection_with_create_with_patch_and_policy(self):
 1241         self.set_property_protections(use_policies=True)
 1242         enforcer = glance.api.policy.Enforcer()
 1243         self.controller = glance.api.v2.images.ImagesController(self.db,
 1244                                                                 enforcer,
 1245                                                                 self.notifier,
 1246                                                                 self.store)
 1247         request = unit_test_utils.get_fake_request(roles=['spl_role', 'admin'])
 1248         image = {'name': 'image-1'}
 1249         extra_props = {'spl_default_policy': 'bar'}
 1250         created_image = self.controller.create(request, image=image,
 1251                                                extra_properties=extra_props,
 1252                                                tags=[])
 1253         another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
 1254         changes = [
 1255             {'op': 'add', 'path': ['spl_creator_policy'], 'value': 'bar'},
 1256         ]
 1257         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 1258                           another_request, created_image.image_id, changes)
 1259 
 1260         another_request = unit_test_utils.get_fake_request(roles=['spl_role'])
 1261         output = self.controller.update(another_request,
 1262                                         created_image.image_id, changes)
 1263         self.assertEqual('bar',
 1264                          output.extra_properties['spl_creator_policy'])
 1265 
 1266     def test_prop_protection_with_create_and_unpermitted_role(self):
 1267         enforcer = glance.api.policy.Enforcer()
 1268         self.controller = glance.api.v2.images.ImagesController(self.db,
 1269                                                                 enforcer,
 1270                                                                 self.notifier,
 1271                                                                 self.store)
 1272         self.set_property_protections()
 1273         request = unit_test_utils.get_fake_request(roles=['admin'])
 1274         image = {'name': 'image-1'}
 1275         created_image = self.controller.create(request, image=image,
 1276                                                extra_properties={},
 1277                                                tags=[])
 1278         roles = ['fake_member']
 1279         another_request = unit_test_utils.get_fake_request(roles=roles)
 1280         changes = [
 1281             {'op': 'add', 'path': ['x_owner_foo'], 'value': 'bar'},
 1282         ]
 1283         self.assertRaises(webob.exc.HTTPForbidden,
 1284                           self.controller.update, another_request,
 1285                           created_image.image_id, changes)
 1286 
 1287     def test_prop_protection_with_show_and_permitted_role(self):
 1288         enforcer = glance.api.policy.Enforcer()
 1289         self.controller = glance.api.v2.images.ImagesController(self.db,
 1290                                                                 enforcer,
 1291                                                                 self.notifier,
 1292                                                                 self.store)
 1293         self.set_property_protections()
 1294         request = unit_test_utils.get_fake_request(roles=['admin'])
 1295         image = {'name': 'image-1'}
 1296         extra_props = {'x_owner_foo': 'bar'}
 1297         created_image = self.controller.create(request, image=image,
 1298                                                extra_properties=extra_props,
 1299                                                tags=[])
 1300         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1301         output = self.controller.show(another_request, created_image.image_id)
 1302         self.assertEqual('bar', output.extra_properties['x_owner_foo'])
 1303 
 1304     def test_prop_protection_with_show_and_unpermitted_role(self):
 1305         enforcer = glance.api.policy.Enforcer()
 1306         self.controller = glance.api.v2.images.ImagesController(self.db,
 1307                                                                 enforcer,
 1308                                                                 self.notifier,
 1309                                                                 self.store)
 1310         self.set_property_protections()
 1311         request = unit_test_utils.get_fake_request(roles=['member'])
 1312         image = {'name': 'image-1'}
 1313         extra_props = {'x_owner_foo': 'bar'}
 1314         created_image = self.controller.create(request, image=image,
 1315                                                extra_properties=extra_props,
 1316                                                tags=[])
 1317         another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
 1318         output = self.controller.show(another_request, created_image.image_id)
 1319         self.assertRaises(KeyError, output.extra_properties.__getitem__,
 1320                           'x_owner_foo')
 1321 
 1322     def test_prop_protection_with_update_and_permitted_role(self):
 1323         enforcer = glance.api.policy.Enforcer()
 1324         self.controller = glance.api.v2.images.ImagesController(self.db,
 1325                                                                 enforcer,
 1326                                                                 self.notifier,
 1327                                                                 self.store)
 1328         self.set_property_protections()
 1329         request = unit_test_utils.get_fake_request(roles=['admin'])
 1330         image = {'name': 'image-1'}
 1331         extra_props = {'x_owner_foo': 'bar'}
 1332         created_image = self.controller.create(request, image=image,
 1333                                                extra_properties=extra_props,
 1334                                                tags=[])
 1335         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1336         changes = [
 1337             {'op': 'replace', 'path': ['x_owner_foo'], 'value': 'baz'},
 1338         ]
 1339         output = self.controller.update(another_request,
 1340                                         created_image.image_id, changes)
 1341         self.assertEqual('baz', output.extra_properties['x_owner_foo'])
 1342 
 1343     def test_prop_protection_with_update_and_unpermitted_role(self):
 1344         enforcer = glance.api.policy.Enforcer()
 1345         self.controller = glance.api.v2.images.ImagesController(self.db,
 1346                                                                 enforcer,
 1347                                                                 self.notifier,
 1348                                                                 self.store)
 1349         self.set_property_protections()
 1350         request = unit_test_utils.get_fake_request(roles=['admin'])
 1351         image = {'name': 'image-1'}
 1352         extra_props = {'x_owner_foo': 'bar'}
 1353         created_image = self.controller.create(request, image=image,
 1354                                                extra_properties=extra_props,
 1355                                                tags=[])
 1356         another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
 1357         changes = [
 1358             {'op': 'replace', 'path': ['x_owner_foo'], 'value': 'baz'},
 1359         ]
 1360         self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
 1361                           another_request, created_image.image_id, changes)
 1362 
 1363     def test_prop_protection_with_delete_and_permitted_role(self):
 1364         enforcer = glance.api.policy.Enforcer()
 1365         self.controller = glance.api.v2.images.ImagesController(self.db,
 1366                                                                 enforcer,
 1367                                                                 self.notifier,
 1368                                                                 self.store)
 1369         self.set_property_protections()
 1370         request = unit_test_utils.get_fake_request(roles=['admin'])
 1371         image = {'name': 'image-1'}
 1372         extra_props = {'x_owner_foo': 'bar'}
 1373         created_image = self.controller.create(request, image=image,
 1374                                                extra_properties=extra_props,
 1375                                                tags=[])
 1376         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1377         changes = [
 1378             {'op': 'remove', 'path': ['x_owner_foo']}
 1379         ]
 1380         output = self.controller.update(another_request,
 1381                                         created_image.image_id, changes)
 1382         self.assertRaises(KeyError, output.extra_properties.__getitem__,
 1383                           'x_owner_foo')
 1384 
 1385     def test_prop_protection_with_delete_and_unpermitted_role(self):
 1386         enforcer = glance.api.policy.Enforcer()
 1387         self.controller = glance.api.v2.images.ImagesController(self.db,
 1388                                                                 enforcer,
 1389                                                                 self.notifier,
 1390                                                                 self.store)
 1391         self.set_property_protections()
 1392         request = unit_test_utils.get_fake_request(roles=['admin'])
 1393         image = {'name': 'image-1'}
 1394         extra_props = {'x_owner_foo': 'bar'}
 1395         created_image = self.controller.create(request, image=image,
 1396                                                extra_properties=extra_props,
 1397                                                tags=[])
 1398         another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
 1399         changes = [
 1400             {'op': 'remove', 'path': ['x_owner_foo']}
 1401         ]
 1402         self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
 1403                           another_request, created_image.image_id, changes)
 1404 
 1405     def test_create_protected_prop_case_insensitive(self):
 1406         enforcer = glance.api.policy.Enforcer()
 1407         self.controller = glance.api.v2.images.ImagesController(self.db,
 1408                                                                 enforcer,
 1409                                                                 self.notifier,
 1410                                                                 self.store)
 1411         self.set_property_protections()
 1412         request = unit_test_utils.get_fake_request(roles=['admin'])
 1413         image = {'name': 'image-1'}
 1414         created_image = self.controller.create(request, image=image,
 1415                                                extra_properties={},
 1416                                                tags=[])
 1417         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1418         changes = [
 1419             {'op': 'add', 'path': ['x_case_insensitive'], 'value': '1'},
 1420         ]
 1421         output = self.controller.update(another_request,
 1422                                         created_image.image_id, changes)
 1423         self.assertEqual('1', output.extra_properties['x_case_insensitive'])
 1424 
 1425     def test_read_protected_prop_case_insensitive(self):
 1426         enforcer = glance.api.policy.Enforcer()
 1427         self.controller = glance.api.v2.images.ImagesController(self.db,
 1428                                                                 enforcer,
 1429                                                                 self.notifier,
 1430                                                                 self.store)
 1431         self.set_property_protections()
 1432         request = unit_test_utils.get_fake_request(roles=['admin'])
 1433         image = {'name': 'image-1'}
 1434         extra_props = {'x_case_insensitive': '1'}
 1435         created_image = self.controller.create(request, image=image,
 1436                                                extra_properties=extra_props,
 1437                                                tags=[])
 1438         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1439         output = self.controller.show(another_request, created_image.image_id)
 1440         self.assertEqual('1', output.extra_properties['x_case_insensitive'])
 1441 
 1442     def test_update_protected_prop_case_insensitive(self):
 1443         enforcer = glance.api.policy.Enforcer()
 1444         self.controller = glance.api.v2.images.ImagesController(self.db,
 1445                                                                 enforcer,
 1446                                                                 self.notifier,
 1447                                                                 self.store)
 1448         self.set_property_protections()
 1449         request = unit_test_utils.get_fake_request(roles=['admin'])
 1450         image = {'name': 'image-1'}
 1451         extra_props = {'x_case_insensitive': '1'}
 1452         created_image = self.controller.create(request, image=image,
 1453                                                extra_properties=extra_props,
 1454                                                tags=[])
 1455         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1456         changes = [
 1457             {'op': 'replace', 'path': ['x_case_insensitive'], 'value': '2'},
 1458         ]
 1459         output = self.controller.update(another_request,
 1460                                         created_image.image_id, changes)
 1461         self.assertEqual('2', output.extra_properties['x_case_insensitive'])
 1462 
 1463     def test_delete_protected_prop_case_insensitive(self):
 1464         enforcer = glance.api.policy.Enforcer()
 1465         self.controller = glance.api.v2.images.ImagesController(self.db,
 1466                                                                 enforcer,
 1467                                                                 self.notifier,
 1468                                                                 self.store)
 1469         self.set_property_protections()
 1470         request = unit_test_utils.get_fake_request(roles=['admin'])
 1471         image = {'name': 'image-1'}
 1472         extra_props = {'x_case_insensitive': 'bar'}
 1473         created_image = self.controller.create(request, image=image,
 1474                                                extra_properties=extra_props,
 1475                                                tags=[])
 1476         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1477         changes = [
 1478             {'op': 'remove', 'path': ['x_case_insensitive']}
 1479         ]
 1480         output = self.controller.update(another_request,
 1481                                         created_image.image_id, changes)
 1482         self.assertRaises(KeyError, output.extra_properties.__getitem__,
 1483                           'x_case_insensitive')
 1484 
 1485     def test_create_non_protected_prop(self):
 1486         """Property marked with special char @ creatable by an unknown role"""
 1487         self.set_property_protections()
 1488         request = unit_test_utils.get_fake_request(roles=['admin'])
 1489         image = {'name': 'image-1'}
 1490         extra_props = {'x_all_permitted_1': '1'}
 1491         created_image = self.controller.create(request, image=image,
 1492                                                extra_properties=extra_props,
 1493                                                tags=[])
 1494         self.assertEqual('1',
 1495                          created_image.extra_properties['x_all_permitted_1'])
 1496         another_request = unit_test_utils.get_fake_request(roles=['joe_soap'])
 1497         extra_props = {'x_all_permitted_2': '2'}
 1498         created_image = self.controller.create(another_request, image=image,
 1499                                                extra_properties=extra_props,
 1500                                                tags=[])
 1501         self.assertEqual('2',
 1502                          created_image.extra_properties['x_all_permitted_2'])
 1503 
 1504     def test_read_non_protected_prop(self):
 1505         """Property marked with special char @ readable by an unknown role"""
 1506         self.set_property_protections()
 1507         request = unit_test_utils.get_fake_request(roles=['admin'])
 1508         image = {'name': 'image-1'}
 1509         extra_props = {'x_all_permitted': '1'}
 1510         created_image = self.controller.create(request, image=image,
 1511                                                extra_properties=extra_props,
 1512                                                tags=[])
 1513         another_request = unit_test_utils.get_fake_request(roles=['joe_soap'])
 1514         output = self.controller.show(another_request, created_image.image_id)
 1515         self.assertEqual('1', output.extra_properties['x_all_permitted'])
 1516 
 1517     def test_update_non_protected_prop(self):
 1518         """Property marked with special char @ updatable by an unknown role"""
 1519         self.set_property_protections()
 1520         request = unit_test_utils.get_fake_request(roles=['admin'])
 1521         image = {'name': 'image-1'}
 1522         extra_props = {'x_all_permitted': 'bar'}
 1523         created_image = self.controller.create(request, image=image,
 1524                                                extra_properties=extra_props,
 1525                                                tags=[])
 1526         another_request = unit_test_utils.get_fake_request(roles=['joe_soap'])
 1527         changes = [
 1528             {'op': 'replace', 'path': ['x_all_permitted'], 'value': 'baz'},
 1529         ]
 1530         output = self.controller.update(another_request,
 1531                                         created_image.image_id, changes)
 1532         self.assertEqual('baz', output.extra_properties['x_all_permitted'])
 1533 
 1534     def test_delete_non_protected_prop(self):
 1535         """Property marked with special char @ deletable by an unknown role"""
 1536         self.set_property_protections()
 1537         request = unit_test_utils.get_fake_request(roles=['admin'])
 1538         image = {'name': 'image-1'}
 1539         extra_props = {'x_all_permitted': 'bar'}
 1540         created_image = self.controller.create(request, image=image,
 1541                                                extra_properties=extra_props,
 1542                                                tags=[])
 1543         another_request = unit_test_utils.get_fake_request(roles=['member'])
 1544         changes = [
 1545             {'op': 'remove', 'path': ['x_all_permitted']}
 1546         ]
 1547         output = self.controller.update(another_request,
 1548                                         created_image.image_id, changes)
 1549         self.assertRaises(KeyError, output.extra_properties.__getitem__,
 1550                           'x_all_permitted')
 1551 
 1552     def test_create_locked_down_protected_prop(self):
 1553         """Property marked with special char ! creatable by no one"""
 1554         self.set_property_protections()
 1555         request = unit_test_utils.get_fake_request(roles=['admin'])
 1556         image = {'name': 'image-1'}
 1557         created_image = self.controller.create(request, image=image,
 1558                                                extra_properties={},
 1559                                                tags=[])
 1560         roles = ['fake_member']
 1561         another_request = unit_test_utils.get_fake_request(roles=roles)
 1562         changes = [
 1563             {'op': 'add', 'path': ['x_none_permitted'], 'value': 'bar'},
 1564         ]
 1565         self.assertRaises(webob.exc.HTTPForbidden,
 1566                           self.controller.update, another_request,
 1567                           created_image.image_id, changes)
 1568 
 1569     def test_read_locked_down_protected_prop(self):
 1570         """Property marked with special char ! readable by no one"""
 1571         self.set_property_protections()
 1572         request = unit_test_utils.get_fake_request(roles=['member'])
 1573         image = {'name': 'image-1'}
 1574         extra_props = {'x_none_read': 'bar'}
 1575         created_image = self.controller.create(request, image=image,
 1576                                                extra_properties=extra_props,
 1577                                                tags=[])
 1578         another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
 1579         output = self.controller.show(another_request, created_image.image_id)
 1580         self.assertRaises(KeyError, output.extra_properties.__getitem__,
 1581                           'x_none_read')
 1582 
 1583     def test_update_locked_down_protected_prop(self):
 1584         """Property marked with special char ! updatable by no one"""
 1585         self.set_property_protections()
 1586         request = unit_test_utils.get_fake_request(roles=['admin'])
 1587         image = {'name': 'image-1'}
 1588         extra_props = {'x_none_update': 'bar'}
 1589         created_image = self.controller.create(request, image=image,
 1590                                                extra_properties=extra_props,
 1591                                                tags=[])
 1592         another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
 1593         changes = [
 1594             {'op': 'replace', 'path': ['x_none_update'], 'value': 'baz'},
 1595         ]
 1596         self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
 1597                           another_request, created_image.image_id, changes)
 1598 
 1599     def test_delete_locked_down_protected_prop(self):
 1600         """Property marked with special char ! deletable by no one"""
 1601         self.set_property_protections()
 1602         request = unit_test_utils.get_fake_request(roles=['admin'])
 1603         image = {'name': 'image-1'}
 1604         extra_props = {'x_none_delete': 'bar'}
 1605         created_image = self.controller.create(request, image=image,
 1606                                                extra_properties=extra_props,
 1607                                                tags=[])
 1608         another_request = unit_test_utils.get_fake_request(roles=['fake_role'])
 1609         changes = [
 1610             {'op': 'remove', 'path': ['x_none_delete']}
 1611         ]
 1612         self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
 1613                           another_request, created_image.image_id, changes)
 1614 
 1615     def test_update_replace_locations_non_empty(self):
 1616         self.config(show_multiple_locations=True)
 1617         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 1618         request = unit_test_utils.get_fake_request()
 1619         changes = [{'op': 'replace', 'path': ['locations'],
 1620                     'value': [new_location]}]
 1621         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 1622                           request, UUID1, changes)
 1623 
 1624     def test_update_replace_locations_metadata_update(self):
 1625         self.config(show_multiple_locations=True)
 1626         location = {'url': '%s/%s' % (BASE_URI, UUID1),
 1627                     'metadata': {'a': 1}}
 1628         request = unit_test_utils.get_fake_request()
 1629         changes = [{'op': 'replace', 'path': ['locations'],
 1630                     'value': [location]}]
 1631         output = self.controller.update(request, UUID1, changes)
 1632         self.assertEqual({'a': 1}, output.locations[0]['metadata'])
 1633 
 1634     def test_locations_actions_with_locations_invisible(self):
 1635         self.config(show_multiple_locations=False)
 1636         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 1637         request = unit_test_utils.get_fake_request()
 1638         changes = [{'op': 'replace', 'path': ['locations'],
 1639                     'value': [new_location]}]
 1640         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 1641                           request, UUID1, changes)
 1642 
 1643     def test_update_replace_locations_invalid(self):
 1644         request = unit_test_utils.get_fake_request()
 1645         changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
 1646         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 1647                           request, UUID1, changes)
 1648 
 1649     def test_update_add_property(self):
 1650         request = unit_test_utils.get_fake_request()
 1651 
 1652         changes = [
 1653             {'op': 'add', 'path': ['foo'], 'value': 'baz'},
 1654             {'op': 'add', 'path': ['snitch'], 'value': 'golden'},
 1655         ]
 1656         output = self.controller.update(request, UUID1, changes)
 1657         self.assertEqual(UUID1, output.image_id)
 1658         self.assertEqual('baz', output.extra_properties['foo'])
 1659         self.assertEqual('golden', output.extra_properties['snitch'])
 1660         self.assertNotEqual(output.created_at, output.updated_at)
 1661 
 1662     def test_update_add_base_property_json_schema_version_4(self):
 1663         request = unit_test_utils.get_fake_request()
 1664         changes = [{
 1665             'json_schema_version': 4, 'op': 'add',
 1666             'path': ['name'], 'value': 'fedora'
 1667         }]
 1668         self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
 1669                           request, UUID1, changes)
 1670 
 1671     def test_update_add_extra_property_json_schema_version_4(self):
 1672         self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
 1673         request = unit_test_utils.get_fake_request()
 1674         changes = [{
 1675             'json_schema_version': 4, 'op': 'add',
 1676             'path': ['foo'], 'value': 'baz'
 1677         }]
 1678         self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
 1679                           request, UUID1, changes)
 1680 
 1681     def test_update_add_base_property_json_schema_version_10(self):
 1682         request = unit_test_utils.get_fake_request()
 1683         changes = [{
 1684             'json_schema_version': 10, 'op': 'add',
 1685             'path': ['name'], 'value': 'fedora'
 1686         }]
 1687         output = self.controller.update(request, UUID1, changes)
 1688         self.assertEqual(UUID1, output.image_id)
 1689         self.assertEqual('fedora', output.name)
 1690 
 1691     def test_update_add_extra_property_json_schema_version_10(self):
 1692         self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
 1693         request = unit_test_utils.get_fake_request()
 1694         changes = [{
 1695             'json_schema_version': 10, 'op': 'add',
 1696             'path': ['foo'], 'value': 'baz'
 1697         }]
 1698         output = self.controller.update(request, UUID1, changes)
 1699         self.assertEqual(UUID1, output.image_id)
 1700         self.assertEqual({'foo': 'baz'}, output.extra_properties)
 1701 
 1702     def test_update_add_property_already_present_json_schema_version_4(self):
 1703         request = unit_test_utils.get_fake_request()
 1704         properties = {'foo': 'bar'}
 1705         self.db.image_update(None, UUID1, {'properties': properties})
 1706 
 1707         output = self.controller.show(request, UUID1)
 1708         self.assertEqual('bar', output.extra_properties['foo'])
 1709 
 1710         changes = [
 1711             {'json_schema_version': 4, 'op': 'add',
 1712              'path': ['foo'], 'value': 'baz'},
 1713         ]
 1714         self.assertRaises(webob.exc.HTTPConflict,
 1715                           self.controller.update, request, UUID1, changes)
 1716 
 1717     def test_update_add_property_already_present_json_schema_version_10(self):
 1718         request = unit_test_utils.get_fake_request()
 1719         properties = {'foo': 'bar'}
 1720         self.db.image_update(None, UUID1, {'properties': properties})
 1721 
 1722         output = self.controller.show(request, UUID1)
 1723         self.assertEqual('bar', output.extra_properties['foo'])
 1724 
 1725         changes = [
 1726             {'json_schema_version': 10, 'op': 'add',
 1727              'path': ['foo'], 'value': 'baz'},
 1728         ]
 1729         output = self.controller.update(request, UUID1, changes)
 1730         self.assertEqual(UUID1, output.image_id)
 1731         self.assertEqual({'foo': 'baz'}, output.extra_properties)
 1732 
 1733     def test_update_add_locations(self):
 1734         self.config(show_multiple_locations=True)
 1735         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 1736         request = unit_test_utils.get_fake_request()
 1737         changes = [{'op': 'add', 'path': ['locations', '-'],
 1738                     'value': new_location}]
 1739         output = self.controller.update(request, UUID1, changes)
 1740         self.assertEqual(UUID1, output.image_id)
 1741         self.assertEqual(2, len(output.locations))
 1742         self.assertEqual(new_location, output.locations[1])
 1743 
 1744     @mock.patch.object(glance.quota, '_calc_required_size')
 1745     @mock.patch.object(glance.location, '_check_image_location')
 1746     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 1747     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 1748     @mock.patch.object(store, 'get_size_from_backend')
 1749     def test_replace_locations_on_queued(self,
 1750                                          mock_get_size,
 1751                                          mock_get_size_uri,
 1752                                          mock_set_acls,
 1753                                          mock_check_loc,
 1754                                          mock_calc):
 1755         mock_calc.return_value = 1
 1756         mock_get_size.return_value = 1
 1757         mock_get_size_uri.return_value = 1
 1758         self.config(show_multiple_locations=True)
 1759         image_id = str(uuid.uuid4())
 1760         self.images = [
 1761             _db_fixture(image_id, owner=TENANT1,
 1762                         name='1',
 1763                         disk_format='raw',
 1764                         container_format='bare',
 1765                         status='queued',
 1766                         checksum=None,
 1767                         os_hash_algo=None,
 1768                         os_hash_value=None),
 1769         ]
 1770         self.db.image_create(None, self.images[0])
 1771         request = unit_test_utils.get_fake_request()
 1772         new_location1 = {'url': '%s/fake_location_1' % BASE_URI,
 1773                          'metadata': {},
 1774                          'validation_data': {'checksum': CHKSUM,
 1775                                              'os_hash_algo': 'sha512',
 1776                                              'os_hash_value': MULTIHASH1}}
 1777         new_location2 = {'url': '%s/fake_location_2' % BASE_URI,
 1778                          'metadata': {},
 1779                          'validation_data': {'checksum': CHKSUM,
 1780                                              'os_hash_algo': 'sha512',
 1781                                              'os_hash_value': MULTIHASH1}}
 1782         changes = [{'op': 'replace', 'path': ['locations'],
 1783                     'value': [new_location1, new_location2]}]
 1784         output = self.controller.update(request, image_id, changes)
 1785         self.assertEqual(image_id, output.image_id)
 1786         self.assertEqual(2, len(output.locations))
 1787         self.assertEqual(new_location1['url'], output.locations[0]['url'])
 1788         self.assertEqual(new_location2['url'], output.locations[1]['url'])
 1789         self.assertEqual('active', output.status)
 1790         self.assertEqual(CHKSUM, output.checksum)
 1791         self.assertEqual('sha512', output.os_hash_algo)
 1792         self.assertEqual(MULTIHASH1, output.os_hash_value)
 1793 
 1794     @mock.patch.object(glance.quota, '_calc_required_size')
 1795     @mock.patch.object(glance.location, '_check_image_location')
 1796     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 1797     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 1798     @mock.patch.object(store, 'get_size_from_backend')
 1799     def test_replace_locations_identify_associated_store(
 1800             self, mock_get_size, mock_get_size_uri, mock_set_acls,
 1801             mock_check_loc, mock_calc):
 1802         mock_calc.return_value = 1
 1803         mock_get_size.return_value = 1
 1804         mock_get_size_uri.return_value = 1
 1805         self.config(show_multiple_locations=True)
 1806         self.config(enabled_backends={'fake-store': 'http'})
 1807         image_id = str(uuid.uuid4())
 1808         self.images = [
 1809             _db_fixture(image_id, owner=TENANT1,
 1810                         name='1',
 1811                         disk_format='raw',
 1812                         container_format='bare',
 1813                         status='queued',
 1814                         checksum=None,
 1815                         os_hash_algo=None,
 1816                         os_hash_value=None),
 1817         ]
 1818         self.db.image_create(None, self.images[0])
 1819         request = unit_test_utils.get_fake_request()
 1820         new_location1 = {'url': '%s/fake_location_1' % BASE_URI,
 1821                          'metadata': {},
 1822                          'validation_data': {'checksum': CHKSUM,
 1823                                              'os_hash_algo': 'sha512',
 1824                                              'os_hash_value': MULTIHASH1}}
 1825         new_location2 = {'url': '%s/fake_location_2' % BASE_URI,
 1826                          'metadata': {},
 1827                          'validation_data': {'checksum': CHKSUM,
 1828                                              'os_hash_algo': 'sha512',
 1829                                              'os_hash_value': MULTIHASH1}}
 1830         changes = [{'op': 'replace', 'path': ['locations'],
 1831                     'value': [new_location1, new_location2]}]
 1832 
 1833         with mock.patch.object(store_utils,
 1834                                '_get_store_id_from_uri') as mock_store:
 1835             mock_store.return_value = 'fake-store'
 1836             # ensure location metadata is updated
 1837             new_location1['metadata']['store'] = 'fake-store'
 1838             new_location1['metadata']['store'] = 'fake-store'
 1839 
 1840             output = self.controller.update(request, image_id, changes)
 1841             self.assertEqual(2, len(output.locations))
 1842             self.assertEqual(image_id, output.image_id)
 1843             self.assertEqual(new_location1, output.locations[0])
 1844             self.assertEqual(new_location2, output.locations[1])
 1845             self.assertEqual('active', output.status)
 1846             self.assertEqual(CHKSUM, output.checksum)
 1847             self.assertEqual('sha512', output.os_hash_algo)
 1848             self.assertEqual(MULTIHASH1, output.os_hash_value)
 1849 
 1850     @mock.patch.object(glance.quota, '_calc_required_size')
 1851     @mock.patch.object(glance.location, '_check_image_location')
 1852     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 1853     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 1854     @mock.patch.object(store, 'get_size_from_backend')
 1855     def test_replace_locations_unknon_locations(
 1856             self, mock_get_size, mock_get_size_uri, mock_set_acls,
 1857             mock_check_loc, mock_calc):
 1858         mock_calc.return_value = 1
 1859         mock_get_size.return_value = 1
 1860         mock_get_size_uri.return_value = 1
 1861         self.config(show_multiple_locations=True)
 1862         self.config(enabled_backends={'fake-store': 'http'})
 1863         image_id = str(uuid.uuid4())
 1864         self.images = [
 1865             _db_fixture(image_id, owner=TENANT1,
 1866                         name='1',
 1867                         disk_format='raw',
 1868                         container_format='bare',
 1869                         status='queued',
 1870                         checksum=None,
 1871                         os_hash_algo=None,
 1872                         os_hash_value=None),
 1873         ]
 1874         self.db.image_create(None, self.images[0])
 1875         request = unit_test_utils.get_fake_request()
 1876         new_location1 = {'url': 'unknown://whocares',
 1877                          'metadata': {},
 1878                          'validation_data': {'checksum': CHKSUM,
 1879                                              'os_hash_algo': 'sha512',
 1880                                              'os_hash_value': MULTIHASH1}}
 1881         new_location2 = {'url': 'unknown://whatever',
 1882                          'metadata': {'store': 'unkstore'},
 1883                          'validation_data': {'checksum': CHKSUM,
 1884                                              'os_hash_algo': 'sha512',
 1885                                              'os_hash_value': MULTIHASH1}}
 1886         changes = [{'op': 'replace', 'path': ['locations'],
 1887                     'value': [new_location1, new_location2]}]
 1888 
 1889         output = self.controller.update(request, image_id, changes)
 1890         self.assertEqual(2, len(output.locations))
 1891         self.assertEqual(image_id, output.image_id)
 1892         self.assertEqual('active', output.status)
 1893         self.assertEqual(CHKSUM, output.checksum)
 1894         self.assertEqual('sha512', output.os_hash_algo)
 1895         self.assertEqual(MULTIHASH1, output.os_hash_value)
 1896         # ensure location metadata is same
 1897         self.assertEqual(new_location1, output.locations[0])
 1898         self.assertEqual(new_location2, output.locations[1])
 1899 
 1900     @mock.patch.object(glance.quota, '_calc_required_size')
 1901     @mock.patch.object(glance.location, '_check_image_location')
 1902     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 1903     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 1904     @mock.patch.object(store, 'get_size_from_backend')
 1905     def test_add_location_new_validation_data_on_active(self,
 1906                                                         mock_get_size,
 1907                                                         mock_get_size_uri,
 1908                                                         mock_set_acls,
 1909                                                         mock_check_loc,
 1910                                                         mock_calc):
 1911         mock_calc.return_value = 1
 1912         mock_get_size.return_value = 1
 1913         mock_get_size_uri.return_value = 1
 1914         self.config(show_multiple_locations=True)
 1915         image_id = str(uuid.uuid4())
 1916         self.images = [
 1917             _db_fixture(image_id, owner=TENANT1,
 1918                         name='1',
 1919                         disk_format='raw',
 1920                         container_format='bare',
 1921                         status='active',
 1922                         checksum=None,
 1923                         os_hash_algo=None,
 1924                         os_hash_value=None),
 1925         ]
 1926         self.db.image_create(None, self.images[0])
 1927         request = unit_test_utils.get_fake_request()
 1928         new_location = {'url': '%s/fake_location_1' % BASE_URI,
 1929                         'metadata': {},
 1930                         'validation_data': {'checksum': CHKSUM,
 1931                                             'os_hash_algo': 'sha512',
 1932                                             'os_hash_value': MULTIHASH1}}
 1933         changes = [{'op': 'add', 'path': ['locations', '-'],
 1934                     'value': new_location}]
 1935         six.assertRaisesRegex(self,
 1936                               webob.exc.HTTPConflict,
 1937                               "may only be provided when image status "
 1938                               "is 'queued'",
 1939                               self.controller.update,
 1940                               request, image_id, changes)
 1941 
 1942     @mock.patch.object(glance.quota, '_calc_required_size')
 1943     @mock.patch.object(glance.location, '_check_image_location')
 1944     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 1945     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 1946     @mock.patch.object(store, 'get_size_from_backend')
 1947     def test_replace_locations_different_validation_data(self,
 1948                                                          mock_get_size,
 1949                                                          mock_get_size_uri,
 1950                                                          mock_set_acls,
 1951                                                          mock_check_loc,
 1952                                                          mock_calc):
 1953         mock_calc.return_value = 1
 1954         mock_get_size.return_value = 1
 1955         mock_get_size_uri.return_value = 1
 1956         self.config(show_multiple_locations=True)
 1957         image_id = str(uuid.uuid4())
 1958         self.images = [
 1959             _db_fixture(image_id, owner=TENANT1,
 1960                         name='1',
 1961                         disk_format='raw',
 1962                         container_format='bare',
 1963                         status='active',
 1964                         checksum=CHKSUM,
 1965                         os_hash_algo='sha512',
 1966                         os_hash_value=MULTIHASH1),
 1967         ]
 1968         self.db.image_create(None, self.images[0])
 1969         request = unit_test_utils.get_fake_request()
 1970         new_location = {'url': '%s/fake_location_1' % BASE_URI,
 1971                         'metadata': {},
 1972                         'validation_data': {'checksum': CHKSUM1,
 1973                                             'os_hash_algo': 'sha512',
 1974                                             'os_hash_value': MULTIHASH2}}
 1975         changes = [{'op': 'replace', 'path': ['locations'],
 1976                     'value': [new_location]}]
 1977         six.assertRaisesRegex(self,
 1978                               webob.exc.HTTPConflict,
 1979                               "already set with a different value",
 1980                               self.controller.update,
 1981                               request, image_id, changes)
 1982 
 1983     @mock.patch.object(glance.quota, '_calc_required_size')
 1984     @mock.patch.object(glance.location, '_check_image_location')
 1985     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 1986     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 1987     @mock.patch.object(store, 'get_size_from_backend')
 1988     def test_add_location_on_queued(self,
 1989                                     mock_get_size,
 1990                                     mock_get_size_uri,
 1991                                     mock_set_acls,
 1992                                     mock_check_loc,
 1993                                     mock_calc):
 1994         mock_calc.return_value = 1
 1995         mock_get_size.return_value = 1
 1996         mock_get_size_uri.return_value = 1
 1997         self.config(show_multiple_locations=True)
 1998         image_id = str(uuid.uuid4())
 1999         self.images = [
 2000             _db_fixture(image_id, owner=TENANT1, checksum=CHKSUM,
 2001                         name='1',
 2002                         disk_format='raw',
 2003                         container_format='bare',
 2004                         status='queued'),
 2005         ]
 2006         self.db.image_create(None, self.images[0])
 2007         request = unit_test_utils.get_fake_request()
 2008         new_location = {'url': '%s/fake_location_1' % BASE_URI,
 2009                         'metadata': {}}
 2010         changes = [{'op': 'add', 'path': ['locations', '-'],
 2011                     'value': new_location}]
 2012         output = self.controller.update(request, image_id, changes)
 2013         self.assertEqual(image_id, output.image_id)
 2014         self.assertEqual(1, len(output.locations))
 2015         self.assertEqual(new_location, output.locations[0])
 2016         self.assertEqual('active', output.status)
 2017 
 2018     @mock.patch.object(glance.quota, '_calc_required_size')
 2019     @mock.patch.object(glance.location, '_check_image_location')
 2020     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 2021     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 2022     @mock.patch.object(store, 'get_size_from_backend')
 2023     def test_add_location_identify_associated_store(
 2024             self, mock_get_size, mock_get_size_uri, mock_set_acls,
 2025             mock_check_loc, mock_calc):
 2026         mock_calc.return_value = 1
 2027         mock_get_size.return_value = 1
 2028         mock_get_size_uri.return_value = 1
 2029         self.config(show_multiple_locations=True)
 2030         self.config(enabled_backends={'fake-store': 'http'})
 2031         image_id = str(uuid.uuid4())
 2032         self.images = [
 2033             _db_fixture(image_id, owner=TENANT1, checksum=CHKSUM,
 2034                         name='1',
 2035                         disk_format='raw',
 2036                         container_format='bare',
 2037                         status='queued'),
 2038         ]
 2039         self.db.image_create(None, self.images[0])
 2040         request = unit_test_utils.get_fake_request()
 2041         new_location = {'url': '%s/fake_location_1' % BASE_URI,
 2042                         'metadata': {}}
 2043         changes = [{'op': 'add', 'path': ['locations', '-'],
 2044                     'value': new_location}]
 2045         with mock.patch.object(store_utils,
 2046                                '_get_store_id_from_uri') as mock_store:
 2047             mock_store.return_value = 'fake-store'
 2048             output = self.controller.update(request, image_id, changes)
 2049 
 2050             self.assertEqual(image_id, output.image_id)
 2051             self.assertEqual(1, len(output.locations))
 2052             self.assertEqual('active', output.status)
 2053             # ensure location metadata is updated
 2054             new_location['metadata']['store'] = 'fake-store'
 2055             self.assertEqual(new_location, output.locations[0])
 2056 
 2057     @mock.patch.object(glance.quota, '_calc_required_size')
 2058     @mock.patch.object(glance.location, '_check_image_location')
 2059     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 2060     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 2061     @mock.patch.object(store, 'get_size_from_backend')
 2062     def test_add_location_unknown_locations(
 2063             self, mock_get_size, mock_get_size_uri, mock_set_acls,
 2064             mock_check_loc, mock_calc):
 2065         mock_calc.return_value = 1
 2066         mock_get_size.return_value = 1
 2067         mock_get_size_uri.return_value = 1
 2068         self.config(show_multiple_locations=True)
 2069         self.config(enabled_backends={'fake-store': 'http'})
 2070         image_id = str(uuid.uuid4())
 2071 
 2072         self.images = [
 2073             _db_fixture(image_id, owner=TENANT1, checksum=CHKSUM,
 2074                         name='1',
 2075                         disk_format='raw',
 2076                         container_format='bare',
 2077                         status='queued'),
 2078         ]
 2079         self.db.image_create(None, self.images[0])
 2080 
 2081         new_location = {'url': 'unknown://whocares', 'metadata': {}}
 2082         request = unit_test_utils.get_fake_request()
 2083         changes = [{'op': 'add', 'path': ['locations', '-'],
 2084                     'value': new_location}]
 2085 
 2086         output = self.controller.update(request, image_id, changes)
 2087 
 2088         self.assertEqual(image_id, output.image_id)
 2089         self.assertEqual('active', output.status)
 2090         self.assertEqual(1, len(output.locations))
 2091         # ensure location metadata is same
 2092         self.assertEqual(new_location, output.locations[0])
 2093 
 2094     @mock.patch.object(glance.quota, '_calc_required_size')
 2095     @mock.patch.object(glance.location, '_check_image_location')
 2096     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 2097     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 2098     @mock.patch.object(store, 'get_size_from_backend')
 2099     def test_add_location_invalid_validation_data(self,
 2100                                                   mock_get_size,
 2101                                                   mock_get_size_uri,
 2102                                                   mock_set_acls,
 2103                                                   mock_check_loc,
 2104                                                   mock_calc):
 2105         mock_calc.return_value = 1
 2106         mock_get_size.return_value = 1
 2107         mock_get_size_uri.return_value = 1
 2108         self.config(show_multiple_locations=True)
 2109         image_id = str(uuid.uuid4())
 2110         self.images = [
 2111             _db_fixture(image_id, owner=TENANT1,
 2112                         checksum=None,
 2113                         os_hash_algo=None,
 2114                         os_hash_value=None,
 2115                         name='1',
 2116                         disk_format='raw',
 2117                         container_format='bare',
 2118                         status='queued'),
 2119         ]
 2120         self.db.image_create(None, self.images[0])
 2121         request = unit_test_utils.get_fake_request()
 2122 
 2123         location = {
 2124             'url': '%s/fake_location_1' % BASE_URI,
 2125             'metadata': {},
 2126             'validation_data': {}
 2127         }
 2128         changes = [{'op': 'add', 'path': ['locations', '-'],
 2129                     'value': location}]
 2130 
 2131         changes[0]['value']['validation_data'] = {
 2132             'checksum': 'something the same length as md5',
 2133             'os_hash_algo': 'sha512',
 2134             'os_hash_value': MULTIHASH1,
 2135         }
 2136         six.assertRaisesRegex(self,
 2137                               webob.exc.HTTPConflict,
 2138                               'checksum .* is not a valid hexadecimal value',
 2139                               self.controller.update,
 2140                               request, image_id, changes)
 2141 
 2142         changes[0]['value']['validation_data'] = {
 2143             'checksum': '0123456789abcdef',
 2144             'os_hash_algo': 'sha512',
 2145             'os_hash_value': MULTIHASH1,
 2146         }
 2147         six.assertRaisesRegex(self,
 2148                               webob.exc.HTTPConflict,
 2149                               'checksum .* is not the correct size',
 2150                               self.controller.update,
 2151                               request, image_id, changes)
 2152 
 2153         changes[0]['value']['validation_data'] = {
 2154             'checksum': CHKSUM,
 2155             'os_hash_algo': 'sha256',
 2156             'os_hash_value': MULTIHASH1,
 2157         }
 2158         six.assertRaisesRegex(self,
 2159                               webob.exc.HTTPConflict,
 2160                               'os_hash_algo must be sha512',
 2161                               self.controller.update,
 2162                               request, image_id, changes)
 2163 
 2164         changes[0]['value']['validation_data'] = {
 2165             'checksum': CHKSUM,
 2166             'os_hash_algo': 'sha512',
 2167             'os_hash_value': 'not a hex value',
 2168         }
 2169         six.assertRaisesRegex(self,
 2170                               webob.exc.HTTPConflict,
 2171                               'os_hash_value .* is not a valid hexadecimal '
 2172                               'value',
 2173                               self.controller.update,
 2174                               request, image_id, changes)
 2175 
 2176         changes[0]['value']['validation_data'] = {
 2177             'checksum': CHKSUM,
 2178             'os_hash_algo': 'sha512',
 2179             'os_hash_value': '0123456789abcdef',
 2180         }
 2181         six.assertRaisesRegex(self,
 2182                               webob.exc.HTTPConflict,
 2183                               'os_hash_value .* is not the correct size '
 2184                               'for sha512',
 2185                               self.controller.update,
 2186                               request, image_id, changes)
 2187 
 2188     @mock.patch.object(glance.quota, '_calc_required_size')
 2189     @mock.patch.object(glance.location, '_check_image_location')
 2190     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 2191     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 2192     @mock.patch.object(store, 'get_size_from_backend')
 2193     def test_add_location_same_validation_data(self,
 2194                                                mock_get_size,
 2195                                                mock_get_size_uri,
 2196                                                mock_set_acls,
 2197                                                mock_check_loc,
 2198                                                mock_calc):
 2199         mock_calc.return_value = 1
 2200         mock_get_size.return_value = 1
 2201         mock_get_size_uri.return_value = 1
 2202         self.config(show_multiple_locations=True)
 2203         image_id = str(uuid.uuid4())
 2204         os_hash_value = '6513f21e44aa3da349f248188a44bc304a3653a04122d8fb45' \
 2205                         '35423c8e1d14cd6a153f735bb0982e2161b5b5186106570c17' \
 2206                         'a9e58b64dd39390617cd5a350f78'
 2207         self.images = [
 2208             _db_fixture(image_id, owner=TENANT1,
 2209                         name='1',
 2210                         disk_format='raw',
 2211                         container_format='bare',
 2212                         status='active',
 2213                         checksum='checksum1',
 2214                         os_hash_algo='sha512',
 2215                         os_hash_value=os_hash_value),
 2216         ]
 2217         self.db.image_create(None, self.images[0])
 2218         request = unit_test_utils.get_fake_request()
 2219         new_location = {'url': '%s/fake_location_1' % BASE_URI,
 2220                         'metadata': {},
 2221                         'validation_data': {'checksum': 'checksum1',
 2222                                             'os_hash_algo': 'sha512',
 2223                                             'os_hash_value': os_hash_value}}
 2224         changes = [{'op': 'add', 'path': ['locations', '-'],
 2225                     'value': new_location}]
 2226         output = self.controller.update(request, image_id, changes)
 2227         self.assertEqual(image_id, output.image_id)
 2228         self.assertEqual(1, len(output.locations))
 2229         self.assertEqual(new_location, output.locations[0])
 2230         self.assertEqual('active', output.status)
 2231 
 2232     @mock.patch.object(glance.quota, '_calc_required_size')
 2233     @mock.patch.object(glance.location, '_check_image_location')
 2234     @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
 2235     @mock.patch.object(store, 'get_size_from_uri_and_backend')
 2236     @mock.patch.object(store, 'get_size_from_backend')
 2237     def test_add_location_different_validation_data(self,
 2238                                                     mock_get_size,
 2239                                                     mock_get_size_uri,
 2240                                                     mock_set_acls,
 2241                                                     mock_check_loc,
 2242                                                     mock_calc):
 2243         mock_calc.return_value = 1
 2244         mock_get_size.return_value = 1
 2245         mock_get_size_uri.return_value = 1
 2246         self.config(show_multiple_locations=True)
 2247         image_id = str(uuid.uuid4())
 2248         self.images = [
 2249             _db_fixture(image_id, owner=TENANT1,
 2250                         name='1',
 2251                         disk_format='raw',
 2252                         container_format='bare',
 2253                         status='active',
 2254                         checksum=CHKSUM,
 2255                         os_hash_algo='sha512',
 2256                         os_hash_value=MULTIHASH1),
 2257         ]
 2258         self.db.image_create(None, self.images[0])
 2259         request = unit_test_utils.get_fake_request()
 2260         new_location = {'url': '%s/fake_location_1' % BASE_URI,
 2261                         'metadata': {},
 2262                         'validation_data': {'checksum': CHKSUM1,
 2263                                             'os_hash_algo': 'sha512',
 2264                                             'os_hash_value': MULTIHASH2}}
 2265         changes = [{'op': 'add', 'path': ['locations', '-'],
 2266                     'value': new_location}]
 2267         six.assertRaisesRegex(self,
 2268                               webob.exc.HTTPConflict,
 2269                               "already set with a different value",
 2270                               self.controller.update,
 2271                               request, image_id, changes)
 2272 
 2273     def _test_update_locations_status(self, image_status, update):
 2274         self.config(show_multiple_locations=True)
 2275         self.images = [
 2276             _db_fixture('1', owner=TENANT1, checksum=CHKSUM,
 2277                         name='1',
 2278                         disk_format='raw',
 2279                         container_format='bare',
 2280                         status=image_status),
 2281         ]
 2282         request = unit_test_utils.get_fake_request()
 2283         if image_status == 'deactivated':
 2284             self.db.image_create(request.context, self.images[0])
 2285         else:
 2286             self.db.image_create(None, self.images[0])
 2287         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 2288         changes = [{'op': update, 'path': ['locations', '-'],
 2289                     'value': new_location}]
 2290         self.assertRaises(webob.exc.HTTPConflict,
 2291                           self.controller.update, request, '1', changes)
 2292 
 2293     def test_location_add_not_permitted_status_saving(self):
 2294         self._test_update_locations_status('saving', 'add')
 2295 
 2296     def test_location_add_not_permitted_status_deactivated(self):
 2297         self._test_update_locations_status('deactivated', 'add')
 2298 
 2299     def test_location_add_not_permitted_status_deleted(self):
 2300         self._test_update_locations_status('deleted', 'add')
 2301 
 2302     def test_location_add_not_permitted_status_pending_delete(self):
 2303         self._test_update_locations_status('pending_delete', 'add')
 2304 
 2305     def test_location_add_not_permitted_status_killed(self):
 2306         self._test_update_locations_status('killed', 'add')
 2307 
 2308     def test_location_add_not_permitted_status_importing(self):
 2309         self._test_update_locations_status('importing', 'add')
 2310 
 2311     def test_location_add_not_permitted_status_uploading(self):
 2312         self._test_update_locations_status('uploading', 'add')
 2313 
 2314     def test_location_remove_not_permitted_status_saving(self):
 2315         self._test_update_locations_status('saving', 'remove')
 2316 
 2317     def test_location_remove_not_permitted_status_deactivated(self):
 2318         self._test_update_locations_status('deactivated', 'remove')
 2319 
 2320     def test_location_remove_not_permitted_status_deleted(self):
 2321         self._test_update_locations_status('deleted', 'remove')
 2322 
 2323     def test_location_remove_not_permitted_status_pending_delete(self):
 2324         self._test_update_locations_status('pending_delete', 'remove')
 2325 
 2326     def test_location_remove_not_permitted_status_killed(self):
 2327         self._test_update_locations_status('killed', 'remove')
 2328 
 2329     def test_location_remove_not_permitted_status_queued(self):
 2330         self._test_update_locations_status('queued', 'remove')
 2331 
 2332     def test_location_remove_not_permitted_status_importing(self):
 2333         self._test_update_locations_status('importing', 'remove')
 2334 
 2335     def test_location_remove_not_permitted_status_uploading(self):
 2336         self._test_update_locations_status('uploading', 'remove')
 2337 
 2338     def test_location_replace_not_permitted_status_saving(self):
 2339         self._test_update_locations_status('saving', 'replace')
 2340 
 2341     def test_location_replace_not_permitted_status_deactivated(self):
 2342         self._test_update_locations_status('deactivated', 'replace')
 2343 
 2344     def test_location_replace_not_permitted_status_deleted(self):
 2345         self._test_update_locations_status('deleted', 'replace')
 2346 
 2347     def test_location_replace_not_permitted_status_pending_delete(self):
 2348         self._test_update_locations_status('pending_delete', 'replace')
 2349 
 2350     def test_location_replace_not_permitted_status_killed(self):
 2351         self._test_update_locations_status('killed', 'replace')
 2352 
 2353     def test_location_replace_not_permitted_status_importing(self):
 2354         self._test_update_locations_status('importing', 'replace')
 2355 
 2356     def test_location_replace_not_permitted_status_uploading(self):
 2357         self._test_update_locations_status('uploading', 'replace')
 2358 
 2359     def test_update_add_locations_insertion(self):
 2360         self.config(show_multiple_locations=True)
 2361         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 2362         request = unit_test_utils.get_fake_request()
 2363         changes = [{'op': 'add', 'path': ['locations', '0'],
 2364                     'value': new_location}]
 2365         output = self.controller.update(request, UUID1, changes)
 2366         self.assertEqual(UUID1, output.image_id)
 2367         self.assertEqual(2, len(output.locations))
 2368         self.assertEqual(new_location, output.locations[0])
 2369 
 2370     def test_update_add_locations_list(self):
 2371         self.config(show_multiple_locations=True)
 2372         request = unit_test_utils.get_fake_request()
 2373         changes = [{'op': 'add', 'path': ['locations', '-'],
 2374                     'value': {'url': 'foo', 'metadata': {}}}]
 2375         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2376                           request, UUID1, changes)
 2377 
 2378     def test_update_add_locations_invalid(self):
 2379         self.config(show_multiple_locations=True)
 2380         request = unit_test_utils.get_fake_request()
 2381         changes = [{'op': 'add', 'path': ['locations', '-'],
 2382                     'value': {'url': 'unknow://foo', 'metadata': {}}}]
 2383         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2384                           request, UUID1, changes)
 2385 
 2386         changes = [{'op': 'add', 'path': ['locations', None],
 2387                     'value': {'url': 'unknow://foo', 'metadata': {}}}]
 2388         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2389                           request, UUID1, changes)
 2390 
 2391     def test_update_add_duplicate_locations(self):
 2392         self.config(show_multiple_locations=True)
 2393         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 2394         request = unit_test_utils.get_fake_request()
 2395         changes = [{'op': 'add', 'path': ['locations', '-'],
 2396                     'value': new_location}]
 2397         output = self.controller.update(request, UUID1, changes)
 2398         self.assertEqual(UUID1, output.image_id)
 2399         self.assertEqual(2, len(output.locations))
 2400         self.assertEqual(new_location, output.locations[1])
 2401 
 2402         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2403                           request, UUID1, changes)
 2404 
 2405     def test_update_add_too_many_locations(self):
 2406         self.config(show_multiple_locations=True)
 2407         self.config(image_location_quota=1)
 2408         request = unit_test_utils.get_fake_request()
 2409         changes = [
 2410             {'op': 'add', 'path': ['locations', '-'],
 2411              'value': {'url': '%s/fake_location_1' % BASE_URI,
 2412                        'metadata': {}}},
 2413             {'op': 'add', 'path': ['locations', '-'],
 2414              'value': {'url': '%s/fake_location_2' % BASE_URI,
 2415                        'metadata': {}}},
 2416         ]
 2417         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
 2418                           self.controller.update, request,
 2419                           UUID1, changes)
 2420 
 2421     def test_update_add_and_remove_too_many_locations(self):
 2422         self.config(show_multiple_locations=True)
 2423         request = unit_test_utils.get_fake_request()
 2424         changes = [
 2425             {'op': 'add', 'path': ['locations', '-'],
 2426              'value': {'url': '%s/fake_location_1' % BASE_URI,
 2427                        'metadata': {}}},
 2428             {'op': 'add', 'path': ['locations', '-'],
 2429              'value': {'url': '%s/fake_location_2' % BASE_URI,
 2430                        'metadata': {}}},
 2431         ]
 2432         self.controller.update(request, UUID1, changes)
 2433         self.config(image_location_quota=1)
 2434 
 2435         # We must remove two properties to avoid being
 2436         # over the limit of 1 property
 2437         changes = [
 2438             {'op': 'remove', 'path': ['locations', '0']},
 2439             {'op': 'add', 'path': ['locations', '-'],
 2440              'value': {'url': '%s/fake_location_3' % BASE_URI,
 2441                        'metadata': {}}},
 2442         ]
 2443         self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
 2444                           self.controller.update, request,
 2445                           UUID1, changes)
 2446 
 2447     def test_update_add_unlimited_locations(self):
 2448         self.config(show_multiple_locations=True)
 2449         self.config(image_location_quota=-1)
 2450         request = unit_test_utils.get_fake_request()
 2451         changes = [
 2452             {'op': 'add', 'path': ['locations', '-'],
 2453              'value': {'url': '%s/fake_location_1' % BASE_URI,
 2454                        'metadata': {}}},
 2455         ]
 2456         output = self.controller.update(request, UUID1, changes)
 2457         self.assertEqual(UUID1, output.image_id)
 2458         self.assertNotEqual(output.created_at, output.updated_at)
 2459 
 2460     def test_update_remove_location_while_over_limit(self):
 2461         """Ensure that image locations can be removed.
 2462 
 2463         Image locations should be able to be removed as long as the image has
 2464         fewer than the limited number of image locations after the
 2465         transaction.
 2466         """
 2467         self.config(show_multiple_locations=True)
 2468         request = unit_test_utils.get_fake_request()
 2469         changes = [
 2470             {'op': 'add', 'path': ['locations', '-'],
 2471              'value': {'url': '%s/fake_location_1' % BASE_URI,
 2472                        'metadata': {}}},
 2473             {'op': 'add', 'path': ['locations', '-'],
 2474              'value': {'url': '%s/fake_location_2' % BASE_URI,
 2475                        'metadata': {}}},
 2476         ]
 2477         self.controller.update(request, UUID1, changes)
 2478         self.config(image_location_quota=1)
 2479         self.config(show_multiple_locations=True)
 2480 
 2481         # We must remove two locations to avoid being over
 2482         # the limit of 1 location
 2483         changes = [
 2484             {'op': 'remove', 'path': ['locations', '0']},
 2485             {'op': 'remove', 'path': ['locations', '0']},
 2486         ]
 2487         output = self.controller.update(request, UUID1, changes)
 2488         self.assertEqual(UUID1, output.image_id)
 2489         self.assertEqual(1, len(output.locations))
 2490         self.assertIn('fake_location_2', output.locations[0]['url'])
 2491         self.assertNotEqual(output.created_at, output.updated_at)
 2492 
 2493     def test_update_add_and_remove_location_under_limit(self):
 2494         """Ensure that image locations can be removed.
 2495 
 2496         Image locations should be able to be added and removed simultaneously
 2497         as long as the image has fewer than the limited number of image
 2498         locations after the transaction.
 2499         """
 2500         self.mock_object(store, 'get_size_from_backend',
 2501                          unit_test_utils.fake_get_size_from_backend)
 2502         self.config(show_multiple_locations=True)
 2503         request = unit_test_utils.get_fake_request()
 2504 
 2505         changes = [
 2506             {'op': 'add', 'path': ['locations', '-'],
 2507              'value': {'url': '%s/fake_location_1' % BASE_URI,
 2508                        'metadata': {}}},
 2509             {'op': 'add', 'path': ['locations', '-'],
 2510              'value': {'url': '%s/fake_location_2' % BASE_URI,
 2511                        'metadata': {}}},
 2512         ]
 2513         self.controller.update(request, UUID1, changes)
 2514         self.config(image_location_quota=2)
 2515 
 2516         # We must remove two properties to avoid being
 2517         # over the limit of 1 property
 2518         changes = [
 2519             {'op': 'remove', 'path': ['locations', '0']},
 2520             {'op': 'remove', 'path': ['locations', '0']},
 2521             {'op': 'add', 'path': ['locations', '-'],
 2522              'value': {'url': '%s/fake_location_3' % BASE_URI,
 2523                        'metadata': {}}},
 2524         ]
 2525         output = self.controller.update(request, UUID1, changes)
 2526         self.assertEqual(UUID1, output.image_id)
 2527         self.assertEqual(2, len(output.locations))
 2528         self.assertIn('fake_location_3', output.locations[1]['url'])
 2529         self.assertNotEqual(output.created_at, output.updated_at)
 2530 
 2531     def test_update_remove_base_property(self):
 2532         self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
 2533         request = unit_test_utils.get_fake_request()
 2534         changes = [{'op': 'remove', 'path': ['name']}]
 2535         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 2536                           request, UUID1, changes)
 2537 
 2538     def test_update_remove_property(self):
 2539         request = unit_test_utils.get_fake_request()
 2540         properties = {'foo': 'bar', 'snitch': 'golden'}
 2541         self.db.image_update(None, UUID1, {'properties': properties})
 2542 
 2543         output = self.controller.show(request, UUID1)
 2544         self.assertEqual('bar', output.extra_properties['foo'])
 2545         self.assertEqual('golden', output.extra_properties['snitch'])
 2546 
 2547         changes = [
 2548             {'op': 'remove', 'path': ['snitch']},
 2549         ]
 2550         output = self.controller.update(request, UUID1, changes)
 2551         self.assertEqual(UUID1, output.image_id)
 2552         self.assertEqual({'foo': 'bar'}, output.extra_properties)
 2553         self.assertNotEqual(output.created_at, output.updated_at)
 2554 
 2555     def test_update_remove_missing_property(self):
 2556         request = unit_test_utils.get_fake_request()
 2557 
 2558         changes = [
 2559             {'op': 'remove', 'path': ['foo']},
 2560         ]
 2561         self.assertRaises(webob.exc.HTTPConflict,
 2562                           self.controller.update, request, UUID1, changes)
 2563 
 2564     def test_update_remove_location(self):
 2565         self.config(show_multiple_locations=True)
 2566         self.mock_object(store, 'get_size_from_backend',
 2567                          unit_test_utils.fake_get_size_from_backend)
 2568 
 2569         request = unit_test_utils.get_fake_request()
 2570         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 2571         changes = [{'op': 'add', 'path': ['locations', '-'],
 2572                     'value': new_location}]
 2573         self.controller.update(request, UUID1, changes)
 2574         changes = [{'op': 'remove', 'path': ['locations', '0']}]
 2575         output = self.controller.update(request, UUID1, changes)
 2576         self.assertEqual(UUID1, output.image_id)
 2577         self.assertEqual(1, len(output.locations))
 2578         self.assertEqual('active', output.status)
 2579 
 2580     def test_update_remove_location_invalid_pos(self):
 2581         self.config(show_multiple_locations=True)
 2582         request = unit_test_utils.get_fake_request()
 2583         changes = [
 2584             {'op': 'add', 'path': ['locations', '-'],
 2585              'value': {'url': '%s/fake_location' % BASE_URI,
 2586                        'metadata': {}}}]
 2587         self.controller.update(request, UUID1, changes)
 2588         changes = [{'op': 'remove', 'path': ['locations', None]}]
 2589         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2590                           request, UUID1, changes)
 2591         changes = [{'op': 'remove', 'path': ['locations', '-1']}]
 2592         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2593                           request, UUID1, changes)
 2594         changes = [{'op': 'remove', 'path': ['locations', '99']}]
 2595         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2596                           request, UUID1, changes)
 2597         changes = [{'op': 'remove', 'path': ['locations', 'x']}]
 2598         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
 2599                           request, UUID1, changes)
 2600 
 2601     def test_update_remove_location_store_exception(self):
 2602         self.config(show_multiple_locations=True)
 2603 
 2604         def fake_delete_image_location_from_backend(self, *args, **kwargs):
 2605             raise Exception('fake_backend_exception')
 2606 
 2607         self.mock_object(self.store_utils,
 2608                          'delete_image_location_from_backend',
 2609                          fake_delete_image_location_from_backend)
 2610 
 2611         request = unit_test_utils.get_fake_request()
 2612         changes = [
 2613             {'op': 'add', 'path': ['locations', '-'],
 2614              'value': {'url': '%s/fake_location' % BASE_URI,
 2615                        'metadata': {}}}]
 2616         self.controller.update(request, UUID1, changes)
 2617         changes = [{'op': 'remove', 'path': ['locations', '0']}]
 2618         self.assertRaises(webob.exc.HTTPInternalServerError,
 2619                           self.controller.update, request, UUID1, changes)
 2620 
 2621     def test_update_multiple_changes(self):
 2622         request = unit_test_utils.get_fake_request()
 2623         properties = {'foo': 'bar', 'snitch': 'golden'}
 2624         self.db.image_update(None, UUID1, {'properties': properties})
 2625 
 2626         changes = [
 2627             {'op': 'replace', 'path': ['min_ram'], 'value': 128},
 2628             {'op': 'replace', 'path': ['foo'], 'value': 'baz'},
 2629             {'op': 'remove', 'path': ['snitch']},
 2630             {'op': 'add', 'path': ['kb'], 'value': 'dvorak'},
 2631         ]
 2632         output = self.controller.update(request, UUID1, changes)
 2633         self.assertEqual(UUID1, output.image_id)
 2634         self.assertEqual(128, output.min_ram)
 2635         self.addDetail('extra_properties',
 2636                        testtools.content.json_content(
 2637                            jsonutils.dumps(output.extra_properties)))
 2638         self.assertEqual(2, len(output.extra_properties))
 2639         self.assertEqual('baz', output.extra_properties['foo'])
 2640         self.assertEqual('dvorak', output.extra_properties['kb'])
 2641         self.assertNotEqual(output.created_at, output.updated_at)
 2642 
 2643     def test_update_invalid_operation(self):
 2644         request = unit_test_utils.get_fake_request()
 2645         change = {'op': 'test', 'path': 'options', 'value': 'puts'}
 2646         try:
 2647             self.controller.update(request, UUID1, [change])
 2648         except AttributeError:
 2649             pass  # AttributeError is the desired behavior
 2650         else:
 2651             self.fail('Failed to raise AssertionError on %s' % change)
 2652 
 2653     def test_update_duplicate_tags(self):
 2654         request = unit_test_utils.get_fake_request()
 2655         changes = [
 2656             {'op': 'replace', 'path': ['tags'], 'value': ['ping', 'ping']},
 2657         ]
 2658         output = self.controller.update(request, UUID1, changes)
 2659         self.assertEqual(1, len(output.tags))
 2660         self.assertIn('ping', output.tags)
 2661         output_logs = self.notifier.get_logs()
 2662         self.assertEqual(1, len(output_logs))
 2663         output_log = output_logs[0]
 2664         self.assertEqual('INFO', output_log['notification_type'])
 2665         self.assertEqual('image.update', output_log['event_type'])
 2666         self.assertEqual(UUID1, output_log['payload']['id'])
 2667 
 2668     def test_update_disabled_notification(self):
 2669         self.config(disabled_notifications=["image.update"])
 2670         request = unit_test_utils.get_fake_request()
 2671         changes = [
 2672             {'op': 'replace', 'path': ['name'], 'value': 'Ping Pong'},
 2673         ]
 2674         output = self.controller.update(request, UUID1, changes)
 2675         self.assertEqual('Ping Pong', output.name)
 2676         output_logs = self.notifier.get_logs()
 2677         self.assertEqual(0, len(output_logs))
 2678 
 2679     def test_delete(self):
 2680         request = unit_test_utils.get_fake_request()
 2681         self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2682         try:
 2683             self.controller.delete(request, UUID1)
 2684             output_logs = self.notifier.get_logs()
 2685             self.assertEqual(1, len(output_logs))
 2686             output_log = output_logs[0]
 2687             self.assertEqual('INFO', output_log['notification_type'])
 2688             self.assertEqual("image.delete", output_log['event_type'])
 2689         except Exception as e:
 2690             self.fail("Delete raised exception: %s" % e)
 2691 
 2692         deleted_img = self.db.image_get(request.context, UUID1,
 2693                                         force_show_deleted=True)
 2694         self.assertTrue(deleted_img['deleted'])
 2695         self.assertEqual('deleted', deleted_img['status'])
 2696         self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2697 
 2698     @mock.patch.object(store, 'get_store_from_store_identifier')
 2699     @mock.patch.object(store.location, 'get_location_from_uri_and_backend')
 2700     @mock.patch.object(store_utils, 'get_dir_separator')
 2701     def test_verify_staging_data_deleted_on_image_delete(
 2702             self, mock_get_dir_separator, mock_location,
 2703             mock_store):
 2704         self.config(enabled_backends={'fake-store': 'file'})
 2705         fake_staging_store = mock.Mock()
 2706         mock_store.return_value = fake_staging_store
 2707         mock_get_dir_separator.return_value = (
 2708             "/", "/tmp/os_glance_staging_store")
 2709         image_id = str(uuid.uuid4())
 2710         self.images = [
 2711             _db_fixture(image_id, owner=TENANT1,
 2712                         name='1',
 2713                         disk_format='raw',
 2714                         container_format='bare',
 2715                         status='importing',
 2716                         checksum=None,
 2717                         os_hash_algo=None,
 2718                         os_hash_value=None),
 2719         ]
 2720         self.db.image_create(None, self.images[0])
 2721         request = unit_test_utils.get_fake_request()
 2722         try:
 2723             self.controller.delete(request, image_id)
 2724             self.assertEqual(1, mock_store.call_count)
 2725             mock_store.assert_called_once_with("os_glance_staging_store")
 2726             self.assertEqual(1, mock_location.call_count)
 2727             fake_staging_store.delete.assert_called_once()
 2728         except Exception as e:
 2729             self.fail("Delete raised exception: %s" % e)
 2730 
 2731         deleted_img = self.db.image_get(request.context, image_id,
 2732                                         force_show_deleted=True)
 2733         self.assertTrue(deleted_img['deleted'])
 2734         self.assertEqual('deleted', deleted_img['status'])
 2735 
 2736     def test_delete_with_tags(self):
 2737         request = unit_test_utils.get_fake_request()
 2738         changes = [
 2739             {'op': 'replace', 'path': ['tags'],
 2740              'value': ['many', 'cool', 'new', 'tags']},
 2741         ]
 2742         self.controller.update(request, UUID1, changes)
 2743         self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2744         self.controller.delete(request, UUID1)
 2745         output_logs = self.notifier.get_logs()
 2746 
 2747         # Get `delete` event from logs
 2748         output_delete_logs = [output_log for output_log in output_logs
 2749                               if output_log['event_type'] == 'image.delete']
 2750 
 2751         self.assertEqual(1, len(output_delete_logs))
 2752         output_log = output_delete_logs[0]
 2753 
 2754         self.assertEqual('INFO', output_log['notification_type'])
 2755 
 2756         deleted_img = self.db.image_get(request.context, UUID1,
 2757                                         force_show_deleted=True)
 2758         self.assertTrue(deleted_img['deleted'])
 2759         self.assertEqual('deleted', deleted_img['status'])
 2760         self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2761 
 2762     def test_delete_disabled_notification(self):
 2763         self.config(disabled_notifications=["image.delete"])
 2764         request = unit_test_utils.get_fake_request()
 2765         self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2766         try:
 2767             self.controller.delete(request, UUID1)
 2768             output_logs = self.notifier.get_logs()
 2769             self.assertEqual(0, len(output_logs))
 2770         except Exception as e:
 2771             self.fail("Delete raised exception: %s" % e)
 2772 
 2773         deleted_img = self.db.image_get(request.context, UUID1,
 2774                                         force_show_deleted=True)
 2775         self.assertTrue(deleted_img['deleted'])
 2776         self.assertEqual('deleted', deleted_img['status'])
 2777         self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2778 
 2779     def test_delete_queued_updates_status(self):
 2780         """Ensure status of queued image is updated (LP bug #1048851)"""
 2781         request = unit_test_utils.get_fake_request(is_admin=True)
 2782         image = self.db.image_create(request.context, {'status': 'queued'})
 2783         image_id = image['id']
 2784         self.controller.delete(request, image_id)
 2785 
 2786         image = self.db.image_get(request.context, image_id,
 2787                                   force_show_deleted=True)
 2788         self.assertTrue(image['deleted'])
 2789         self.assertEqual('deleted', image['status'])
 2790 
 2791     def test_delete_queued_updates_status_delayed_delete(self):
 2792         """Ensure status of queued image is updated (LP bug #1048851).
 2793 
 2794         Must be set to 'deleted' when delayed_delete isenabled.
 2795         """
 2796         self.config(delayed_delete=True)
 2797 
 2798         request = unit_test_utils.get_fake_request(is_admin=True)
 2799         image = self.db.image_create(request.context, {'status': 'queued'})
 2800         image_id = image['id']
 2801         self.controller.delete(request, image_id)
 2802 
 2803         image = self.db.image_get(request.context, image_id,
 2804                                   force_show_deleted=True)
 2805         self.assertTrue(image['deleted'])
 2806         self.assertEqual('deleted', image['status'])
 2807 
 2808     def test_delete_not_in_store(self):
 2809         request = unit_test_utils.get_fake_request()
 2810         self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2811         for k in self.store.data:
 2812             if UUID1 in k:
 2813                 del self.store.data[k]
 2814                 break
 2815 
 2816         self.controller.delete(request, UUID1)
 2817         deleted_img = self.db.image_get(request.context, UUID1,
 2818                                         force_show_deleted=True)
 2819         self.assertTrue(deleted_img['deleted'])
 2820         self.assertEqual('deleted', deleted_img['status'])
 2821         self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2822 
 2823     def test_delayed_delete(self):
 2824         self.config(delayed_delete=True)
 2825         request = unit_test_utils.get_fake_request()
 2826         self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2827 
 2828         self.controller.delete(request, UUID1)
 2829         deleted_img = self.db.image_get(request.context, UUID1,
 2830                                         force_show_deleted=True)
 2831         self.assertTrue(deleted_img['deleted'])
 2832         self.assertEqual('pending_delete', deleted_img['status'])
 2833         self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
 2834 
 2835     def test_delete_non_existent(self):
 2836         request = unit_test_utils.get_fake_request()
 2837         self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
 2838                           request, str(uuid.uuid4()))
 2839 
 2840     def test_delete_already_deleted_image_admin(self):
 2841         request = unit_test_utils.get_fake_request(is_admin=True)
 2842         self.controller.delete(request, UUID1)
 2843         self.assertRaises(webob.exc.HTTPNotFound,
 2844                           self.controller.delete, request, UUID1)
 2845 
 2846     def test_delete_not_allowed(self):
 2847         request = unit_test_utils.get_fake_request()
 2848         self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
 2849                           request, UUID4)
 2850 
 2851     def test_delete_in_use(self):
 2852         def fake_safe_delete_from_backend(self, *args, **kwargs):
 2853             raise store.exceptions.InUseByStore()
 2854         self.mock_object(self.store_utils, 'safe_delete_from_backend',
 2855                          fake_safe_delete_from_backend)
 2856         request = unit_test_utils.get_fake_request()
 2857         self.assertRaises(webob.exc.HTTPConflict, self.controller.delete,
 2858                           request, UUID1)
 2859 
 2860     def test_delete_has_snapshot(self):
 2861         def fake_safe_delete_from_backend(self, *args, **kwargs):
 2862             raise store.exceptions.HasSnapshot()
 2863         self.mock_object(self.store_utils, 'safe_delete_from_backend',
 2864                          fake_safe_delete_from_backend)
 2865         request = unit_test_utils.get_fake_request()
 2866         self.assertRaises(webob.exc.HTTPConflict, self.controller.delete,
 2867                           request, UUID1)
 2868 
 2869     def test_delete_to_unallowed_status(self):
 2870         # from deactivated to pending-delete
 2871         self.config(delayed_delete=True)
 2872         request = unit_test_utils.get_fake_request(is_admin=True)
 2873         self.action_controller.deactivate(request, UUID1)
 2874 
 2875         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete,
 2876                           request, UUID1)
 2877 
 2878     def test_delete_uploading_status_image(self):
 2879         """Ensure uploading image is deleted (LP bug #1733289)
 2880         Ensure image stuck in uploading state is deleted (LP bug #1836140)
 2881         """
 2882         request = unit_test_utils.get_fake_request(is_admin=True)
 2883         image = self.db.image_create(request.context, {'status': 'uploading'})
 2884         image_id = image['id']
 2885         with mock.patch.object(os.path, 'exists') as mock_exists:
 2886             mock_exists.return_value = True
 2887             with mock.patch.object(os, "unlink") as mock_unlik:
 2888                 self.controller.delete(request, image_id)
 2889 
 2890                 self.assertEqual(1, mock_exists.call_count)
 2891                 self.assertEqual(1, mock_unlik.call_count)
 2892 
 2893         # Ensure that image is deleted
 2894         image = self.db.image_get(request.context, image_id,
 2895                                   force_show_deleted=True)
 2896         self.assertTrue(image['deleted'])
 2897         self.assertEqual('deleted', image['status'])
 2898 
 2899     def test_deletion_of_staging_data_failed(self):
 2900         """Ensure uploading image is deleted (LP bug #1733289)
 2901         Ensure image stuck in uploading state is deleted (LP bug #1836140)
 2902         """
 2903         request = unit_test_utils.get_fake_request(is_admin=True)
 2904         image = self.db.image_create(request.context, {'status': 'uploading'})
 2905         image_id = image['id']
 2906         with mock.patch.object(os.path, 'exists') as mock_exists:
 2907             mock_exists.return_value = False
 2908             with mock.patch.object(os, "unlink") as mock_unlik:
 2909                 self.controller.delete(request, image_id)
 2910 
 2911                 self.assertEqual(1, mock_exists.call_count)
 2912                 self.assertEqual(0, mock_unlik.call_count)
 2913 
 2914         # Ensure that image is deleted
 2915         image = self.db.image_get(request.context, image_id,
 2916                                   force_show_deleted=True)
 2917         self.assertTrue(image['deleted'])
 2918         self.assertEqual('deleted', image['status'])
 2919 
 2920     def test_delete_from_store_no_multistore(self):
 2921         request = unit_test_utils.get_fake_request()
 2922         self.assertRaises(webob.exc.HTTPNotFound,
 2923                           self.controller.delete_from_store, request,
 2924                           "the IDs should", "not matter")
 2925 
 2926     def test_index_with_invalid_marker(self):
 2927         fake_uuid = str(uuid.uuid4())
 2928         request = unit_test_utils.get_fake_request()
 2929         self.assertRaises(webob.exc.HTTPBadRequest,
 2930                           self.controller.index, request, marker=fake_uuid)
 2931 
 2932     def test_invalid_locations_op_pos(self):
 2933         pos = self.controller._get_locations_op_pos(None, 2, True)
 2934         self.assertIsNone(pos)
 2935         pos = self.controller._get_locations_op_pos('1', None, True)
 2936         self.assertIsNone(pos)
 2937 
 2938     def test_image_import(self):
 2939         request = unit_test_utils.get_fake_request()
 2940         with mock.patch.object(
 2941                 glance.api.authorization.ImageRepoProxy, 'get') as mock_get:
 2942             mock_get.return_value = FakeImage(status='uploading')
 2943             output = self.controller.import_image(
 2944                 request, UUID4, {'method': {'name': 'glance-direct'}})
 2945 
 2946         self.assertEqual(UUID4, output)
 2947 
 2948     @mock.patch.object(glance.domain.TaskFactory, 'new_task')
 2949     @mock.patch.object(glance.api.authorization.ImageRepoProxy, 'get')
 2950     def test_image_import_not_allowed(self, mock_get, mock_new_task):
 2951         # NOTE(danms): FakeImage is owned by utils.TENANT1. Try to do the
 2952         # import as TENANT2 and we should get an HTTPForbidden
 2953         request = unit_test_utils.get_fake_request(tenant=TENANT2)
 2954         mock_get.return_value = FakeImage(status='uploading')
 2955         self.assertRaises(webob.exc.HTTPForbidden,
 2956                           self.controller.import_image,
 2957                           request, UUID4, {'method': {'name':
 2958                                                       'glance-direct'}})
 2959         # NOTE(danms): Make sure we failed early and never even created
 2960         # a task
 2961         mock_new_task.assert_not_called()
 2962 
 2963     def test_delete_encryption_key_no_encryption_key(self):
 2964         request = unit_test_utils.get_fake_request()
 2965         fake_encryption_key = self.controller._key_manager.store(
 2966             request.context, mock.Mock())
 2967         image = _domain_fixture(
 2968             UUID2, name='image-2', owner=TENANT2,
 2969             checksum='ca425b88f047ce8ec45ee90e813ada91',
 2970             os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
 2971             created_at=DATETIME, updated_at=DATETIME, size=1024,
 2972             virtual_size=3072, extra_properties={})
 2973         self.controller._delete_encryption_key(request.context, image)
 2974         # Make sure the encrytion key is still there
 2975         key = self.controller._key_manager.get(request.context,
 2976                                                fake_encryption_key)
 2977         self.assertEqual(fake_encryption_key, key._id)
 2978 
 2979     def test_delete_encryption_key_no_deletion_policy(self):
 2980         request = unit_test_utils.get_fake_request()
 2981         fake_encryption_key = self.controller._key_manager.store(
 2982             request.context, mock.Mock())
 2983         props = {
 2984             'cinder_encryption_key_id': fake_encryption_key,
 2985         }
 2986         image = _domain_fixture(
 2987             UUID2, name='image-2', owner=TENANT2,
 2988             checksum='ca425b88f047ce8ec45ee90e813ada91',
 2989             os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
 2990             created_at=DATETIME, updated_at=DATETIME, size=1024,
 2991             virtual_size=3072, extra_properties=props)
 2992         self.controller._delete_encryption_key(request.context, image)
 2993         # Make sure the encrytion key is still there
 2994         key = self.controller._key_manager.get(request.context,
 2995                                                fake_encryption_key)
 2996         self.assertEqual(fake_encryption_key, key._id)
 2997 
 2998     def test_delete_encryption_key_do_not_delete(self):
 2999         request = unit_test_utils.get_fake_request()
 3000         fake_encryption_key = self.controller._key_manager.store(
 3001             request.context, mock.Mock())
 3002         props = {
 3003             'cinder_encryption_key_id': fake_encryption_key,
 3004             'cinder_encryption_key_deletion_policy': 'do_not_delete',
 3005         }
 3006         image = _domain_fixture(
 3007             UUID2, name='image-2', owner=TENANT2,
 3008             checksum='ca425b88f047ce8ec45ee90e813ada91',
 3009             os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
 3010             created_at=DATETIME, updated_at=DATETIME, size=1024,
 3011             virtual_size=3072, extra_properties=props)
 3012         self.controller._delete_encryption_key(request.context, image)
 3013         # Make sure the encrytion key is still there
 3014         key = self.controller._key_manager.get(request.context,
 3015                                                fake_encryption_key)
 3016         self.assertEqual(fake_encryption_key, key._id)
 3017 
 3018     def test_delete_encryption_key_forbidden(self):
 3019         request = unit_test_utils.get_fake_request()
 3020         fake_encryption_key = self.controller._key_manager.store(
 3021             request.context, mock.Mock())
 3022         props = {
 3023             'cinder_encryption_key_id': fake_encryption_key,
 3024             'cinder_encryption_key_deletion_policy': 'on_image_deletion',
 3025         }
 3026         image = _domain_fixture(
 3027             UUID2, name='image-2', owner=TENANT2,
 3028             checksum='ca425b88f047ce8ec45ee90e813ada91',
 3029             os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
 3030             created_at=DATETIME, updated_at=DATETIME, size=1024,
 3031             virtual_size=3072, extra_properties=props)
 3032         with mock.patch.object(self.controller._key_manager, 'delete',
 3033                                side_effect=castellan_exception.Forbidden):
 3034             self.controller._delete_encryption_key(request.context, image)
 3035         # Make sure the encrytion key is still there
 3036         key = self.controller._key_manager.get(request.context,
 3037                                                fake_encryption_key)
 3038         self.assertEqual(fake_encryption_key, key._id)
 3039 
 3040     def test_delete_encryption_key_not_found(self):
 3041         request = unit_test_utils.get_fake_request()
 3042         fake_encryption_key = self.controller._key_manager.store(
 3043             request.context, mock.Mock())
 3044         props = {
 3045             'cinder_encryption_key_id': fake_encryption_key,
 3046             'cinder_encryption_key_deletion_policy': 'on_image_deletion',
 3047         }
 3048         image = _domain_fixture(
 3049             UUID2, name='image-2', owner=TENANT2,
 3050             checksum='ca425b88f047ce8ec45ee90e813ada91',
 3051             os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
 3052             created_at=DATETIME, updated_at=DATETIME, size=1024,
 3053             virtual_size=3072, extra_properties=props)
 3054         with mock.patch.object(self.controller._key_manager, 'delete',
 3055                                side_effect=castellan_exception.ManagedObjectNotFoundError):  # noqa
 3056             self.controller._delete_encryption_key(request.context, image)
 3057         # Make sure the encrytion key is still there
 3058         key = self.controller._key_manager.get(request.context,
 3059                                                fake_encryption_key)
 3060         self.assertEqual(fake_encryption_key, key._id)
 3061 
 3062     def test_delete_encryption_key_error(self):
 3063         request = unit_test_utils.get_fake_request()
 3064         fake_encryption_key = self.controller._key_manager.store(
 3065             request.context, mock.Mock())
 3066         props = {
 3067             'cinder_encryption_key_id': fake_encryption_key,
 3068             'cinder_encryption_key_deletion_policy': 'on_image_deletion',
 3069         }
 3070         image = _domain_fixture(
 3071             UUID2, name='image-2', owner=TENANT2,
 3072             checksum='ca425b88f047ce8ec45ee90e813ada91',
 3073             os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
 3074             created_at=DATETIME, updated_at=DATETIME, size=1024,
 3075             virtual_size=3072, extra_properties=props)
 3076         with mock.patch.object(self.controller._key_manager, 'delete',
 3077                                side_effect=castellan_exception.KeyManagerError):  # noqa
 3078             self.controller._delete_encryption_key(request.context, image)
 3079         # Make sure the encrytion key is still there
 3080         key = self.controller._key_manager.get(request.context,
 3081                                                fake_encryption_key)
 3082         self.assertEqual(fake_encryption_key, key._id)
 3083 
 3084     def test_delete_encryption_key(self):
 3085         request = unit_test_utils.get_fake_request()
 3086         fake_encryption_key = self.controller._key_manager.store(
 3087             request.context, mock.Mock())
 3088         props = {
 3089             'cinder_encryption_key_id': fake_encryption_key,
 3090             'cinder_encryption_key_deletion_policy': 'on_image_deletion',
 3091         }
 3092         image = _domain_fixture(
 3093             UUID2, name='image-2', owner=TENANT2,
 3094             checksum='ca425b88f047ce8ec45ee90e813ada91',
 3095             os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH1,
 3096             created_at=DATETIME, updated_at=DATETIME, size=1024,
 3097             virtual_size=3072, extra_properties=props)
 3098         self.controller._delete_encryption_key(request.context, image)
 3099         # Make sure the encrytion key is gone
 3100         self.assertRaises(KeyError,
 3101                           self.controller._key_manager.get,
 3102                           request.context, fake_encryption_key)
 3103 
 3104     def test_delete_no_encryption_key_id(self):
 3105         request = unit_test_utils.get_fake_request()
 3106         extra_props = {
 3107             'cinder_encryption_key_deletion_policy': 'on_image_deletion',
 3108         }
 3109         created_image = self.controller.create(request,
 3110                                                image={'name': 'image-1'},
 3111                                                extra_properties=extra_props,
 3112                                                tags=[])
 3113         image_id = created_image.image_id
 3114         self.controller.delete(request, image_id)
 3115         # Ensure that image is deleted
 3116         image = self.db.image_get(request.context, image_id,
 3117                                   force_show_deleted=True)
 3118         self.assertTrue(image['deleted'])
 3119         self.assertEqual('deleted', image['status'])
 3120 
 3121     def test_delete_invalid_encryption_key_id(self):
 3122         request = unit_test_utils.get_fake_request()
 3123         extra_props = {
 3124             'cinder_encryption_key_id': 'invalid',
 3125             'cinder_encryption_key_deletion_policy': 'on_image_deletion',
 3126         }
 3127         created_image = self.controller.create(request,
 3128                                                image={'name': 'image-1'},
 3129                                                extra_properties=extra_props,
 3130                                                tags=[])
 3131         image_id = created_image.image_id
 3132         self.controller.delete(request, image_id)
 3133         # Ensure that image is deleted
 3134         image = self.db.image_get(request.context, image_id,
 3135                                   force_show_deleted=True)
 3136         self.assertTrue(image['deleted'])
 3137         self.assertEqual('deleted', image['status'])
 3138 
 3139     def test_delete_invalid_encryption_key_deletion_policy(self):
 3140         request = unit_test_utils.get_fake_request()
 3141         extra_props = {
 3142             'cinder_encryption_key_deletion_policy': 'invalid',
 3143         }
 3144         created_image = self.controller.create(request,
 3145                                                image={'name': 'image-1'},
 3146                                                extra_properties=extra_props,
 3147                                                tags=[])
 3148         image_id = created_image.image_id
 3149         self.controller.delete(request, image_id)
 3150         # Ensure that image is deleted
 3151         image = self.db.image_get(request.context, image_id,
 3152                                   force_show_deleted=True)
 3153         self.assertTrue(image['deleted'])
 3154         self.assertEqual('deleted', image['status'])
 3155 
 3156 
 3157 class TestImagesControllerPolicies(base.IsolatedUnitTest):
 3158 
 3159     def setUp(self):
 3160         super(TestImagesControllerPolicies, self).setUp()
 3161         self.db = unit_test_utils.FakeDB()
 3162         self.policy = unit_test_utils.FakePolicyEnforcer()
 3163         self.controller = glance.api.v2.images.ImagesController(self.db,
 3164                                                                 self.policy)
 3165         store = unit_test_utils.FakeStoreAPI()
 3166         self.store_utils = unit_test_utils.FakeStoreUtils(store)
 3167 
 3168     def test_index_unauthorized(self):
 3169         rules = {"get_images": False}
 3170         self.policy.set_rules(rules)
 3171         request = unit_test_utils.get_fake_request()
 3172         self.assertRaises(webob.exc.HTTPForbidden, self.controller.index,
 3173                           request)
 3174 
 3175     def test_show_unauthorized(self):
 3176         rules = {"get_image": False}
 3177         self.policy.set_rules(rules)
 3178         request = unit_test_utils.get_fake_request()
 3179         self.assertRaises(webob.exc.HTTPForbidden, self.controller.show,
 3180                           request, image_id=UUID2)
 3181 
 3182     def test_create_image_unauthorized(self):
 3183         rules = {"add_image": False}
 3184         self.policy.set_rules(rules)
 3185         request = unit_test_utils.get_fake_request()
 3186         image = {'name': 'image-1'}
 3187         extra_properties = {}
 3188         tags = []
 3189         self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
 3190                           request, image, extra_properties, tags)
 3191 
 3192     def test_create_public_image_unauthorized(self):
 3193         rules = {"publicize_image": False}
 3194         self.policy.set_rules(rules)
 3195         request = unit_test_utils.get_fake_request()
 3196         image = {'name': 'image-1', 'visibility': 'public'}
 3197         extra_properties = {}
 3198         tags = []
 3199         self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
 3200                           request, image, extra_properties, tags)
 3201 
 3202     def test_create_community_image_unauthorized(self):
 3203         rules = {"communitize_image": False}
 3204         self.policy.set_rules(rules)
 3205         request = unit_test_utils.get_fake_request()
 3206         image = {'name': 'image-c1', 'visibility': 'community'}
 3207         extra_properties = {}
 3208         tags = []
 3209         self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
 3210                           request, image, extra_properties, tags)
 3211 
 3212     def test_update_unauthorized(self):
 3213         rules = {"modify_image": False}
 3214         self.policy.set_rules(rules)
 3215         request = unit_test_utils.get_fake_request()
 3216         changes = [{'op': 'replace', 'path': ['name'], 'value': 'image-2'}]
 3217         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 3218                           request, UUID1, changes)
 3219 
 3220     def test_update_publicize_image_unauthorized(self):
 3221         rules = {"publicize_image": False}
 3222         self.policy.set_rules(rules)
 3223         request = unit_test_utils.get_fake_request()
 3224         changes = [{'op': 'replace', 'path': ['visibility'],
 3225                     'value': 'public'}]
 3226         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 3227                           request, UUID1, changes)
 3228 
 3229     def test_update_communitize_image_unauthorized(self):
 3230         rules = {"communitize_image": False}
 3231         self.policy.set_rules(rules)
 3232         request = unit_test_utils.get_fake_request()
 3233         changes = [{'op': 'replace', 'path': ['visibility'],
 3234                     'value': 'community'}]
 3235         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 3236                           request, UUID1, changes)
 3237 
 3238     def test_update_depublicize_image_unauthorized(self):
 3239         rules = {"publicize_image": False}
 3240         self.policy.set_rules(rules)
 3241         request = unit_test_utils.get_fake_request()
 3242         changes = [{'op': 'replace', 'path': ['visibility'],
 3243                     'value': 'private'}]
 3244         output = self.controller.update(request, UUID1, changes)
 3245         self.assertEqual('private', output.visibility)
 3246 
 3247     def test_update_decommunitize_image_unauthorized(self):
 3248         rules = {"communitize_image": False}
 3249         self.policy.set_rules(rules)
 3250         request = unit_test_utils.get_fake_request()
 3251         changes = [{'op': 'replace', 'path': ['visibility'],
 3252                     'value': 'private'}]
 3253         output = self.controller.update(request, UUID1, changes)
 3254         self.assertEqual('private', output.visibility)
 3255 
 3256     def test_update_get_image_location_unauthorized(self):
 3257         rules = {"get_image_location": False}
 3258         self.policy.set_rules(rules)
 3259         request = unit_test_utils.get_fake_request()
 3260         changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
 3261         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 3262                           request, UUID1, changes)
 3263 
 3264     def test_update_set_image_location_unauthorized(self):
 3265         def fake_delete_image_location_from_backend(self, *args, **kwargs):
 3266             pass
 3267 
 3268         rules = {"set_image_location": False}
 3269         self.policy.set_rules(rules)
 3270         new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
 3271         request = unit_test_utils.get_fake_request()
 3272         changes = [{'op': 'add', 'path': ['locations', '-'],
 3273                     'value': new_location}]
 3274         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 3275                           request, UUID1, changes)
 3276 
 3277     def test_update_delete_image_location_unauthorized(self):
 3278         rules = {"delete_image_location": False}
 3279         self.policy.set_rules(rules)
 3280         request = unit_test_utils.get_fake_request()
 3281         changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
 3282         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
 3283                           request, UUID1, changes)
 3284 
 3285     def test_delete_unauthorized(self):
 3286         rules = {"delete_image": False}
 3287         self.policy.set_rules(rules)
 3288         request = unit_test_utils.get_fake_request()
 3289         self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
 3290                           request, UUID1)
 3291 
 3292 
 3293 class TestImagesDeserializer(test_utils.BaseTestCase):
 3294 
 3295     def setUp(self):
 3296         super(TestImagesDeserializer, self).setUp()
 3297         self.deserializer = glance.api.v2.images.RequestDeserializer()
 3298 
 3299     def test_create_minimal(self):
 3300         request = unit_test_utils.get_fake_request()
 3301         request.body = jsonutils.dump_as_bytes({})
 3302         output = self.deserializer.create(request)
 3303         expected = {'image': {}, 'extra_properties': {}, 'tags': []}
 3304         self.assertEqual(expected, output)
 3305 
 3306     def test_create_invalid_id(self):
 3307         request = unit_test_utils.get_fake_request()
 3308         request.body = jsonutils.dump_as_bytes({'id': 'gabe'})
 3309         self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
 3310                           request)
 3311 
 3312     def test_create_id_to_image_id(self):
 3313         request = unit_test_utils.get_fake_request()
 3314         request.body = jsonutils.dump_as_bytes({'id': UUID4})
 3315         output = self.deserializer.create(request)
 3316         expected = {'image': {'image_id': UUID4},
 3317                     'extra_properties': {},
 3318                     'tags': []}
 3319         self.assertEqual(expected, output)
 3320 
 3321     def test_create_no_body(self):
 3322         request = unit_test_utils.get_fake_request()
 3323         self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
 3324                           request)
 3325 
 3326     def test_create_full(self):
 3327         request = unit_test_utils.get_fake_request()
 3328         request.body = jsonutils.dump_as_bytes({
 3329             'id': UUID3,
 3330             'name': 'image-1',
 3331             'visibility': 'public',
 3332             'tags': ['one', 'two'],
 3333             'container_format': 'ami',
 3334             'disk_format': 'ami',
 3335             'min_ram': 128,
 3336             'min_disk': 10,
 3337             'foo': 'bar',
 3338             'protected': True,
 3339         })
 3340         output = self.deserializer.create(request)
 3341         properties = {
 3342             'image_id': UUID3,
 3343             'name': 'image-1',
 3344             'visibility': 'public',
 3345             'container_format': 'ami',
 3346             'disk_format': 'ami',
 3347             'min_ram': 128,
 3348             'min_disk': 10,
 3349             'protected': True,
 3350         }
 3351         self.maxDiff = None
 3352         expected = {'image': properties,
 3353                     'extra_properties': {'foo': 'bar'},
 3354                     'tags': ['one', 'two']}
 3355         self.assertEqual(expected, output)
 3356 
 3357     def test_create_invalid_property_key(self):
 3358         request = unit_test_utils.get_fake_request()
 3359         request.body = jsonutils.dump_as_bytes({
 3360             'id': UUID3,
 3361             'name': 'image-1',
 3362             'visibility': 'public',
 3363             'tags': ['one', 'two'],
 3364             'container_format': 'ami',
 3365             'disk_format': 'ami',
 3366             'min_ram': 128,
 3367             'min_disk': 10,
 3368             'f' * 256: 'bar',
 3369             'protected': True,
 3370         })
 3371         self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
 3372                           request)
 3373 
 3374     def test_create_readonly_attributes_forbidden(self):
 3375         bodies = [
 3376             {'direct_url': 'http://example.com'},
 3377             {'self': 'http://example.com'},
 3378             {'file': 'http://example.com'},
 3379             {'schema': 'http://example.com'},
 3380         ]
 3381 
 3382         for body in bodies:
 3383             request = unit_test_utils.get_fake_request()
 3384             request.body = jsonutils.dump_as_bytes(body)
 3385             self.assertRaises(webob.exc.HTTPForbidden,
 3386                               self.deserializer.create, request)
 3387 
 3388     def _get_fake_patch_request(self, content_type_minor_version=1):
 3389         request = unit_test_utils.get_fake_request()
 3390         template = 'application/openstack-images-v2.%d-json-patch'
 3391         request.content_type = template % content_type_minor_version
 3392         return request
 3393 
 3394     def test_update_empty_body(self):
 3395         request = self._get_fake_patch_request()
 3396         request.body = jsonutils.dump_as_bytes([])
 3397         output = self.deserializer.update(request)
 3398         expected = {'changes': []}
 3399         self.assertEqual(expected, output)
 3400 
 3401     def test_update_unsupported_content_type(self):
 3402         request = unit_test_utils.get_fake_request()
 3403         request.content_type = 'application/json-patch'
 3404         request.body = jsonutils.dump_as_bytes([])
 3405         try:
 3406             self.deserializer.update(request)
 3407         except webob.exc.HTTPUnsupportedMediaType as e:
 3408             # desired result, but must have correct Accept-Patch header
 3409             accept_patch = ['application/openstack-images-v2.1-json-patch',
 3410                             'application/openstack-images-v2.0-json-patch']
 3411             expected = ', '.join(sorted(accept_patch))
 3412             self.assertEqual(expected, e.headers['Accept-Patch'])
 3413         else:
 3414             self.fail('Did not raise HTTPUnsupportedMediaType')
 3415 
 3416     def test_update_body_not_a_list(self):
 3417         bodies = [
 3418             {'op': 'add', 'path': '/someprop', 'value': 'somevalue'},
 3419             'just some string',
 3420             123,
 3421             True,
 3422             False,
 3423             None,
 3424         ]
 3425         for body in bodies:
 3426             request = self._get_fake_patch_request()
 3427             request.body = jsonutils.dump_as_bytes(body)
 3428             self.assertRaises(webob.exc.HTTPBadRequest,
 3429                               self.deserializer.update, request)
 3430 
 3431     def test_update_invalid_changes(self):
 3432         changes = [
 3433             ['a', 'list', 'of', 'stuff'],
 3434             'just some string',
 3435             123,
 3436             True,
 3437             False,
 3438             None,
 3439             {'op': 'invalid', 'path': '/name', 'value': 'fedora'}
 3440         ]
 3441         for change in changes:
 3442             request = self._get_fake_patch_request()
 3443             request.body = jsonutils.dump_as_bytes([change])
 3444             self.assertRaises(webob.exc.HTTPBadRequest,
 3445                               self.deserializer.update, request)
 3446 
 3447     def test_update_invalid_validation_data(self):
 3448         request = self._get_fake_patch_request()
 3449         changes = [{
 3450             'op': 'add',
 3451             'path': '/locations/0',
 3452             'value': {
 3453                 'url': 'http://localhost/fake',
 3454                 'metadata': {},
 3455             }
 3456         }]
 3457 
 3458         changes[0]['value']['validation_data'] = {
 3459             'os_hash_algo': 'sha512',
 3460             'os_hash_value': MULTIHASH1,
 3461             'checksum': CHKSUM,
 3462         }
 3463         request.body = jsonutils.dump_as_bytes(changes)
 3464         self.deserializer.update(request)
 3465 
 3466         changes[0]['value']['validation_data'] = {
 3467             'os_hash_algo': 'sha512',
 3468             'os_hash_value': MULTIHASH1,
 3469             'checksum': CHKSUM,
 3470             'bogus_key': 'bogus_value',
 3471         }
 3472         request.body = jsonutils.dump_as_bytes(changes)
 3473         six.assertRaisesRegex(self,
 3474                               webob.exc.HTTPBadRequest,
 3475                               'Additional properties are not allowed',
 3476                               self.deserializer.update, request)
 3477 
 3478         changes[0]['value']['validation_data'] = {
 3479             'checksum': CHKSUM,
 3480         }
 3481         request.body = jsonutils.dump_as_bytes(changes)
 3482         six.assertRaisesRegex(self,
 3483                               webob.exc.HTTPBadRequest,
 3484                               'os_hash.* is a required property',
 3485                               self.deserializer.update, request)
 3486 
 3487     def test_update(self):
 3488         request = self._get_fake_patch_request()
 3489         body = [
 3490             {'op': 'replace', 'path': '/name', 'value': 'fedora'},
 3491             {'op': 'replace', 'path': '/tags', 'value': ['king', 'kong']},
 3492             {'op': 'replace', 'path': '/foo', 'value': 'bar'},
 3493             {'op': 'add', 'path': '/bebim', 'value': 'bap'},
 3494             {'op': 'remove', 'path': '/sparks'},
 3495             {'op': 'add', 'path': '/locations/-',
 3496              'value': {'url': 'scheme3://path3', 'metadata': {}}},
 3497             {'op': 'add', 'path': '/locations/10',
 3498              'value': {'url': 'scheme4://path4', 'metadata': {}}},
 3499             {'op': 'remove', 'path': '/locations/2'},
 3500             {'op': 'replace', 'path': '/locations', 'value': []},
 3501             {'op': 'replace', 'path': '/locations',
 3502              'value': [{'url': 'scheme5://path5', 'metadata': {}},
 3503                        {'url': 'scheme6://path6', 'metadata': {}}]},
 3504         ]
 3505         request.body = jsonutils.dump_as_bytes(body)
 3506         output = self.deserializer.update(request)
 3507         expected = {'changes': [
 3508             {'json_schema_version': 10, 'op': 'replace',
 3509              'path': ['name'], 'value': 'fedora'},
 3510             {'json_schema_version': 10, 'op': 'replace',
 3511              'path': ['tags'], 'value': ['king', 'kong']},
 3512             {'json_schema_version': 10, 'op': 'replace',
 3513              'path': ['foo'], 'value': 'bar'},
 3514             {'json_schema_version': 10, 'op': 'add',
 3515              'path': ['bebim'], 'value': 'bap'},
 3516             {'json_schema_version': 10, 'op': 'remove',
 3517              'path': ['sparks']},
 3518             {'json_schema_version': 10, 'op': 'add',
 3519              'path': ['locations', '-'],
 3520              'value': {'url': 'scheme3://path3', 'metadata': {}}},
 3521             {'json_schema_version': 10, 'op': 'add',
 3522              'path': ['locations', '10'],
 3523              'value': {'url': 'scheme4://path4', 'metadata': {}}},
 3524             {'json_schema_version': 10, 'op': 'remove',
 3525              'path': ['locations', '2']},
 3526             {'json_schema_version': 10, 'op': 'replace',
 3527              'path': ['locations'], 'value': []},
 3528             {'json_schema_version': 10, 'op': 'replace',
 3529              'path': ['locations'],
 3530              'value': [{'url': 'scheme5://path5', 'metadata': {}},
 3531                        {'url': 'scheme6://path6', 'metadata': {}}]},
 3532         ]}
 3533         self.assertEqual(expected, output)
 3534 
 3535     def test_update_v2_0_compatibility(self):
 3536         request = self._get_fake_patch_request(content_type_minor_version=0)
 3537         body = [
 3538             {'replace': '/name', 'value': 'fedora'},
 3539             {'replace': '/tags', 'value': ['king', 'kong']},
 3540             {'replace': '/foo', 'value': 'bar'},
 3541             {'add': '/bebim', 'value': 'bap'},
 3542             {'remove': '/sparks'},
 3543             {'add': '/locations/-', 'value': {'url': 'scheme3://path3',
 3544                                               'metadata': {}}},
 3545             {'add': '/locations/10', 'value': {'url': 'scheme4://path4',
 3546                                                'metadata': {}}},
 3547             {'remove': '/locations/2'},
 3548             {'replace': '/locations', 'value': []},
 3549             {'replace': '/locations',
 3550              'value': [{'url': 'scheme5://path5', 'metadata': {}},
 3551                        {'url': 'scheme6://path6', 'metadata': {}}]},
 3552         ]
 3553         request.body = jsonutils.dump_as_bytes(body)
 3554         output = self.deserializer.update(request)
 3555         expected = {'changes': [
 3556             {'json_schema_version': 4, 'op': 'replace',
 3557              'path': ['name'], 'value': 'fedora'},
 3558             {'json_schema_version': 4, 'op': 'replace',
 3559              'path': ['tags'], 'value': ['king', 'kong']},
 3560             {'json_schema_version': 4, 'op': 'replace',
 3561              'path': ['foo'], 'value': 'bar'},
 3562             {'json_schema_version': 4, 'op': 'add',
 3563              'path': ['bebim'], 'value': 'bap'},
 3564             {'json_schema_version': 4, 'op': 'remove', 'path': ['sparks']},
 3565             {'json_schema_version': 4, 'op': 'add',
 3566              'path': ['locations', '-'],
 3567              'value': {'url': 'scheme3://path3', 'metadata': {}}},
 3568             {'json_schema_version': 4, 'op': 'add',
 3569              'path': ['locations', '10'],
 3570              'value': {'url': 'scheme4://path4', 'metadata': {}}},
 3571             {'json_schema_version': 4, 'op': 'remove',
 3572              'path': ['locations', '2']},
 3573             {'json_schema_version': 4, 'op': 'replace',
 3574              'path': ['locations'], 'value': []},
 3575             {'json_schema_version': 4, 'op': 'replace', 'path': ['locations'],
 3576              'value': [{'url': 'scheme5://path5', 'metadata': {}},
 3577                        {'url': 'scheme6://path6', 'metadata': {}}]},
 3578         ]}
 3579         self.assertEqual(expected, output)
 3580 
 3581     def test_update_base_attributes(self):
 3582         request = self._get_fake_patch_request()
 3583         body = [
 3584             {'op': 'replace', 'path': '/name', 'value': 'fedora'},
 3585             {'op': 'replace', 'path': '/visibility', 'value': 'public'},
 3586             {'op': 'replace', 'path': '/tags', 'value': ['king', 'kong']},
 3587             {'op': 'replace', 'path': '/protected', 'value': True},
 3588             {'op': 'replace', 'path': '/container_format', 'value': 'bare'},
 3589             {'op': 'replace', 'path': '/disk_format', 'value': 'raw'},
 3590             {'op': 'replace', 'path': '/min_ram', 'value': 128},
 3591             {'op': 'replace', 'path': '/min_disk', 'value': 10},
 3592             {'op': 'replace', 'path': '/locations', 'value': []},
 3593             {'op': 'replace', 'path': '/locations',
 3594              'value': [{'url': 'scheme5://path5', 'metadata': {}},
 3595                        {'url': 'scheme6://path6', 'metadata': {}}]}
 3596         ]
 3597         request.body = jsonutils.dump_as_bytes(body)
 3598         output = self.deserializer.update(request)
 3599         expected = {'changes': [
 3600             {'json_schema_version': 10, 'op': 'replace',
 3601              'path': ['name'], 'value': 'fedora'},
 3602             {'json_schema_version': 10, 'op': 'replace',
 3603              'path': ['visibility'], 'value': 'public'},
 3604             {'json_schema_version': 10, 'op': 'replace',
 3605              'path': ['tags'], 'value': ['king', 'kong']},
 3606             {'json_schema_version': 10, 'op': 'replace',
 3607              'path': ['protected'], 'value': True},
 3608             {'json_schema_version': 10, 'op': 'replace',
 3609              'path': ['container_format'], 'value': 'bare'},
 3610             {'json_schema_version': 10, 'op': 'replace',
 3611              'path': ['disk_format'], 'value': 'raw'},
 3612             {'json_schema_version': 10, 'op': 'replace',
 3613              'path': ['min_ram'], 'value': 128},
 3614             {'json_schema_version': 10, 'op': 'replace',
 3615              'path': ['min_disk'], 'value': 10},
 3616             {'json_schema_version': 10, 'op': 'replace',
 3617              'path': ['locations'], 'value': []},
 3618             {'json_schema_version': 10, 'op': 'replace', 'path': ['locations'],
 3619              'value': [{'url': 'scheme5://path5', 'metadata': {}},
 3620                        {'url': 'scheme6://path6', 'metadata': {}}]}
 3621         ]}
 3622         self.assertEqual(expected, output)
 3623 
 3624     def test_update_disallowed_attributes(self):
 3625         samples = {
 3626             'direct_url': '/a/b/c/d',
 3627             'self': '/e/f/g/h',
 3628             'file': '/e/f/g/h/file',
 3629             'schema': '/i/j/k',
 3630         }
 3631 
 3632         for key, value in samples.items():
 3633             request = self._get_fake_patch_request()
 3634             body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
 3635             request.body = jsonutils.dump_as_bytes(body)
 3636             try:
 3637                 self.deserializer.update(request)
 3638             except webob.exc.HTTPForbidden:
 3639                 pass  # desired behavior
 3640             else:
 3641                 self.fail("Updating %s did not result in HTTPForbidden" % key)
 3642 
 3643     def test_update_readonly_attributes(self):
 3644         samples = {
 3645             'id': '00000000-0000-0000-0000-000000000000',
 3646             'status': 'active',
 3647             'checksum': 'abcdefghijklmnopqrstuvwxyz012345',
 3648             'os_hash_algo': 'supersecure',
 3649             'os_hash_value': 'a' * 32 + 'b' * 32 + 'c' * 32 + 'd' * 32,
 3650             'size': 9001,
 3651             'virtual_size': 9001,
 3652             'created_at': ISOTIME,
 3653             'updated_at': ISOTIME,
 3654         }
 3655 
 3656         for key, value in samples.items():
 3657             request = self._get_fake_patch_request()
 3658             body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
 3659             request.body = jsonutils.dump_as_bytes(body)
 3660             try:
 3661                 self.deserializer.update(request)
 3662             except webob.exc.HTTPForbidden:
 3663                 pass  # desired behavior
 3664             else:
 3665                 self.fail("Updating %s did not result in HTTPForbidden" % key)
 3666 
 3667     def test_update_reserved_attributes(self):
 3668         samples = {
 3669             'deleted': False,
 3670             'deleted_at': ISOTIME,
 3671         }
 3672 
 3673         for key, value in samples.items():
 3674             request = self._get_fake_patch_request()
 3675             body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
 3676             request.body = jsonutils.dump_as_bytes(body)
 3677             try:
 3678                 self.deserializer.update(request)
 3679             except webob.exc.HTTPForbidden:
 3680                 pass  # desired behavior
 3681             else:
 3682                 self.fail("Updating %s did not result in HTTPForbidden" % key)
 3683 
 3684     def test_update_invalid_attributes(self):
 3685         keys = [
 3686             'noslash',
 3687             '///twoslash',
 3688             '/two/   /slash',
 3689             '/      /      ',
 3690             '/trailingslash/',
 3691             '/lone~tilde',
 3692             '/trailingtilde~'
 3693         ]
 3694 
 3695         for key in keys:
 3696             request = self._get_fake_patch_request()
 3697             body = [{'op': 'replace', 'path': '%s' % key, 'value': 'dummy'}]
 3698             request.body = jsonutils.dump_as_bytes(body)
 3699             try:
 3700                 self.deserializer.update(request)
 3701             except webob.exc.HTTPBadRequest:
 3702                 pass  # desired behavior
 3703             else:
 3704                 self.fail("Updating %s did not result in HTTPBadRequest" % key)
 3705 
 3706     def test_update_pointer_encoding(self):
 3707         samples = {
 3708             '/keywith~1slash': [u'keywith/slash'],
 3709             '/keywith~0tilde': [u'keywith~tilde'],
 3710             '/tricky~01': [u'tricky~1'],
 3711         }
 3712 
 3713         for encoded, decoded in samples.items():
 3714             request = self._get_fake_patch_request()
 3715             doc = [{'op': 'replace', 'path': '%s' % encoded, 'value': 'dummy'}]
 3716             request.body = jsonutils.dump_as_bytes(doc)
 3717             output = self.deserializer.update(request)
 3718             self.assertEqual(decoded, output['changes'][0]['path'])
 3719 
 3720     def test_update_deep_limited_attributes(self):
 3721         samples = {
 3722             'locations/1/2': [],
 3723         }
 3724 
 3725         for key, value in samples.items():
 3726             request = self._get_fake_patch_request()
 3727             body = [{'op': 'replace', 'path': '/%s' % key, 'value': value}]
 3728             request.body = jsonutils.dump_as_bytes(body)
 3729             try:
 3730                 self.deserializer.update(request)
 3731             except webob.exc.HTTPBadRequest:
 3732                 pass  # desired behavior
 3733             else:
 3734                 self.fail("Updating %s did not result in HTTPBadRequest" % key)
 3735 
 3736     def test_update_v2_1_missing_operations(self):
 3737         request = self._get_fake_patch_request()
 3738         body = [{'path': '/colburn', 'value': 'arcata'}]
 3739         request.body = jsonutils.dump_as_bytes(body)
 3740         self.assertRaises(webob.exc.HTTPBadRequest,
 3741                           self.deserializer.update, request)
 3742 
 3743     def test_update_v2_1_missing_value(self):
 3744         request = self._get_fake_patch_request()
 3745         body = [{'op': 'replace', 'path': '/colburn'}]
 3746         request.body = jsonutils.dump_as_bytes(body)
 3747         self.assertRaises(webob.exc.HTTPBadRequest,
 3748                           self.deserializer.update, request)
 3749 
 3750     def test_update_v2_1_missing_path(self):
 3751         request = self._get_fake_patch_request()
 3752         body = [{'op': 'replace', 'value': 'arcata'}]
 3753         request.body = jsonutils.dump_as_bytes(body)
 3754         self.assertRaises(webob.exc.HTTPBadRequest,
 3755                           self.deserializer.update, request)
 3756 
 3757     def test_update_v2_0_multiple_operations(self):
 3758         request = self._get_fake_patch_request(content_type_minor_version=0)
 3759         body = [{'replace': '/foo', 'add': '/bar', 'value': 'snore'}]
 3760         request.body = jsonutils.dump_as_bytes(body)
 3761         self.assertRaises(webob.exc.HTTPBadRequest,
 3762                           self.deserializer.update, request)
 3763 
 3764     def test_update_v2_0_missing_operations(self):
 3765         request = self._get_fake_patch_request(content_type_minor_version=0)
 3766         body = [{'value': 'arcata'}]
 3767         request.body = jsonutils.dump_as_bytes(body)
 3768         self.assertRaises(webob.exc.HTTPBadRequest,
 3769                           self.deserializer.update, request)
 3770 
 3771     def test_update_v2_0_missing_value(self):
 3772         request = self._get_fake_patch_request(content_type_minor_version=0)
 3773         body = [{'replace': '/colburn'}]
 3774         request.body = jsonutils.dump_as_bytes(body)
 3775         self.assertRaises(webob.exc.HTTPBadRequest,
 3776                           self.deserializer.update, request)
 3777 
 3778     def test_index(self):
 3779         marker = str(uuid.uuid4())
 3780         path = '/images?limit=1&marker=%s&member_status=pending' % marker
 3781         request = unit_test_utils.get_fake_request(path)
 3782         expected = {'limit': 1,
 3783                     'marker': marker,
 3784                     'sort_key': ['created_at'],
 3785                     'sort_dir': ['desc'],
 3786                     'member_status': 'pending',
 3787                     'filters': {}}
 3788         output = self.deserializer.index(request)
 3789         self.assertEqual(expected, output)
 3790 
 3791     def test_index_with_filter(self):
 3792         name = 'My Little Image'
 3793         path = '/images?name=%s' % name
 3794         request = unit_test_utils.get_fake_request(path)
 3795         output = self.deserializer.index(request)
 3796         self.assertEqual(name, output['filters']['name'])
 3797 
 3798     def test_index_strip_params_from_filters(self):
 3799         name = 'My Little Image'
 3800         path = '/images?name=%s' % name
 3801         request = unit_test_utils.get_fake_request(path)
 3802         output = self.deserializer.index(request)
 3803         self.assertEqual(name, output['filters']['name'])
 3804         self.assertEqual(1, len(output['filters']))
 3805 
 3806     def test_index_with_many_filter(self):
 3807         name = 'My Little Image'
 3808         instance_id = str(uuid.uuid4())
 3809         path = ('/images?name=%(name)s&id=%(instance_id)s' %
 3810                 {'name': name, 'instance_id': instance_id})
 3811         request = unit_test_utils.get_fake_request(path)
 3812         output = self.deserializer.index(request)
 3813         self.assertEqual(name, output['filters']['name'])
 3814         self.assertEqual(instance_id, output['filters']['id'])
 3815 
 3816     def test_index_with_filter_and_limit(self):
 3817         name = 'My Little Image'
 3818         path = '/images?name=%s&limit=1' % name
 3819         request = unit_test_utils.get_fake_request(path)
 3820         output = self.deserializer.index(request)
 3821         self.assertEqual(name, output['filters']['name'])
 3822         self.assertEqual(1, output['limit'])
 3823 
 3824     def test_index_non_integer_limit(self):
 3825         request = unit_test_utils.get_fake_request('/images?limit=blah')
 3826         self.assertRaises(webob.exc.HTTPBadRequest,
 3827                           self.deserializer.index, request)
 3828 
 3829     def test_index_zero_limit(self):
 3830         request = unit_test_utils.get_fake_request('/images?limit=0')
 3831         expected = {'limit': 0,
 3832                     'sort_key': ['created_at'],
 3833                     'member_status': 'accepted',
 3834                     'sort_dir': ['desc'],
 3835                     'filters': {}}
 3836         output = self.deserializer.index(request)
 3837         self.assertEqual(expected, output)
 3838 
 3839     def test_index_negative_limit(self):
 3840         request = unit_test_utils.get_fake_request('/images?limit=-1')
 3841         self.assertRaises(webob.exc.HTTPBadRequest,
 3842                           self.deserializer.index, request)
 3843 
 3844     def test_index_fraction(self):
 3845         request = unit_test_utils.get_fake_request('/images?limit=1.1')
 3846         self.assertRaises(webob.exc.HTTPBadRequest,
 3847                           self.deserializer.index, request)
 3848 
 3849     def test_index_invalid_status(self):
 3850         path = '/images?member_status=blah'
 3851         request = unit_test_utils.get_fake_request(path)
 3852         self.assertRaises(webob.exc.HTTPBadRequest,
 3853                           self.deserializer.index, request)
 3854 
 3855     def test_index_marker(self):
 3856         marker = str(uuid.uuid4())
 3857         path = '/images?marker=%s' % marker
 3858         request = unit_test_utils.get_fake_request(path)
 3859         output = self.deserializer.index(request)
 3860         self.assertEqual(marker, output.get('marker'))
 3861 
 3862     def test_index_marker_not_specified(self):
 3863         request = unit_test_utils.get_fake_request('/images')
 3864         output = self.deserializer.index(request)
 3865         self.assertNotIn('marker', output)
 3866 
 3867     def test_index_limit_not_specified(self):
 3868         request = unit_test_utils.get_fake_request('/images')
 3869         output = self.deserializer.index(request)
 3870         self.assertNotIn('limit', output)
 3871 
 3872     def test_index_sort_key_id(self):
 3873         request = unit_test_utils.get_fake_request('/images?sort_key=id')
 3874         output = self.deserializer.index(request)
 3875         expected = {
 3876             'sort_key': ['id'],
 3877             'sort_dir': ['desc'],
 3878             'member_status': 'accepted',
 3879             'filters': {}
 3880         }
 3881         self.assertEqual(expected, output)
 3882 
 3883     def test_index_multiple_sort_keys(self):
 3884         request = unit_test_utils.get_fake_request('/images?'
 3885                                                    'sort_key=name&'
 3886                                                    'sort_key=size')
 3887         output = self.deserializer.index(request)
 3888         expected = {
 3889             'sort_key': ['name', 'size'],
 3890             'sort_dir': ['desc'],
 3891             'member_status': 'accepted',
 3892             'filters': {}
 3893         }
 3894         self.assertEqual(expected, output)
 3895 
 3896     def test_index_invalid_multiple_sort_keys(self):
 3897         # blah is an invalid sort key
 3898         request = unit_test_utils.get_fake_request('/images?'
 3899                                                    'sort_key=name&'
 3900                                                    'sort_key=blah')
 3901         self.assertRaises(webob.exc.HTTPBadRequest,
 3902                           self.deserializer.index, request)
 3903 
 3904     def test_index_sort_dir_asc(self):
 3905         request = unit_test_utils.get_fake_request('/images?sort_dir=asc')
 3906         output = self.deserializer.index(request)
 3907         expected = {
 3908             'sort_key': ['created_at'],
 3909             'sort_dir': ['asc'],
 3910             'member_status': 'accepted',
 3911             'filters': {}}
 3912         self.assertEqual(expected, output)
 3913 
 3914     def test_index_multiple_sort_dirs(self):
 3915         req_string = ('/images?sort_key=name&sort_dir=asc&'
 3916                       'sort_key=id&sort_dir=desc')
 3917         request = unit_test_utils.get_fake_request(req_string)
 3918         output = self.deserializer.index(request)
 3919         expected = {
 3920             'sort_key': ['name', 'id'],
 3921             'sort_dir': ['asc', 'desc'],
 3922             'member_status': 'accepted',
 3923             'filters': {}}
 3924         self.assertEqual(expected, output)
 3925 
 3926     def test_index_new_sorting_syntax_single_key_default_dir(self):
 3927         req_string = '/images?sort=name'
 3928         request = unit_test_utils.get_fake_request(req_string)
 3929         output = self.deserializer.index(request)
 3930         expected = {
 3931             'sort_key': ['name'],
 3932             'sort_dir': ['desc'],
 3933             'member_status': 'accepted',
 3934             'filters': {}}
 3935         self.assertEqual(expected, output)
 3936 
 3937     def test_index_new_sorting_syntax_single_key_desc_dir(self):
 3938         req_string = '/images?sort=name:desc'
 3939         request = unit_test_utils.get_fake_request(req_string)
 3940         output = self.deserializer.index(request)
 3941         expected = {
 3942             'sort_key': ['name'],
 3943             'sort_dir': ['desc'],
 3944             'member_status': 'accepted',
 3945             'filters': {}}
 3946         self.assertEqual(expected, output)
 3947 
 3948     def test_index_new_sorting_syntax_multiple_keys_default_dir(self):
 3949         req_string = '/images?sort=name,size'
 3950         request = unit_test_utils.get_fake_request(req_string)
 3951         output = self.deserializer.index(request)
 3952         expected = {
 3953             'sort_key': ['name', 'size'],
 3954             'sort_dir': ['desc', 'desc'],
 3955             'member_status': 'accepted',
 3956             'filters': {}}
 3957         self.assertEqual(expected, output)
 3958 
 3959     def test_index_new_sorting_syntax_multiple_keys_asc_dir(self):
 3960         req_string = '/images?sort=name:asc,size:asc'
 3961         request = unit_test_utils.get_fake_request(req_string)
 3962         output = self.deserializer.index(request)
 3963         expected = {
 3964             'sort_key': ['name', 'size'],
 3965             'sort_dir': ['asc', 'asc'],
 3966             'member_status': 'accepted',
 3967             'filters': {}}
 3968         self.assertEqual(expected, output)
 3969 
 3970     def test_index_new_sorting_syntax_multiple_keys_different_dirs(self):
 3971         req_string = '/images?sort=name:desc,size:asc'
 3972         request = unit_test_utils.get_fake_request(req_string)
 3973         output = self.deserializer.index(request)
 3974         expected = {
 3975             'sort_key': ['name', 'size'],
 3976             'sort_dir': ['desc', 'asc'],
 3977             'member_status': 'accepted',
 3978             'filters': {}}
 3979         self.assertEqual(expected, output)
 3980 
 3981     def test_index_new_sorting_syntax_multiple_keys_optional_dir(self):
 3982         req_string = '/images?sort=name:asc,size'
 3983         request = unit_test_utils.get_fake_request(req_string)
 3984         output = self.deserializer.index(request)
 3985         expected = {
 3986             'sort_key': ['name', 'size'],
 3987             'sort_dir': ['asc', 'desc'],
 3988             'member_status': 'accepted',
 3989             'filters': {}}
 3990         self.assertEqual(expected, output)
 3991 
 3992         req_string = '/images?sort=name,size:asc'
 3993         request = unit_test_utils.get_fake_request(req_string)
 3994         output = self.deserializer.index(request)
 3995         expected = {
 3996             'sort_key': ['name', 'size'],
 3997             'sort_dir': ['desc', 'asc'],
 3998             'member_status': 'accepted',
 3999             'filters': {}}
 4000         self.assertEqual(expected, output)
 4001 
 4002         req_string = '/images?sort=name,id:asc,size'
 4003         request = unit_test_utils.get_fake_request(req_string)
 4004         output = self.deserializer.index(request)
 4005         expected = {
 4006             'sort_key': ['name', 'id', 'size'],
 4007             'sort_dir': ['desc', 'asc', 'desc'],
 4008             'member_status': 'accepted',
 4009             'filters': {}}
 4010         self.assertEqual(expected, output)
 4011 
 4012         req_string = '/images?sort=name:asc,id,size:asc'
 4013         request = unit_test_utils.get_fake_request(req_string)
 4014         output = self.deserializer.index(request)
 4015         expected = {
 4016             'sort_key': ['name', 'id', 'size'],
 4017             'sort_dir': ['asc', 'desc', 'asc'],
 4018             'member_status': 'accepted',
 4019             'filters': {}}
 4020         self.assertEqual(expected, output)
 4021 
 4022     def test_index_sort_wrong_sort_dirs_number(self):
 4023         req_string = '/images?sort_key=name&sort_dir=asc&sort_dir=desc'
 4024         request = unit_test_utils.get_fake_request(req_string)
 4025         self.assertRaises(webob.exc.HTTPBadRequest,
 4026                           self.deserializer.index, request)
 4027 
 4028     def test_index_sort_dirs_fewer_than_keys(self):
 4029         req_string = ('/images?sort_key=name&sort_dir=asc&sort_key=id&'
 4030                       'sort_dir=asc&sort_key=created_at')
 4031         request = unit_test_utils.get_fake_request(req_string)
 4032         self.assertRaises(webob.exc.HTTPBadRequest,
 4033                           self.deserializer.index, request)
 4034 
 4035     def test_index_sort_wrong_sort_dirs_number_without_key(self):
 4036         req_string = '/images?sort_dir=asc&sort_dir=desc'
 4037         request = unit_test_utils.get_fake_request(req_string)
 4038         self.assertRaises(webob.exc.HTTPBadRequest,
 4039                           self.deserializer.index, request)
 4040 
 4041     def test_index_sort_private_key(self):
 4042         request = unit_test_utils.get_fake_request('/images?sort_key=min_ram')
 4043         self.assertRaises(webob.exc.HTTPBadRequest,
 4044                           self.deserializer.index, request)
 4045 
 4046     def test_index_sort_key_invalid_value(self):
 4047         # blah is an invalid sort key
 4048         request = unit_test_utils.get_fake_request('/images?sort_key=blah')
 4049         self.assertRaises(webob.exc.HTTPBadRequest,
 4050                           self.deserializer.index, request)
 4051 
 4052     def test_index_sort_dir_invalid_value(self):
 4053         # foo is an invalid sort dir
 4054         request = unit_test_utils.get_fake_request('/images?sort_dir=foo')
 4055         self.assertRaises(webob.exc.HTTPBadRequest,
 4056                           self.deserializer.index, request)
 4057 
 4058     def test_index_new_sorting_syntax_invalid_request(self):
 4059         # 'blah' is not a supported sorting key
 4060         req_string = '/images?sort=blah'
 4061         request = unit_test_utils.get_fake_request(req_string)
 4062         self.assertRaises(webob.exc.HTTPBadRequest,
 4063                           self.deserializer.index, request)
 4064 
 4065         req_string = '/images?sort=name,blah'
 4066         request = unit_test_utils.get_fake_request(req_string)
 4067         self.assertRaises(webob.exc.HTTPBadRequest,
 4068                           self.deserializer.index, request)
 4069 
 4070         # 'foo' isn't a valid sort direction
 4071         req_string = '/images?sort=name:foo'
 4072         request = unit_test_utils.get_fake_request(req_string)
 4073         self.assertRaises(webob.exc.HTTPBadRequest,
 4074                           self.deserializer.index, request)
 4075         # 'asc:desc' isn't a valid sort direction
 4076         req_string = '/images?sort=name:asc:desc'
 4077         request = unit_test_utils.get_fake_request(req_string)
 4078         self.assertRaises(webob.exc.HTTPBadRequest,
 4079                           self.deserializer.index, request)
 4080 
 4081     def test_index_combined_sorting_syntax(self):
 4082         req_string = '/images?sort_dir=name&sort=name'
 4083         request = unit_test_utils.get_fake_request(req_string)
 4084         self.assertRaises(webob.exc.HTTPBadRequest,
 4085                           self.deserializer.index, request)
 4086 
 4087     def test_index_with_tag(self):
 4088         path = '/images?tag=%s&tag=%s' % ('x86', '64bit')
 4089         request = unit_test_utils.get_fake_request(path)
 4090         output = self.deserializer.index(request)
 4091         self.assertEqual(sorted(['x86', '64bit']),
 4092                          sorted(output['filters']['tags']))
 4093 
 4094     def test_image_import(self):
 4095         # Bug 1754634: make sure that what's considered valid
 4096         # is determined by the config option
 4097         self.config(enabled_import_methods=['party-time'])
 4098         request = unit_test_utils.get_fake_request()
 4099         import_body = {
 4100             "method": {
 4101                 "name": "party-time"
 4102             }
 4103         }
 4104         request.body = jsonutils.dump_as_bytes(import_body)
 4105         output = self.deserializer.import_image(request)
 4106         expected = {"body": import_body}
 4107         self.assertEqual(expected, output)
 4108 
 4109     def test_import_image_invalid_body(self):
 4110         request = unit_test_utils.get_fake_request()
 4111         import_body = {
 4112             "method1": {
 4113                 "name": "glance-direct"