# Copyright (c) 2014 Cisco Systems, Inc.
# 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 copy
import mock
from oslo_utils.fixture import uuidsentinel
from oslo_utils import uuidutils
import six
import webob

from nova.api.openstack import api_version_request as avr
from nova.api.openstack.compute import server_groups as sg_v21
from nova import context
from nova import exception
from nova import objects
from nova.policies import server_groups as sg_policies
from nova import test
from nova.tests import fixtures
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import policy_fixture


class AttrDict(dict):
    def __getattr__(self, k):
        return self[k]


def server_group_template(**kwargs):
    sgroup = kwargs.copy()
    sgroup.setdefault('name', 'test')
    return sgroup


def server_group_resp_template(**kwargs):
    sgroup = kwargs.copy()
    sgroup.setdefault('name', 'test')
    if 'policy' not in kwargs:
        sgroup.setdefault('policies', [])
    sgroup.setdefault('members', [])
    return sgroup


def server_group_db(sg):
    attrs = copy.deepcopy(sg)
    if 'id' in attrs:
        attrs['uuid'] = attrs.pop('id')
    if 'policies' in attrs:
        policies = attrs.pop('policies')
        attrs['policies'] = policies
    else:
        attrs['policies'] = []
    if 'policy' in attrs:
        del attrs['policies']
    if 'members' in attrs:
        members = attrs.pop('members')
        attrs['members'] = members
    else:
        attrs['members'] = []
    attrs['deleted'] = 0
    attrs['deleted_at'] = None
    attrs['created_at'] = None
    attrs['updated_at'] = None
    if 'user_id' not in attrs:
        attrs['user_id'] = fakes.FAKE_USER_ID
    if 'project_id' not in attrs:
        attrs['project_id'] = fakes.FAKE_PROJECT_ID
    attrs['id'] = 7

    return AttrDict(attrs)


