Ejemplo n.º 1
0
    def delete(self, context, image_id):
        """Delete the given image.

        :raises: ImageNotFound if the image does not exist.
        :raises: NotAuthorized if the user is not an owner.
        :raises: ImageNotAuthorized if the user is not authorized.

        """
        try:
            self._client.call(context, 1, 'delete', image_id)
        except glanceclient.exc.NotFound:
            raise exception.ImageNotFound(image_id=image_id)
        except glanceclient.exc.HTTPForbidden:
            raise exception.ImageNotAuthorized(image_id=image_id)
        return True
Ejemplo n.º 2
0
    def test_show_client_failure(self, is_avail_mock, trans_from_mock,
                                 reraise_mock):
        raised = exception.ImageNotAuthorized(image_id=123)
        client = mock.MagicMock()
        client.call.side_effect = glanceclient.exc.Forbidden
        ctx = mock.sentinel.ctx
        reraise_mock.side_effect = raised
        service = glance.GlanceImageService(client)

        with testtools.ExpectedException(exception.ImageNotAuthorized):
            service.show(ctx, mock.sentinel.image_id)
            client.call.assert_called_once_with(ctx, 1, 'get',
                                                mock.sentinel.image_id)
            self.assertFalse(is_avail_mock.called)
            self.assertFalse(trans_from_mock.called)
            reraise_mock.assert_called_once_with(mock.sentinel.image_id)
Ejemplo n.º 3
0
    def delete(self, context, image_id):
        """Delete the given image.

        :raises: ImageNotFound if the image does not exist.
        :raises: NotAuthorized if the user is not an owner.
        :raises: ImageNotAuthorized if the user is not authorized.
        :raises: ImageDeleteConflict if the image is conflicted to delete.

        """
        try:
            self._client.call(context, 2, 'delete', image_id)
        except glanceclient.exc.NotFound:
            raise exception.ImageNotFound(image_id=image_id)
        except glanceclient.exc.HTTPForbidden:
            raise exception.ImageNotAuthorized(image_id=image_id)
        except glanceclient.exc.HTTPConflict as exc:
            raise exception.ImageDeleteConflict(reason=six.text_type(exc))
        return True
    def test_resize_with_image_exceptions(self):
        body = dict(resize=dict(flavorRef="http://localhost/3"))
        self.resize_called = 0
        image_id = 'fake_image_id'

        exceptions = [
            (exception.ImageNotAuthorized(image_id=image_id),
             webob.exc.HTTPUnauthorized),
            (exception.ImageNotFound(image_id=image_id),
             webob.exc.HTTPBadRequest),
            (exception.Invalid, webob.exc.HTTPBadRequest),
            (exception.NoValidHost(reason='Bad host'),
             webob.exc.HTTPBadRequest),
            (exception.AutoDiskConfigDisabledByImage(image=image_id),
             webob.exc.HTTPBadRequest),
        ]

        raised, expected = map(iter, zip(*exceptions))

        def _fake_resize(obj, context, instance, flavor_id):
            self.resize_called += 1
            raise raised.next()

        self.stubs.Set(compute_api.API, 'resize', _fake_resize)

        for call_no in range(len(exceptions)):
            req = fakes.HTTPRequestV3.blank(self.url)
            next_exception = expected.next()
            actual = self.assertRaises(next_exception,
                                       self.controller._action_resize,
                                       req,
                                       FAKE_UUID,
                                       body=body)
            if (isinstance(exceptions[call_no][0], exception.NoValidHost)):
                self.assertEqual(actual.explanation,
                                 'No valid host was found. Bad host')
            elif (isinstance(exceptions[call_no][0],
                             exception.AutoDiskConfigDisabledByImage)):
                self.assertEqual(
                    actual.explanation,
                    'Requested image fake_image_id has automatic'
                    ' disk resize disabled.')
            self.assertEqual(self.resize_called, call_no + 1)
