#    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 os

from gabbi import fixture
from oslo_middleware import cors
from oslo_utils import uuidutils

from nova.api.openstack.placement import deploy
from nova import conf
from nova import config
from nova import context
from nova import objects
from nova.objects import resource_provider as rp_obj
from nova.tests import fixtures


CONF = conf.CONF


def setup_app():
    return deploy.loadapp(CONF)


class APIFixture(fixture.GabbiFixture):
    """Setup the required backend fixtures for a basic placement service."""

    def __init__(self):
        self.conf = None

    def start_fixture(self):
        # Set up stderr and stdout captures by directly driving the
        # existing nova fixtures that do that. This captures the
        # output that happens outside individual tests (for
        # example database migrations).
        self.standard_logging_fixture = fixtures.StandardLogging()
        self.standard_logging_fixture.setUp()
        self.output_stream_fixture = fixtures.OutputStreamCapture()
        self.output_stream_fixture.setUp()

        self.conf = CONF
        self.conf.set_override('auth_strategy', 'noauth2', group='api')
        # Be explicit about all three database connections to avoid
        # potential conflicts with config on disk.
        self.conf.set_override('connection', "sqlite://", group='database')
        self.conf.set_override('connection', "sqlite://",
                               group='api_database')

        # Register CORS opts, but do not set config. This has the
        # effect of exercising the "don't use cors" path in
        # deploy.py. Without setting some config the group will not
        # be present.
        self.conf.register_opts(cors.CORS_OPTS, 'cors')

        # Make sure default_config_files is an empty list, not None.
        # If None /etc/nova/nova.conf is read and confuses results.
        config.parse_args([], default_config_files=[], configure_db=False,
                          init_rpc=False)

        # NOTE(cdent): api and main database are not used but we still need
        # to manage them to make the fixtures work correctly and not cause
        # conflicts with other tests in the same process.
        self.api_db_fixture = fixtures.Database('api')
        self.main_db_fixture = fixtures.Database('main')
        self.api_db_fixture.reset()
        self.main_db_fixture.reset()

        os.environ['RP_UUID'] = uuidutils.generate_uuid()
        os.environ['RP_NAME'] = uuidutils.generate_uuid()
        os.environ['CUSTOM_RES_CLASS'] = 'CUSTOM_IRON_NFV'
        os.environ['PROJECT_ID'] = uuidutils.generate_uuid()
        os.environ['USER_ID'] = uuidutils.generate_uuid()
        os.environ['PROJECT_ID_ALT'] = uuidutils.generate_uuid()
        os.environ['USER_ID_ALT'] = uuidutils.generate_uuid()
        os.environ['INSTANCE_UUID'] = uuidutils.generate_uuid()
        os.environ['MIGRATION_UUID'] = uuidutils.generate_uuid()
        os.environ['CONSUMER_UUID'] = uuidutils.generate_uuid()
        os.environ['PARENT_PROVIDER_UUID'] = uuidutils.generate_uuid()
        os.environ['ALT_PARENT_PROVIDER_UUID'] = uuidutils.generate_uuid()

    def stop_fixture(self):
        self.api_db_fixture.cleanup()
        self.main_db_fixture.cleanup()

        # Since we clean up the DB, we need to reset the traits sync
        # flag to make sure the next run will recreate the traits and
        # reset the _RC_CACHE so that any cached resource classes
        # are flushed.
        objects.resource_provider._TRAITS_SYNCED = False
        objects.resource_provider._RC_CACHE = None

        self.output_stream_fixture.cleanUp()
        self.standard_logging_fixture.cleanUp()
        if self.conf:
            self.conf.reset()


class AllocationFixture(APIFixture):
    """An APIFixture that has some pre-made Allocations."""

    def start_fixture(self):
        super(AllocationFixture, self).start_fixture()
        self.context = context.get_admin_context()

        # For use creating and querying allocations/usages
        os.environ['ALT_USER_ID'] = uuidutils.generate_uuid()
        project_id = os.environ['PROJECT_ID']
        user_id = os.environ['USER_ID']
        alt_user_id = os.environ['ALT_USER_ID']

        # Stealing from the super
        rp_name = os.environ['RP_NAME']
        rp_uuid = os.environ['RP_UUID']
        rp = rp_obj.ResourceProvider(
            self.context, name=rp_name, uuid=rp_uuid)
        rp.create()

        # Create some DISK_GB inventory and allocations.
        consumer_id = uuidutils.generate_uuid()
        inventory = rp_obj.Inventory(
            self.context, resource_provider=rp,
            resource_class='DISK_GB', total=2048,
            step_size=10, min_unit=10, max_unit=600)
        inventory.obj_set_defaults()
        rp.add_inventory(inventory)
        alloc1 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='DISK_GB',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=500)
        alloc2 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='DISK_GB',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=500)
        alloc_list = rp_obj.AllocationList(
            self.context,
            objects=[alloc1, alloc2]
        )
        alloc_list.create_all()

        # Create some VCPU inventory and allocations.
        consumer_id = uuidutils.generate_uuid()
        os.environ['CONSUMER_ID'] = consumer_id
        inventory = rp_obj.Inventory(
            self.context, resource_provider=rp,
            resource_class='VCPU', total=10,
            max_unit=4)
        inventory.obj_set_defaults()
        rp.add_inventory(inventory)
        alloc1 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='VCPU',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=2)
        alloc2 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='VCPU',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=4)
        alloc_list = rp_obj.AllocationList(
                self.context,
                objects=[alloc1, alloc2])
        alloc_list.create_all()

        # Create a couple of allocations for a different user.
        consumer_id = uuidutils.generate_uuid()
        alloc1 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='DISK_GB',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=alt_user_id,
            used=20)
        alloc2 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='VCPU',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=alt_user_id,
            used=1)
        alloc_list = rp_obj.AllocationList(
                self.context,
                objects=[alloc1, alloc2])
        alloc_list.create_all()

        # The ALT_RP_XXX variables are for a resource provider that has
        # not been created in the Allocation fixture
        os.environ['ALT_RP_UUID'] = uuidutils.generate_uuid()
        os.environ['ALT_RP_NAME'] = uuidutils.generate_uuid()


