# Copyright 2013 OpenStack Foundation
# 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 netaddr
import netaddr.core as netexc
from oslo.config import cfg
import six
from webob import exc

from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import context as nova_context
from nova import exception
from nova.i18n import _
from nova.i18n import _LE
import nova.network
from nova.openstack.common import log as logging
from nova import quota


CONF = cfg.CONF
CONF.import_opt('enable_network_quota',
                'nova.api.openstack.compute.contrib.os_tenant_networks')
CONF.import_opt('use_neutron_default_nets',
                'nova.api.openstack.compute.contrib.os_tenant_networks')
CONF.import_opt('neutron_default_tenant_id',
                'nova.api.openstack.compute.contrib.os_tenant_networks')
CONF.import_opt('quota_networks',
                'nova.api.openstack.compute.contrib.os_tenant_networks')


ALIAS = 'os-tenant-networks'

QUOTAS = quota.QUOTAS
LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)


def network_dict(network):
    return {"id": network.get("uuid") or network.get("id"),
                        "cidr": str(network.get("cidr")),
                        "label": network.get("label")}


class TenantNetworkController(object):
    def __init__(self, network_api=None):
        self.network_api = nova.network.API()
        self._default_networks = []

    def _refresh_default_networks(self):
        self._default_networks = []
        if CONF.use_neutron_default_nets == "True":
            try:
                self._default_networks = self._get_default_networks()
            except Exception:
                LOG.exception(_LE("Failed to get default networks"))

    def _get_default_networks(self):
        project_id = CONF.neutron_default_tenant_id
        ctx = nova_context.RequestContext(user_id=None,
                                          project_id=project_id)
        networks = {}
        for n in self.network_api.get_all(ctx):
            networks[n['id']] = n['label']
        return [{'id': k, 'label': v} for k, v in networks.iteritems()]

    @extensions.expected_errors(())
    def index(self, req):
        context = req.environ['nova.context']
        authorize(context)
        networks = list(self.network_api.get_all(context))
        if not self._default_networks:
            self._refresh_default_networks()
        networks.extend(self._default_networks)
        return {'networks': [network_dict(n) for n in networks]}

    @extensions.expected_errors(404)
    def show(self, req, id):
        context = req.environ['nova.context']
        authorize(context)
        try:
            network = self.network_api.get(context, id)
        except exception.NetworkNotFound:
            msg = _("Network not found")
            raise exc.HTTPNotFound(explanation=msg)
        return {'network': network_dict(network)}

    @extensions.expected_errors((403, 404, 409))
    @wsgi.response(202)
    def delete(self, req, id):
        context = req.environ['nova.context']
        authorize(context)
        reservation = None
        try:
            if CONF.enable_network_quota:
                reservation = QUOTAS.reserve(context, networks=-1)
        except Exception:
            reservation = None
            LOG.exception(_LE("Failed to update usages deallocating "
                              "network."))

        def _rollback_quota(reservation):
            if CONF.enable_network_quota and reservation:
                QUOTAS.rollback(context, reservation)

        try:
            self.network_api.delete(context, id)
        except exception.PolicyNotAuthorized as e:
            _rollback_quota(reservation)
            raise exc.HTTPForbidden(explanation=six.text_type(e))
        except exception.NetworkInUse as e:
            _rollback_quota(reservation)
            raise exc.HTTPConflict(explanation=e.format_message())
        except exception.NetworkNotFound:
            _rollback_quota(reservation)
            msg = _("Network not found")
            raise exc.HTTPNotFound(explanation=msg)

        if CONF.enable_network_quota and reservation:
            QUOTAS.commit(context, reservation)

    @extensions.expected_errors((400, 403, 503))
    def create(self, req, body):
        if not body:
            _msg = _("Missing request body")
            raise exc.HTTPBadRequest(explanation=_msg)

        context = req.environ["nova.context"]
        authorize(context)

        network = body["network"]
        keys = ["cidr", "cidr_v6", "ipam", "vlan_start", "network_size",
                "num_networks"]
        kwargs = dict((k, network.get(k)) for k in keys)

        label = network["label"]

        if not (kwargs["cidr"] or kwargs["cidr_v6"]):
            msg = _("No CIDR requested")
            raise exc.HTTPBadRequest(explanation=msg)
        if kwargs["cidr"]:
            try:
                net = netaddr.IPNetwork(kwargs["cidr"])
                if net.size < 4:
                    msg = _("Requested network does not contain "
                            "enough (2+) usable hosts")
                    raise exc.HTTPBadRequest(explanation=msg)
            except netexc.AddrFormatError:
                msg = _("CIDR is malformed.")
                raise exc.HTTPBadRequest(explanation=msg)
            except netexc.AddrConversionError:
                msg = _("Address could not be converted.")
                raise exc.HTTPBadRequest(explanation=msg)

        networks = []
        try:
            if CONF.enable_network_quota:
                reservation = QUOTAS.reserve(context, networks=1)
        except exception.OverQuota:
            msg = _("Quota exceeded, too many networks.")
            raise exc.HTTPBadRequest(explanation=msg)

        try:
            networks = self.network_api.create(context,
                                               label=label, **kwargs)
            if CONF.enable_network_quota:
                QUOTAS.commit(context, reservation)
        except exception.PolicyNotAuthorized as e:
            raise exc.HTTPForbidden(explanation=six.text_type(e))
        except Exception:
            if CONF.enable_network_quota:
                QUOTAS.rollback(context, reservation)
            msg = _("Create networks failed")
            LOG.exception(msg, extra=network)
            raise exc.HTTPServiceUnavailable(explanation=msg)
        return {"network": network_dict(networks[0])}


class TenantNetworks(extensions.V3APIExtensionBase):
    """Tenant-based Network Management Extension."""

    name = "TenantNetworks"
    alias = ALIAS
    version = 1

    def get_resources(self):
        ext = extensions.ResourceExtension(ALIAS, TenantNetworkController())
        return [ext]

    def get_controller_extensions(self):
        return []


def _sync_networks(context, project_id, session):
    ctx = nova_context.RequestContext(user_id=None, project_id=project_id)
    ctx = ctx.elevated()
    networks = nova.network.api.API().get_all(ctx)
    return dict(networks=len(networks))


if CONF.enable_network_quota:
    QUOTAS.register_resource(quota.ReservableResource('networks',
                                                      _sync_networks,
                                                      'quota_networks'))
