# Copyright 2012 OpenStack Foundation
# 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.

from oslo_config import cfg
from oslo_serialization import jsonutils
from six.moves import http_client
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range

from glance.tests.integration.v2 import base

CONF = cfg.CONF


class TestPropertyQuotaViolations(base.ApiTest):
    def __init__(self, *args, **kwargs):
        super(TestPropertyQuotaViolations, self).__init__(*args, **kwargs)
        self.api_flavor = 'noauth'
        self.registry_flavor = 'fakeauth'

    def _headers(self, custom_headers=None):
        base_headers = {
            'X-Identity-Status': 'Confirmed',
            'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
            'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
            'X-Tenant-Id': "foo",
            'X-Roles': 'member',
        }
        base_headers.update(custom_headers or {})
        return base_headers

    def _get(self, image_id=""):
        path = ('/v2/images/%s' % image_id).rstrip('/')
        rsp, content = self.http.request(path, 'GET', headers=self._headers())
        self.assertEqual(http_client.OK, rsp.status)
        content = jsonutils.loads(content)
        return content

    def _create_image(self, body):
        path = '/v2/images'
        headers = self._headers({'content-type': 'application/json'})
        rsp, content = self.http.request(path, 'POST', headers=headers,
                                         body=jsonutils.dumps(body))
        self.assertEqual(http_client.CREATED, rsp.status)
        return jsonutils.loads(content)

    def _patch(self, image_id, body, expected_status):
        path = '/v2/images/%s' % image_id
        media_type = 'application/openstack-images-v2.1-json-patch'
        headers = self._headers({'content-type': media_type})
        rsp, content = self.http.request(path, 'PATCH', headers=headers,
                                         body=jsonutils.dumps(body))
        self.assertEqual(expected_status, rsp.status, content)
        return content

    def test_property_ops_when_quota_violated(self):
        # Image list must be empty to begin with
        image_list = self._get()['images']
        self.assertEqual(0, len(image_list))

        orig_property_quota = 10
        CONF.set_override('image_property_quota', orig_property_quota)

        # Create an image (with deployer-defined properties)
        req_body = {'name': 'testimg',
                    'disk_format': 'aki',
                    'container_format': 'aki'}
        for i in range(orig_property_quota):
            req_body['k_%d' % i] = 'v_%d' % i
        image = self._create_image(req_body)
        image_id = image['id']
        for i in range(orig_property_quota):
            self.assertEqual('v_%d' % i, image['k_%d' % i])

        # Now reduce property quota. We should be allowed to modify/delete
        # existing properties (even if the result still exceeds property quota)
        # but not add new properties nor replace existing properties with new
        # properties (as long as we're over the quota)
        self.config(image_property_quota=2)

        patch_body = [{'op': 'replace', 'path': '/k_4', 'value': 'v_4.new'}]
        image = jsonutils.loads(self._patch(image_id, patch_body,
                                            http_client.OK))
        self.assertEqual('v_4.new', image['k_4'])

        patch_body = [{'op': 'remove', 'path': '/k_7'}]
        image = jsonutils.loads(self._patch(image_id, patch_body,
                                            http_client.OK))
        self.assertNotIn('k_7', image)

        patch_body = [{'op': 'add', 'path': '/k_100', 'value': 'v_100'}]
        self._patch(image_id, patch_body, http_client.REQUEST_ENTITY_TOO_LARGE)
        image = self._get(image_id)
        self.assertNotIn('k_100', image)

        patch_body = [
            {'op': 'remove', 'path': '/k_5'},
            {'op': 'add', 'path': '/k_100', 'value': 'v_100'},
        ]
        self._patch(image_id, patch_body, http_client.REQUEST_ENTITY_TOO_LARGE)
        image = self._get(image_id)
        self.assertNotIn('k_100', image)
        self.assertIn('k_5', image)

        # temporary violations to property quota should be allowed as long as
        # it's within one PATCH request and the end result does not violate
        # quotas.
        patch_body = [{'op': 'add', 'path': '/k_100', 'value': 'v_100'},
                      {'op': 'add', 'path': '/k_99', 'value': 'v_99'}]
        to_rm = ['k_%d' % i for i in range(orig_property_quota) if i != 7]
        patch_body.extend([{'op': 'remove', 'path': '/%s' % k} for k in to_rm])
        image = jsonutils.loads(self._patch(image_id, patch_body,
                                            http_client.OK))
        self.assertEqual('v_99', image['k_99'])
        self.assertEqual('v_100', image['k_100'])
        for k in to_rm:
            self.assertNotIn(k, image)
