# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import datetime
import json

import webob

import glance.api.v2.images
from glance.common import utils
from glance.openstack.common import cfg
import glance.schema
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
import glance.store


DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
ISOTIME = '2012-05-16T15:27:36Z'


CONF = cfg.CONF

BASE_URI = unit_test_utils.BASE_URI


UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc'
UUID3 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
UUID4 = '6bbe7cc2-eae7-4c0f-b50d-a7160b0c6a86'

TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'


def _fixture(id, **kwargs):
    obj = {
        'id': id,
        'name': None,
        'is_public': False,
        'properties': {},
        'checksum': None,
        'owner': None,
        'status': 'queued',
        'tags': [],
        'size': None,
        'location': None,
        'protected': False,
        'disk_format': None,
        'container_format': None,
        'deleted': False,
        'min_ram': None,
        'min_disk': None,
    }
    obj.update(kwargs)
    return obj


class TestImagesController(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesController, self).setUp()
        self.db = unit_test_utils.FakeDB()
        self.policy = unit_test_utils.FakePolicyEnforcer()
        self.notifier = unit_test_utils.FakeNotifier()
        self.store = unit_test_utils.FakeStoreAPI()
        self._create_images()
        self.controller = glance.api.v2.images.ImagesController(self.db,
                                                                self.policy,
                                                                self.notifier,
                                                                self.store)
        glance.store.create_stores()

    def _create_images(self):
        self.db.reset()
        self.images = [
            _fixture(UUID1, owner=TENANT1, name='1', size=256, is_public=True,
                     location='%s/%s' % (BASE_URI, UUID1)),
            _fixture(UUID2, owner=TENANT1, name='2', size=512, is_public=True),
            _fixture(UUID3, owner=TENANT3, name='3', size=512, is_public=True),
            _fixture(UUID4, owner=TENANT4, name='4', size=1024),
        ]
        [self.db.image_create(None, image) for image in self.images]

        self.db.image_tag_set_all(None, UUID1, ['ping', 'pong'])

    def test_index(self):
        self.config(limit_param_default=1, api_limit_max=3)
        request = unit_test_utils.get_fake_request()
        output = self.controller.index(request)
        self.assertEqual(1, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID3])
        self.assertEqual(actual, expected)

    def test_index_return_parameters(self):
        self.config(limit_param_default=1, api_limit_max=3)
        request = unit_test_utils.get_fake_request()
        output = self.controller.index(request, marker=UUID3, limit=1,
                                       sort_key='created_at', sort_dir='desc')
        self.assertEqual(1, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID2])
        self.assertEqual(actual, expected)
        self.assertEqual(UUID2, output['next_marker'])

    def test_index_next_marker(self):
        self.config(limit_param_default=1, api_limit_max=3)
        request = unit_test_utils.get_fake_request()
        output = self.controller.index(request, marker=UUID3, limit=2)
        self.assertEqual(2, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID2, UUID1])
        self.assertEqual(actual, expected)
        self.assertEqual(UUID1, output['next_marker'])

    def test_index_no_next_marker(self):
        self.config(limit_param_default=1, api_limit_max=3)
        request = unit_test_utils.get_fake_request()
        output = self.controller.index(request, marker=UUID1, limit=2)
        self.assertEqual(0, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([])
        self.assertEqual(actual, expected)
        self.assertTrue('next_marker' not in output)

    def test_index_with_id_filter(self):
        request = unit_test_utils.get_fake_request('/images?id=%s' % UUID1)
        output = self.controller.index(request, filters={'id': UUID1})
        self.assertEqual(1, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID1])
        self.assertEqual(actual, expected)

    def test_index_size_max_filter(self):
        request = unit_test_utils.get_fake_request('/images?size_max=512')
        output = self.controller.index(request, filters={'size_max': 512})
        self.assertEqual(3, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID1, UUID2, UUID3])
        self.assertEqual(actual, expected)

    def test_index_size_min_filter(self):
        request = unit_test_utils.get_fake_request('/images?size_min=512')
        output = self.controller.index(request, filters={'size_min': 512})
        self.assertEqual(2, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID2, UUID3])
        self.assertEqual(actual, expected)

    def test_index_size_range_filter(self):
        path = '/images?size_min=512&size_max=512'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request,
                                       filters={'size_min': 512,
                                                'size_max': 512})
        self.assertEqual(2, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID2, UUID3])
        self.assertEqual(actual, expected)

    def test_index_with_invalid_max_range_filter_value(self):
        request = unit_test_utils.get_fake_request('/images?size_max=blah')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.index,
                          request,
                          filters={'size_max': 'blah'})

    def test_index_with_filters_return_many(self):
        path = '/images?status=queued'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request, filters={'status': 'queued'})
        self.assertEqual(3, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID1, UUID2, UUID3])
        self.assertEqual(actual, expected)

    def test_index_with_nonexistent_name_filter(self):
        request = unit_test_utils.get_fake_request('/images?name=%s' % 'blah')
        images = self.controller.index(request,
                                       filters={'name': 'blah'})['images']
        self.assertEqual(0, len(images))

    def test_index_with_non_default_is_public_filter(self):
        image = _fixture(utils.generate_uuid(), is_public=False, owner=TENANT3)
        self.db.image_create(None, image)
        path = '/images?visibility=private'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request, filters={'is_public': False})
        self.assertEqual(2, len(output['images']))

    def test_index_with_many_filters(self):
        url = '/images?status=queued&name=2'
        request = unit_test_utils.get_fake_request(url)
        output = self.controller.index(request,
                filters={'status': 'queued', 'name': '2'})
        self.assertEqual(1, len(output['images']))
        actual = set([image['id'] for image in output['images']])
        expected = set([UUID2])
        self.assertEqual(actual, expected)

    def test_index_with_marker(self):
        self.config(limit_param_default=1, api_limit_max=3)
        path = '/images'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request, marker=UUID3)
        actual = set([image['id'] for image in output['images']])
        self.assertEquals(1, len(actual))
        self.assertTrue(UUID2 in actual)

    def test_index_with_limit(self):
        path = '/images'
        limit = 2
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request, limit=limit)
        actual = set([image['id'] for image in output['images']])
        self.assertEquals(limit, len(actual))
        self.assertTrue(UUID3 in actual)
        self.assertTrue(UUID2 in actual)

    def test_index_greater_than_limit_max(self):
        self.config(limit_param_default=1, api_limit_max=3)
        path = '/images'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request, limit=4)
        actual = set([image['id'] for image in output['images']])
        self.assertEquals(3, len(actual))
        self.assertTrue(output['next_marker'] not in output)

    def test_index_default_limit(self):
        self.config(limit_param_default=1, api_limit_max=3)
        path = '/images'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request)
        actual = set([image['id'] for image in output['images']])
        self.assertEquals(1, len(actual))

    def test_index_with_sort_dir(self):
        path = '/images'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request, sort_dir='asc', limit=3)
        actual = [image['id'] for image in output['images']]
        self.assertEquals(3, len(actual))
        self.assertEquals(UUID1, actual[0])
        self.assertEquals(UUID2, actual[1])
        self.assertEquals(UUID3, actual[2])

    def test_index_with_sort_key(self):
        path = '/images'
        request = unit_test_utils.get_fake_request(path)
        output = self.controller.index(request, sort_key='created_at', limit=3)
        actual = [image['id'] for image in output['images']]
        self.assertEquals(3, len(actual))
        self.assertEquals(UUID3, actual[0])
        self.assertEquals(UUID2, actual[1])
        self.assertEquals(UUID1, actual[2])

    def test_index_with_marker_not_found(self):
        fake_uuid = utils.generate_uuid()
        path = '/images'
        request = unit_test_utils.get_fake_request(path)
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.index, request, marker=fake_uuid)

    def test_index_invalid_sort_key(self):
        path = '/images'
        request = unit_test_utils.get_fake_request(path)
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.index, request, sort_key='foo')

    def test_index_zero_images(self):
        self.db.reset()
        request = unit_test_utils.get_fake_request()
        output = self.controller.index(request)
        self.assertEqual([], output['images'])

    def test_show(self):
        request = unit_test_utils.get_fake_request()
        output = self.controller.show(request, image_id=UUID2)
        self.assertEqual(UUID2, output['id'])
        self.assertEqual('2', output['name'])

    def test_show_deleted_properties(self):
        """ Ensure that the api filters out deleted image properties. """

        # get the image properties into the odd state
        image = {
            'id': utils.generate_uuid(),
            'status': 'active',
            'properties': {'poo': 'bear'},
        }
        self.db.image_create(None, image)
        self.db.image_update(None, image['id'],
                             {'properties': {'yin': 'yang'}},
                             purge_props=True)

        request = unit_test_utils.get_fake_request()
        output = self.controller.show(request, image['id'])
        self.assertEqual(output['properties'], {'yin': 'yang'})

    def test_show_non_existent(self):
        request = unit_test_utils.get_fake_request()
        image_id = utils.generate_uuid()
        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.show, request, image_id)

    def test_create(self):
        request = unit_test_utils.get_fake_request()
        image = {'name': 'image-1'}
        output = self.controller.create(request, image)
        self.assertEqual('image-1', output['name'])
        self.assertEqual({}, output['properties'])
        self.assertEqual([], output['tags'])
        self.assertEqual(False, output['is_public'])
        output_log = self.notifier.get_log()
        expected_log = {
            'notification_type': "INFO",
            'event_type': "image.update",
            'payload': output,
        }
        self.assertEqual(output_log, expected_log)

    def test_create_public_image_as_admin(self):
        request = unit_test_utils.get_fake_request()
        image = {'name': 'image-1', 'is_public': True}
        output = self.controller.create(request, image)
        self.assertEqual(True, output['is_public'])
        output_log = self.notifier.get_log()
        expected_log = {
            'notification_type': "INFO",
            'event_type': "image.update",
            'payload': output,
        }
        self.assertEqual(output_log, expected_log)

    def test_create_duplicate_tags(self):
        request = unit_test_utils.get_fake_request()
        image = {'tags': ['ping', 'ping']}
        output = self.controller.create(request, image)
        self.assertEqual(['ping'], output['tags'])
        output_log = self.notifier.get_log()
        expected_log = {
            'notification_type': "INFO",
            'event_type': "image.update",
            'payload': output,
        }
        self.assertEqual(output_log, expected_log)

    def test_update_no_changes(self):
        request = unit_test_utils.get_fake_request()
        output = self.controller.update(request, UUID1, changes=[])
        self.assertEqual(output['id'], UUID1)
        self.assertEqual(output['created_at'], output['updated_at'])
        self.assertTrue('tags' in output)
        output_log = self.notifier.get_log()
        expected_log = {
            'notification_type': "INFO",
            'event_type': "image.update",
            'payload': output,
        }
        self.assertEqual(output_log, expected_log)

    def test_update_image_doesnt_exist(self):
        request = unit_test_utils.get_fake_request()
        self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
                          request, utils.generate_uuid(), changes=[])

    def test_update_replace_base_attribute(self):
        self.db.image_update(None, UUID1, {'properties': {'foo': 'bar'}})
        request = unit_test_utils.get_fake_request()
        changes = [{'op': 'replace', 'path': ['name'], 'value': 'fedora'}]
        output = self.controller.update(request, UUID1, changes)
        self.assertEqual(output['id'], UUID1)
        self.assertEqual(output['name'], 'fedora')
        self.assertEqual(output['properties'], {'foo': 'bar'})
        self.assertNotEqual(output['created_at'], output['updated_at'])

    def test_update_replace_tags(self):
        request = unit_test_utils.get_fake_request()
        changes = [
            {'op': 'replace', 'path': ['tags'], 'value': ['king', 'kong']},
        ]
        output = self.controller.update(request, UUID1, changes)
        self.assertEqual(output['id'], UUID1)
        self.assertEqual(output['tags'], ['king', 'kong'])
        self.assertNotEqual(output['created_at'], output['updated_at'])

    def test_update_replace_property(self):
        request = unit_test_utils.get_fake_request()
        properties = {'foo': 'bar', 'snitch': 'golden'}
        self.db.image_update(None, UUID1, {'properties': properties})

        output = self.controller.show(request, UUID1)
        self.assertEqual(output['properties']['foo'], 'bar')
        self.assertEqual(output['properties']['snitch'], 'golden')

        changes = [
            {'op': 'replace', 'path': ['properties', 'foo'], 'value': 'baz'},
        ]
        output = self.controller.update(request, UUID1, changes)
        self.assertEqual(output['id'], UUID1)
        self.assertEqual(output['properties']['foo'], 'baz')
        self.assertEqual(output['properties']['snitch'], 'golden')
        self.assertNotEqual(output['created_at'], output['updated_at'])

    def test_update_add_property(self):
        request = unit_test_utils.get_fake_request()
        output = self.controller.show(request, UUID1)
        self.assertEqual(len(output['properties']), 0)

        changes = [
            {'op': 'add', 'path': ['properties', 'murphy'], 'value': 'brown'},
        ]
        output = self.controller.update(request, UUID1, changes)
        self.assertEqual(output['id'], UUID1)
        self.assertEqual(output['properties'], {'murphy': 'brown'})
        self.assertNotEqual(output['created_at'], output['updated_at'])

    def test_update_remove_property(self):
        request = unit_test_utils.get_fake_request()
        properties = {'foo': 'bar', 'snitch': 'golden'}
        self.db.image_update(None, UUID1, {'properties': properties})

        output = self.controller.show(request, UUID1)
        self.assertEqual(output['properties']['foo'], 'bar')
        self.assertEqual(output['properties']['snitch'], 'golden')

        changes = [
            {'op': 'remove', 'path': ['properties', 'snitch']},
        ]
        output = self.controller.update(request, UUID1, changes)
        self.assertEqual(output['id'], UUID1)
        self.assertEqual(output['properties'], {'foo': 'bar'})
        self.assertNotEqual(output['created_at'], output['updated_at'])

    def test_update_multiple_changes(self):
        request = unit_test_utils.get_fake_request()
        properties = {'foo': 'bar', 'snitch': 'golden'}
        self.db.image_update(None, UUID1, {'properties': properties})

        changes = [
            {'op': 'replace', 'path': ['min_ram'], 'value': 128},
            {'op': 'replace', 'path': ['properties', 'foo'], 'value': 'baz'},
            {'op': 'remove', 'path': ['properties', 'snitch']},
            {'op': 'add', 'path': ['properties', 'kb'], 'value': 'dvorak'},
        ]
        output = self.controller.update(request, UUID1, changes)
        self.assertEqual(output['id'], UUID1)
        self.assertEqual(output['min_ram'], 128)
        self.assertEqual(len(output['properties']), 2)
        self.assertEqual(output['properties']['foo'], 'baz')
        self.assertEqual(output['properties']['kb'], 'dvorak')
        self.assertNotEqual(output['created_at'], output['updated_at'])

    def test_update_replace_missing_property(self):
        request = unit_test_utils.get_fake_request()
        output = self.controller.show(request, UUID1)
        self.assertEqual(len(output['properties']), 0)

        changes = [
            {'op': 'replace', 'path': ['properties', 'foo'], 'value': 'baz'},
        ]
        self.assertRaises(webob.exc.HTTPConflict,
                          self.controller.update, request, UUID1, changes)

    def test_update_add_property_already_present(self):
        request = unit_test_utils.get_fake_request()
        properties = {'foo': 'bar'}
        self.db.image_update(None, UUID1, {'properties': properties})

        output = self.controller.show(request, UUID1)
        self.assertEqual(output['properties'], {'foo': 'bar'})

        changes = [
            {'op': 'add', 'path': ['properties', 'foo'], 'value': 'baz'},
        ]
        self.assertRaises(webob.exc.HTTPConflict,
                          self.controller.update, request, UUID1, changes)

    def test_update_remove_missing_property(self):
        request = unit_test_utils.get_fake_request()
        output = self.controller.show(request, UUID1)
        self.assertEqual(len(output['properties']), 0)

        changes = [
            {'op': 'remove', 'path': ['properties', 'foo']},
        ]
        self.assertRaises(webob.exc.HTTPConflict,
                          self.controller.update, request, UUID1, changes)

    def test_update_assertion_errors(self):
        request = unit_test_utils.get_fake_request()
        changes = [
            {'op': 'add', 'path': ['properties', 'a', 'b'], 'value': 'c'},
            {'op': 'add', 'path': ['prolilies', 'foo'], 'value': 'bar'},
            {'op': 'replace', 'path': [], 'value': 'stuff'},
            {'op': 'add', 'path': ['name'], 'value': 'Beulah'},
            {'op': 'remove', 'path': ['name']},
            {'op': 'test', 'path': ['properties', 'options'], 'value': 'puts'},
        ]
        for change in changes:
            try:
                self.controller.update(request, UUID1, [change])
            except AssertionError:
                pass  # AssertionError is the desired behavior
            else:
                self.fail('Failed to raise AssertionError on %s' % change)

    def test_update_duplicate_tags(self):
        request = unit_test_utils.get_fake_request()
        changes = [
            {'op': 'replace', 'path': ['tags'], 'value': ['ping', 'ping']},
        ]
        output = self.controller.update(request, UUID1, changes)
        self.assertEqual(['ping'], output['tags'])
        output_log = self.notifier.get_log()
        expected_log = {
            'notification_type': "INFO",
            'event_type': "image.update",
            'payload': output,
        }
        self.assertEqual(output_log, expected_log)

    def test_delete(self):
        request = unit_test_utils.get_fake_request()
        self.assertTrue(filter(lambda k: UUID1 in k, self.store.data))
        try:
            image = self.controller.delete(request, UUID1)
            output_log = self.notifier.get_log()
            self.assertEqual(output_log['notification_type'], "INFO")
            self.assertEqual(output_log['event_type'], "image.delete")
        except Exception as e:
            self.fail("Delete raised exception: %s" % e)

        deleted_img = self.db.image_get(request.context, UUID1,
                                        force_show_deleted=True)
        self.assertTrue(deleted_img['deleted'])
        self.assertEqual(deleted_img['status'], 'deleted')
        self.assertFalse(filter(lambda k: UUID1 in k, self.store.data))

    def test_delete_queued_updates_status(self):
        """Ensure status of queued image is updated (LP bug #1048851)"""
        request = unit_test_utils.get_fake_request()
        image = self.db.image_create(request.context, {'status': 'queued'})
        image_id = image['id']
        self.controller.delete(request, image_id)

        image = self.db.image_get(request.context, image_id,
                                  force_show_deleted=True)
        self.assertTrue(image['deleted'])
        self.assertEqual(image['status'], 'deleted')

    def test_delete_queued_updates_status_delayed_delete(self):
        """
        Ensure status of queued image is updated (LP bug #1048851)
        to 'deleted' when delayed_delete isenabled
        """
        self.config(delayed_delete=True)
        request = unit_test_utils.get_fake_request()
        image = self.db.image_create(request.context, {'status': 'queued'})
        image_id = image['id']
        self.controller.delete(request, image_id)

        image = self.db.image_get(request.context, image_id,
                                  force_show_deleted=True)
        self.assertTrue(image['deleted'])
        self.assertEqual(image['status'], 'deleted')

    def test_delete_not_in_store(self):
        request = unit_test_utils.get_fake_request()
        self.assertTrue(filter(lambda k: UUID1 in k, self.store.data))
        for k in self.store.data:
            if UUID1 in k:
                del self.store.data[k]
                break

        self.controller.delete(request, UUID1)
        deleted_img = self.db.image_get(request.context, UUID1,
                                        force_show_deleted=True)
        self.assertTrue(deleted_img['deleted'])
        self.assertEqual(deleted_img['status'], 'deleted')
        self.assertFalse(filter(lambda k: UUID1 in k, self.store.data))

    def test_delayed_delete(self):
        self.config(delayed_delete=True)
        request = unit_test_utils.get_fake_request()
        self.assertTrue(filter(lambda k: UUID1 in k, self.store.data))

        self.controller.delete(request, UUID1)
        deleted_img = self.db.image_get(request.context, UUID1,
                                        force_show_deleted=True)
        self.assertTrue(deleted_img['deleted'])
        self.assertEqual(deleted_img['status'], 'pending_delete')
        self.assertTrue(filter(lambda k: UUID1 in k, self.store.data))

    def test_delete_non_existent(self):
        request = unit_test_utils.get_fake_request()
        self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
                          request, utils.generate_uuid())

    def test_index_with_invalid_marker(self):
        fake_uuid = utils.generate_uuid()
        request = unit_test_utils.get_fake_request()
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.index, request, marker=fake_uuid)


