# Copyright 2018 Huawei
#
#    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 http.client
import uuid

from keystone.common import provider_api
from keystone.common.validation import validators
import keystone.conf
from keystone.tests import unit
from keystone.tests.unit import test_v3

CONF = keystone.conf.CONF
PROVIDERS = provider_api.ProviderAPIs


class LimitModelTestCase(test_v3.RestfulTestCase):
    def test_get_default_limit_model_response_schema(self):
        schema = {
            'type': 'object',
            'properties': {
                'model': {
                    'type': 'object',
                    'properties': {
                        'name': {'type': 'string'},
                        'description': {'type': 'string'},
                    },
                    'required': ['name', 'description'],
                    'additionalProperties': False,
                }
            },
            'required': ['model'],
            'additionalProperties': False,
        }
        validator = validators.SchemaValidator(schema)
        response = self.get('/limits/model')
        validator.validate(response.json_body)

    def test_head_limit_model(self):
        self.head('/limits/model', expected_status=http.client.OK)

    def test_get_limit_model_returns_default_model(self):
        response = self.get('/limits/model')
        model = response.result
        expected = {
            'model': {
                'name': 'flat',
                'description': (
                    'Limit enforcement and validation does not take project '
                    'hierarchy into consideration.'
                ),
            }
        }
        self.assertDictEqual(expected, model)

    def test_get_limit_model_without_token_fails(self):
        self.get(
            '/limits/model',
            noauth=True,
            expected_status=http.client.UNAUTHORIZED,
        )

    def test_head_limit_model_without_token_fails(self):
        self.head(
            '/limits/model',
            noauth=True,
            expected_status=http.client.UNAUTHORIZED,
        )


