# Copyright 2014 Brocade Communications System, 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.
#


"""Implentation of Brocade SVI service Plugin."""

from oslo.config import cfg

from neutron.common import constants as l3_constants
from neutron.openstack.common import excutils
from neutron.openstack.common import log as logging
from neutron.plugins.ml2 import db
from neutron.plugins.ml2.drivers.brocade.db import models as brocade_db
from neutron.plugins.ml2.drivers.brocade.nos import nosdriver as driver
from neutron.services.l3_router import l3_router_plugin as router


DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF
DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW
DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP

ML2_BROCADE = [cfg.StrOpt('address', default='',
                          help=_('The address of the host to SSH to')),
               cfg.StrOpt('username', default='admin',
                          help=_('The SSH username to use')),
               cfg.StrOpt('password', default='password', secret=True,
                          help=_('The SSH password to use')),
               cfg.StrOpt('rbridge_id', default=1,
                          help=_('Rbridge id of provider edge router(s)')),
               ]

cfg.CONF.register_opts(ML2_BROCADE, "ml2_brocade")

LOG = logging.getLogger(__name__)


class BrocadeSVIPlugin(router.L3RouterPlugin):
    """Brocade SVI service Plugin."""

    def __init__(self):
        """Initialize Brocade Plugin

        Specify switch address and db configuration.
        """
        super(BrocadeSVIPlugin, self).__init__()
        self._switch = None
        self._driver = None
        self.brocade_init()

    def brocade_init(self):
        """Brocade specific initialization."""
        LOG.debug("brocadeSVIPlugin::brocade_init()")

        self._switch = {'address': cfg.CONF.ml2_brocade.address,
                        'username': cfg.CONF.ml2_brocade.username,
                        'password': cfg.CONF.ml2_brocade.password,
                        'rbridge_id': cfg.CONF.ml2_brocade.rbridge_id
                        }
        self._driver = driver.NOSdriver()
        LOG.info(_("rbridge id %s"), self._switch['rbridge_id'])

    def create_router(self, context, router):
        """ creates a vrf on NOS device."""
        LOG.debug("BrocadeSVIPlugin.create_router called: ")
        with context.session.begin(subtransactions=True):
            new_router = super(BrocadeSVIPlugin, self).create_router(context,
                                                                     router)
            # Router on VDX
            try:
                switch = self._switch
                self._driver.create_router(switch['address'],
                                           switch['username'],
                                           switch['password'],
                                           switch['rbridge_id'],
                                           str(new_router['id']))
            except Exception:
                with excutils.save_and_reraise_exception():
                    with context.session.begin(subtransactions=True):
                        super(BrocadeSVIPlugin, self).delete_router(
                            context,
                            new_router['id'])

        LOG.debug("BrocadeSVIPlugin.create_router: "
                  "router created on VDX switch")
        return new_router

    def delete_router(self, context, router_id):
        """ delete a vrf on NOS device."""
        router = super(BrocadeSVIPlugin, self).get_router(context, router_id)
        super(BrocadeSVIPlugin, self).delete_router(context, router_id)
        try:
            switch = self._switch
            self._driver.delete_router(switch['address'],
                                       switch['username'],
                                       switch['password'],
                                       switch['rbridge_id'],
                                       str(router['id']))
        except Exception:
            excutils.save_and_reraise_exception()

    def add_router_interface(self, context, router_id, interface_info):
        """creates svi on NOS device and assigns ip addres to SVI."""
        LOG.debug("BrocadeSVIPlugin.add_router_interface on VDX: "
                  "router_id=%(router_id)s "
                  "interface_info=%(interface_info)r",
                  {'router_id': router_id, 'interface_info': interface_info})

        with context.session.begin(subtransactions=True):

            info = super(BrocadeSVIPlugin, self).add_router_interface(
                context, router_id, interface_info)

            port = db.get_port(context.session, info["port_id"])

            # shutting down neutron port to allow NOS to do Arp/Routing
            port['admin_state_up'] = False
            port['port'] = port
            self._core_plugin.update_port(context, info["port_id"], port)

            interface_info = info
            subnet = self._core_plugin._get_subnet(context,
                                                   interface_info["subnet_id"])
            cidr = subnet["cidr"]
            net_addr, net_len = self.net_addr(cidr)
            gateway_ip = subnet["gateway_ip"]
            network_id = subnet['network_id']
            bnet = brocade_db.get_network(context, network_id)
            vlan_id = bnet['vlan']
            gateway_ip_cidr = gateway_ip + '/' + str(net_len)
            LOG.debug("Allocated cidr %(cidr)s from the pool, "
                      "network_id %(net_id)s "
                      "bnet %(bnet)s "
                      "vlan %(vlan_id)d " % ({'cidr': gateway_ip_cidr,
                                              'net_id': network_id,
                                              'bnet': bnet,
                                              'vlan_id': int(vlan_id)}))
            port_filters = {'network_id': [network_id],
                            'device_owner': [DEVICE_OWNER_ROUTER_INTF]}
            port_count = self._core_plugin.get_ports_count(context,
                                                           port_filters)
            LOG.info(_("BrocadeSVIPlugin.add_router_interface ports_count %d"),
                     port_count)

            # port count is checked against 2 since the current port is already
            # added to db
            if port_count == 2:
                # This subnet is already part of some router
                # (this is not supported in this version of brocade svi plugin)
                LOG.error(_("BrocadeSVIPlugin: adding redundant router "
                            "interface is not supported"))
                raise Exception(_("BrocadeSVIPlugin:adding redundant router "
                                  "interface is not supported"))

        try:
            switch = self._switch
            self._driver.create_svi(switch['address'],
                                    switch['username'],
                                    switch['password'],
                                    switch['rbridge_id'],
                                    vlan_id,
                                    gateway_ip_cidr,
                                    str(router_id))
        except Exception:
            LOG.error(_("Failed to create Brocade resources to add router "
                        "interface. info=%(info)s, router_id=%(router_id)s"),
                      {"info": info, "router_id": router_id})
            with excutils.save_and_reraise_exception():
                with context.session.begin(subtransactions=True):
                    self.remove_router_interface(context, router_id,
                                                 interface_info)
        return info

    def remove_router_interface(self, context, router_id, interface_info):
        """Deletes svi from NOS device."""
        LOG.debug("BrocadeSVIPlugin.remove_router_interface called: "
                  "router_id=%(router_id)s "
                  "interface_info=%(interface_info)r",
                  {'router_id': router_id, 'interface_info': interface_info})

        with context.session.begin(subtransactions=True):
            info = super(BrocadeSVIPlugin, self).remove_router_interface(
                context, router_id, interface_info)
            try:
                subnet = self._core_plugin._get_subnet(context,
                                                       info['subnet_id'])
                cidr = subnet['cidr']
                net_addr, net_len = self.net_addr(cidr)
                gateway_ip = subnet['gateway_ip']
                network_id = subnet['network_id']
                bnet = brocade_db.get_network(context, network_id)
                vlan_id = bnet['vlan']
                gateway_ip_cidr = gateway_ip + '/' + str(net_len)
                LOG.debug("remove_router_interface removed cidr %(cidr)s"
                          " from the pool,"
                          " network_id %(net_id)s bnet %(bnet)s"
                          " vlan %(vlan_id)d" %
                          ({'cidr': gateway_ip_cidr,
                            'net_id': network_id,
                            'bnet': bnet, 'vlan_id': int(vlan_id)}))
                switch = self._switch
                self._driver.delete_svi(switch['address'],
                                        switch['username'],
                                        switch['password'],
                                        switch['rbridge_id'],
                                        vlan_id,
                                        gateway_ip_cidr,
                                        str(router_id))
            except Exception:
                with excutils.save_and_reraise_exception():
                    LOG.error(_("Fail remove of interface from brocade router "
                                "interface. info=%(info)s, "
                                "router_id=%(router_id)s") %
                              ({"info": info, "router_id": router_id}))
        return True

    @staticmethod
    def net_addr(addr):
        """Get network address prefix and length from a given address."""
        if addr is None:
            return None, None
        nw_addr, nw_len = addr.split('/')
        nw_len = int(nw_len)
        return nw_addr, nw_len
