# 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 nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional import integrated_helpers
from nova.tests.unit.image import fake as fake_image
from nova.tests.unit import policy_fixture


class TestMultiCreateServerGroupMemberOverQuota(
        test.TestCase, integrated_helpers.InstanceHelperMixin):
    """This tests a regression introduced in the Pike release.

    Starting in the Pike release, quotas are no longer tracked using usages
    and reservations tables but instead perform a resource counting operation
    at the point of resource creation.

    When creating multiple servers in the same request that belong in the same
    server group, the [quota]/server_group_members config option is checked
    to determine if those servers can belong in the same group based on quota.
    However, the quota check for server_group_members only counts existing
    group members based on live instances in the cell database(s). But the
    actual instance record isn't created in the cell database until *after* the
    server_group_members quota check happens. Because of this, it is possible
    to bypass the server_group_members quota check when creating multiple
    servers in the same request.
    """
    def setUp(self):
        super(TestMultiCreateServerGroupMemberOverQuota, self).setUp()
        self.flags(server_group_members=2, group='quota')
        self.useFixture(policy_fixture.RealPolicyFixture())
        self.useFixture(nova_fixtures.NeutronFixture(self))
        self.useFixture(nova_fixtures.PlacementFixture())

        api_fixture = self.useFixture(nova_fixtures.OSAPIFixture(
            api_version='v2.1'))
        self.api = api_fixture.api
        self.api.microversion = '2.37'  # so we can specify networks='none'

        fake_image.stub_out_image_service(self)
        self.addCleanup(fake_image.FakeImageService_reset)

        group = {'name': 'test group', 'policies': ['soft-anti-affinity']}
        self.created_group = self.api.post_server_groups(group)

    def test_multi_create_server_group_members_over_quota(self):
        """Recreate scenario for the bug where we create an anti-affinity
        server group and then create 3 servers in the group using a
        multi-create POST /servers request.
        """
        server_req = self._build_minimal_create_server_request(
            self.api, 'test_multi_create_server_group_members_over_quota',
            image_uuid=fake_image.AUTO_DISK_CONFIG_ENABLED_IMAGE_UUID,
            networks='none')
        server_req['min_count'] = 3
        server_req['return_reservation_id'] = True
        hints = {'group': self.created_group['id']}
        # We should get a 403 response due to going over quota on server
        # group members in a single request.
        self.api.api_post(
            '/servers', {'server': server_req, 'os:scheduler_hints': hints},
            check_response_status=[403])
        group = self.api.api_get(
            '/os-server-groups/%s' %
            self.created_group['id']).body['server_group']
        self.assertEqual(0, len(group['members']))

    def test_concurrent_request_server_group_members_over_quota(self):
        """Recreate scenario for the bug where we create 3 servers in the
        same group but in separate requests. The NoopConductorFixture is used
        to ensure the instances are not created in the nova cell database which
        means the quota check will have to rely on counting group members using
        build requests from the API DB.
        """
        # These aren't really concurrent requests, but we can simulate that
        # by using NoopConductorFixture.
        self.useFixture(nova_fixtures.NoopConductorFixture())
        for x in range(3):
            server_req = self._build_minimal_create_server_request(
                self.api, 'test_concurrent_request_%s' % x,
                image_uuid=fake_image.AUTO_DISK_CONFIG_ENABLED_IMAGE_UUID,
                networks='none')
            hints = {'group': self.created_group['id']}
            # This should result in a 403 response on the 3rd server.
            if x == 2:
                self.api.api_post(
                    '/servers',
                    {'server': server_req, 'os:scheduler_hints': hints},
                    check_response_status=[403])
            else:
                self.api.post_server(
                    {'server': server_req, 'os:scheduler_hints': hints})
        # There should only be two servers created which are both members of
        # the same group.
        servers = self.api.get_servers(detail=False)
        self.assertEqual(2, len(servers))
        group = self.api.api_get(
            '/os-server-groups/%s' %
            self.created_group['id']).body['server_group']
        self.assertEqual(2, len(group['members']))