class RegisteredLimitsTestCase(test_v3.RestfulTestCase):
    """Test registered_limits CRUD."""

    def setUp(self):
        super().setUp()

        # Most of these tests require system-scoped tokens. Let's have one on
        # hand so that we can use it in tests when we need it.
        PROVIDERS.assignment_api.create_system_grant_for_user(
            self.user_id, self.role_id
        )
        self.system_admin_token = self.get_system_scoped_token()

        # There is already a sample service and region created from
        # load_sample_data() but we're going to create another service and
        # region for specific testing purposes.
        response = self.post('/regions', body={'region': {}})
        self.region2 = response.json_body['region']
        self.region_id2 = self.region2['id']

        service_ref = {
            'service': {
                'name': uuid.uuid4().hex,
                'enabled': True,
                'type': 'type2',
            }
        }
        response = self.post('/services', body=service_ref)
        self.service2 = response.json_body['service']
        self.service_id2 = self.service2['id']

    def test_create_registered_limit(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id, region_id=self.region_id
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        registered_limits = r.result['registered_limits']
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'default_limit',
            'description',
        ]:
            self.assertEqual(registered_limits[0][key], ref[key])

    def test_create_registered_limit_without_region(self):
        ref = unit.new_registered_limit_ref(service_id=self.service_id)
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        registered_limits = r.result['registered_limits']
        for key in ['service_id', 'resource_name', 'default_limit']:
            self.assertEqual(registered_limits[0][key], ref[key])
        self.assertIsNone(registered_limits[0].get('region_id'))

    def test_create_registered_without_description(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id, region_id=self.region_id
        )
        ref.pop('description')
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        registered_limits = r.result['registered_limits']
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'default_limit',
        ]:
            self.assertEqual(registered_limits[0][key], ref[key])
        self.assertIsNone(registered_limits[0]['description'])

    def test_create_multi_registered_limit(self):
        ref1 = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_registered_limit_ref(
            service_id=self.service_id, resource_name='snapshot'
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        registered_limits = r.result['registered_limits']
        for key in ['service_id', 'resource_name', 'default_limit']:
            self.assertEqual(registered_limits[0][key], ref1[key])
            self.assertEqual(registered_limits[1][key], ref2[key])
        self.assertEqual(registered_limits[0]['region_id'], ref1['region_id'])
        self.assertIsNone(registered_limits[1].get('region_id'))

    def test_create_registered_limit_return_count(self):
        ref1 = unit.new_registered_limit_ref(
            service_id=self.service_id, region_id=self.region_id
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref1]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        registered_limits = r.result['registered_limits']
        self.assertEqual(1, len(registered_limits))

        ref2 = unit.new_registered_limit_ref(
            service_id=self.service_id2, region_id=self.region_id2
        )
        ref3 = unit.new_registered_limit_ref(service_id=self.service_id2)
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref2, ref3]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        registered_limits = r.result['registered_limits']
        self.assertEqual(2, len(registered_limits))

    def test_create_registered_limit_with_invalid_input(self):
        ref1 = unit.new_registered_limit_ref()
        ref2 = unit.new_registered_limit_ref(default_limit='not_int')
        ref3 = unit.new_registered_limit_ref(resource_name=123)
        ref4 = unit.new_registered_limit_ref(region_id='fake_region')
        for input_limit in [ref1, ref2, ref3, ref4]:
            self.post(
                '/registered_limits',
                body={'registered_limits': [input_limit]},
                token=self.system_admin_token,
                expected_status=http.client.BAD_REQUEST,
            )

    def test_create_registered_limit_duplicate(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id, region_id=self.region_id
        )
        self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CONFLICT,
        )

    def test_update_registered_limit(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            default_limit=10,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        update_ref = {
            'service_id': self.service_id2,
            'region_id': self.region_id2,
            'resource_name': 'snapshot',
            'default_limit': 5,
            'description': 'test description',
        }
        r = self.patch(
            '/registered_limits/{}'.format(
                r.result['registered_limits'][0]['id']
            ),
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        new_registered_limits = r.result['registered_limit']

        self.assertEqual(new_registered_limits['service_id'], self.service_id2)
        self.assertEqual(new_registered_limits['region_id'], self.region_id2)
        self.assertEqual(new_registered_limits['resource_name'], 'snapshot')
        self.assertEqual(new_registered_limits['default_limit'], 5)
        self.assertEqual(
            new_registered_limits['description'], 'test description'
        )

    def test_update_registered_limit_region_failed(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id,
            resource_name='volume',
            default_limit=10,
            description='test description',
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        update_ref = {'region_id': self.region_id}
        registered_limit_id = r.result['registered_limits'][0]['id']
        r = self.patch(
            f'/registered_limits/{registered_limit_id}',
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        new_registered_limits = r.result['registered_limit']
        self.assertEqual(self.region_id, new_registered_limits['region_id'])

        update_ref['region_id'] = ''
        r = self.patch(
            f'/registered_limits/{registered_limit_id}',
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.BAD_REQUEST,
        )

    def test_update_registered_limit_description(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            default_limit=10,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        update_ref = {'description': 'test description'}
        registered_limit_id = r.result['registered_limits'][0]['id']
        r = self.patch(
            f'/registered_limits/{registered_limit_id}',
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        new_registered_limits = r.result['registered_limit']
        self.assertEqual(
            new_registered_limits['description'], 'test description'
        )

        update_ref['description'] = ''
        r = self.patch(
            f'/registered_limits/{registered_limit_id}',
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        new_registered_limits = r.result['registered_limit']
        self.assertEqual(new_registered_limits['description'], '')

    def test_update_registered_limit_region_id_to_none(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            default_limit=10,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        update_ref = {'region_id': None}
        registered_limit_id = r.result['registered_limits'][0]['id']
        r = self.patch(
            f'/registered_limits/{registered_limit_id}',
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        self.assertIsNone(r.result['registered_limit']['region_id'])

    def test_update_registered_limit_region_id_to_none_conflict(self):
        ref1 = unit.new_registered_limit_ref(
            service_id=self.service_id,
            resource_name='volume',
            default_limit=10,
        )
        ref2 = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            default_limit=10,
        )
        self.post(
            '/registered_limits',
            body={'registered_limits': [ref1]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        update_ref = {'region_id': None}
        registered_limit_id = r.result['registered_limits'][0]['id']
        # There is a registered limit with "service_id=self.service_id,
        # region_id=None" already. So update ref2's region_id to None will
        # raise 409 Conflict Error.
        self.patch(
            f'/registered_limits/{registered_limit_id}',
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.CONFLICT,
        )

    def test_update_registered_limit_not_found(self):
        update_ref = {
            'service_id': self.service_id,
            'region_id': self.region_id,
            'resource_name': 'snapshot',
            'default_limit': 5,
        }
        self.patch(
            f'/registered_limits/{uuid.uuid4().hex}',
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.NOT_FOUND,
        )

    def test_update_registered_limit_with_invalid_input(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            default_limit=10,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        reg_id = r.result['registered_limits'][0]['id']

        update_ref1 = unit.new_registered_limit_ref(service_id='fake_id')
        update_ref2 = unit.new_registered_limit_ref(default_limit='not_int')
        update_ref3 = unit.new_registered_limit_ref(resource_name=123)
        update_ref4 = unit.new_registered_limit_ref(region_id='fake_region')
        update_ref5 = unit.new_registered_limit_ref(description=123)
        for input_limit in [
            update_ref1,
            update_ref2,
            update_ref3,
            update_ref4,
            update_ref5,
        ]:
            self.patch(
                f'/registered_limits/{reg_id}',
                body={'registered_limit': input_limit},
                token=self.system_admin_token,
                expected_status=http.client.BAD_REQUEST,
            )

    def test_update_registered_limit_with_referenced_limit(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            default_limit=10,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        update_ref = {
            'service_id': self.service_id2,
            'region_id': self.region_id2,
            'resource_name': 'snapshot',
            'default_limit': 5,
        }
        self.patch(
            '/registered_limits/{}'.format(
                r.result['registered_limits'][0]['id']
            ),
            body={'registered_limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_list_registered_limit(self):
        r = self.get('/registered_limits', expected_status=http.client.OK)
        self.assertEqual([], r.result.get('registered_limits'))

        ref1 = unit.new_registered_limit_ref(
            service_id=self.service_id,
            resource_name='test_resource',
            region_id=self.region_id,
        )
        ref2 = unit.new_registered_limit_ref(
            service_id=self.service_id2,
            resource_name='test_resource',
            region_id=self.region_id2,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        id1 = r.result['registered_limits'][0]['id']
        r = self.get('/registered_limits', expected_status=http.client.OK)
        registered_limits = r.result['registered_limits']
        self.assertEqual(len(registered_limits), 2)
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'default_limit',
        ]:
            if registered_limits[0]['id'] == id1:
                self.assertEqual(registered_limits[0][key], ref1[key])
                self.assertEqual(registered_limits[1][key], ref2[key])
                break
            self.assertEqual(registered_limits[1][key], ref1[key])
            self.assertEqual(registered_limits[0][key], ref2[key])

        r = self.get(
            f'/registered_limits?service_id={self.service_id}',
            expected_status=http.client.OK,
        )
        registered_limits = r.result['registered_limits']
        self.assertEqual(len(registered_limits), 1)
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'default_limit',
        ]:
            self.assertEqual(registered_limits[0][key], ref1[key])

        r = self.get(
            f'/registered_limits?region_id={self.region_id2}',
            expected_status=http.client.OK,
        )
        registered_limits = r.result['registered_limits']
        self.assertEqual(len(registered_limits), 1)
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'default_limit',
        ]:
            self.assertEqual(registered_limits[0][key], ref2[key])

        r = self.get(
            '/registered_limits?resource_name=test_resource',
            expected_status=http.client.OK,
        )
        registered_limits = r.result['registered_limits']
        self.assertEqual(len(registered_limits), 2)

    def test_show_registered_limit(self):
        ref1 = unit.new_registered_limit_ref(
            service_id=self.service_id, region_id=self.region_id
        )
        ref2 = unit.new_registered_limit_ref(
            service_id=self.service_id2, region_id=self.region_id2
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        id1 = r.result['registered_limits'][0]['id']
        self.get(
            '/registered_limits/fake_id', expected_status=http.client.NOT_FOUND
        )
        r = self.get(
            f'/registered_limits/{id1}', expected_status=http.client.OK
        )
        registered_limit = r.result['registered_limit']
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'default_limit',
            'description',
        ]:
            self.assertEqual(registered_limit[key], ref1[key])

    def test_delete_registered_limit(self):
        ref1 = unit.new_registered_limit_ref(
            service_id=self.service_id, region_id=self.region_id
        )
        ref2 = unit.new_registered_limit_ref(
            service_id=self.service_id2, region_id=self.region_id2
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        id1 = r.result['registered_limits'][0]['id']
        self.delete(
            f'/registered_limits/{id1}',
            token=self.system_admin_token,
            expected_status=http.client.NO_CONTENT,
        )
        self.delete(
            '/registered_limits/fake_id',
            token=self.system_admin_token,
            expected_status=http.client.NOT_FOUND,
        )
        r = self.get('/registered_limits', expected_status=http.client.OK)
        registered_limits = r.result['registered_limits']
        self.assertEqual(len(registered_limits), 1)

    def test_delete_registered_limit_with_referenced_limit(self):
        ref = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            default_limit=10,
        )
        r = self.post(
            '/registered_limits',
            body={'registered_limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        id = r.result['registered_limits'][0]['id']
        self.delete(
            f'/registered_limits/{id}', expected_status=http.client.FORBIDDEN
        )


class LimitsTestCase(test_v3.RestfulTestCase):
    """Test limits CRUD."""

    def setUp(self):
        super().setUp()
        # FIXME(lbragstad): Remove all this duplicated logic once we get all
        # keystone tests using bootstrap consistently. This is something the
        # bootstrap utility already does for us.
        reader_role = {'id': uuid.uuid4().hex, 'name': 'reader'}
        reader_role = PROVIDERS.role_api.create_role(
            reader_role['id'], reader_role
        )

        member_role = {'id': uuid.uuid4().hex, 'name': 'member'}
        member_role = PROVIDERS.role_api.create_role(
            member_role['id'], member_role
        )
        self.member_role_id = member_role['id']
        PROVIDERS.role_api.create_implied_role(self.role_id, member_role['id'])
        PROVIDERS.role_api.create_implied_role(
            member_role['id'], reader_role['id']
        )

        # Create a user with member role for testing non-admin scenarios
        self.member_user = unit.create_user(
            PROVIDERS.identity_api, domain_id=self.domain_id
        )
        self.member_user_id = self.member_user['id']
        PROVIDERS.assignment_api.create_grant(
            self.member_role_id,
            user_id=self.member_user_id,
            domain_id=self.domain_id,
        )
        # Also grant member role on project for project-scoped token tests
        PROVIDERS.assignment_api.create_grant(
            self.member_role_id,
            user_id=self.member_user_id,
            project_id=self.project_id,
        )

        # Most of these tests require system-scoped tokens. Let's have one on
        # hand so that we can use it in tests when we need it.
        PROVIDERS.assignment_api.create_system_grant_for_user(
            self.user_id, self.role_id
        )
        self.system_admin_token = self.get_system_scoped_token()

        # There is already a sample service and region created from
        # load_sample_data() but we're going to create another service and
        # region for specific testing purposes.
        response = self.post('/regions', body={'region': {}})
        self.region2 = response.json_body['region']
        self.region_id2 = self.region2['id']

        service_ref = {
            'service': {
                'name': uuid.uuid4().hex,
                'enabled': True,
                'type': 'type2',
            }
        }
        response = self.post('/services', body=service_ref)
        self.service2 = response.json_body['service']
        self.service_id2 = self.service2['id']

        ref1 = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_registered_limit_ref(
            service_id=self.service_id2, resource_name='snapshot'
        )
        ref3 = unit.new_registered_limit_ref(
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='backup',
        )
        self.post(
            '/registered_limits',
            body={'registered_limits': [ref1, ref2, ref3]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        # Create more assignments, all are:
        #
        # self.user -- admin -- self.project
        # self.user -- non-admin -- self.project_2
        # self.user -- admin -- self.domain
        # self.user -- non-admin -- self.domain_2
        # self.user -- admin -- system
        self.project_2 = unit.new_project_ref(domain_id=self.domain_id)
        self.project_2_id = self.project_2['id']
        PROVIDERS.resource_api.create_project(
            self.project_2_id, self.project_2
        )

        self.domain_2 = unit.new_domain_ref()
        self.domain_2_id = self.domain_2['id']
        PROVIDERS.resource_api.create_domain(self.domain_2_id, self.domain_2)

        self.role_2 = unit.new_role_ref(name='non-admin')
        self.role_2_id = self.role_2['id']
        PROVIDERS.role_api.create_role(self.role_2_id, self.role_2)

        PROVIDERS.assignment_api.create_grant(
            self.role_2_id, user_id=self.user_id, project_id=self.project_2_id
        )
        PROVIDERS.assignment_api.create_grant(
            self.role_id, user_id=self.user_id, domain_id=self.domain_id
        )
        PROVIDERS.assignment_api.create_grant(
            self.role_2_id, user_id=self.user_id, domain_id=self.domain_2_id
        )
        PROVIDERS.assignment_api.create_system_grant_for_user(
            self.user_id, self.role_id
        )
        # Grant member_user access to project_2 for testing
        PROVIDERS.assignment_api.create_grant(
            self.member_role_id,
            user_id=self.member_user_id,
            project_id=self.project_2_id,
        )

    def test_create_project_limit(self):
        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limits = r.result['limits']

        self.assertIsNotNone(limits[0]['id'])
        self.assertIsNone(limits[0]['domain_id'])
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'resource_limit',
            'description',
            'project_id',
        ]:
            self.assertEqual(limits[0][key], ref[key])

    def test_create_domain_limit(self):
        ref = unit.new_limit_ref(
            domain_id=self.domain_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limits = r.result['limits']

        self.assertIsNotNone(limits[0]['id'])
        self.assertIsNone(limits[0]['project_id'])
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'resource_limit',
            'description',
            'domain_id',
        ]:
            self.assertEqual(limits[0][key], ref[key])

    def test_create_limit_without_region(self):
        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limits = r.result['limits']

        self.assertIsNotNone(limits[0]['id'])
        self.assertIsNotNone(limits[0]['project_id'])
        for key in ['service_id', 'resource_name', 'resource_limit']:
            self.assertEqual(limits[0][key], ref[key])
        self.assertIsNone(limits[0].get('region_id'))

    def test_create_limit_without_description(self):
        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref.pop('description')
        r = self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limits = r.result['limits']

        self.assertIsNotNone(limits[0]['id'])
        self.assertIsNotNone(limits[0]['project_id'])
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'resource_limit',
        ]:
            self.assertEqual(limits[0][key], ref[key])
        self.assertIsNone(limits[0]['description'])

    def test_create_limit_with_domain_as_project(self):
        ref = unit.new_limit_ref(
            project_id=self.domain_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        r = self.post(
            '/limits', body={'limits': [ref]}, token=self.system_admin_token
        )
        limits = r.result['limits']
        self.assertIsNone(limits[0]['project_id'])
        self.assertEqual(self.domain_id, limits[0]['domain_id'])

    def test_create_multi_limit(self):
        ref1 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limits = r.result['limits']
        for key in ['service_id', 'resource_name', 'resource_limit']:
            self.assertEqual(limits[0][key], ref1[key])
            self.assertEqual(limits[1][key], ref2[key])
        self.assertEqual(limits[0]['region_id'], ref1['region_id'])
        self.assertIsNone(limits[1].get('region_id'))

    def test_create_limit_return_count(self):
        ref1 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref1]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))

        ref2 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        ref3 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='backup',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref2, ref3]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limits = r.result['limits']
        self.assertEqual(2, len(limits))

    def test_create_limit_with_invalid_input(self):
        ref1 = unit.new_limit_ref(
            project_id=self.project_id, resource_limit='not_int'
        )
        ref2 = unit.new_limit_ref(
            project_id=self.project_id, resource_name=123
        )
        ref3 = unit.new_limit_ref(
            project_id=self.project_id, region_id='fake_region'
        )
        for input_limit in [ref1, ref2, ref3]:
            self.post(
                '/limits',
                body={'limits': [input_limit]},
                token=self.system_admin_token,
                expected_status=http.client.BAD_REQUEST,
            )

    def test_create_limit_duplicate(self):
        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CONFLICT,
        )

    def test_create_limit_without_reference_registered_limit(self):
        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id2,
            resource_name='volume',
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_update_limit(self):
        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=10,
        )
        r = self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        update_ref = {'resource_limit': 5, 'description': 'test description'}
        r = self.patch(
            '/limits/{}'.format(r.result['limits'][0]['id']),
            body={'limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        new_limits = r.result['limit']

        self.assertEqual(new_limits['resource_limit'], 5)
        self.assertEqual(new_limits['description'], 'test description')

    def test_update_limit_not_found(self):
        update_ref = {'resource_limit': 5}
        self.patch(
            f'/limits/{uuid.uuid4().hex}',
            body={'limit': update_ref},
            token=self.system_admin_token,
            expected_status=http.client.NOT_FOUND,
        )

    def test_update_limit_with_invalid_input(self):
        ref = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=10,
        )
        r = self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        limit_id = r.result['limits'][0]['id']

        invalid_resource_limit_update = {'resource_limit': 'not_int'}
        invalid_description_update = {'description': 123}
        for input_limit in [
            invalid_resource_limit_update,
            invalid_description_update,
        ]:
            self.patch(
                f'/limits/{limit_id}',
                body={'limit': input_limit},
                token=self.system_admin_token,
                expected_status=http.client.BAD_REQUEST,
            )

    def test_list_limit(self):
        r = self.get(
            '/limits',
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        self.assertEqual([], r.result.get('limits'))

        ref1 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        id1 = r.result['limits'][0]['id']
        r = self.get('/limits', expected_status=http.client.OK)
        limits = r.result['limits']
        self.assertEqual(len(limits), 2)
        if limits[0]['id'] == id1:
            self.assertEqual(limits[0]['region_id'], ref1['region_id'])
            self.assertIsNone(limits[1].get('region_id'))
            for key in ['service_id', 'resource_name', 'resource_limit']:
                self.assertEqual(limits[0][key], ref1[key])
                self.assertEqual(limits[1][key], ref2[key])
        else:
            self.assertEqual(limits[1]['region_id'], ref1['region_id'])
            self.assertIsNone(limits[0].get('region_id'))
            for key in ['service_id', 'resource_name', 'resource_limit']:
                self.assertEqual(limits[1][key], ref1[key])
                self.assertEqual(limits[0][key], ref2[key])

        r = self.get(
            f'/limits?service_id={self.service_id2}',
            expected_status=http.client.OK,
        )
        limits = r.result['limits']
        self.assertEqual(len(limits), 1)
        for key in ['service_id', 'resource_name', 'resource_limit']:
            self.assertEqual(limits[0][key], ref2[key])

        r = self.get(
            f'/limits?region_id={self.region_id}',
            expected_status=http.client.OK,
        )
        limits = r.result['limits']
        self.assertEqual(len(limits), 1)
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'resource_limit',
        ]:
            self.assertEqual(limits[0][key], ref1[key])

        r = self.get(
            '/limits?resource_name=volume', expected_status=http.client.OK
        )
        limits = r.result['limits']
        self.assertEqual(len(limits), 1)
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'resource_limit',
        ]:
            self.assertEqual(limits[0][key], ref1[key])

    def test_list_limit_with_project_id_filter(self):
        # create two limit in different projects for test.
        self.config_fixture.config(group='oslo_policy', enforce_scope=True)
        ref1 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_limit_ref(
            project_id=self.project_2_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        self.post(
            '/limits',
            body={'limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        # non system scoped request will get the limits in its project.
        r = self.get(
            '/limits',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.member_user['id'],
                password=self.member_user['password'],
                project_id=self.project_id,
            ),
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))
        self.assertEqual(self.project_id, limits[0]['project_id'])

        r = self.get(
            '/limits',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.member_user['id'],
                password=self.member_user['password'],
                project_id=self.project_2_id,
            ),
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))
        self.assertEqual(self.project_2_id, limits[0]['project_id'])

        # any project user can filter by their own project
        r = self.get(
            f'/limits?project_id={self.project_id}',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.member_user['id'],
                password=self.member_user['password'],
                project_id=self.project_id,
            ),
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))
        self.assertEqual(self.project_id, limits[0]['project_id'])

        # a system scoped request can specify the project_id filter
        r = self.get(
            f'/limits?project_id={self.project_id}',
            expected_status=http.client.OK,
            token=self.system_admin_token,
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))
        self.assertEqual(self.project_id, limits[0]['project_id'])

    def test_list_limit_with_domain_id_filter(self):
        # create two limit in different domains for test.
        ref1 = unit.new_limit_ref(
            domain_id=self.domain_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_limit_ref(
            domain_id=self.domain_2_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        self.post(
            '/limits',
            body={'limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        # non system scoped request will get the limits in its domain.
        r = self.get(
            '/limits',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.member_user['id'],
                password=self.member_user['password'],
                domain_id=self.domain_id,
            ),
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))
        self.assertEqual(self.domain_id, limits[0]['domain_id'])

        r = self.get(
            '/limits',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.user['id'],
                password=self.user['password'],
                domain_id=self.domain_2_id,
            ),
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))
        self.assertEqual(self.domain_2_id, limits[0]['domain_id'])

        # if non system scoped request contain domain_id filter, keystone
        # will return an empty list.
        r = self.get(
            f'/limits?domain_id={self.domain_id}',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.member_user['id'],
                password=self.member_user['password'],
                project_id=self.project_id,
            ),
        )
        limits = r.result['limits']
        self.assertEqual(0, len(limits))

        # a system scoped request can specify the domain_id filter
        r = self.get(
            f'/limits?domain_id={self.domain_id}',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.user['id'],
                password=self.user['password'],
                system=True,
            ),
        )
        limits = r.result['limits']
        self.assertEqual(1, len(limits))
        self.assertEqual(self.domain_id, limits[0]['domain_id'])

    def test_show_project_limit(self):
        ref1 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        if r.result['limits'][0]['resource_name'] == 'volume':
            id1 = r.result['limits'][0]['id']
        else:
            id1 = r.result['limits'][1]['id']
        self.get(
            '/limits/fake_id',
            token=self.system_admin_token,
            expected_status=http.client.NOT_FOUND,
        )
        r = self.get(f'/limits/{id1}', expected_status=http.client.OK)
        limit = r.result['limit']
        self.assertIsNone(limit['domain_id'])
        for key in [
            'service_id',
            'region_id',
            'resource_name',
            'resource_limit',
            'description',
            'project_id',
        ]:
            self.assertEqual(limit[key], ref1[key])

    def test_show_domain_limit(self):
        ref1 = unit.new_limit_ref(
            domain_id=self.domain_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref1]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        id1 = r.result['limits'][0]['id']

        r = self.get(
            f'/limits/{id1}',
            expected_status=http.client.OK,
            auth=self.build_authentication_request(
                user_id=self.user['id'],
                password=self.user['password'],
                domain_id=self.domain_id,
            ),
        )
        limit = r.result['limit']
        self.assertIsNone(limit['project_id'])
        self.assertIsNone(limit['region_id'])
        for key in [
            'service_id',
            'resource_name',
            'resource_limit',
            'description',
            'domain_id',
        ]:
            self.assertEqual(limit[key], ref1[key])

    def test_delete_limit(self):
        ref1 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
        )
        ref2 = unit.new_limit_ref(
            project_id=self.project_id,
            service_id=self.service_id2,
            resource_name='snapshot',
        )
        r = self.post(
            '/limits',
            body={'limits': [ref1, ref2]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        id1 = r.result['limits'][0]['id']
        self.delete(
            f'/limits/{id1}',
            token=self.system_admin_token,
            expected_status=http.client.NO_CONTENT,
        )
        self.delete(
            '/limits/fake_id',
            token=self.system_admin_token,
            expected_status=http.client.NOT_FOUND,
        )
        r = self.get(
            '/limits',
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )
        limits = r.result['limits']
        self.assertEqual(len(limits), 1)


class StrictTwoLevelLimitsTestCase(LimitsTestCase):
    def setUp(self):
        super().setUp()
        # Most of these tests require system-scoped tokens. Let's have one on
        # hand so that we can use it in tests when we need it.
        PROVIDERS.assignment_api.create_system_grant_for_user(
            self.user_id, self.role_id
        )
        self.system_admin_token = self.get_system_scoped_token()

        # create two hierarchical projects trees for test. The first level is
        # domain.
        #   A        D
        #  / \      / \
        # B   C    E   F
        domain_ref = {'domain': {'name': 'A', 'enabled': True}}
        response = self.post('/domains', body=domain_ref)
        self.domain_A = response.json_body['domain']
        project_ref = {
            'project': {
                'name': 'B',
                'enabled': True,
                'domain_id': self.domain_A['id'],
            }
        }
        response = self.post('/projects', body=project_ref)
        self.project_B = response.json_body['project']
        project_ref = {
            'project': {
                'name': 'C',
                'enabled': True,
                'domain_id': self.domain_A['id'],
            }
        }
        response = self.post('/projects', body=project_ref)
        self.project_C = response.json_body['project']

        domain_ref = {'domain': {'name': 'D', 'enabled': True}}
        response = self.post('/domains', body=domain_ref)
        self.domain_D = response.json_body['domain']
        project_ref = {
            'project': {
                'name': 'E',
                'enabled': True,
                'domain_id': self.domain_D['id'],
            }
        }
        response = self.post('/projects', body=project_ref)
        self.project_E = response.json_body['project']
        project_ref = {
            'project': {
                'name': 'F',
                'enabled': True,
                'domain_id': self.domain_D['id'],
            }
        }
        response = self.post('/projects', body=project_ref)
        self.project_F = response.json_body['project']

    def config_overrides(self):
        super().config_overrides()
        self.config_fixture.config(
            group='unified_limit', enforcement_model='strict_two_level'
        )

    def test_create_child_limit(self):
        # when A is 20, success to create B to 15, C to 18.
        #     A,20             A,20
        #    / \      -->     / \
        #   B   C           B,15 C,18
        ref = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=20,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=15,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=18,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

    def test_create_child_limit_break_hierarchical_tree(self):
        # when A is 20, success to create B to 15, but fail to create C to 21.
        #     A,20             A,20
        #    / \      -->     / \
        #   B   C           B,15 C
        #
        #     A,20              A,20
        #    / \      -/->      / \
        #  B,15 C            B,15 C,21
        ref = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=20,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=15,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=21,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_create_child_with_default_parent(self):
        # If A is not set, the default value is 10 (from registered limit).
        # success to create B to 5, but fail to create C to 11.
        #     A(10)             A(10)
        #    / \      -->      / \
        #   B   C            B,5  C
        #
        #     A(10)             A(10)
        #    / \     -/->      / \
        #   B,5   C          B,5  C,11
        ref = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=5,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=11,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_create_parent_limit(self):
        # When B is 9 , success to set A to 12
        #     A              A,12
        #    / \    -->      / \
        #  B,9  C          B,9  C
        ref = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=9,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=12,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

    def test_create_parent_limit_break_hierarchical_tree(self):
        # When B is 9 , fail to set A to 8
        #     A              A,8
        #    / \    -/->     / \
        #  B,9  C          B,9  C
        ref = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=9,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=8,
        )
        self.post(
            '/limits',
            body={'limits': [ref]},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_create_multi_limits(self):
        # success to create a tree in one request like:
        #    A,12         D,9
        #    / \          / \
        #  B,9  C,5    E,5   F,4
        ref_A = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=12,
        )
        ref_B = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=9,
        )
        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=5,
        )
        ref_D = unit.new_limit_ref(
            domain_id=self.domain_D['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=9,
        )
        ref_E = unit.new_limit_ref(
            project_id=self.project_E['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=5,
        )
        ref_F = unit.new_limit_ref(
            project_id=self.project_F['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=4,
        )
        self.post(
            '/limits',
            body={'limits': [ref_A, ref_B, ref_C, ref_D, ref_E, ref_F]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

    def test_create_multi_limits_invalid_input(self):
        # fail to create a tree in one request like:
        #    A,12         D,9
        #    / \          / \
        #  B,9  C,5    E,5   F,10
        # because F will break the second limit tree.
        ref_A = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=12,
        )
        ref_B = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=9,
        )
        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=5,
        )
        ref_D = unit.new_limit_ref(
            domain_id=self.domain_D['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=9,
        )
        ref_E = unit.new_limit_ref(
            project_id=self.project_E['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=5,
        )
        ref_F = unit.new_limit_ref(
            project_id=self.project_F['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=10,
        )
        self.post(
            '/limits',
            body={'limits': [ref_A, ref_B, ref_C, ref_D, ref_E, ref_F]},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_create_multi_limits_break_hierarchical_tree(self):
        # when there is some hierarchical_trees already like:
        #    A,12          D
        #    / \          / \
        #  B,9  C       E,5   F
        # fail to set C to 5 and D to 4 in one request like:
        #    A,12         D,4
        #    / \          / \
        #  B,9  C,5    E,5   F
        # because D will break the second limit tree.
        ref_A = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=12,
        )
        ref_B = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=9,
        )
        ref_E = unit.new_limit_ref(
            project_id=self.project_E['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=5,
        )
        self.post(
            '/limits',
            body={'limits': [ref_A, ref_B, ref_E]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=5,
        )
        ref_D = unit.new_limit_ref(
            domain_id=self.domain_D['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=4,
        )
        self.post(
            '/limits',
            body={'limits': [ref_C, ref_D]},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_update_child_limit(self):
        # Success to update C to 9
        #     A,10             A,10
        #    / \      -->     / \
        #  B,6  C,7         B,6  C,9
        ref_A = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=10,
        )
        ref_B = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=6,
        )
        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=7,
        )
        self.post(
            '/limits',
            body={'limits': [ref_A, ref_B]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        r = self.post(
            '/limits',
            body={'limits': [ref_C]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        update_dict = {'resource_limit': 9}
        self.patch(
            '/limits/{}'.format(r.result['limits'][0]['id']),
            body={'limit': update_dict},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )

    def test_update_child_limit_break_hierarchical_tree(self):
        # Fail to update C to 11
        #     A,10             A,10
        #    / \      -/->     / \
        #  B,6  C,7         B,6  C,11
        ref_A = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=10,
        )
        ref_B = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=6,
        )
        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=7,
        )
        self.post(
            '/limits',
            body={'limits': [ref_A, ref_B]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        r = self.post(
            '/limits',
            body={'limits': [ref_C]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        update_dict = {'resource_limit': 11}
        self.patch(
            '/limits/{}'.format(r.result['limits'][0]['id']),
            body={'limit': update_dict},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_update_child_limit_with_default_parent(self):
        # If A is not set, the default value is 10 (from registered limit).
        # Success to update C to 9 but fail to update C to 11
        #     A,(10)           A,(10)
        #    / \      -->     / \
        #   B,  C,7          B  C,9
        #
        #     A,(10)            A,(10)
        #    / \      -/->     / \
        #   B,  C,7           B  C,11
        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=7,
        )
        r = self.post(
            '/limits',
            body={'limits': [ref_C]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        update_dict = {'resource_limit': 9}
        self.patch(
            '/limits/{}'.format(r.result['limits'][0]['id']),
            body={'limit': update_dict},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )

        update_dict = {'resource_limit': 11}
        self.patch(
            '/limits/{}'.format(r.result['limits'][0]['id']),
            body={'limit': update_dict},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )

    def test_update_parent_limit(self):
        # Success to update A to 8
        #     A,10             A,8
        #    / \      -->     / \
        #  B,6  C,7         B,6  C,7
        ref_A = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=10,
        )
        ref_B = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=6,
        )
        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=7,
        )
        r = self.post(
            '/limits',
            body={'limits': [ref_A]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        self.post(
            '/limits',
            body={'limits': [ref_B, ref_C]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        update_dict = {'resource_limit': 8}
        self.patch(
            '/limits/{}'.format(r.result['limits'][0]['id']),
            body={'limit': update_dict},
            token=self.system_admin_token,
            expected_status=http.client.OK,
        )

    def test_update_parent_limit_break_hierarchical_tree(self):
        # Fail to update A to 6
        #     A,10             A,6
        #    / \      -/->     / \
        #  B,6  C,7         B,6  C,7
        ref_A = unit.new_limit_ref(
            domain_id=self.domain_A['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=10,
        )
        ref_B = unit.new_limit_ref(
            project_id=self.project_B['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=6,
        )
        ref_C = unit.new_limit_ref(
            project_id=self.project_C['id'],
            service_id=self.service_id,
            region_id=self.region_id,
            resource_name='volume',
            resource_limit=7,
        )
        r = self.post(
            '/limits',
            body={'limits': [ref_A]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )
        self.post(
            '/limits',
            body={'limits': [ref_B, ref_C]},
            token=self.system_admin_token,
            expected_status=http.client.CREATED,
        )

        update_dict = {'resource_limit': 6}
        self.patch(
            '/limits/{}'.format(r.result['limits'][0]['id']),
            body={'limit': update_dict},
            token=self.system_admin_token,
            expected_status=http.client.FORBIDDEN,
        )
