# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2013 IBM Corp.
# 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 functools
import inspect

from nova.cells import rpcapi as cells_rpcapi
from nova.compute import flavors
from nova.db import base
from nova import exception
from nova.network import floating_ips
from nova.network import model as network_model
from nova.network import rpcapi as network_rpcapi
from nova.openstack.common import log as logging
from nova import policy
from nova import utils

LOG = logging.getLogger(__name__)


def refresh_cache(f):
    """
    Decorator to update the instance_info_cache

    Requires context and instance as function args
    """
    argspec = inspect.getargspec(f)

    @functools.wraps(f)
    def wrapper(self, context, *args, **kwargs):
        res = f(self, context, *args, **kwargs)

        try:
            # get the instance from arguments (or raise ValueError)
            instance = kwargs.get('instance')
            if not instance:
                instance = args[argspec.args.index('instance') - 2]
        except ValueError:
            msg = _('instance is a required argument to use @refresh_cache')
            raise Exception(msg)

        update_instance_cache_with_nw_info(self, context, instance,
                nw_info=res, conductor_api=kwargs.get('conductor_api'))

        # return the original function's return value
        return res
    return wrapper


def update_instance_cache_with_nw_info(api, context, instance,
                                       nw_info=None, conductor_api=None,
                                       update_cells=True):
    try:
        if not isinstance(nw_info, network_model.NetworkInfo):
            nw_info = None
        if not nw_info:
            nw_info = api._get_instance_nw_info(context, instance)
        # update cache
        cache = {'network_info': nw_info.json()}
        if conductor_api:
            rv = conductor_api.instance_info_cache_update(context,
                                                          instance,
                                                          cache)
        else:
            rv = api.db.instance_info_cache_update(context,
                                                   instance['uuid'],
                                                   cache)
        if update_cells:
            cells_api = cells_rpcapi.CellsAPI()
            try:
                cells_api.instance_info_cache_update_at_top(context, rv)
            except Exception:
                LOG.exception(_("Failed to notify cells of instance info "
                                "cache update"))
    except Exception:
        LOG.exception(_('Failed storing info cache'), 
instance=instance)


def wrap_check_policy(func):
    """Check policy corresponding to the wrapped methods prior to execution."""

    @functools.wraps(func)
    def wrapped(self, context, *args, **kwargs):
        action = func.__name__
        check_policy(context, action)
        return func(self, context, *args, **kwargs)

    return wrapped


def check_policy(context, action):
    target = {
        'project_id': context.project_id,
        'user_id': conte
xt.user_id,
    }
    _action = 'network:%s' % action
    policy.enforce(context, _action, target)


class API(base.Base):
    """API for doing networking via the nova-network network manager.

    This is a pluggable module - other implementations do networking via
    other services (such as Quantum).
    """
    _sentinel = object()

    def __init__(self, **kwargs):
        self.network_rpcapi = network_rpcapi.NetworkAPI()
        helper = utils.ExceptionHelper
        # NOTE(vish): this local version of floating_manager has to convert
        #             ClientExceptions back since they aren't going over rpc.
        self.floating_manager = helper(floating_ips.LocalManager())
        super(API, self).__init__(**kwargs)

    @wrap_check_policy
    def get_all(self, context):
        try:
            return self.db.network_get_all(context)
        except exception.NoNetworksFound:
            return []

    @wrap_check_policy
    def get(self, context, network_uuid):
        return self.db.network_get_by_uuid(context.elevated(), network_uuid)

    @wrap_check_policy
    def create(self, context, **kwargs):
        return self.network_rpcapi.create_networks(context, **kwargs)

    @wrap_check_policy
    def delete(self, context, network_uuid):
        return self.network_rpcapi.delete_network(context, network_uuid, None)

    @wrap_check_policy
    def disassociate(self, context, network_uuid):
        network = self.get(context, network_uuid)
        self.db.network_disassociate(context, network['id'])

    @wrap_check_policy
    def get_fixed_ip(self, context, id):
        return self.db.fixed_ip_get(context, id)

    @wrap_check_policy
    def get_fixed_ip_by_address(self, context, address):
        return self.db.fixed_ip_get_by_address(context, address)

    @wrap_check_policy
    def get_floating_ip(self, context, id):
        return self.db.floating_ip_get(context, id)

    @wrap_check_policy
    def get_floating_ip_pools(self, context):
        return self.db.floating_ip_get_pools(context)

    @wrap_check_policy
    def get_floating_ip_by_address(self, context, address):
        return self.db.floating_ip_get_by_address(context, address)

    @wrap_check_policy
    def get_floating_ips_by_project(self, context):
        return self.db.floating_ip_get_all_by_project(context,
                                                      context.project_id)

    @wrap_check_policy
    def get_floating_ips_by_fixed_address(self, context, fixed_address):
        floating_ips = self.db.floating_ip_get_by_fixed_address(context,
                                                                fixed_address)
        return [floating_ip['address'] for floating_ip in floating_ips]

    @wrap_check_policy
    def get_instance_id_by_floating_address(self, context, address):
        fixed_ip = self.db.fixed_ip_get_by_floating_address(context, address)
        if fixed_ip is None:
            return None
        else:
            return fixed_ip['instance_uuid']

    @wrap_check_policy
    def get_vifs_by_instance(self, context, instance):
        vifs = self.db.virtual_interface_get_by_instance(context,
                                                         instance['uuid'])
        for vif in vifs:
            if vif.get('network_id') is not None:
                network = self.db.network_get(context, vif['network_id'],
                                              project_only="allow_none")
                vif['net_uuid'] = network['uuid']
        return vifs

    @wrap_check_policy
    def get_vif_by_mac_address(self, context, mac_address):
        vif = self.db.virtual_interface_get_by_address(context,
                                                       mac_address)
        if vif.get('network_id') is not None:
            network = self.db.network_get(context, vif['network_id'],
                                          project_only="allow_none")
            vif['net_uuid'] = network['uuid']
        return vif

    @wrap_check_policy
    def allocate_floating_ip(self, context, pool=None):
        """Adds (allocates) a floating ip to a project from a pool."""
        return self.floating_manager.allocate_floating_ip(context,
                 context.project_id, False