class TestImagesControllerPolicies(base.IsolatedUnitTest):

    def setUp(self):
        super(TestImagesControllerPolicies, self).setUp()
        self.db = unit_test_utils.FakeDB()
        self.policy = unit_test_utils.FakePolicyEnforcer()
        self.controller = glance.api.v2.images.ImagesController(self.db,
                                                                self.policy)

    def test_index_unauthorized(self):
        rules = {"get_images": False}
        self.policy.set_rules(rules)
        request = unit_test_utils.get_fake_request()
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.index,
                          request)

    def test_show_unauthorized(self):
        rules = {"get_image": False}
        self.policy.set_rules(rules)
        request = unit_test_utils.get_fake_request()
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.show,
                          request, image_id=UUID2)

    def test_create_public_image_unauthorized(self):
        rules = {"publicize_image": False}
        self.policy.set_rules(rules)
        request = unit_test_utils.get_fake_request()
        image = {'name': 'image-1', 'is_public': True}
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
                          request, image)

    def test_update_unauthorized(self):
        rules = {"modify_image": False}
        self.policy.set_rules(rules)
        request = unit_test_utils.get_fake_request()
        changes = [{'op': 'replace', 'path': ['name'], 'value': 'image-2'}]
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
                          request, UUID1, changes)

    def test_update_publicize_image_unauthorized(self):
        rules = {"publicize_image": False}
        self.policy.set_rules(rules)
        request = unit_test_utils.get_fake_request()
        changes = [{'op': 'replace', 'path': ['is_public'], 'value': True}]
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
                          request, UUID1, changes)

    def test_update_depublicize_image_unauthorized(self):
        rules = {"publicize_image": False}
        self.policy.set_rules(rules)
        request = unit_test_utils.get_fake_request()
        changes = [{'op': 'replace', 'path': ['is_public'], 'value': False}]
        output = self.controller.update(request, UUID1, changes)
        self.assertFalse(output['is_public'])

    def test_delete_unauthorized(self):
        rules = {"delete_image": False}
        self.policy.set_rules(rules)
        request = unit_test_utils.get_fake_request()
        self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
                          request, UUID1)