class SharedStorageFixture(APIFixture):
    """An APIFixture that has some two compute nodes without local storage
    associated by aggregate to a provider of shared storage.
    """

    def start_fixture(self):
        super(SharedStorageFixture, self).start_fixture()
        self.context = context.get_admin_context()

        cn1_uuid = uuidutils.generate_uuid()
        cn2_uuid = uuidutils.generate_uuid()
        ss_uuid = uuidutils.generate_uuid()
        agg_uuid = uuidutils.generate_uuid()
        os.environ['CN1_UUID'] = cn1_uuid
        os.environ['CN2_UUID'] = cn2_uuid
        os.environ['SS_UUID'] = ss_uuid
        os.environ['AGG_UUID'] = agg_uuid

        cn1 = rp_obj.ResourceProvider(
            self.context,
            name='cn1',
            uuid=cn1_uuid)
        cn1.create()

        cn2 = rp_obj.ResourceProvider(
            self.context,
            name='cn2',
            uuid=cn2_uuid)
        cn2.create()

        ss = rp_obj.ResourceProvider(
            self.context,
            name='ss',
            uuid=ss_uuid)
        ss.create()

        # Populate compute node inventory for VCPU and RAM
        for cn in (cn1, cn2):
            vcpu_inv = rp_obj.Inventory(
                self.context,
                resource_provider=cn,
                resource_class='VCPU',
                total=24,
                reserved=0,
                max_unit=24,
                min_unit=1,
                step_size=1,
                allocation_ratio=16.0)
            vcpu_inv.obj_set_defaults()
            ram_inv = rp_obj.Inventory(
                self.context,
                resource_provider=cn,
                resource_class='MEMORY_MB',
                total=128 * 1024,
                reserved=0,
                max_unit=128 * 1024,
                min_unit=256,
                step_size=256,
                allocation_ratio=1.5)
            ram_inv.obj_set_defaults()
            inv_list = rp_obj.InventoryList(objects=[vcpu_inv, ram_inv])
            cn.set_inventory(inv_list)

        t_avx_sse = rp_obj.Trait.get_by_name(self.context, "HW_CPU_X86_SSE")
        t_avx_sse2 = rp_obj.Trait.get_by_name(self.context, "HW_CPU_X86_SSE2")
        cn1.set_traits(rp_obj.TraitList(objects=[t_avx_sse, t_avx_sse2]))

        # Populate shared storage provider with DISK_GB inventory
        disk_inv = rp_obj.Inventory(
            self.context,
            resource_provider=ss,
            resource_class='DISK_GB',
            total=2000,
            reserved=100,
            max_unit=2000,
            min_unit=10,
            step_size=10,
            allocation_ratio=1.0)
        disk_inv.obj_set_defaults()
        inv_list = rp_obj.InventoryList(objects=[disk_inv])
        ss.set_inventory(inv_list)

        # Mark the shared storage pool as having inventory shared among any
        # provider associated via aggregate
        t = rp_obj.Trait.get_by_name(
            self.context,
            "MISC_SHARES_VIA_AGGREGATE",
        )
        ss.set_traits(rp_obj.TraitList(objects=[t]))

        # Now associate the shared storage pool and both compute nodes with the
        # same aggregate
        cn1.set_aggregates([agg_uuid])
        cn2.set_aggregates([agg_uuid])
        ss.set_aggregates([agg_uuid])


class CORSFixture(APIFixture):
    """An APIFixture that turns on CORS."""

    def start_fixture(self):
        super(CORSFixture, self).start_fixture()
        # NOTE(cdent): If we remove this override, then the cors
        # group ends up not existing in the conf, so when deploy.py
        # wants to load the CORS middleware, it will not.
        self.conf.set_override('allowed_origin', 'http://valid.example.com',
                               group='cors')
