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

# Copyright (C) 2012 Midokura Japan K.K.
# Copyright (C) 2013 Midokura PTE LTD
# 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.
#
# @author: Takaaki Suzuki, Midokura Japan KK
# @author: Tomoe Sugihara, Midokura Japan KK
# @author: Ryu Ishimoto, Midokura Japan KK

from midonetclient import api
from oslo.config import cfg
from webob import exc as w_exc

from quantum.common import exceptions as q_exc
from quantum.db import api as db
from quantum.db import db_base_plugin_v2
from quantum.db import l3_db
from quantum.db import models_v2
from quantum.db import securitygroups_db
from quantum.extensions import securitygroup as ext_sg
from quantum.openstack.common import log as logging
from quantum.plugins.midonet import config  # noqa
from quantum.plugins.midonet import midonet_lib


LOG = logging.getLogger(__name__)

OS_TENANT_ROUTER_RULE_KEY = 'OS_TENANT_ROUTER_RULE'
OS_FLOATING_IP_RULE_KEY = 'OS_FLOATING_IP'
SNAT_RULE = 'SNAT'
SNAT_RULE_PROPERTY = {OS_TENANT_ROUTER_RULE_KEY: SNAT_RULE}


class MidonetResourceNotFound(q_exc.NotFound):
    message = _('MidoNet %(resource_type)s %(id)s could not be found')


class MidonetPluginException(q_exc.QuantumException):
    message = _("%(msg)s")