class TestImagesDeserializer(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesDeserializer, self).setUp()
        self.deserializer = glance.api.v2.images.RequestDeserializer()

    def test_create_minimal(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({})
        output = self.deserializer.create(request)
        expected = {'image': {'properties': {}}}
        self.assertEqual(expected, output)

    def test_create_invalid_id(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({'id': 'gabe'})
        self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
                          request)

    def test_create_no_body(self):
        request = unit_test_utils.get_fake_request()
        self.assertRaises(webob.exc.HTTPBadRequest, self.deserializer.create,
                          request)

    def test_create_full(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({
            'id': UUID3,
            'name': 'image-1',
            'visibility': 'public',
            'tags': ['one', 'two'],
            'container_format': 'ami',
            'disk_format': 'ami',
            'min_ram': 128,
            'min_disk': 10,
            'foo': 'bar',
            'protected': True,
        })
        output = self.deserializer.create(request)
        expected = {'image': {
            'id': UUID3,
            'name': 'image-1',
            'is_public': True,
            'tags': ['one', 'two'],
            'container_format': 'ami',
            'disk_format': 'ami',
            'min_ram': 128,
            'min_disk': 10,
            'properties': {'foo': 'bar'},
            'protected': True,
        }}
        self.assertEqual(expected, output)

    def test_create_readonly_attributes_forbidden(self):
        bodies = [
            {'created_at': ISOTIME},
            {'updated_at': ISOTIME},
            {'status': 'saving'},
            {'direct_url': 'http://example.com'},
            {'size': 10},
            {'checksum': 'asdf'},
            {'self': 'http://example.com'},
            {'file': 'http://example.com'},
            {'schema': 'http://example.com'},
        ]

        for body in bodies:
            request = unit_test_utils.get_fake_request()
            request.body = json.dumps(body)
            self.assertRaises(webob.exc.HTTPForbidden,
                              self.deserializer.create, request)

    def _get_fake_patch_request(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/openstack-images-v2.0-json-patch'
        return request

    def test_update_empty_body(self):
        request = self._get_fake_patch_request()
        request.body = json.dumps([])
        output = self.deserializer.update(request)
        expected = {'changes': []}
        self.assertEquals(output, expected)

    def test_update_invalid_content_type(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/json-patch'
        request.body = json.dumps([])
        try:
            self.deserializer.update(request)
        except webob.exc.HTTPUnsupportedMediaType as e:
            # desired result, but must have correct Accept-Patch header
            self.assertEqual(e.headers['Accept-Patch'],
                             'application/openstack-images-v2.0-json-patch')
        else:
            self.fail('Did not raise HTTPUnsupportedMediaType')

    def test_update_body_not_a_list(self):
        bodies = [
            {'add': '/someprop', 'value': 'somevalue'},
            'just some string',
            123,
            True,
            False,
            None,
        ]
        for body in bodies:
            request = self._get_fake_patch_request()
            request.body = json.dumps(body)
            self.assertRaises(webob.exc.HTTPBadRequest,
                              self.deserializer.update, request)

    def test_update_invalid_changes(self):
        changes = [
            ['a', 'list', 'of', 'stuff'],
            'just some string',
            123,
            True,
            False,
            None,
        ]
        for change in changes:
            request = self._get_fake_patch_request()
            request.body = json.dumps([change])
            self.assertRaises(webob.exc.HTTPBadRequest,
                              self.deserializer.update, request)

    def test_update(self):
        request = self._get_fake_patch_request()
        body = [
            {'replace': '/name', 'value': 'fedora'},
            {'replace': '/tags', 'value': ['king', 'kong']},
            {'replace': '/foo', 'value': 'bar'},
            {'add': '/bebim', 'value': 'bap'},
            {'remove': '/sparks'},
        ]
        request.body = json.dumps(body)
        output = self.deserializer.update(request)
        expected = {'changes': [
            {'op': 'replace', 'path': ['name'], 'value': 'fedora'},
            {'op': 'replace', 'path': ['tags'], 'value': ['king', 'kong']},
            {'op': 'replace', 'path': ['properties', 'foo'], 'value': 'bar'},
            {'op': 'add', 'path': ['properties', 'bebim'], 'value': 'bap'},
            {'op': 'remove', 'path': ['properties', 'sparks']},
        ]}
        self.assertEquals(output, expected)

    def test_update_base_attributes(self):
        request = self._get_fake_patch_request()
        body = [
            {'replace': '/id', 'value': UUID1},
            {'replace': '/name', 'value': 'fedora'},
            {'replace': '/visibility', 'value': 'public'},
            {'replace': '/visibility', 'value': 'private'},
            {'replace': '/tags', 'value': ['king', 'kong']},
            {'replace': '/protected', 'value': True},
            {'replace': '/container_format', 'value': 'bare'},
            {'replace': '/disk_format', 'value': 'raw'},
            {'replace': '/min_ram', 'value': 128},
            {'replace': '/min_disk', 'value': 10},
        ]
        request.body = json.dumps(body)
        output = self.deserializer.update(request)
        expected = {'changes': [
            {'op': 'replace', 'path': ['id'], 'value': UUID1},
            {'op': 'replace', 'path': ['name'], 'value': 'fedora'},
            {'op': 'replace', 'path': ['is_public'], 'value': True},
            {'op': 'replace', 'path': ['is_public'], 'value': False},
            {'op': 'replace', 'path': ['tags'], 'value': ['king', 'kong']},
            {'op': 'replace', 'path': ['protected'], 'value': True},
            {'op': 'replace', 'path': ['container_format'], 'value': 'bare'},
            {'op': 'replace', 'path': ['disk_format'], 'value': 'raw'},
            {'op': 'replace', 'path': ['min_ram'], 'value': 128},
            {'op': 'replace', 'path': ['min_disk'], 'value': 10},
        ]}
        self.assertEquals(output, expected)

    def test_update_readonly_attributes(self):
        samples = {
            'status': 'active',
            'checksum': 'abcdefghijklmnopqrstuvwxyz012345',
            'size': 9001,
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'direct_url': '/a/b/c/d',
            'self': '/e/f/g/h',
            'file': '/e/f/g/h/file',
            'schema': '/i/j/k',
        }

        for key, value in samples.items():
            request = self._get_fake_patch_request()
            body = [{'replace': '/%s' % key, 'value': value}]
            request.body = json.dumps(body)
            try:
                self.deserializer.update(request)
            except webob.exc.HTTPForbidden:
                pass  # desired behavior
            else:
                self.fail("Updating %s did not result in HTTPForbidden" % key)

    def test_update_reserved_attributes(self):
        samples = {
            'owner': TENANT1,
            'is_public': True,
            'location': '/a/b/c/d',
            'deleted': False,
            'deleted_at': ISOTIME,
        }

        for key, value in samples.items():
            request = self._get_fake_patch_request()
            body = [{'replace': '/%s' % key, 'value': value}]
            request.body = json.dumps(body)
            try:
                self.deserializer.update(request)
            except webob.exc.HTTPForbidden:
                pass  # desired behavior
            else:
                self.fail("Updating %s did not result in HTTPForbidden" % key)

    def test_update_invalid_attributes(self):
        keys = [
            'noslash',
            '//twoslash',
            '/other/twoslash'
            '/trailingslash/',
            '/lone~tilde'
            '/trailingtilde~'
        ]

        for key in keys:
            request = self._get_fake_patch_request()
            body = [{'replace': '%s' % key, 'value': 'dummy'}]
            request.body = json.dumps(body)
            try:
                self.deserializer.update(request)
            except webob.exc.HTTPBadRequest:
                pass  # desired behavior
            else:
                self.fail("Updating %s did not result in HTTPBadRequest" % key)

    def test_update_pointer_encoding(self):
        samples = {
            '/keywith~1slash': 'keywith/slash',
            '/keywith~0tilde': 'keywith~tilde',
            '/tricky~01': 'tricky~1',
        }

        for encoded, decoded in samples.items():
            request = self._get_fake_patch_request()
            body = [{'replace': '%s' % encoded, 'value': 'dummy'}]
            request.body = json.dumps(body)
            output = self.deserializer.update(request)
            self.assertEqual(output['changes'][0]['path'][1], decoded)

    def test_update_multiple_operations(self):
        request = self._get_fake_patch_request()
        body = [{'replace': '/foo', 'add': '/bar', 'value': 'snore'}]
        request.body = json.dumps(body)
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.update, request)

    def test_update_missing_operations(self):
        request = self._get_fake_patch_request()
        body = [{'value': 'arcata'}]
        request.body = json.dumps(body)
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.update, request)

    def test_update_missing_value(self):
        request = self._get_fake_patch_request()
        body = [{'replace': '/colburn'}]
        request.body = json.dumps(body)
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.update, request)

    def test_index(self):
        marker = utils.generate_uuid()
        path = '/images?limit=1&marker=%s' % marker
        request = unit_test_utils.get_fake_request(path)
        expected = {'limit': 1,
                    'marker': marker,
                    'sort_key': 'created_at',
                    'sort_dir': 'desc',
                    'filters': {}}
        output = self.deserializer.index(request)
        self.assertEqual(output, expected)

    def test_index_with_filter(self):
        name = 'My Little Image'
        path = '/images?name=%s' % name
        request = unit_test_utils.get_fake_request(path)
        output = self.deserializer.index(request)
        self.assertEqual(output['filters']['name'], name)

    def test_index_strip_params_from_filters(self):
        name = 'My Little Image'
        path = '/images?name=%s' % name
        request = unit_test_utils.get_fake_request(path)
        output = self.deserializer.index(request)
        self.assertEqual(output['filters']['name'], name)
        self.assertEqual(len(output['filters']), 1)

    def test_index_with_many_filter(self):
        name = 'My Little Image'
        instance_id = utils.generate_uuid()
        path = '/images?name=%(name)s&id=%(instance_id)s' % locals()
        request = unit_test_utils.get_fake_request(path)
        output = self.deserializer.index(request)
        self.assertEqual(output['filters']['name'], name)
        self.assertEqual(output['filters']['id'], instance_id)

    def test_index_with_filter_and_limit(self):
        name = 'My Little Image'
        path = '/images?name=%s&limit=1' % name
        request = unit_test_utils.get_fake_request(path)
        output = self.deserializer.index(request)
        self.assertEqual(output['filters']['name'], name)
        self.assertEqual(output['limit'], 1)

    def test_index_non_integer_limit(self):
        request = unit_test_utils.get_fake_request('/images?limit=blah')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.index, request)

    def test_index_zero_limit(self):
        request = unit_test_utils.get_fake_request('/images?limit=0')
        expected = {'limit': 0,
                    'sort_key': 'created_at',
                    'sort_dir': 'desc',
                    'filters': {}}
        output = self.deserializer.index(request)
        self.assertEqual(expected, output)

    def test_index_negative_limit(self):
        request = unit_test_utils.get_fake_request('/images?limit=-1')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.index, request)

    def test_index_fraction(self):
        request = unit_test_utils.get_fake_request('/images?limit=1.1')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.index, request)

    def test_index_marker(self):
        marker = utils.generate_uuid()
        path = '/images?marker=%s' % marker
        request = unit_test_utils.get_fake_request(path)
        output = self.deserializer.index(request)
        self.assertEqual(output.get('marker'), marker)

    def test_index_marker_not_specified(self):
        request = unit_test_utils.get_fake_request('/images')
        output = self.deserializer.index(request)
        self.assertFalse('marker' in output)

    def test_index_limit_not_specified(self):
        request = unit_test_utils.get_fake_request('/images')
        output = self.deserializer.index(request)
        self.assertFalse('limit' in output)

    def test_index_sort_key_id(self):
        request = unit_test_utils.get_fake_request('/images?sort_key=id')
        output = self.deserializer.index(request)
        expected = {
            'sort_key': 'id',
            'sort_dir': 'desc',
            'filters': {}
        }
        self.assertEqual(output, expected)

    def test_index_sort_dir_asc(self):
        request = unit_test_utils.get_fake_request('/images?sort_dir=asc')
        output = self.deserializer.index(request)
        expected = {
            'sort_key': 'created_at',
            'sort_dir': 'asc',
            'filters': {}}
        self.assertEqual(output, expected)

    def test_index_sort_dir_bad_value(self):
        request = unit_test_utils.get_fake_request('/images?sort_dir=blah')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.index, request)