Ejemplo n.º 5
0
    def test_update_client_failure(self, trans_to_mock, trans_from_mock,
                                   reraise_mock):
        translated = {'name': mock.sentinel.name}
        trans_to_mock.return_value = translated
        trans_from_mock.return_value = mock.sentinel.trans_from
        image_mock = mock.MagicMock(spec=dict)
        raised = exception.ImageNotAuthorized(image_id=123)
        client = mock.MagicMock()
        client.call.side_effect = glanceclient.exc.Forbidden
        ctx = mock.sentinel.ctx
        reraise_mock.side_effect = raised
        service = glance.GlanceImageService(client)

        self.assertRaises(exception.ImageNotAuthorized, service.update, ctx,
                          mock.sentinel.image_id, image_mock)
        client.call.assert_called_once_with(ctx,
                                            1,
                                            'update',
                                            mock.sentinel.image_id,
                                            purge_props=True,
                                            name=mock.sentinel.name)
        self.assertFalse(trans_from_mock.called)
        reraise_mock.assert_called_once_with(mock.sentinel.image_id)
Ejemplo n.º 6
0
class ImagesControllerTestV21(test.NoDBTestCase):
    """Test of the OpenStack API /images application controller w/Glance.
    """
    image_controller_class = images_v21.ImagesController
    url_base = '/v3'
    bookmark_base = ''
    http_request = fakes.HTTPRequestV21

    def setUp(self):
        """Run before each test."""
        super(ImagesControllerTestV21, self).setUp()
        fakes.stub_out_networking(self.stubs)
        fakes.stub_out_rate_limiting(self.stubs)
        fakes.stub_out_key_pair_funcs(self.stubs)
        fakes.stub_out_compute_api_snapshot(self.stubs)
        fakes.stub_out_compute_api_backup(self.stubs)

        self.controller = self.image_controller_class()
        self.url_prefix = "http://localhost%s/images" % self.url_base
        self.bookmark_prefix = "http://localhost%s/images" % self.bookmark_base
        self.uuid = 'fa95aaf5-ab3b-4cd8-88c0-2be7dd051aaf'
        self.server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74"
        self.server_href = ("http://localhost%s/servers/%s" %
                            (self.url_base, self.server_uuid))
        self.server_bookmark = ("http://localhost%s/servers/%s" %
                                (self.bookmark_base, self.server_uuid))
        self.alternate = "%s/images/%s"

        self.expected_image_123 = {
            "image": {
                'id':
                '123',
                'name':
                'public image',
                'metadata': {
                    'key1': 'value1'
                },
                'updated':
                NOW_API_FORMAT,
                'created':
                NOW_API_FORMAT,
                'status':
                'ACTIVE',
                'minDisk':
                10,
                'progress':
                100,
                'minRam':
                128,
                "links": [{
                    "rel": "self",
                    "href": "%s/123" % self.url_prefix
                }, {
                    "rel": "bookmark",
                    "href": "%s/123" % self.bookmark_prefix
                }, {
                    "rel":
                    "alternate",
                    "type":
                    "application/vnd.openstack.image",
                    "href":
                    self.alternate % (glance.generate_glance_url(), 123),
                }],
            },
        }

        self.expected_image_124 = {
            "image": {
                'id':
                '124',
                'name':
                'queued snapshot',
                'metadata': {
                    u'instance_uuid': self.server_uuid,
                    u'user_id': u'fake',
                },
                'updated':
                NOW_API_FORMAT,
                'created':
                NOW_API_FORMAT,
                'status':
                'SAVING',
                'progress':
                25,
                'minDisk':
                0,
                'minRam':
                0,
                'server': {
                    'id':
                    self.server_uuid,
                    "links": [{
                        "rel": "self",
                        "href": self.server_href,
                    }, {
                        "rel": "bookmark",
                        "href": self.server_bookmark,
                    }],
                },
                "links": [{
                    "rel": "self",
                    "href": "%s/124" % self.url_prefix
                }, {
                    "rel": "bookmark",
                    "href": "%s/124" % self.bookmark_prefix
                }, {
                    "rel":
                    "alternate",
                    "type":
                    "application/vnd.openstack.image",
                    "href":
                    self.alternate % (glance.generate_glance_url(), 124),
                }],
            },
        }

    @mock.patch('nova.image.api.API.get', return_value=IMAGE_FIXTURES[0])
    def test_get_image(self, get_mocked):
        request = self.http_request.blank(self.url_base + 'images/123')
        actual_image = self.controller.show(request, '123')
        self.assertThat(actual_image,
                        matchers.DictMatches(self.expected_image_123))
        get_mocked.assert_called_once_with(mock.ANY, '123')

    @mock.patch('nova.image.api.API.get', return_value=IMAGE_FIXTURES[1])
    def test_get_image_with_custom_prefix(self, _get_mocked):
        self.flags(osapi_compute_link_prefix='https://zoo.com:42',
                   osapi_glance_link_prefix='http://circus.com:34')
        fake_req = self.http_request.blank(self.url_base + 'images/124')
        actual_image = self.controller.show(fake_req, '124')

        expected_image = self.expected_image_124
        expected_image["image"]["links"][0]["href"] = (
            "https://zoo.com:42%s/images/124" % self.url_base)
        expected_image["image"]["links"][1]["href"] = (
            "https://zoo.com:42%s/images/124" % self.bookmark_base)
        expected_image["image"]["links"][2]["href"] = (
            "http://circus.com:34/images/124")
        expected_image["image"]["server"]["links"][0]["href"] = (
            "https://zoo.com:42%s/servers/%s" %
            (self.url_base, self.server_uuid))
        expected_image["image"]["server"]["links"][1]["href"] = (
            "https://zoo.com:42%s/servers/%s" %
            (self.bookmark_base, self.server_uuid))

        self.assertThat(actual_image, matchers.DictMatches(expected_image))

    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotFound(image_id=''))
    def test_get_image_404(self, _get_mocked):
        fake_req = self.http_request.blank(self.url_base + 'images/unknown')
        self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
                          fake_req, 'unknown')

    @mock.patch('nova.image.api.API.get_all', return_value=IMAGE_FIXTURES)
    def test_get_image_details(self, get_all_mocked):
        request = self.http_request.blank(self.url_base + 'images/detail')
        response = self.controller.detail(request)

        get_all_mocked.assert_called_once_with(mock.ANY, filters={})
        response_list = response["images"]

        image_125 = copy.deepcopy(self.expected_image_124["image"])
        image_125['id'] = '125'
        image_125['name'] = 'saving snapshot'
        image_125['progress'] = 50
        image_125["links"][0]["href"] = "%s/125" % self.url_prefix
        image_125["links"][1]["href"] = "%s/125" % self.bookmark_prefix
        image_125["links"][2]["href"] = ("%s/images/125" %
                                         glance.generate_glance_url())

        image_126 = copy.deepcopy(self.expected_image_124["image"])
        image_126['id'] = '126'
        image_126['name'] = 'active snapshot'
        image_126['status'] = 'ACTIVE'
        image_126['progress'] = 100
        image_126["links"][0]["href"] = "%s/126" % self.url_prefix
        image_126["links"][1]["href"] = "%s/126" % self.bookmark_prefix
        image_126["links"][2]["href"] = ("%s/images/126" %
                                         glance.generate_glance_url())

        image_127 = copy.deepcopy(self.expected_image_124["image"])
        image_127['id'] = '127'
        image_127['name'] = 'killed snapshot'
        image_127['status'] = 'ERROR'
        image_127['progress'] = 0
        image_127["links"][0]["href"] = "%s/127" % self.url_prefix
        image_127["links"][1]["href"] = "%s/127" % self.bookmark_prefix
        image_127["links"][2]["href"] = ("%s/images/127" %
                                         glance.generate_glance_url())

        image_128 = copy.deepcopy(self.expected_image_124["image"])
        image_128['id'] = '128'
        image_128['name'] = 'deleted snapshot'
        image_128['status'] = 'DELETED'
        image_128['progress'] = 0
        image_128["links"][0]["href"] = "%s/128" % self.url_prefix
        image_128["links"][1]["href"] = "%s/128" % self.bookmark_prefix
        image_128["links"][2]["href"] = ("%s/images/128" %
                                         glance.generate_glance_url())

        image_129 = copy.deepcopy(self.expected_image_124["image"])
        image_129['id'] = '129'
        image_129['name'] = 'pending_delete snapshot'
        image_129['status'] = 'DELETED'
        image_129['progress'] = 0
        image_129["links"][0]["href"] = "%s/129" % self.url_prefix
        image_129["links"][1]["href"] = "%s/129" % self.bookmark_prefix
        image_129["links"][2]["href"] = ("%s/images/129" %
                                         glance.generate_glance_url())

        image_130 = copy.deepcopy(self.expected_image_123["image"])
        image_130['id'] = '130'
        image_130['name'] = None
        image_130['metadata'] = {}
        image_130['minDisk'] = 0
        image_130['minRam'] = 0
        image_130["links"][0]["href"] = "%s/130" % self.url_prefix
        image_130["links"][1]["href"] = "%s/130" % self.bookmark_prefix
        image_130["links"][2]["href"] = ("%s/images/130" %
                                         glance.generate_glance_url())

        image_131 = copy.deepcopy(self.expected_image_123["image"])
        image_131['id'] = '131'
        image_131['name'] = None
        image_131['metadata'] = {}
        image_131['minDisk'] = 0
        image_131['minRam'] = 0
        image_131["links"][0]["href"] = "%s/131" % self.url_prefix
        image_131["links"][1]["href"] = "%s/131" % self.bookmark_prefix
        image_131["links"][2]["href"] = ("%s/images/131" %
                                         glance.generate_glance_url())

        expected = [
            self.expected_image_123["image"], self.expected_image_124["image"],
            image_125, image_126, image_127, image_128, image_129, image_130,
            image_131
        ]

        self.assertThat(expected, matchers.DictListMatches(response_list))

    @mock.patch('nova.image.api.API.get_all')
    def test_get_image_details_with_limit(self, get_all_mocked):
        request = self.http_request.blank(self.url_base +
                                          'images/detail?limit=2')
        self.controller.detail(request)
        get_all_mocked.assert_called_once_with(mock.ANY, limit=2, filters={})

    @mock.patch('nova.image.api.API.get_all')
    def test_get_image_details_with_limit_and_page_size(self, get_all_mocked):
        request = self.http_request.blank(self.url_base +
                                          'images/detail?limit=2&page_size=1')
        self.controller.detail(request)
        get_all_mocked.assert_called_once_with(mock.ANY,
                                               limit=2,
                                               filters={},
                                               page_size=1)

    @mock.patch('nova.image.api.API.get_all')
    def _detail_request(self, filters, request, get_all_mocked):
        self.controller.detail(request)
        get_all_mocked.assert_called_once_with(mock.ANY, filters=filters)

    def test_image_detail_filter_with_name(self):
        filters = {'name': 'testname'}
        request = self.http_request.blank(self.url_base + 'images/detail'
                                          '?name=testname')
        self._detail_request(filters, request)

    def test_image_detail_filter_with_status(self):
        filters = {'status': 'active'}
        request = self.http_request.blank(self.url_base + 'images/detail'
                                          '?status=ACTIVE')
        self._detail_request(filters, request)

    def test_image_detail_filter_with_property(self):
        filters = {'property-test': '3'}
        request = self.http_request.blank(self.url_base + 'images/detail'
                                          '?property-test=3')
        self._detail_request(filters, request)

    def test_image_detail_filter_server_href(self):
        filters = {'property-instance_uuid': self.uuid}
        request = self.http_request.blank(self.url_base +
                                          'images/detail?server=' + self.uuid)
        self._detail_request(filters, request)

    def test_image_detail_filter_server_uuid(self):
        filters = {'property-instance_uuid': self.uuid}
        request = self.http_request.blank(self.url_base +
                                          'images/detail?server=' + self.uuid)
        self._detail_request(filters, request)

    def test_image_detail_filter_changes_since(self):
        filters = {'changes-since': '2011-01-24T17:08Z'}
        request = self.http_request.blank(self.url_base + 'images/detail'
                                          '?changes-since=2011-01-24T17:08Z')
        self._detail_request(filters, request)

    def test_image_detail_filter_with_type(self):
        filters = {'property-image_type': 'BASE'}
        request = self.http_request.blank(self.url_base +
                                          'images/detail?type=BASE')
        self._detail_request(filters, request)

    def test_image_detail_filter_not_supported(self):
        filters = {'status': 'active'}
        request = self.http_request.blank(self.url_base +
                                          'images/detail?status='
                                          'ACTIVE&UNSUPPORTEDFILTER=testname')
        self._detail_request(filters, request)

    def test_image_detail_no_filters(self):
        filters = {}
        request = self.http_request.blank(self.url_base + 'images/detail')
        self._detail_request(filters, request)

    @mock.patch('nova.image.api.API.get_all', side_effect=exception.Invalid)
    def test_image_detail_invalid_marker(self, _get_all_mocked):
        request = self.http_request.blank(self.url_base + '?marker=invalid')
        self.assertRaises(webob.exc.HTTPBadRequest, self.controller.detail,
                          request)

    def test_generate_alternate_link(self):
        view = images_view.ViewBuilder()
        request = self.http_request.blank(self.url_base + 'images/1')
        generated_url = view._get_alternate_link(request, 1)
        actual_url = "%s/images/1" % glance.generate_glance_url()
        self.assertEqual(generated_url, actual_url)

    def _check_response(self, controller_method, response, expected_code):
        self.assertEqual(expected_code, controller_method.wsgi_code)

    @mock.patch('nova.image.api.API.delete')
    def test_delete_image(self, delete_mocked):
        request = self.http_request.blank(self.url_base + 'images/124')
        request.method = 'DELETE'
        response = self.controller.delete(request, '124')
        self._check_response(self.controller.delete, response, 204)
        delete_mocked.assert_called_once_with(mock.ANY, '124')

    @mock.patch('nova.image.api.API.delete',
                side_effect=exception.ImageNotAuthorized(image_id='123'))
    def test_delete_deleted_image(self, _delete_mocked):
        # If you try to delete a deleted image, you get back 403 Forbidden.
        request = self.http_request.blank(self.url_base + 'images/123')
        request.method = 'DELETE'
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
                          request, '123')

    @mock.patch('nova.image.api.API.delete',
                side_effect=exception.ImageNotFound(image_id='123'))
    def test_delete_image_not_found(self, _delete_mocked):
        request = self.http_request.blank(self.url_base + 'images/300')
        request.method = 'DELETE'
        self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
                          request, '300')

    @mock.patch('nova.image.api.API.get_all', return_value=[IMAGE_FIXTURES[0]])
    def test_get_image_next_link(self, get_all_mocked):
        request = self.http_request.blank(self.url_base + 'imagesl?limit=1')
        response = self.controller.index(request)
        response_links = response['images_links']
        href_parts = urlparse.urlparse(response_links[0]['href'])
        self.assertEqual(self.url_base + '/images', href_parts.path)
        params = urlparse.parse_qs(href_parts.query)
        self.assertThat({
            'limit': ['1'],
            'marker': [IMAGE_FIXTURES[0]['id']]
        }, matchers.DictMatches(params))

    @mock.patch('nova.image.api.API.get_all', return_value=[IMAGE_FIXTURES[0]])
    def test_get_image_details_next_link(self, get_all_mocked):
        request = self.http_request.blank(self.url_base +
                                          'images/detail?limit=1')
        response = self.controller.detail(request)
        response_links = response['images_links']
        href_parts = urlparse.urlparse(response_links[0]['href'])
        self.assertEqual(self.url_base + '/images/detail', href_parts.path)
        params = urlparse.parse_qs(href_parts.query)
        self.assertThat({
            'limit': ['1'],
            'marker': [IMAGE_FIXTURES[0]['id']]
        }, matchers.DictMatches(params))