class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                      l3_db.L3_NAT_db_mixin,
                      securitygroups_db.SecurityGroupDbMixin):

    supported_extension_aliases = ['router', 'security-group']

    def __init__(self):

        # Read config values
        midonet_conf = cfg.CONF.MIDONET
        midonet_uri = midonet_conf.midonet_uri
        admin_user = midonet_conf.username
        admin_pass = midonet_conf.password
        admin_project_id = midonet_conf.project_id
        provider_router_id = midonet_conf.provider_router_id
        metadata_router_id = midonet_conf.metadata_router_id
        mode = midonet_conf.mode

        self.mido_api = api.MidonetApi(midonet_uri, admin_user,
                                       admin_pass,
                                       project_id=admin_project_id)

        # get MidoNet provider router and metadata router
        if provider_router_id and metadata_router_id:
            self.provider_router = self.mido_api.get_router(provider_router_id)
            self.metadata_router = self.mido_api.get_router(metadata_router_id)

        # for dev purpose only
        elif mode == 'dev':
            msg = _('No provider router and metadata device ids found. '
                    'But skipping because running in dev env.')
            LOG.debug(msg)
        else:
            msg = _('provider_router_id and metadata_router_id '
                    'should be configured in the plugin config file')
            LOG.exception(msg)
            raise MidonetPluginException(msg=msg)

        self.chain_manager = midonet_lib.ChainManager(self.mido_api)
        self.pg_manager = midonet_lib.PortGroupManager(self.mido_api)
        self.rule_manager = midonet_lib.RuleManager(self.mido_api)

        db.configure_db()

    def create_subnet(self, context, subnet):
        """Create Quantum subnet.

        Creates a Quantum subnet and a DHCP entry in MidoNet bridge.
        """
        LOG.debug(_("MidonetPluginV2.create_subnet called: subnet=%r"), subnet)

        if subnet['subnet']['ip_version'] == 6:
            raise q_exc.NotImplementedError(
                _("MidoNet doesn't support IPv6."))

        net = super(MidonetPluginV2, self).get_network(
            context, subnet['subnet']['network_id'], fields=None)
        if net['subnets']:
            raise q_exc.NotImplementedError(
                _("MidoNet doesn't support multiple subnets "
                  "on the same network."))

        session = context.session
        with session.begin(subtransactions=True):
            sn_entry = super(MidonetPluginV2, self).create_subnet(context,
                                                                  subnet)
            try:
                bridge = self.mido_api.get_bridge(sn_entry['network_id'])
            except w_exc.HTTPNotFound:
                raise MidonetResourceNotFound(resource_type='Bridge',
                                              id=sn_entry['network_id'])

            gateway_ip = subnet['subnet']['gateway_ip']
            network_address, prefix = subnet['subnet']['cidr'].split('/')
            bridge.add_dhcp_subnet().default_gateway(gateway_ip).subnet_prefix(
                network_address).subnet_length(prefix).create()

            # If the network is external, link the bridge to MidoNet provider
            # router
            self._extend_network_dict_l3(context, net)
            if net['router:external']:
                gateway_ip = sn_entry['gateway_ip']
                network_address, length = sn_entry['cidr'].split('/')

                # create a interior port in the MidoNet provider router
                in_port = self.provider_router.add_interior_port()
                pr_port = in_port.port_address(gateway_ip).network_address(
                    network_address).network_length(length).create()

                # create a interior port in the bridge, then link
                # it to the provider router.
                br_port = bridge.add_interior_port().create()
                pr_port.link(br_port.get_id())

                # add a route for the subnet in the provider router
                self.provider_router.add_route().type(
                    'Normal').src_network_addr('0.0.0.0').src_network_length(
                        0).dst_network_addr(
                            network_address).dst_network_length(
                                length).weight(100).next_hop_port(
                                    pr_port.get_id()).create()

        LOG.debug(_("MidonetPluginV2.create_subnet exiting: sn_entry=%r"),
                  sn_entry)
        return sn_entry

    def get_subnet(self, context, id, fields=None):
        """Get Quantum subnet.

        Retrieves a Quantum subnet record but also including the DHCP entry
        data stored in MidoNet.
        """
        LOG.debug(_("MidonetPluginV2.get_subnet called: id=%(id)s "
                    "fields=%(fields)s"), {'id': id, 'fields': fields})

        qsubnet = super(MidonetPluginV2, self).get_subnet(context, id)
        bridge_id = qsubnet['network_id']
        try:
            bridge = self.mido_api.get_bridge(bridge_id)
        except w_exc.HTTPNotFound:
            raise MidonetResourceNotFound(resource_type='Bridge',
                                          id=bridge_id)

        # get dhcp subnet data from MidoNet bridge.
        dhcps = bridge.get_dhcp_subnets()
        b_network_address = dhcps[0].get_subnet_prefix()
        b_prefix = dhcps[0].get_subnet_length()

        # Validate against quantum database.
        network_address, prefix = qsubnet['cidr'].split('/')
        if network_address != b_network_address or int(prefix) != b_prefix:
            raise MidonetResourceNotFound(resource_type='DhcpSubnet',
                                          id=qsubnet['cidr'])

        LOG.debug(_("MidonetPluginV2.get_subnet exiting: qsubnet=%s"), qsubnet)
        return qsubnet

    def get_subnets(self, context, filters=None, fields=None):
        """List Quantum subnets.

        Retrieves Quantum subnets with some fields populated by the data
        stored in MidoNet.
        """
        LOG.debug(_("MidonetPluginV2.get_subnets called: filters=%(filters)r, "
                    "fields=%(fields)r"),
                  {'filters': filters, 'fields': fields})
        subnets = super(MidonetPluginV2, self).get_subnets(context, filters,
                                                           fields)
        for sn in subnets:
            if not 'network_id' in sn:
                continue
            try:
                bridge = self.mido_api.get_bridge(sn['network_id'])
            except w_exc.HTTPNotFound:
                raise MidonetResourceNotFound(resource_type='Bridge',
                                              id=sn['network_id'])

            # TODO(tomoe): dedupe this part.
            # get dhcp subnet data from MidoNet bridge.
            dhcps = bridge.get_dhcp_subnets()
            b_network_address = dhcps[0].get_subnet_prefix()
            b_prefix = dhcps[0].get_subnet_length()

            # Validate against quantum database.
            if sn.get('cidr'):
                network_address, prefix = sn['cidr'].split('/')
                if network_address != b_network_address or int(
                    prefix) != b_prefix:
                    raise MidonetResourceNotFound(resource_type='DhcpSubnet',
                                                  id=sn['cidr'])

        LOG.debug(_("MidonetPluginV2.create_subnet exiting"))
        return subnets

    def delete_subnet(self, context, id):
        """Delete Quantum subnet.

        Delete quantum network and its corresponding MidoNet bridge.
        """
        LOG.debug(_("MidonetPluginV2.delete_subnet called: id=%s"), id)
        subnet = super(MidonetPluginV2, self).get_subnet(context, id,
                                                         fields=None)
        net = super(MidonetPluginV2, self).get_network(context,
                                                       subnet['network_id'],
                                                       fields=None)
        bridge_id = subnet['network_id']
        try:
            bridge = self.mido_api.get_bridge(bridge_id)
        except w_exc.HTTPNotFound:
            raise MidonetResourceNotFound(resource_type='Bridge', id=bridge_id)

        dhcp = bridge.get_dhcp_subnets()
        dhcp[0].delete()

        # If the network is external, clean up routes, links, ports.
        self._extend_network_dict_l3(context, net)
        if net['router:external']:
            # Delete routes and unlink the router and the bridge.
            routes = self.provider_router.get_routes()

            bridge_ports_to_delete = []
            for p in self.provider_router.get_peer_ports():
                if p.get_device_id() == bridge.get_id():
                    bridge_ports_to_delete.append(p)

            for p in bridge.get_peer_ports():
                if p.get_device_id() == self.provider_router.get_id():
                    # delete the routes going to the brdge
                    for r in routes:
                        if r.get_next_hop_port() == p.get_id():
                            r.delete()
                    p.unlink()
                    p.delete()

            # delete bridge port
            map(lambda x: x.delete(), bridge_ports_to_delete)

        super(MidonetPluginV2, self).delete_subnet(context, id)
        LOG.debug(_("MidonetPluginV2.delete_subnet exiting"))

    def create_network(self, context, network):
        """Create Quantum network.

        Create a new Quantum network and its corresponding MidoNet bridge.
        """
        LOG.debug(_('MidonetPluginV2.create_network called: network=%r'),
                  network)

        if network['network']['admin_state_up'] is False:
            LOG.warning(_('Ignoring admin_state_up=False for network=%r'
                          'Overriding with True'), network)
            network['network']['admin_state_up'] = True

        tenant_id = self._get_tenant_id_for_create(context, network['network'])

        self._ensure_default_security_group(context, tenant_id)

        session = context.session
        with session.begin(subtransactions=True):
            bridge = self.mido_api.add_bridge().name(
                network['network']['name']).tenant_id(tenant_id).create()

            # Set MidoNet bridge ID to the quantum DB entry
            network['network']['id'] = bridge.get_id()
            net = super(MidonetPluginV2, self).create_network(context, network)

            # to handle l3 related data in DB
            self._process_l3_create(context, network['network'], net['id'])
            self._extend_network_dict_l3(context, net)
        LOG.debug(_("MidonetPluginV2.create_network exiting: net=%r"), net)
        return net

    def update_network(self, context, id, network):
        """Update Quantum network.

        Update an existing Quantum network and its corresponding MidoNet
        bridge.
        """
        LOG.debug(_("MidonetPluginV2.update_network called: id=%(id)r, "
                    "network=%(network)r"), {'id': id, 'network': network})

        # Reject admin_state_up=False
        if network['network'].get('admin_state_up') and network['network'][
            'admin_state_up'] is False:
            raise q_exc.NotImplementedError(_('admin_state_up=False '
                                              'networks are not '
                                              'supported.'))

        session = context.session
        with session.begin(subtransactions=True):
            net = super(MidonetPluginV2, self).update_network(
                context, id, network)
            try:
                bridge = self.mido_api.get_bridge(id)
            except w_exc.HTTPNotFound:
                raise MidonetResourceNotFound(resource_type='Bridge', id=id)
            bridge.name(net['name']).update()

        self._extend_network_dict_l3(context, net)
        LOG.debug(_("MidonetPluginV2.update_network exiting: net=%r"), net)
        return net

    def get_network(self, context, id, fields=None):
        """Get Quantum network.

        Retrieves a Quantum network and its corresponding MidoNet bridge.
        """
        LOG.debug(_("MidonetPluginV2.get_network called: id=%(id)r, "
                    "fields=%(fields)r"), {'id': id, 'fields': fields})

        # NOTE: Get network data with all fields (fields=None) for
        #       _extend_network_dict_l3() method, which needs 'id' field
        qnet = super(MidonetPluginV2, self).get_network(context, id, None)
        try:
            self.mido_api.get_bridge(id)
        except w_exc.HTTPNotFound:
            raise MidonetResourceNotFound(resource_type='Bridge', id=id)

        self._extend_network_dict_l3(context, qnet)
        LOG.debug(_("MidonetPluginV2.get_network exiting: qnet=%r"), qnet)
        return self._fields(qnet, fields)

    def get_networks(self, context, filters=None, fields=None):
        """List quantum networks and verify that all exist in MidoNet."""
        LOG.debug(_("MidonetPluginV2.get_networks called: "
                    "filters=%(filters)r, fields=%(fields)r"),
                  {'filters': filters, 'fields': fields})

        # NOTE: Get network data with all fields (fields=None) for
        #       _extend_network_dict_l3() method, which needs 'id' field
        qnets = super(MidonetPluginV2, self).get_networks(context, filters,
                                                          None)
        self.mido_api.get_bridges({'tenant_id': context.tenant_id})
        for n in qnets:
            try:
                self.mido_api.get_bridge(n['id'])
            except w_exc.HTTPNotFound:
                raise MidonetResourceNotFound(resource_type='Bridge',
                                              id=n['id'])
            self._extend_network_dict_l3(context, n)

        return [self._fields(net, fields) for net in qnets]

    def delete_network(self, context, id):
        """Delete a network and its corresponding MidoNet bridge."""
        LOG.debug(_("MidonetPluginV2.delete_network called: id=%r"), id)

        self.mido_api.get_bridge(id).delete()
        try:
            super(MidonetPluginV2, self).delete_network(context, id)
        except Exception:
            LOG.error(_('Failed to delete quantum db, while Midonet bridge=%r'
                      'had been deleted'), id)
            raise

    def create_port(self, context, port):
        """Create a L2 port in Quantum/MidoNet."""
        LOG.debug(_("MidonetPluginV2.create_port called: port=%r"), port)

        is_compute_interface = False
        port_data = port['port']
        # get the bridge and create a port on it.
        try:
            bridge = self.mido_api.get_bridge(port_data['network_id'])
        except w_exc.HTTPNotFound:
            raise MidonetResourceNotFound(resource_type='Bridge',
                                          id=port_data['network_id'])

        device_owner = port_data['device_owner']

        if device_owner.startswith('compute:') or device_owner is '':
            is_compute_interface = True
            bridge_port = bridge.add_exterior_port().create()
        elif device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
            bridge_port = bridge.add_interior_port().create()
        elif (device_owner == l3_db.DEVICE_OWNER_ROUTER_GW or
                device_owner == l3_db.DEVICE_OWNER_FLOATINGIP):

            # This is a dummy port to make l3_db happy.
            # This will not be used in MidoNet
            bridge_port = bridge.add_interior_port().create()

        if bridge_port:
            # set midonet port id to quantum port id and create a DB record.
            port_data['id'] = bridge_port.get_id()

        session = context.session
        with session.begin(subtransactions=True):
            port_db_entry = super(MidonetPluginV2,
                                  self).create_port(context, port)
            # Caveat: port_db_entry is not a db model instance
            sg_ids = self._get_security_groups_on_port(context, port)
            self._process_port_create_security_group(context, port, sg_ids)
            if is_compute_interface:
                # Create a DHCP entry if needed.
                if 'ip_address' in (port_db_entry['fixed_ips'] or [{}])[0]:
                    # get ip and mac from DB record, assuming one IP address
                    # at most since we only support one subnet per network now.
                    fixed_ip = port_db_entry['fixed_ips'][0]['ip_address']
                    mac = port_db_entry['mac_address']
                    # create dhcp host entry under the bridge.
                    dhcp_subnets = bridge.get_dhcp_subnets()
                    if dhcp_subnets:
                        dhcp_subnets[0].add_dhcp_host().ip_addr(
                            fixed_ip).mac_addr(mac).create()
        LOG.debug(_("MidonetPluginV2.create_port exiting: port_db_entry=%r"),
                  port_db_entry)
        return port_db_entry

    def update_port(self, context, id, port):
        """Update port."""
        LOG.debug(_("MidonetPluginV2.update_port called: id=%(id)s "
                    "port=%(port)r"), {'id': id, 'port': port})
        return super(MidonetPluginV2, self).update_port(context, id, port)

    def get_port(self, context, id, fields=None):
        """Retrieve port."""
        LOG.debug(_("MidonetPluginV2.get_port called: id=%(id)s "
                    "fields=%(fields)r"), {'id': id, 'fields': fields})

        # get the quantum port from DB.
        port_db_entry = super(MidonetPluginV2, self).get_port(context,
                                                              id, fields)
        # verify that corresponding port exists in MidoNet.
        try:
            self.mido_api.get_port(id)
        except w_exc.HTTPNotFound:
            raise MidonetResourceNotFound(resource_type='Port', id=id)

        LOG.debug(_("MidonetPluginV2.get_port exiting: port_db_entry=%r"),
                  port_db_entry)
        return port_db_entry

    def get_ports(self, context, filters=None, fields=None):
        """List quantum ports and verify that they exist in MidoNet."""
        LOG.debug(_("MidonetPluginV2.get_ports called: filters=%(filters)s "
                    "fields=%(fields)r"),
                  {'filters': filters, 'fields': fields})
        ports_db_entry = super(MidonetPluginV2, self).get_ports(context,
                                                                filters,
                                                                fields)
        if ports_db_entry:
            try:
                for port in ports_db_entry:
                    self.mido_api.get_port(port['id'])
            except w_exc.HTTPNotFound:
                raise MidonetResourceNotFound(resource_type='Port',
                                              id=port['id'])
        return ports_db_entry

    def delete_port(self, context, id, l3_port_check=True):
        """Delete a quantum port and corresponding MidoNet bridge port."""
        LOG.debug(_("MidonetPluginV2.delete_port called: id=%(id)s "
                    "l3_port_check=%(l3_port_check)r"),
                  {'id': id, 'l3_port_check': l3_port_check})
        # if needed, check to see if this is a port owned by
        # and l3-router.  If so, we should prevent deletion.
        if l3_port_check:
            self.prevent_l3_port_deletion(context, id)

        session = context.session
        with session.begin(subtransactions=True):
            port_db_entry = super(MidonetPluginV2, self).get_port(context,
                                                                  id, None)
            bridge = self.mido_api.get_bridge(port_db_entry['network_id'])
            # Clean up dhcp host entry if needed.
            if 'ip_address' in (port_db_entry['fixed_ips'] or [{}])[0]:
                # get ip and mac from DB record.
                ip = port_db_entry['fixed_ips'][0]['ip_address']
                mac = port_db_entry['mac_address']

                # create dhcp host entry under the bridge.
                dhcp_subnets = bridge.get_dhcp_subnets()
                if dhcp_subnets:
                    for dh in dhcp_subnets[0].get_dhcp_hosts():
                        if dh.get_mac_addr() == mac and dh.get_ip_addr() == ip:
                            dh.delete()

            self.mido_api.get_port(id).delete()
            return super(MidonetPluginV2, self).delete_port(context, id)

    #
    # L3 APIs.
    #

    def create_router(self, context, router):
        LOG.debug(_("MidonetPluginV2.create_router called: router=%r"), router)

        if router['router']['admin_state_up'] is False:
            LOG.warning(_('Ignoring admin_state_up=False for router=%r.  '
                          'Overriding with True'), router)
            router['router']['admin_state_up'] = True

        tenant_id = self._get_tenant_id_for_create(context, router['router'])
        session = context.session
        with session.begin(subtransactions=True):
            mrouter = self.mido_api.add_router().name(
                router['router']['name']).tenant_id(tenant_id).create()
            qrouter = super(MidonetPluginV2, self).create_router(context,
                                                                 router)

            chains = self.chain_manager.create_router_chains(tenant_id,
                                                             mrouter.get_id())

            # set chains to in/out filters
            mrouter.inbound_filter_id(
                chains['in'].get_id()).outbound_filter_id(
                    chains['out'].get_id()).update()

            # get entry from the DB and update 'id' with MidoNet router id.
            qrouter_entry = self._get_router(context, qrouter['id'])
            qrouter['id'] = mrouter.get_id()
            qrouter_entry.update(qrouter)

            # link to metadata router
            in_port = self.metadata_router.add_interior_port()
            mdr_port = in_port.network_address('169.254.255.0').network_length(
                30).port_address('169.254.255.1').create()

            tr_port = mrouter.add_interior_port().network_address(
                '169.254.255.0').network_length(30).port_address(
                    '169.254.255.2').create()
            mdr_port.link(tr_port.get_id())

            # forward metadata traffic to metadata router
            mrouter.add_route().type('Normal').src_network_addr(
                '0.0.0.0').src_network_length(0).dst_network_addr(
                    '169.254.169.254').dst_network_length(32).weight(
                        100).next_hop_port(tr_port.get_id()).create()

            LOG.debug(_("MidonetPluginV2.create_router exiting: qrouter=%r"),
                      qrouter)
            return qrouter

    def update_router(self, context, id, router):
        LOG.debug(_("MidonetPluginV2.update_router called: id=%(id)s "
                    "router=%(router)r"), router)

        if router['router'].get('admin_state_up') is False:
            raise q_exc.NotImplementedError(_('admin_state_up=False '
                                              'routers are not '
                                              'supported.'))

        op_gateway_set = False
        op_gateway_clear = False

        # figure out which operation it is in
        if ('external_gateway_info' in router['router'] and
            'network_id' in router['router']['external_gateway_info']):
            op_gateway_set = True
        elif ('external_gateway_info' in router['router'] and
              router['router']['external_gateway_info'] == {}):
            op_gateway_clear = True

            qports = super(MidonetPluginV2, self).get_ports(
                context, {'device_id': [id],
                          'device_owner': ['network:router_gateway']})

            assert len(qports) == 1
            qport = qports[0]
            snat_ip = qport['fixed_ips'][0]['ip_address']
            qport['network_id']

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

            qrouter = super(MidonetPluginV2, self).update_router(context, id,
                                                                 router)

            changed_name = router['router'].get('name')
            if changed_name:
                self.mido_api.get_router(id).name(changed_name).update()

            tenant_router = self.mido_api.get_router(id)
            if op_gateway_set:
                # find a qport with the network_id for the router
                qports = super(MidonetPluginV2, self).get_ports(
                    context, {'device_id': [id],
                              'device_owner': ['network:router_gateway']})
                assert len(qports) == 1
                qport = qports[0]
                snat_ip = qport['fixed_ips'][0]['ip_address']

                in_port = self.provider_router.add_interior_port()
                pr_port = in_port.network_address(
                    '169.254.255.0').network_length(30).port_address(
                        '169.254.255.1').create()

                # Create a port in the tenant router
                tr_port = tenant_router.add_interior_port().network_address(
                    '169.254.255.0').network_length(30).port_address(
                        '169.254.255.2').create()

                # Link them
                pr_port.link(tr_port.get_id())

                # Add a route for snat_ip to bring it down to tenant
                self.provider_router.add_route().type(
                    'Normal').src_network_addr('0.0.0.0').src_network_length(
                        0).dst_network_addr(snat_ip).dst_network_length(
                            32).weight(100).next_hop_port(
                                pr_port.get_id()).create()

                # Add default route to uplink in the tenant router
                tenant_router.add_route().type('Normal').src_network_addr(
                    '0.0.0.0').src_network_length(0).dst_network_addr(
                        '0.0.0.0').dst_network_length(0).weight(
                            100).next_hop_port(tr_port.get_id()).create()

                # ADD SNAT(masquerade) rules
                chains = self.chain_manager.get_router_chains(
                    tenant_router.get_tenant_id(), tenant_router.get_id())

                chains['in'].add_rule().nw_dst_address(snat_ip).nw_dst_length(
                    32).type('rev_snat').flow_action('accept').in_ports(
                        [tr_port.get_id()]).properties(
                            SNAT_RULE_PROPERTY).position(1).create()

                nat_targets = []
                nat_targets.append(
                    {'addressFrom': snat_ip, 'addressTo': snat_ip,
                     'portFrom': 1, 'portTo': 65535})

                chains['out'].add_rule().type('snat').flow_action(
                    'accept').nat_targets(nat_targets).out_ports(
                        [tr_port.get_id()]).properties(
                            SNAT_RULE_PROPERTY).position(1).create()

            if op_gateway_clear:
                # delete the port that is connected to provider router
                for p in tenant_router.get_ports():
                    if p.get_port_address() == '169.254.255.2':
                        peer_port_id = p.get_peer_id()
                        p.unlink()
                        self.mido_api.get_port(peer_port_id).delete()
                        p.delete()

                # delete default route
                for r in tenant_router.get_routes():
                    if (r.get_dst_network_addr() == '0.0.0.0' and
                            r.get_dst_network_length() == 0):
                        r.delete()

                # delete SNAT(masquerade) rules
                chains = self.chain_manager.get_router_chains(
                    tenant_router.get_tenant_id(),
                    tenant_router.get_id())

                for r in chains['in'].get_rules():
                    if OS_TENANT_ROUTER_RULE_KEY in r.get_properties():
                        if r.get_properties()[
                            OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE:
                            r.delete()

                for r in chains['out'].get_rules():
                    if OS_TENANT_ROUTER_RULE_KEY in r.get_properties():
                        if r.get_properties()[
                            OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE:
                            r.delete()

        LOG.debug(_("MidonetPluginV2.update_router exiting: qrouter=%r"),
                  qrouter)
        return qrouter

    def delete_router(self, context, id):
        LOG.debug(_("MidonetPluginV2.delete_router called: id=%s"), id)

        mrouter = self.mido_api.get_router(id)
        tenant_id = mrouter.get_tenant_id()

        # unlink from metadata router and delete the interior ports
        # that connect metadata router and this router.
        for pp in self.metadata_router.get_peer_ports():
            if pp.get_device_id() == mrouter.get_id():
                mdr_port = self.mido_api.get_port(pp.get_peer_id())
                pp.unlink()
                pp.delete()
                mdr_port.delete()

        # delete corresponding chains
        chains = self.chain_manager.get_router_chains(tenant_id,
                                                      mrouter.get_id())
        chains['in'].delete()
        chains['out'].delete()

        # delete the router
        mrouter.delete()

        result = super(MidonetPluginV2, self).delete_router(context, id)
        LOG.debug(_("MidonetPluginV2.delete_router exiting: result=%s"),
                  result)
        return result

    def get_router(self, context, id, fields=None):
        LOG.debug(_("MidonetPluginV2.get_router called: id=%(id)s "
                    "fields=%(fields)r"), {'id': id, 'fields': fields})
        qrouter = super(MidonetPluginV2, self).get_router(context, id, fields)

        try:
            self.mido_api.get_router(id)
        except w_exc.HTTPNotFound:
            raise MidonetResourceNotFound(resource_type='Router', id=id)

        LOG.debug(_("MidonetPluginV2.get_router exiting: qrouter=%r"),
                  qrouter)
        return qrouter

    def get_routers(self, context, filters=None, fields=None):
        LOG.debug(_("MidonetPluginV2.get_routers called: filters=%(filters)s "
                    "fields=%(fields)r"),
                  {'filters': filters, 'fields': fields})

        qrouters = super(MidonetPluginV2, self).get_routers(
            context, filters, fields)
        for qr in qrouters:
            try:
                self.mido_api.get_router(qr['id'])
            except w_exc.HTTPNotFound:
                raise MidonetResourceNotFound(resource_type='Router',
                                              id=qr['id'])
        return qrouters

    def add_router_interface(self, context, router_id, interface_info):
        LOG.debug(_("MidonetPluginV2.add_router_interface called: "
                    "router_id=%(router_id)s "
                    "interface_info=%(interface_info)r"),
                  {'router_id': router_id, 'interface_info': interface_info})

        qport = super(MidonetPluginV2, self).add_router_interface(
            context, router_id, interface_info)

        # TODO(tomoe): handle a case with 'port' in interface_info
        if 'subnet_id' in interface_info:
            subnet_id = interface_info['subnet_id']
            subnet = self._get_subnet(context, subnet_id)

            gateway_ip = subnet['gateway_ip']
            network_address, length = subnet['cidr'].split('/')

            # Link the router and the bridge port.
            mrouter = self.mido_api.get_router(router_id)
            mrouter_port = mrouter.add_interior_port().port_address(
                gateway_ip).network_address(
                    network_address).network_length(length).create()

            mbridge_port = self.mido_api.get_port(qport['port_id'])
            mrouter_port.link(mbridge_port.get_id())

            # Add a route entry to the subnet
            mrouter.add_route().type('Normal').src_network_addr(
                '0.0.0.0').src_network_length(0).dst_network_addr(
                    network_address).dst_network_length(length).weight(
                        100).next_hop_port(mrouter_port.get_id()).create()

            # add a route for the subnet in metadata router; forward
            # packets destined to the subnet to the tenant router
            found = False
            for pp in self.metadata_router.get_peer_ports():
                if pp.get_device_id() == mrouter.get_id():
                    mdr_port_id = pp.get_peer_id()
                    found = True
            assert found

            self.metadata_router.add_route().type(
                'Normal').src_network_addr('0.0.0.0').src_network_length(
                    0).dst_network_addr(network_address).dst_network_length(
                        length).weight(100).next_hop_port(mdr_port_id).create()

        LOG.debug(_("MidonetPluginV2.add_router_interface exiting: "
                    "qport=%r"), qport)
        return qport

    def remove_router_interface(self, context, router_id, interface_info):
        """Remove interior router ports."""
        LOG.debug(_("MidonetPluginV2.remove_router_interface called: "
                    "router_id=%(router_id)s "
                    "interface_info=%(interface_info)r"),
                  {'router_id': router_id, 'interface_info': interface_info})
        if 'port_id' in interface_info:

            mbridge_port = self.mido_api.get_port(interface_info['port_id'])
            subnet_id = self.get_port(context,
                                      interface_info['port_id']
                                      )['fixed_ips'][0]['subnet_id']

            subnet = self._get_subnet(context, subnet_id)

        if 'subnet_id' in interface_info:

            subnet_id = interface_info['subnet_id']
            subnet = self._get_subnet(context, subnet_id)
            network_id = subnet['network_id']

            # find a quantum port for the network
            rport_qry = context.session.query(models_v2.Port)
            ports = rport_qry.filter_by(
                device_id=router_id,
                device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF,
                network_id=network_id)
            network_port = None
            for p in ports:
                if p['fixed_ips'][0]['subnet_id'] == subnet_id:
                    network_port = p
                    break
            assert network_port
            mbridge_port = self.mido_api.get_port(network_port['id'])

        # get network information from subnet data
        network_addr, network_length = subnet['cidr'].split('/')
        network_length = int(network_length)

        # Unlink the router and the bridge.
        mrouter = self.mido_api.get_router(router_id)
        mrouter_port = self.mido_api.get_port(mbridge_port.get_peer_id())
        mrouter_port.unlink()

        # Delete the route for the subnet.
        found = False
        for r in mrouter.get_routes():
            if r.get_next_hop_port() == mrouter_port.get_id():
                r.delete()
                found = True
                #break   # commented out due to issue#314
        assert found

        # delete the route for the subnet in the metadata router
        found = False
        for r in self.metadata_router.get_routes():
            if (r.get_dst_network_addr() == network_addr and
                r.get_dst_network_length() == network_length):
                LOG.debug(_('Deleting route=%r ...'), r)
                r.delete()
                found = True
                break
        assert found

        info = super(MidonetPluginV2, self).remove_router_interface(
            context, router_id, interface_info)
        LOG.debug(_("MidonetPluginV2.remove_router_interface exiting"))
        return info

    def update_floatingip(self, context, id, floatingip):
        LOG.debug(_("MidonetPluginV2.update_floatingip called: id=%(id)s "
                    "floatingip=%(floatingip)s "),
                  {'id': id, 'floatingip': floatingip})

        session = context.session
        with session.begin(subtransactions=True):
            if floatingip['floatingip']['port_id']:
                fip = super(MidonetPluginV2, self).update_floatingip(
                    context, id, floatingip)
                router_id = fip['router_id']
                floating_address = fip['floating_ip_address']
                fixed_address = fip['fixed_ip_address']

                tenant_router = self.mido_api.get_router(router_id)
                # find the provider router port that is connected to the tenant
                # of the floating ip
                for p in tenant_router.get_peer_ports():
                    if p.get_device_id() == self.provider_router.get_id():
                        pr_port = p

                # get the tenant router port id connected to provider router
                tr_port_id = pr_port.get_peer_id()

                # add a route for the floating ip to bring it to the tenant
                self.provider_router.add_route().type(
                    'Normal').src_network_addr('0.0.0.0').src_network_length(
                        0).dst_network_addr(
                            floating_address).dst_network_length(
                                32).weight(100).next_hop_port(
                                    pr_port.get_id()).create()

                chains = self.chain_manager.get_router_chains(fip['tenant_id'],
                                                              fip['router_id'])
                # add dnat/snat rule pair for the floating ip
                nat_targets = []
                nat_targets.append(
                    {'addressFrom': fixed_address, 'addressTo': fixed_address,
                     'portFrom': 0, 'portTo': 0})

                floating_property = {OS_FLOATING_IP_RULE_KEY: id}
                chains['in'].add_rule().nw_dst_address(
                    floating_address).nw_dst_length(32).type(
                        'dnat').flow_action('accept').nat_targets(
                            nat_targets).in_ports([tr_port_id]).position(
                                1).properties(floating_property).create()

                nat_targets = []
                nat_targets.append(
                    {'addressFrom': floating_address,
                     'addressTo': floating_address,
                     'portFrom': 0,
                     'portTo': 0})

                chains['out'].add_rule().nw_src_address(
                    fixed_address).nw_src_length(32).type(
                        'snat').flow_action('accept').nat_targets(
                            nat_targets).out_ports(
                                [tr_port_id]).position(1).properties(
                                    floating_property).create()

            # disassociate floating IP
            elif floatingip['floatingip']['port_id'] is None:

                fip = super(MidonetPluginV2, self).get_floatingip(context, id)

                router_id = fip['router_id']
                floating_address = fip['floating_ip_address']
                fixed_address = fip['fixed_ip_address']

                # delete the route for this floating ip
                for r in self.provider_router.get_routes():
                    if (r.get_dst_network_addr() == floating_address and
                            r.get_dst_network_length() == 32):
                        r.delete()

                # delete snat/dnat rule pair for this floating ip
                chains = self.chain_manager.get_router_chains(fip['tenant_id'],
                                                              fip['router_id'])
                LOG.debug(_('chains=%r'), chains)

                for r in chains['in'].get_rules():
                    if OS_FLOATING_IP_RULE_KEY in r.get_properties():
                        if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == id:
                            LOG.debug(_('deleting rule=%r'), r)
                            r.delete()
                            break

                for r in chains['out'].get_rules():
                    if OS_FLOATING_IP_RULE_KEY in r.get_properties():
                        if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == id:
                            LOG.debug(_('deleting rule=%r'), r)
                            r.delete()
                            break

                super(MidonetPluginV2, self).update_floatingip(context, id,
                                                               floatingip)

        LOG.debug(_("MidonetPluginV2.update_floating_ip exiting: fip=%s"), fip)
        return fip

    #
    # Security groups supporting methods
    #

    def create_security_group(self, context, security_group, default_sg=False):
        """Create chains for Quantum security group."""
        LOG.debug(_("MidonetPluginV2.create_security_group called: "
                    "security_group=%(security_group)s "
                    "default_sg=%(default_sg)s "),
                  {'security_group': security_group, 'default_sg': default_sg})

        sg = security_group.get('security_group')
        tenant_id = self._get_tenant_id_for_create(context, sg)

        with context.session.begin(subtransactions=True):
            sg_db_entry = super(MidonetPluginV2, self).create_security_group(
                context, security_group, default_sg)

            # Create MidoNet chains and portgroup for the SG
            sg_id = sg_db_entry['id']
            sg_name = sg_db_entry['name']
            self.chain_manager.create_for_sg(tenant_id, sg_id, sg_name)
            self.pg_manager.create(tenant_id, sg_id, sg_name)

            LOG.debug(_("MidonetPluginV2.create_security_group exiting: "
                        "sg_db_entry=%r"), sg_db_entry)
            return sg_db_entry

    def delete_security_group(self, context, id):
        """Delete chains for Quantum security group."""
        LOG.debug(_("MidonetPluginV2.delete_security_group called: id=%s"), id)

        with context.session.begin(subtransactions=True):
            sg_db_entry = super(MidonetPluginV2, self).get_security_group(
                context, id)

            if not sg_db_entry:
                raise ext_sg.SecurityGroupNotFound(id=id)

            sg_name = sg_db_entry['name']
            sg_id = sg_db_entry['id']
            tenant_id = sg_db_entry['tenant_id']

            if sg_name == 'default' and not context.is_admin:
                raise ext_sg.SecurityGroupCannotRemoveDefault()

            filters = {'security_group_id': [sg_id]}
            if super(MidonetPluginV2, self)._get_port_security_group_bindings(
                context, filters):
                raise ext_sg.SecurityGroupInUse(id=sg_id)

            # Delete MidoNet Chains and portgroup for the SG
            self.chain_manager.delete_for_sg(tenant_id, sg_id, sg_name)
            self.pg_manager.delete(tenant_id, sg_id, sg_name)

            return super(MidonetPluginV2, self).delete_security_group(
                context, id)

    def get_security_groups(self, context, filters=None, fields=None,
                            default_sg=False):
        LOG.debug(_("MidonetPluginV2.get_security_groups called: "
                    "filters=%(filters)r fields=%(fields)r"),
                  {'filters': filters, 'fields': fields})
        return super(MidonetPluginV2, self).get_security_groups(
            context, filters, fields, default_sg=default_sg)

    def get_security_group(self, context, id, fields=None, tenant_id=None):
        LOG.debug(_("MidonetPluginV2.get_security_group called: id=%(id)s "
                    "fields=%(fields)r tenant_id=%(tenant_id)s"),
                  {'id': id, 'fields': fields, 'tenant_id': tenant_id})
        return super(MidonetPluginV2, self).get_security_group(context, id,
                                                               fields)

    def create_security_group_rule(self, context, security_group_rule):
        LOG.debug(_("MidonetPluginV2.create_security_group_rule called: "
                    "security_group_rule=%(security_group_rule)r"),
                  {'security_group_rule': security_group_rule})

        with context.session.begin(subtransactions=True):
            rule_db_entry = super(
                MidonetPluginV2, self).create_security_group_rule(
                    context, security_group_rule)

            self.rule_manager.create_for_sg_rule(rule_db_entry)
            LOG.debug(_("MidonetPluginV2.create_security_group_rule exiting: "
                        "rule_db_entry=%r"), rule_db_entry)
            return rule_db_entry

    def delete_security_group_rule(self, context, sgrid):
        LOG.debug(_("MidonetPluginV2.delete_security_group_rule called: "
                    "sgrid=%s"), sgrid)

        with context.session.begin(subtransactions=True):
            rule_db_entry = super(MidonetPluginV2,
                                  self).get_security_group_rule(context, sgrid)

            if not rule_db_entry:
                raise ext_sg.SecurityGroupRuleNotFound(id=sgrid)

            self.rule_manager.delete_for_sg_rule(rule_db_entry)
            return super(MidonetPluginV2,
                         self).delete_security_group_rule(context, sgrid)

    def get_security_group_rules(self, context, filters=None, fields=None):
        LOG.debug(_("MidonetPluginV2.get_security_group_rules called: "
                    "filters=%(filters)r fields=%(fields)r"),
                  {'filters': filters, 'fields': fields})
        return super(MidonetPluginV2, self).get_security_group_rules(
            context, filters, fields)

    def get_security_group_rule(self, context, id, fields=None):
        LOG.debug(_("MidonetPluginV2.get_security_group_rule called: "
                    "id=%(id)s fields=%(fields)r"),
                  {'id': id, 'fields': fields})
        return super(MidonetPluginV2, self).get_security_group_rule(
            context, id, fields)