class TestImagesDeserializerWithExtendedSchema(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesDeserializerWithExtendedSchema, self).setUp()
        self.config(allow_additional_image_properties=False)
        custom_image_properties = {
            'pants': {
                'type': 'string',
                'required': True,
                'enum': ['on', 'off'],
            },
        }
        schema = glance.api.v2.images.get_schema(custom_image_properties)
        self.deserializer = glance.api.v2.images.RequestDeserializer(schema)

    def test_create(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({'name': 'image-1', 'pants': 'on'})
        output = self.deserializer.create(request)
        expected = {
            'image': {
                'name': 'image-1',
                'properties': {'pants': 'on'},
            },
        }
        self.assertEqual(expected, output)

    def test_create_bad_data(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({'name': 'image-1', 'pants': 'borked'})
        self.assertRaises(webob.exc.HTTPBadRequest,
                self.deserializer.create, request)

    def test_update(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/openstack-images-v2.0-json-patch'
        request.body = json.dumps([{'add': '/pants', 'value': 'off'}])
        output = self.deserializer.update(request)
        expected = {'changes': [
            {'op': 'add', 'path': ['properties', 'pants'], 'value': 'off'},
        ]}
        self.assertEqual(expected, output)

    def test_update_bad_data(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/openstack-images-v2.0-json-patch'
        request.body = json.dumps([{'add': '/pants', 'value': 'cutoffs'}])
        self.assertRaises(webob.exc.HTTPBadRequest,
                self.deserializer.update, request)


class TestImagesDeserializerWithAdditionalProperties(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesDeserializerWithAdditionalProperties, self).setUp()
        self.config(allow_additional_image_properties=True)
        self.deserializer = glance.api.v2.images.RequestDeserializer()

    def test_create(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({'foo': 'bar'})
        output = self.deserializer.create(request)
        expected = {'image': {'properties': {'foo': 'bar'}}}
        self.assertEqual(expected, output)

    def test_create_with_numeric_property(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({'abc': 123})
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.create, request)

    def test_update_with_numeric_property(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/openstack-images-v2.0-json-patch'
        request.body = json.dumps([{'add': '/foo', 'value': 123}])
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.update, request)

    def test_create_with_list_property(self):
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({'foo': ['bar']})
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.create, request)

    def test_update_with_list_property(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/openstack-images-v2.0-json-patch'
        request.body = json.dumps([{'add': '/foo', 'value': ['bar', 'baz']}])
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.update, request)

    def test_update(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/openstack-images-v2.0-json-patch'
        request.body = json.dumps([{'add': '/foo', 'value': 'bar'}])
        output = self.deserializer.update(request)
        change = {'op': 'add', 'path': ['properties', 'foo'], 'value': 'bar'}
        self.assertEqual(output, {'changes': [change]})


class TestImagesDeserializerNoAdditionalProperties(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesDeserializerNoAdditionalProperties, self).setUp()
        self.config(allow_additional_image_properties=False)
        self.deserializer = glance.api.v2.images.RequestDeserializer()

    def test_create_with_additional_properties_disallowed(self):
        self.config(allow_additional_image_properties=False)
        request = unit_test_utils.get_fake_request()
        request.body = json.dumps({'foo': 'bar'})
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.create, request)

    def test_update(self):
        request = unit_test_utils.get_fake_request()
        request.content_type = 'application/openstack-images-v2.0-json-patch'
        request.body = json.dumps([{'add': '/foo', 'value': 'bar'}])
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.deserializer.update, request)


class TestImagesSerializer(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesSerializer, self).setUp()
        self.serializer = glance.api.v2.images.ResponseSerializer()
        self.fixtures = [
            #NOTE(bcwaldon): This first fixture has every property defined
            _fixture(UUID1, name='image-1', size=1024, tags=['one', 'two'],
                    created_at=DATETIME, updated_at=DATETIME, owner=TENANT1,
                    is_public=True, container_format='ami', disk_format='ami',
                    checksum='ca425b88f047ce8ec45ee90e813ada91',
                    min_ram=128, min_disk=10),

            #NOTE(bcwaldon): This second fixture depends on default behavior
            # and sets most values to None
            _fixture(UUID2, created_at=DATETIME, updated_at=DATETIME),
        ]

    def test_index(self):
        expected = {
            'images': [
                {
                    'id': UUID1,
                    'name': 'image-1',
                    'status': 'queued',
                    'visibility': 'public',
                    'protected': False,
                    'tags': ['one', 'two'],
                    'size': 1024,
                    'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
                    'container_format': 'ami',
                    'disk_format': 'ami',
                    'min_ram': 128,
                    'min_disk': 10,
                    'created_at': ISOTIME,
                    'updated_at': ISOTIME,
                    'self': '/v2/images/%s' % UUID1,
                    'file': '/v2/images/%s/file' % UUID1,
                    'schema': '/v2/schemas/image',
                },
                {
                    'id': UUID2,
                    'status': 'queued',
                    'visibility': 'private',
                    'protected': False,
                    'tags': [],
                    'created_at': ISOTIME,
                    'updated_at': ISOTIME,
                    'self': '/v2/images/%s' % UUID2,
                    'file': '/v2/images/%s/file' % UUID2,
                    'schema': '/v2/schemas/image',
                },
            ],
            'first': '/v2/images',
            'schema': '/v2/schemas/images',
        }
        request = webob.Request.blank('/v2/images')
        response = webob.Response(request=request)
        result = {'images': self.fixtures}
        self.serializer.index(response, result)
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)

    def test_index_next_marker(self):
        request = webob.Request.blank('/v2/images')
        response = webob.Response(request=request)
        result = {'images': self.fixtures, 'next_marker': UUID2}
        self.serializer.index(response, result)
        output = json.loads(response.body)
        self.assertEqual('/v2/images?marker=%s' % UUID2, output['next'])

    def test_index_carries_query_parameters(self):
        url = '/v2/images?limit=10&sort_key=id&sort_dir=asc'
        request = webob.Request.blank(url)
        response = webob.Response(request=request)
        result = {'images': self.fixtures, 'next_marker': UUID2}
        self.serializer.index(response, result)
        output = json.loads(response.body)
        self.assertEqual('/v2/images?sort_key=id&sort_dir=asc&limit=10',
                         output['first'])
        expect_next = '/v2/images?sort_key=id&sort_dir=asc&limit=10&marker=%s'
        self.assertEqual(expect_next % UUID2, output['next'])

    def test_show_full_fixture(self):
        expected = {
            'id': UUID1,
            'name': 'image-1',
            'status': 'queued',
            'visibility': 'public',
            'protected': False,
            'tags': ['one', 'two'],
            'size': 1024,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'container_format': 'ami',
            'disk_format': 'ami',
            'min_ram': 128,
            'min_disk': 10,
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID1,
            'file': '/v2/images/%s/file' % UUID1,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        self.serializer.show(response, self.fixtures[0])
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)

    def test_show_minimal_fixture(self):
        expected = {
            'id': UUID2,
            'status': 'queued',
            'visibility': 'private',
            'protected': False,
            'tags': [],
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID2,
            'file': '/v2/images/%s/file' % UUID2,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        self.serializer.show(response, self.fixtures[1])
        self.assertEqual(expected, json.loads(response.body))

    def test_create(self):
        expected = {
            'id': UUID1,
            'name': 'image-1',
            'status': 'queued',
            'visibility': 'public',
            'protected': False,
            'tags': ['one', 'two'],
            'size': 1024,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'container_format': 'ami',
            'disk_format': 'ami',
            'min_ram': 128,
            'min_disk': 10,
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID1,
            'file': '/v2/images/%s/file' % UUID1,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        self.serializer.create(response, self.fixtures[0])
        self.assertEqual(response.status_int, 201)
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)
        self.assertEqual(response.location, '/v2/images/%s' % UUID1)

    def test_update(self):
        expected = {
            'id': UUID1,
            'name': 'image-1',
            'status': 'queued',
            'visibility': 'public',
            'protected': False,
            'tags': ['one', 'two'],
            'size': 1024,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'container_format': 'ami',
            'disk_format': 'ami',
            'min_ram': 128,
            'min_disk': 10,
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID1,
            'file': '/v2/images/%s/file' % UUID1,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        self.serializer.update(response, self.fixtures[0])
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)


class TestImagesSerializerWithUnicode(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesSerializerWithUnicode, self).setUp()
        self.serializer = glance.api.v2.images.ResponseSerializer()
        self.fixtures = [
            #NOTE(bcwaldon): This first fixture has every property defined
            _fixture(UUID1, name=u'OpenStack\u2122-1',
                    size=1024, tags=[u'\u2160', u'\u2161'],
                    created_at=DATETIME, updated_at=DATETIME, owner=TENANT1,
                    is_public=True, container_format='ami', disk_format='ami',
                    checksum=u'ca425b88f047ce8ec45ee90e813ada91',
                    min_ram=128, min_disk=10,
                    properties={'lang': u'Fran\u00E7ais',
                                u'dispos\u00E9': u'f\u00E2ch\u00E9'}),
        ]

    def test_index(self):
        expected = {
            u'images': [
                {
                    u'id': UUID1,
                    u'name': u'OpenStack\u2122-1',
                    u'status': u'queued',
                    u'visibility': u'public',
                    u'protected': False,
                    u'tags': [u'\u2160', u'\u2161'],
                    u'size': 1024,
                    u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
                    u'container_format': u'ami',
                    u'disk_format': u'ami',
                    u'min_ram': 128,
                    u'min_disk': 10,
                    u'created_at': unicode(ISOTIME),
                    u'updated_at': unicode(ISOTIME),
                    u'self': u'/v2/images/%s' % UUID1,
                    u'file': u'/v2/images/%s/file' % UUID1,
                    u'schema': u'/v2/schemas/image',
                    u'lang': u'Fran\u00E7ais',
                    u'dispos\u00E9': u'f\u00E2ch\u00E9',
                },
            ],
            u'first': u'/v2/images',
            u'schema': u'/v2/schemas/images',
        }
        request = webob.Request.blank('/v2/images')
        response = webob.Response(request=request)
        result = {u'images': self.fixtures}
        self.serializer.index(response, result)
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)

    def test_show_full_fixture(self):
        expected = {
            u'id': UUID1,
            u'name': u'OpenStack\u2122-1',
            u'status': u'queued',
            u'visibility': u'public',
            u'protected': False,
            u'tags': [u'\u2160', u'\u2161'],
            u'size': 1024,
            u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
            u'container_format': u'ami',
            u'disk_format': u'ami',
            u'min_ram': 128,
            u'min_disk': 10,
            u'created_at': unicode(ISOTIME),
            u'updated_at': unicode(ISOTIME),
            u'self': u'/v2/images/%s' % UUID1,
            u'file': u'/v2/images/%s/file' % UUID1,
            u'schema': u'/v2/schemas/image',
            u'lang': u'Fran\u00E7ais',
            u'dispos\u00E9': u'f\u00E2ch\u00E9',
        }
        response = webob.Response()
        self.serializer.show(response, self.fixtures[0])
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)

    def test_create(self):
        expected = {
            u'id': UUID1,
            u'name': u'OpenStack\u2122-1',
            u'status': u'queued',
            u'visibility': u'public',
            u'protected': False,
            u'tags': [u'\u2160', u'\u2161'],
            u'size': 1024,
            u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
            u'container_format': u'ami',
            u'disk_format': u'ami',
            u'min_ram': 128,
            u'min_disk': 10,
            u'created_at': unicode(ISOTIME),
            u'updated_at': unicode(ISOTIME),
            u'self': u'/v2/images/%s' % UUID1,
            u'file': u'/v2/images/%s/file' % UUID1,
            u'schema': u'/v2/schemas/image',
            u'lang': u'Fran\u00E7ais',
            u'dispos\u00E9': u'f\u00E2ch\u00E9',
        }
        response = webob.Response()
        self.serializer.create(response, self.fixtures[0])
        self.assertEqual(response.status_int, 201)
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)
        self.assertEqual(response.location, '/v2/images/%s' % UUID1)

    def test_update(self):
        expected = {
            u'id': UUID1,
            u'name': u'OpenStack\u2122-1',
            u'status': u'queued',
            u'visibility': u'public',
            u'protected': False,
            u'tags': [u'\u2160', u'\u2161'],
            u'size': 1024,
            u'checksum': u'ca425b88f047ce8ec45ee90e813ada91',
            u'container_format': u'ami',
            u'disk_format': u'ami',
            u'min_ram': 128,
            u'min_disk': 10,
            u'created_at': unicode(ISOTIME),
            u'updated_at': unicode(ISOTIME),
            u'self': u'/v2/images/%s' % UUID1,
            u'file': u'/v2/images/%s/file' % UUID1,
            u'schema': u'/v2/schemas/image',
            u'lang': u'Fran\u00E7ais',
            u'dispos\u00E9': u'f\u00E2ch\u00E9',
        }
        response = webob.Response()
        self.serializer.update(response, self.fixtures[0])
        self.assertEqual(expected, json.loads(response.body))
        self.assertEqual('application/json', response.content_type)