Ejemplo n.º 7
0
 def test_get_image_meta_not_authorized(self):
     error = exception.ImageNotAuthorized(image_id='fake-image')
     self._test_get_image_meta_exception(error)
Ejemplo n.º 8
0
class ImageMetaDataTestV21(test.NoDBTestCase):
    controller_class = image_metadata_v21.ImageMetadataController
    invalid_request = exception.ValidationError

    def setUp(self):
        super(ImageMetaDataTestV21, self).setUp()
        self.controller = self.controller_class()

    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_index(self, get_all_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata')
        res_dict = self.controller.index(req, '123')
        expected = {'metadata': {'key1': 'value1'}}
        self.assertEqual(res_dict, expected)
        get_all_mocked.assert_called_once_with(mock.ANY, '123')

    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_show(self, get_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/key1')
        res_dict = self.controller.show(req, '123', 'key1')
        self.assertIn('meta', res_dict)
        self.assertEqual(len(res_dict['meta']), 1)
        self.assertEqual('value1', res_dict['meta']['key1'])
        get_mocked.assert_called_once_with(mock.ANY, '123')

    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_show_not_found(self, _get_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/key9')
        self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req,
                          '123', 'key9')

    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotFound(image_id='100'))
    def test_show_image_not_found(self, _get_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/100/metadata/key1')
        self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req,
                          '100', 'key9')

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_create(self, get_mocked, update_mocked, quota_mocked):
        mock_result = copy.deepcopy(get_image_123())
        mock_result['properties']['key7'] = 'value7'
        update_mocked.return_value = mock_result
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata')
        req.method = 'POST'
        body = {"metadata": {"key7": "value7"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"
        res = self.controller.create(req, '123', body=body)
        get_mocked.assert_called_once_with(mock.ANY, '123')
        expected = copy.deepcopy(get_image_123())
        expected['properties'] = {
            'key1': 'value1',  # existing meta
            'key7': 'value7'  # new meta
        }
        quota_mocked.assert_called_once_with(mock.ANY, expected["properties"])
        update_mocked.assert_called_once_with(mock.ANY,
                                              '123',
                                              expected,
                                              data=None,
                                              purge_props=True)

        expected_output = {'metadata': {'key1': 'value1', 'key7': 'value7'}}
        self.assertEqual(expected_output, res)

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotFound(image_id='100'))
    def test_create_image_not_found(self, _get_mocked, update_mocked,
                                    quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/100/metadata')
        req.method = 'POST'
        body = {"metadata": {"key7": "value7"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.create,
                          req,
                          '100',
                          body=body)
        self.assertFalse(quota_mocked.called)
        self.assertFalse(update_mocked.called)

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_update_all(self, get_mocked, update_mocked, quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata')
        req.method = 'PUT'
        body = {"metadata": {"key9": "value9"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"
        res = self.controller.update_all(req, '123', body=body)
        get_mocked.assert_called_once_with(mock.ANY, '123')
        expected = copy.deepcopy(get_image_123())
        expected['properties'] = {
            'key9': 'value9'  # replace meta
        }
        quota_mocked.assert_called_once_with(mock.ANY, expected["properties"])
        update_mocked.assert_called_once_with(mock.ANY,
                                              '123',
                                              expected,
                                              data=None,
                                              purge_props=True)

        expected_output = {'metadata': {'key9': 'value9'}}
        self.assertEqual(expected_output, res)

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotFound(image_id='100'))
    def test_update_all_image_not_found(self, _get_mocked, quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/100/metadata')
        req.method = 'PUT'
        body = {"metadata": {"key9": "value9"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.update_all,
                          req,
                          '100',
                          body=body)
        self.assertFalse(quota_mocked.called)

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_update_item(self, _get_mocked, update_mocked, quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/key1')
        req.method = 'PUT'
        body = {"meta": {"key1": "zz"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"
        res = self.controller.update(req, '123', 'key1', body=body)
        expected = copy.deepcopy(get_image_123())
        expected['properties'] = {
            'key1': 'zz'  # changed meta
        }
        quota_mocked.assert_called_once_with(mock.ANY, expected["properties"])
        update_mocked.assert_called_once_with(mock.ANY,
                                              '123',
                                              expected,
                                              data=None,
                                              purge_props=True)

        expected_output = {'meta': {'key1': 'zz'}}
        self.assertEqual(res, expected_output)

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotFound(image_id='100'))
    def test_update_item_image_not_found(self, _get_mocked, quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/100/metadata/key1')
        req.method = 'PUT'
        body = {"meta": {"key1": "zz"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.update,
                          req,
                          '100',
                          'key1',
                          body=body)
        self.assertFalse(quota_mocked.called)

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get')
    def test_update_item_bad_body(self, get_mocked, update_mocked,
                                  quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/key1')
        req.method = 'PUT'
        body = {"key1": "zz"}
        req.body = b''
        req.headers["content-type"] = "application/json"

        self.assertRaises(self.invalid_request,
                          self.controller.update,
                          req,
                          '123',
                          'key1',
                          body=body)
        self.assertFalse(get_mocked.called)
        self.assertFalse(quota_mocked.called)
        self.assertFalse(update_mocked.called)

    @mock.patch(CHK_QUOTA_STR, side_effect=webob.exc.HTTPBadRequest())
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get')
    def test_update_item_too_many_keys(self, get_mocked, update_mocked,
                                       _quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/key1')
        req.method = 'PUT'
        body = {"meta": {"foo": "bar"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.update,
                          req,
                          '123',
                          'key1',
                          body=body)
        self.assertFalse(get_mocked.called)
        self.assertFalse(update_mocked.called)

    @mock.patch(CHK_QUOTA_STR)
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_update_item_body_uri_mismatch(self, _get_mocked, update_mocked,
                                           quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/bad')
        req.method = 'PUT'
        body = {"meta": {"key1": "value1"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.update,
                          req,
                          '123',
                          'bad',
                          body=body)
        self.assertFalse(quota_mocked.called)
        self.assertFalse(update_mocked.called)

    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_delete(self, _get_mocked, update_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/key1')
        req.method = 'DELETE'
        res = self.controller.delete(req, '123', 'key1')
        expected = copy.deepcopy(get_image_123())
        expected['properties'] = {}
        update_mocked.assert_called_once_with(mock.ANY,
                                              '123',
                                              expected,
                                              data=None,
                                              purge_props=True)

        self.assertIsNone(res)

    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_delete_not_found(self, _get_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/blah')
        req.method = 'DELETE'

        self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, req,
                          '123', 'blah')

    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotFound(image_id='100'))
    def test_delete_image_not_found(self, _get_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/100/metadata/key1')
        req.method = 'DELETE'

        self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, req,
                          '100', 'key1')

    @mock.patch(CHK_QUOTA_STR,
                side_effect=webob.exc.HTTPForbidden(explanation=''))
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_too_many_metadata_items_on_create(self, _get_mocked,
                                               update_mocked, _quota_mocked):
        body = {"metadata": {"foo": "bar"}}
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata')
        req.method = 'POST'
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.create,
                          req,
                          '123',
                          body=body)
        self.assertFalse(update_mocked.called)

    @mock.patch(CHK_QUOTA_STR,
                side_effect=webob.exc.HTTPForbidden(explanation=''))
    @mock.patch('nova.image.api.API.update')
    @mock.patch('nova.image.api.API.get', return_value=get_image_123())
    def test_too_many_metadata_items_on_put(self, _get_mocked, update_mocked,
                                            _quota_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/blah')
        req.method = 'PUT'
        body = {"meta": {"blah": "blah", "blah1": "blah1"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(self.invalid_request,
                          self.controller.update,
                          req,
                          '123',
                          'blah',
                          body=body)
        self.assertFalse(update_mocked.called)

    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotAuthorized(image_id='123'))
    def test_image_not_authorized_update(self, _get_mocked):
        req = fakes.HTTPRequest.blank('/v2/fake/images/123/metadata/key1')
        req.method = 'PUT'
        body = {"meta": {"key1": "value1"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.update,
                          req,
                          '123',
                          'key1',
                          body=body)

    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotAuthorized(image_id='123'))
    def test_image_not_authorized_update_all(self, _get_mocked):
        image_id = 131
        # see nova.tests.unit.api.openstack.fakes:_make_image_fixtures

        req = fakes.HTTPRequest.blank('/v2/fake/images/%s/metadata/key1' %
                                      image_id)
        req.method = 'PUT'
        body = {"metadata": {"key1": "value1"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.update_all,
                          req,
                          image_id,
                          body=body)

    @mock.patch('nova.image.api.API.get',
                side_effect=exception.ImageNotAuthorized(image_id='123'))
    def test_image_not_authorized_create(self, _get_mocked):
        image_id = 131
        # see nova.tests.unit.api.openstack.fakes:_make_image_fixtures

        req = fakes.HTTPRequest.blank('/v2/fake/images/%s/metadata/key1' %
                                      image_id)
        req.method = 'POST'
        body = {"metadata": {"key1": "value1"}}
        req.body = jsonutils.dump_as_bytes(body)
        req.headers["content-type"] = "application/json"

        self.assertRaises(webob.exc.HTTPForbidden,
                          self.controller.create,
                          req,
                          image_id,
                          body=body)