class ServerGroupTestV21(test.NoDBTestCase):
    USES_DB_SELF = True
    validation_error = exception.ValidationError
    wsgi_api_version = '2.1'

    def setUp(self):
        super(ServerGroupTestV21, self).setUp()
        self._setup_controller()
        self.req = fakes.HTTPRequest.blank('')
        self.admin_req = fakes.HTTPRequest.blank('', use_admin_context=True)
        self.foo_req = fakes.HTTPRequest.blank('', project_id='foo')
        self.policy = self.useFixture(policy_fixture.RealPolicyFixture())

        self.useFixture(fixtures.Database(database='api'))
        cells = fixtures.CellDatabases()
        cells.add_cell_database(uuidsentinel.cell1)
        cells.add_cell_database(uuidsentinel.cell2)
        self.useFixture(cells)

        ctxt = context.get_admin_context()
        self.cells = {}
        for uuid in (uuidsentinel.cell1, uuidsentinel.cell2):
            cm = objects.CellMapping(context=ctxt,
                                uuid=uuid,
                                database_connection=uuid,
                                transport_url=uuid)
            cm.create()
            self.cells[cm.uuid] = cm

    def _setup_controller(self):
        self.controller = sg_v21.ServerGroupController()

    def test_create_server_group_with_no_policies(self):
        sgroup = server_group_template()
        self.assertRaises(self.validation_error, self.controller.create,
                          self.req, body={'server_group': sgroup})

    def _create_server_group_normal(self, policies=None, policy=None,
                                    rules=None):
        sgroup = server_group_template()
        sgroup['policies'] = policies
        res_dict = self.controller.create(self.req,
                                          body={'server_group': sgroup})
        self.assertEqual(res_dict['server_group']['name'], 'test')
        self.assertTrue(uuidutils.is_uuid_like(res_dict['server_group']['id']))
        self.assertEqual(res_dict['server_group']['policies'], policies)

    def test_create_server_group_with_new_policy_before_264(self):
        req = fakes.HTTPRequest.blank('', version='2.63')
        policy = 'anti-affinity'
        rules = {'max_server_per_host': 3}
        # 'policy' isn't an acceptable request key before 2.64
        sgroup = server_group_template(policy=policy)
        result = self.assertRaises(
            self.validation_error, self.controller.create,
            req, body={'server_group': sgroup})
        self.assertIn(
            "Invalid input for field/attribute server_group",
            six.text_type(result)
        )
        # 'rules' isn't an acceptable request key before 2.64
        sgroup = server_group_template(rules=rules)
        result = self.assertRaises(
            self.validation_error, self.controller.create,
            req, body={'server_group': sgroup})
        self.assertIn(
            "Invalid input for field/attribute server_group",
            six.text_type(result)
        )

    def test_create_server_group(self):
        policies = ['affinity', 'anti-affinity']
        for policy in policies:
            self._create_server_group_normal(policies=[policy])

    def test_create_server_group_rbac_default(self):
        sgroup = server_group_template()
        sgroup['policies'] = ['affinity']

        # test as admin
        self.controller.create(self.admin_req, body={'server_group': sgroup})

        # test as non-admin
        self.controller.create(self.req, body={'server_group': sgroup})

    def test_create_server_group_rbac_admin_only(self):
        sgroup = server_group_template()
        sgroup['policies'] = ['affinity']

        # override policy to restrict to admin
        rule_name = sg_policies.POLICY_ROOT % 'create'
        rules = {rule_name: 'is_admin:True'}
        self.policy.set_rules(rules, overwrite=False)

        # check for success as admin
        self.controller.create(self.admin_req, body={'server_group': sgroup})

        # check for failure as non-admin
        exc = self.assertRaises(exception.PolicyNotAuthorized,
                                self.controller.create, self.req,
                                body={'server_group': sgroup})
        self.assertEqual(
            "Policy doesn't allow %s to be performed." % rule_name,
            exc.format_message())

    def _create_instance(self, ctx, cell):
        with context.target_cell(ctx, cell) as cctx:
            instance = objects.Instance(context=cctx,
                                        image_ref=uuidsentinel.fake_image_ref,
                                        node='node1', reservation_id='a',
                                        host='host1',
                                        project_id=fakes.FAKE_PROJECT_ID,
                                        vm_state='fake',
                                        system_metadata={'key': 'value'})
            instance.create()
        im = objects.InstanceMapping(context=ctx,
                                     project_id=ctx.project_id,
                                     user_id=ctx.user_id,
                                     cell_mapping=cell,
                                     instance_uuid=instance.uuid)
        im.create()
        return instance

    def _create_instance_group(self, context, members):
        ig = objects.InstanceGroup(context=context, name='fake_name',
                  user_id='fake_user', project_id=fakes.FAKE_PROJECT_ID,
                  members=members)
        ig.create()
        return ig.uuid

    def _create_groups_and_instances(self, ctx):
        cell1 = self.cells[uuidsentinel.cell1]
        cell2 = self.cells[uuidsentinel.cell2]
        instances = [self._create_instance(ctx, cell=cell1),
                     self._create_instance(ctx, cell=cell2),
                     self._create_instance(ctx, cell=None)]
        members = [instance.uuid for instance in instances]
        ig_uuid = self._create_instance_group(ctx, members)
        return (ig_uuid, instances, members)

    def _test_list_server_group_all(self, api_version='2.1'):
        self._test_list_server_group(api_version=api_version,
            limited='',
            path='/os-server-groups?all_projects=True')

    def _test_list_server_group_offset_and_limit(self, api_version='2.1'):
        self._test_list_server_group(api_version=api_version,
            limited='&offset=1&limit=1',
            path='/os-server-groups?all_projects=True')

    @mock.patch('nova.objects.InstanceGroupList.get_by_project_id')
    @mock.patch('nova.objects.InstanceGroupList.get_all')
    def _test_list_server_group(self, mock_get_all, mock_get_by_project,
                                path, api_version='2.1', limited=None):
        policies = ['anti-affinity']
        policy = "anti-affinity"
        members = []
        metadata = {}  # always empty
        names = ['default-x', 'test']
        p_id = fakes.FAKE_PROJECT_ID
        u_id = fakes.FAKE_USER_ID
        ver = avr.APIVersionRequest(api_version)
        if ver >= avr.APIVersionRequest("2.64"):
            sg1 = server_group_resp_template(id=uuidsentinel.sg1_id,
                                             name=names[0],
                                             policy=policy,
                                             rules={},
                                             members=members,
                                             project_id=p_id,
                                             user_id=u_id)
            sg2 = server_group_resp_template(id=uuidsentinel.sg2_id,
                                             name=names[1],
                                             policy=policy,
                                             rules={},
                                             members=members,
                                             project_id=p_id,
                                             user_id=u_id)
        elif ver >= avr.APIVersionRequest("2.13"):
            sg1 = server_group_resp_template(id=uuidsentinel.sg1_id,
                                            name=names[0],
                                            policies=policies,
                                            members=members,
                                            metadata=metadata,
                                            project_id=p_id,
                                            user_id=u_id)
            sg2 = server_group_resp_template(id=uuidsentinel.sg2_id,
                                            name=names[1],
                                            policies=policies,
                                            members=members,
                                            metadata=metadata,
                                            project_id=p_id,
                                            user_id=u_id)
        else:
            sg1 = server_group_resp_template(id=uuidsentinel.sg1_id,
                                            name=names[0],
                                            policies=policies,
                                            members=members,
                                            metadata=metadata)
            sg2 = server_group_resp_template(id=uuidsentinel.sg2_id,
                                            name=names[1],
                                            policies=policies,
                                            members=members,
                                            metadata=metadata)
        tenant_groups = [sg2]
        all_groups = [sg1, sg2]

        if limited:
            all = {'server_groups': [sg2]}
            tenant_specific = {'server_groups': []}
        else:
            all = {'server_groups': all_groups}
            tenant_specific = {'server_groups': tenant_groups}

        def return_all_server_groups():
            return objects.InstanceGroupList(
                objects=[objects.InstanceGroup(
                    **server_group_db(sg)) for sg in all_groups])

        mock_get_all.return_value = return_all_server_groups()

        def return_tenant_server_groups():
            return objects.InstanceGroupList(
                objects=[objects.InstanceGroup(
                    **server_group_db(sg)) for sg in tenant_groups])

        mock_get_by_project.return_value = return_tenant_server_groups()

        path = path or '/os-server-groups?all_projects=True'
        if limited:
            path += limited
        req = fakes.HTTPRequest.blank(path, version=api_version)
        admin_req = fakes.HTTPRequest.blank(path, use_admin_context=True,
                                            version=api_version)

        # test as admin
        res_dict = self.controller.index(admin_req)
        self.assertEqual(all, res_dict)

        # test as non-admin
        res_dict = self.controller.index(req)
        self.assertEqual(tenant_specific, res_dict)

    @mock.patch('nova.objects.InstanceGroupList.get_by_project_id')
    def _test_list_server_group_by_tenant(self, mock_get_by_project,
                                         api_version='2.1'):
        policies = ['anti-affinity']
        members = []
        metadata = {}  # always empty
        names = ['default-x', 'test']
        p_id = fakes.FAKE_PROJECT_ID