class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesSerializerWithExtendedSchema, self).setUp()
        self.config(allow_additional_image_properties=False)
        custom_image_properties = {
            'color': {
                'type': 'string',
                'required': True,
                'enum': ['red', 'green'],
            },
        }
        schema = glance.api.v2.images.get_schema(custom_image_properties)
        self.serializer = glance.api.v2.images.ResponseSerializer(schema)

        self.fixture = _fixture(UUID2, name='image-2', owner=TENANT2,
                checksum='ca425b88f047ce8ec45ee90e813ada91',
                created_at=DATETIME, updated_at=DATETIME,
                size=1024, properties={'color': 'green', 'mood': 'grouchy'})

    def test_show(self):
        expected = {
            'id': UUID2,
            'name': 'image-2',
            'status': 'queued',
            'visibility': 'private',
            'protected': False,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'tags': [],
            'size': 1024,
            'color': 'green',
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID2,
            'file': '/v2/images/%s/file' % UUID2,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        self.serializer.show(response, self.fixture)
        self.assertEqual(expected, json.loads(response.body))

    def test_show_reports_invalid_data(self):
        self.fixture['properties']['color'] = 'invalid'
        expected = {
            'id': UUID2,
            'name': 'image-2',
            'status': 'queued',
            'visibility': 'private',
            'protected': False,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'tags': [],
            'size': 1024,
            'color': 'invalid',
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID2,
            'file': '/v2/images/%s/file' % UUID2,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        self.serializer.show(response, self.fixture)
        self.assertEqual(expected, json.loads(response.body))


class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):

    def setUp(self):
        super(TestImagesSerializerWithAdditionalProperties, self).setUp()
        self.config(allow_additional_image_properties=True)
        self.fixture = _fixture(UUID2, name='image-2', owner=TENANT2,
            checksum='ca425b88f047ce8ec45ee90e813ada91',
            created_at=DATETIME, updated_at=DATETIME,
            properties={'marx': 'groucho'}, size=1024)

    def test_show(self):
        serializer = glance.api.v2.images.ResponseSerializer()
        expected = {
            'id': UUID2,
            'name': 'image-2',
            'status': 'queued',
            'visibility': 'private',
            'protected': False,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'marx': 'groucho',
            'tags': [],
            'size': 1024,
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID2,
            'file': '/v2/images/%s/file' % UUID2,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        serializer.show(response, self.fixture)
        self.assertEqual(expected, json.loads(response.body))

    def test_show_invalid_additional_property(self):
        """Ensure that the serializer passes through invalid additional
        properties (i.e. non-string) without complaining.
        """
        serializer = glance.api.v2.images.ResponseSerializer()
        self.fixture['properties']['marx'] = 123
        expected = {
            'id': UUID2,
            'name': 'image-2',
            'status': 'queued',
            'visibility': 'private',
            'protected': False,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'marx': 123,
            'tags': [],
            'size': 1024,
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID2,
            'file': '/v2/images/%s/file' % UUID2,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        serializer.show(response, self.fixture)
        self.assertEqual(expected, json.loads(response.body))

    def test_show_with_additional_properties_disabled(self):
        self.config(allow_additional_image_properties=False)
        serializer = glance.api.v2.images.ResponseSerializer()
        expected = {
            'id': UUID2,
            'name': 'image-2',
            'status': 'queued',
            'visibility': 'private',
            'protected': False,
            'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
            'tags': [],
            'size': 1024,
            'created_at': ISOTIME,
            'updated_at': ISOTIME,
            'self': '/v2/images/%s' % UUID2,
            'file': '/v2/images/%s/file' % UUID2,
            'schema': '/v2/schemas/image',
        }
        response = webob.Response()
        serializer.show(response, self.fixture)
        self.assertEqual(expected, json.loads(response.body))


class TestImagesSerializerDirectUrl(test_utils.BaseTestCase):
    def setUp(self):
        super(TestImagesSerializerDirectUrl, self).setUp()
        self.serializer = glance.api.v2.images.ResponseSerializer()

        self.active_image = _fixture(UUID1, name='image-1', is_public=True,
                status='active', size=1024,
                created_at=DATETIME, updated_at=DATETIME,
                location='http://some/fake/location')

        self.queued_image = _fixture(UUID2, name='image-2', status='active',
                created_at=DATETIME, updated_at=DATETIME,
                checksum='ca425b88f047ce8ec45ee90e813ada91')

    def _do_index(self):
        request = webob.Request.blank('/v2/images')
        response = webob.Response(request=request)
        self.serializer.index(response,
                {'images': [self.active_image, self.queued_image]})
        return json.loads(response.body)['images']

    def _do_show(self, image):
        request = webob.Request.blank('/v2/images')
        response = webob.Response(request=request)
        self.serializer.show(response, image)
        return json.loads(response.body)

    def test_index_store_location_enabled(self):
        self.config(show_image_direct_url=True)
        images = self._do_index()

        # NOTE(markwash): ordering sanity check
        self.assertEqual(images[0]['id'], UUID1)
        self.assertEqual(images[1]['id'], UUID2)

        self.assertEqual(images[0]['direct_url'], 'http://some/fake/location')
        self.assertFalse('direct_url' in images[1])

    def test_index_store_location_explicitly_disabled(self):
        self.config(show_image_direct_url=False)
        images = self._do_index()
        self.assertFalse('direct_url' in images[0])
        self.assertFalse('direct_url' in images[1])

    def test_show_location_enabled(self):
        self.config(show_image_direct_url=True)
        image = self._do_show(self.active_image)
        self.assertEqual(image['direct_url'], 'http://some/fake/location')

    def test_show_location_enabled_but_not_set(self):
        self.config(show_image_direct_url=True)
        image = self._do_show(self.queued_image)
        self.assertFalse('direct_url' in image)

    def test_show_location_explicitly_disabled(self):
        self.config(show_image_direct_url=False)
        image = self._do_show(self.active_image)
        self.assertFalse('direct_url' in image)
