]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implement MidoNet Neutron plugin for Havana
authorRossella Sblendido <rossella@midokura.com>
Sat, 24 Aug 2013 00:17:38 +0000 (00:17 +0000)
committerRossella Sblendido <rossella@midokura.com>
Tue, 3 Sep 2013 16:39:10 +0000 (16:39 +0000)
Implement L2, L3, security groups, metadata server support for
MidoNet Neutron plugin for Havana.

blueprint midonet-plugin-havana

Change-Id: I0dd1a2ca17d760443c4c7a464a66b6d0a2cf194a

etc/neutron/plugins/midonet/midonet.ini
neutron/plugins/midonet/agent/__init__.py [new file with mode: 0644]
neutron/plugins/midonet/agent/midonet_driver.py [new file with mode: 0644]
neutron/plugins/midonet/common/__init__.py [new file with mode: 0644]
neutron/plugins/midonet/common/config.py [moved from neutron/plugins/midonet/config.py with 91% similarity]
neutron/plugins/midonet/common/net_util.py [new file with mode: 0644]
neutron/plugins/midonet/midonet_lib.py
neutron/plugins/midonet/plugin.py
neutron/tests/unit/midonet/mock_lib.py
neutron/tests/unit/midonet/test_midonet_driver.py [new file with mode: 0644]
neutron/tests/unit/midonet/test_midonet_lib.py

index b4573a907919dde2dbdcd3f2e632a1994a58c830..f2e94052927617def8e54d9d40a9d4c5c1043158 100644 (file)
@@ -15,5 +15,5 @@
 # Virtual provider router ID
 # provider_router_id = 00112233-0011-0011-0011-001122334455
 
-# Virtual metadata router ID
-# metadata_router_id = ffeeddcc-ffee-ffee-ffee-ffeeddccbbaa
+# Path to midonet host uuid file
+# midonet_host_uuid_path = /etc/midolman/host_uuid.properties
diff --git a/neutron/plugins/midonet/agent/__init__.py b/neutron/plugins/midonet/agent/__init__.py
new file mode 100644 (file)
index 0000000..9fddc19
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
diff --git a/neutron/plugins/midonet/agent/midonet_driver.py b/neutron/plugins/midonet/agent/midonet_driver.py
new file mode 100644 (file)
index 0000000..728a019
--- /dev/null
@@ -0,0 +1,140 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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: Rossella Sblendido, 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 neutron.agent.linux import dhcp
+from neutron.agent.linux import interface
+from neutron.agent.linux import ip_lib
+from neutron.openstack.common import log as logging
+from neutron.plugins.midonet.common import config  # noqa
+
+LOG = logging.getLogger(__name__)
+
+
+class DhcpNoOpDriver(dhcp.DhcpLocalProcess):
+
+    @classmethod
+    def existing_dhcp_networks(cls, conf, root_helper):
+        """Return a list of existing networks ids that we have configs for."""
+        return []
+
+    @classmethod
+    def check_version(cls):
+        """Execute version checks on DHCP server."""
+        return float(1.0)
+
+    def disable(self, retain_port=False):
+        """Disable DHCP for this network."""
+        if not retain_port:
+            self.device_delegate.destroy(self.network, self.interface_name)
+        self._remove_config_files()
+
+    def release_lease(self, mac_address, removed_ips):
+        pass
+
+    def reload_allocations(self):
+        """Force the DHCP server to reload the assignment database."""
+        pass
+
+    def spawn_process(self):
+        pass
+
+
+class MidonetInterfaceDriver(interface.LinuxInterfaceDriver):
+    def __init__(self, conf):
+        super(MidonetInterfaceDriver, self).__init__(conf)
+        # Read config values
+        midonet_conf = 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
+
+        self.mido_api = api.MidonetApi(midonet_uri, admin_user,
+                                       admin_pass,
+                                       project_id=admin_project_id)
+
+    def _get_host_uuid(self):
+        """Get MidoNet host id from host_uuid.properties file."""
+        f = open(cfg.CONF.MIDONET.midonet_host_uuid_path)
+        lines = f.readlines()
+        host_uuid = filter(lambda x: x.startswith('host_uuid='),
+                           lines)[0].strip()[len('host_uuid='):]
+        return host_uuid
+
+    def plug(self, network_id, port_id, device_name, mac_address,
+             bridge=None, namespace=None, prefix=None):
+        """This method is called by the Dhcp agent or by the L3 agent
+        when a new network is created
+        """
+        if not ip_lib.device_exists(device_name,
+                                    self.root_helper,
+                                    namespace=namespace):
+            ip = ip_lib.IPWrapper(self.root_helper)
+            tap_name = device_name.replace(prefix or 'tap', 'tap')
+
+            # Create ns_dev in a namespace if one is configured.
+            root_dev, ns_dev = ip.add_veth(tap_name,
+                                           device_name,
+                                           namespace2=namespace)
+
+            ns_dev.link.set_address(mac_address)
+
+            # Add an interface created by ovs to the namespace.
+            namespace_obj = ip.ensure_namespace(namespace)
+            namespace_obj.add_device_to_namespace(ns_dev)
+
+            ns_dev.link.set_up()
+            root_dev.link.set_up()
+
+            vport_id = port_id
+            host_dev_name = device_name
+
+            # create if-vport mapping.
+            host_uuid = self._get_host_uuid()
+            try:
+                host = self.mido_api.get_host(host_uuid)
+            except w_exc.HTTPError as e:
+                LOG.error(_('Failed to create a if-vport mapping on host=%s'),
+                          host_uuid)
+                raise e
+            try:
+                self.mido_api.host.add_host_interface_port(
+                    host, vport_id, host_dev_name)
+            except w_exc.HTTPError as e:
+                LOG.warn(_('Faild binding vport=%(vport) to device=%(device)'),
+                         {"vport": vport_id, "device": host_dev_name})
+        else:
+            LOG.warn(_("Device %s already exists"), device_name)
+
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+        # the port will be deleted by the dhcp agent that will call the plugin
+        device = ip_lib.IPDevice(device_name,
+                                 self.root_helper,
+                                 namespace)
+        device.link.delete()
+        LOG.debug(_("Unplugged interface '%s'"), device_name)
+
+        ip_lib.IPWrapper(
+            self.root_helper, namespace).garbage_collect_namespace()
diff --git a/neutron/plugins/midonet/common/__init__.py b/neutron/plugins/midonet/common/__init__.py
new file mode 100644 (file)
index 0000000..9fddc19
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
similarity index 91%
rename from neutron/plugins/midonet/config.py
rename to neutron/plugins/midonet/common/config.py
index dbeecd95d49ce1aab405084231f128a15cadb2ee..79e2a331162fc480a614ba4fea55ecd3a8c4d971 100644 (file)
@@ -35,12 +35,12 @@ midonet_opts = [
     cfg.StrOpt('provider_router_id',
                default=None,
                help=_('Virtual provider router ID.')),
-    cfg.StrOpt('metadata_router_id',
-               default=None,
-               help=_('Virtual metadata router ID.')),
     cfg.StrOpt('mode',
                default='dev',
-               help=_('Operational mode. Internal dev use only.'))
+               help=_('Operational mode. Internal dev use only.')),
+    cfg.StrOpt('midonet_host_uuid_path',
+               default='/etc/midolman/host_uuid.properties',
+               help=_('Path to midonet host uuid file'))
 ]
 
 
diff --git a/neutron/plugins/midonet/common/net_util.py b/neutron/plugins/midonet/common/net_util.py
new file mode 100644 (file)
index 0000000..868eed3
--- /dev/null
@@ -0,0 +1,64 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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: Ryu Ishimoto, Midokura Japan KK
+
+
+from neutron.common import constants
+
+
+def subnet_str(cidr):
+    """Convert the cidr string to x.x.x.x_y format
+
+    :param cidr: CIDR in x.x.x.x/y format
+    """
+    if cidr is None:
+        return None
+    return cidr.replace("/", "_")
+
+
+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
+
+
+def get_ethertype_value(ethertype):
+    """Convert string representation of ethertype to the numerical."""
+    if ethertype is None:
+        return None
+    mapping = {
+        'ipv4': 0x0800,
+        'ipv6': 0x86DD,
+        'arp': 0x806
+    }
+    return mapping.get(ethertype.lower())
+
+
+def get_protocol_value(protocol):
+    """Convert string representation of protocol to the numerical."""
+    if protocol is None:
+        return None
+    mapping = {
+        'tcp': constants.TCP_PROTOCOL,
+        'udp': constants.UDP_PROTOCOL,
+        'icmp': constants.ICMP_PROTOCOL
+    }
+    return mapping.get(protocol.lower())
index d24a38ed25471e53bb7cb550c9376bccc54474e8..e5f74dabbae92f9477466b9950ad70f8ddb89f51 100644 (file)
 #
 # @author: Tomoe Sugihara, Midokura Japan KK
 # @author: Ryu Ishimoto, Midokura Japan KK
+# @author: Rossella Sblendido, Midokura Japan KK
 
 
+from midonetclient import midoapi_exceptions
 from webob import exc as w_exc
 
-from neutron.common import exceptions as q_exc
+from neutron.common import exceptions as n_exc
 from neutron.openstack.common import log as logging
-
+from neutron.plugins.midonet.common import net_util
 
 LOG = logging.getLogger(__name__)
 
-PREFIX = 'OS_SG_'
-NAME_IDENTIFIABLE_PREFIX_LEN = len(PREFIX) + 36  # 36 = length of uuid
-OS_FLOATING_IP_RULE_KEY = 'OS_FLOATING_IP'
-OS_ROUTER_IN_CHAIN_NAME_FORMAT = 'OS_ROUTER_IN_%s'
-OS_ROUTER_OUT_CHAIN_NAME_FORMAT = 'OS_ROUTER_OUT_%s'
-OS_SG_KEY = 'os_sg_rule_id'
-OS_TENANT_ROUTER_RULE_KEY = 'OS_TENANT_ROUTER_RULE'
-SNAT_RULE = 'SNAT'
-SNAT_RULE_PROPERTY = {OS_TENANT_ROUTER_RULE_KEY: SNAT_RULE}
-SUFFIX_IN = '_IN'
-SUFFIX_OUT = '_OUT'
-
-
-def sg_label(sg_id, sg_name):
-    """Construct the security group ID used as chain identifier in MidoNet."""
-    return PREFIX + str(sg_id) + '_' + sg_name
-
-
-def sg_rule_properties(os_sg_rule_id):
-    return {OS_SG_KEY: str(os_sg_rule_id)}
-
-port_group_name = sg_label
-
-
-def chain_names(sg_id, sg_name):
-    """Get inbound and outbound chain names."""
-    prefix = sg_label(sg_id, sg_name)
-    in_chain_name = prefix + SUFFIX_IN
-    out_chain_name = prefix + SUFFIX_OUT
-    return {'in': in_chain_name, 'out': out_chain_name}
-
-
-def router_chain_names(router_id):
-    in_name = OS_ROUTER_IN_CHAIN_NAME_FORMAT % router_id
-    out_name = OS_ROUTER_OUT_CHAIN_NAME_FORMAT % router_id
-    return {'in': in_name, 'out': out_name}
-
 
 def handle_api_error(fn):
+    """Wrapper for methods that throws custom exceptions."""
     def wrapped(*args, **kwargs):
         try:
             return fn(*args, **kwargs)
-        except w_exc.HTTPException as ex:
+        except (w_exc.HTTPException,
+                midoapi_exceptions.MidoApiConnectionError) as ex:
             raise MidonetApiException(msg=ex)
     return wrapped
 
 
-class MidonetResourceNotFound(q_exc.NotFound):
+class MidonetResourceNotFound(n_exc.NotFound):
     message = _('MidoNet %(resource_type)s %(id)s could not be found')
 
 
-class MidonetApiException(q_exc.NeutronException):
+class MidonetApiException(n_exc.NeutronException):
     message = _("MidoNet API error: %(msg)s")
 
 
@@ -140,58 +107,80 @@ class MidoClient:
             raise MidonetResourceNotFound(resource_type='Bridge', id=id)
 
     @handle_api_error
-    def create_dhcp(self, bridge, gateway_ip, net_addr, net_len):
+    def create_dhcp(self, bridge, gateway_ip, cidr):
         """Create a new DHCP entry
 
         :param bridge: bridge object to add dhcp to
         :param gateway_ip: IP address of gateway
-        :param net_addr: network IP address
-        :param net_len: network IP address length
+        :param cidr: subnet represented as x.x.x.x/y
         :returns: newly created dhcp
         """
         LOG.debug(_("MidoClient.create_dhcp called: bridge=%(bridge)s, "
-                    "net_addr=%(net_addr)s, net_len=%(net_len)s, "
-                    "gateway_ip=%(gateway_ip)s"),
-                  {'bridge': bridge, 'net_addr': net_addr, 'net_len': net_len,
-                   'gateway_ip': gateway_ip})
+                    "cidr=%(cidr)s, gateway_ip=%(gateway_ip)s"),
+                  {'bridge': bridge, 'cidr': cidr, 'gateway_ip': gateway_ip})
+        net_addr, net_len = net_util.net_addr(cidr)
         return bridge.add_dhcp_subnet().default_gateway(
             gateway_ip).subnet_prefix(net_addr).subnet_length(
                 net_len).create()
 
     @handle_api_error
-    def create_dhcp_hosts(self, bridge, ip, mac):
-        """Create DHCP host entries
+    def add_dhcp_host(self, bridge, cidr, ip, mac):
+        """Add DHCP host entry
 
-        :param bridge: bridge of the DHCP
+        :param bridge: bridge the DHCP is configured for
+        :param cidr: subnet represented as x.x.x.x/y
         :param ip: IP address
         :param mac: MAC address
         """
-        LOG.debug(_("MidoClient.create_dhcp_hosts called: bridge=%(bridge)s, "
-                    "ip=%(ip)s, mac=%(mac)s"), {'bridge': bridge, 'ip': ip,
-                                                'mac': mac})
-        dhcp_subnets = bridge.get_dhcp_subnets()
-        if dhcp_subnets:
-            # Add the host to the first subnet as we currently support one
-            # subnet per network.
-            dhcp_subnets[0].add_dhcp_host().ip_addr(ip).mac_addr(mac).create()
+        LOG.debug(_("MidoClient.add_dhcp_host called: bridge=%(bridge)s, "
+                    "cidr=%(cidr)s, ip=%(ip)s, mac=%(mac)s"),
+                  {'bridge': bridge, 'cidr': cidr, 'ip': ip, 'mac': mac})
+        subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
+        if subnet is None:
+            raise MidonetApiException(msg=_("Tried to add to"
+                                            "non-existent DHCP"))
+
+        subnet.add_dhcp_host().ip_addr(ip).mac_addr(mac).create()
 
     @handle_api_error
-    def delete_dhcp_hosts(self, bridge_id, ip, mac):
-        """Delete DHCP host entries
+    def remove_dhcp_host(self, bridge, cidr, ip, mac):
+        """Remove DHCP host entry
+
+        :param bridge: bridge the DHCP is configured for
+        :param cidr: subnet represented as x.x.x.x/y
+        :param ip: IP address
+        :param mac: MAC address
+        """
+        LOG.debug(_("MidoClient.remove_dhcp_host called: bridge=%(bridge)s, "
+                    "cidr=%(cidr)s, ip=%(ip)s, mac=%(mac)s"),
+                  {'bridge': bridge, 'cidr': cidr, 'ip': ip, 'mac': mac})
+        subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
+        if subnet is None:
+            LOG.warn(_("Tried to delete mapping from non-existent subnet"))
+            return
+
+        for dh in subnet.get_dhcp_hosts():
+            if dh.get_mac_addr() == mac and dh.get_ip_addr() == ip:
+                LOG.debug(_("MidoClient.remove_dhcp_host: Deleting %(dh)r"),
+                          {"dh": dh})
+                dh.delete()
+
+    @handle_api_error
+    def delete_dhcp_host(self, bridge_id, cidr, ip, mac):
+        """Delete DHCP host entry
 
         :param bridge_id: id of the bridge of the DHCP
+        :param cidr: subnet represented as x.x.x.x/y
         :param ip: IP address
         :param mac: MAC address
         """
-        LOG.debug(_("MidoClient.delete_dhcp_hosts called: "
-                    "bridge_id=%(bridge_id)s, ip=%(ip)s, mac=%(mac)s"),
-                  {'bridge_id': bridge_id, 'ip': ip, 'mac': mac})
+        LOG.debug(_("MidoClient.delete_dhcp_host called: "
+                    "bridge_id=%(bridge_id)s, cidr=%(cidr)s, ip=%(ip)s, "
+                    "mac=%(mac)s"), {'bridge_id': bridge_id,
+                                     'cidr': cidr,
+                                     'ip': ip, 'mac': mac})
         bridge = self.get_bridge(bridge_id)
-        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.remove_dhcp_host(bridge, net_util.subnet_str(cidr), ip, mac)
 
     @handle_api_error
     def delete_dhcp(self, bridge):
@@ -207,12 +196,17 @@ class MidoClient:
         dhcp[0].delete()
 
     @handle_api_error
-    def delete_port(self, id):
+    def delete_port(self, id, delete_chains=False):
         """Delete a port
 
         :param id: id of the port
         """
-        LOG.debug(_("MidoClient.delete_port called: id=%(id)s"), {'id': id})
+        LOG.debug(_("MidoClient.delete_port called: id=%(id)s, "
+                    "delete_chains=%(delete_chains)s"),
+                  {'id': id, 'delete_chains': delete_chains})
+        if delete_chains:
+            self.delete_port_chains(id)
+
         self.mido_api.delete_port(id)
 
     @handle_api_error
@@ -229,26 +223,24 @@ class MidoClient:
             raise MidonetResourceNotFound(resource_type='Port', id=id)
 
     @handle_api_error
-    def create_exterior_bridge_port(self, bridge):
-        """Create a new exterior bridge port
+    def add_bridge_port(self, bridge):
+        """Add a port on a bridge
 
-        :param bridge: bridge object to add port to
+        :param bridge: Bridge to add a new port to
         :returns: newly created port
         """
-        LOG.debug(_("MidoClient.create_exterior_bridge_port called: "
+        LOG.debug(_("MidoClient.add_bridge_port called: "
                     "bridge=%(bridge)s"), {'bridge': bridge})
-        return bridge.add_exterior_port().create()
+        return self.mido_api.add_bridge_port(bridge)
 
     @handle_api_error
-    def create_interior_bridge_port(self, bridge):
-        """Create a new interior bridge port
-
-        :param bridge: bridge object to add port to
-        :returns: newly created port
-        """
-        LOG.debug(_("MidoClient.create_interior_bridge_port called: "
-                    "bridge=%(bridge)s"), {'bridge': bridge})
-        return bridge.add_interior_port().create()
+    def add_router_port(self, router, port_address=None,
+                        network_address=None, network_length=None):
+        """Add a new port to an existing router."""
+        return self.mido_api.add_router_port(router,
+                                             port_address=port_address,
+                                             network_address=network_address,
+                                             network_length=network_length)
 
     @handle_api_error
     def create_router(self, tenant_id, name):
@@ -264,40 +256,6 @@ class MidoClient:
         return self.mido_api.add_router().name(name).tenant_id(
             tenant_id).create()
 
-    @handle_api_error
-    def create_tenant_router(self, tenant_id, name, metadata_router):
-        """Create a new tenant router
-
-        :param tenant_id: id of tenant creating the router
-        :param name: name of the router
-        :param metadata_router: metadata router
-        :returns: newly created router
-        """
-        LOG.debug(_("MidoClient.create_tenant_router called: "
-                    "tenant_id=%(tenant_id)s, name=%(name)s,"
-                    " metadata_router=%(metadata_router)s"),
-                  {'tenant_id': tenant_id, 'name': name,
-                   'metadata_router': metadata_router})
-        router = self.create_router(tenant_id, name)
-        self.link_router_to_metadata_router(router, metadata_router)
-        return router
-
-    @handle_api_error
-    def delete_tenant_router(self, id, metadata_router):
-        """Delete a tenant router
-
-        :param id: id of router
-        :param metadata_router: metadata router
-        """
-        LOG.debug(_("MidoClient.delete_tenant_router called: "
-                    "id=%(id)s, metadata_router=%(metadata_router)s"),
-                  {'id': id, 'metadata_router': metadata_router})
-        self.unlink_router_from_metadata_router(id, metadata_router)
-        self.destroy_router_chains(id)
-
-        # delete the router
-        self.delete_router(id)
-
     @handle_api_error
     def delete_router(self, id):
         """Delete a router
@@ -336,640 +294,356 @@ class MidoClient:
             raise MidonetResourceNotFound(resource_type='Router', id=id)
 
     @handle_api_error
-    def link_bridge_port_to_router(self, port_id, router_id, gateway_ip,
-                                   net_addr, net_len, metadata_router):
-        """Link a tenant bridge port to the router
-
-        :param port_id: port ID
-        :param router_id: router id to link to
-        :param gateway_ip: IP address of gateway
-        :param net_addr: network IP address
-        :param net_len: network IP address length
-        :param metadata_router: metadata router instance
-        """
-        LOG.debug(_("MidoClient.link_bridge_port_to_router called: "
-                    "port_id=%(port_id)s, router_id=%(router_id)s, "
-                    "gateway_ip=%(gateway_ip)s net_addr=%(net_addr)s, "
-                    "net_len=%(net_len)s, "
-                    "metadata_router=%(metadata_router)s"),
-                  {'port_id': port_id, 'router_id': router_id,
-                   'gateway_ip': gateway_ip, 'net_addr': net_addr,
-                   'net_len': net_len, 'metadata_router': metadata_router})
-        router = self.get_router(router_id)
-
-        # create an interior port on the router
-        in_port = router.add_interior_port()
-        router_port = in_port.port_address(gateway_ip).network_address(
-            net_addr).network_length(net_len).create()
-
-        br_port = self.get_port(port_id)
-        router_port.link(br_port.get_id())
-
-        # add a route for the subnet in the provider router
-        router.add_route().type('Normal').src_network_addr(
-            '0.0.0.0').src_network_length(0).dst_network_addr(
-                net_addr).dst_network_length(net_len).weight(
-                    100).next_hop_port(router_port.get_id()).create()
-
-        # add a route for the subnet in metadata router; forward
-        # packets destined to the subnet to the tenant router
-        for pp in metadata_router.get_peer_ports():
-            if pp.get_device_id() == router.get_id():
-                mdr_port_id = pp.get_peer_id()
-                break
-        else:
-            raise Exception(
-                _("Couldn't find a md router port for the router=%r"), router)
-
-        metadata_router.add_route().type('Normal').src_network_addr(
-            '0.0.0.0').src_network_length(0).dst_network_addr(
-                net_addr).dst_network_length(net_len).weight(
-                    100).next_hop_port(mdr_port_id).create()
+    def delete_route(self, id):
+        return self.mido_api.delete_route(id)
 
     @handle_api_error
-    def unlink_bridge_port_from_router(self, port_id, net_addr, net_len,
-                                       metadata_router):
-        """Unlink a tenant bridge port from the router
+    def add_dhcp_route_option(self, bridge, cidr, gw_ip, dst_ip):
+        """Add Option121 route to subnet
 
-        :param bridge_id: bridge ID
-        :param net_addr: network IP address
-        :param net_len: network IP address length
-        :param metadata_router: metadata router instance
+        :param bridge: Bridge to add the option route to
+        :param cidr: subnet represented as x.x.x.x/y
+        :param gw_ip: IP address of the next hop
+        :param dst_ip: IP address of the destination, in x.x.x.x/y format
         """
-        LOG.debug(_("MidoClient.unlink_bridge_port_from_router called: "
-                    "port_id=%(port_id)s, net_addr=%(net_addr)s, "
-                    "net_len=%(net_len)s, "
-                    "metadata_router=%(metadata_router)s"),
-                  {'port_id': port_id, 'net_addr': net_addr,
-                   'net_len': net_len, 'metadata_router': metadata_router})
-        port = self.get_port(port_id)
-        port.unlink()
-        self.delete_port(port.get_peer_id())
-        self.delete_port(port.get_id())
-
-        # delete the route for the subnet in the metadata router
-        for r in metadata_router.get_routes():
-            if (r.get_dst_network_addr() == net_addr and
-                r.get_dst_network_length() == net_len):
-                LOG.debug(_('Deleting route=%r ...'), r)
-                self.mido_api.delete_route(r.get_id())
-                break
+        LOG.debug(_("MidoClient.add_dhcp_route_option called: "
+                    "bridge=%(bridge)s, cidr=%(cidr)s, gw_ip=%(gw_ip)s"
+                    "dst_ip=%(dst_ip)s"),
+                  {"bridge": bridge, "cidr": cidr, "gw_ip": gw_ip,
+                   "dst_ip": dst_ip})
+        subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
+        if subnet is None:
+            raise MidonetApiException(
+                msg=_("Tried to access non-existent DHCP"))
+        prefix, length = dst_ip.split("/")
+        routes = [{'destinationPrefix': prefix, 'destinationLength': length,
+                   'gatewayAddr': gw_ip}]
+        subnet.opt121_routes(routes).update()
 
     @handle_api_error
-    def link_bridge_to_provider_router(self, bridge, provider_router,
-                                       gateway_ip, net_addr, net_len):
-        """Link a tenant bridge to the provider router
-
-        :param bridge: tenant bridge
-        :param provider_router: provider router to link to
-        :param gateway_ip: IP address of gateway
-        :param net_addr: network IP address
-        :param net_len: network IP address length
-        """
-        LOG.debug(_("MidoClient.link_bridge_to_provider_router called: "
-                    "bridge=%(bridge)s, provider_router=%(provider_router)s, "
-                    "gateway_ip=%(gateway_ip)s, net_addr=%(net_addr)s, "
-                    "net_len=%(net_len)s"),
-                  {'bridge': bridge, 'provider_router': provider_router,
-                   'gateway_ip': gateway_ip, 'net_addr': net_addr,
-                   'net_len': net_len})
-        # create an interior port on the provider router
-        in_port = provider_router.add_interior_port()
-        pr_port = in_port.port_address(gateway_ip).network_address(
-            net_addr).network_length(net_len).create()
-
-        # create an interior bridge port, then link it to the 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
-        provider_router.add_route().type('Normal').src_network_addr(
-            '0.0.0.0').src_network_length(0).dst_network_addr(
-                net_addr).dst_network_length(net_len).weight(
-                    100).next_hop_port(pr_port.get_id()).create()
-
-    @handle_api_error
-    def unlink_bridge_from_provider_router(self, bridge, provider_router):
-        """Unlink a tenant bridge from the provider router
-
-        :param bridge: tenant bridge
-        :param provider_router: provider router to link to
-        """
-        LOG.debug(_("MidoClient.unlink_bridge_from_provider_router called: "
-                    "bridge=%(bridge)s, provider_router=%(provider_router)s"),
-                  {'bridge': bridge, 'provider_router': provider_router})
-        # Delete routes and unlink the router and the bridge.
-        routes = provider_router.get_routes()
-
-        bridge_ports_to_delete = [
-            p for p in provider_router.get_peer_ports()
-            if p.get_device_id() == bridge.get_id()]
-
-        for p in bridge.get_peer_ports():
-            if p.get_device_id() == provider_router.get_id():
-                # delete the routes going to the bridge
-                for r in routes:
-                    if r.get_next_hop_port() == p.get_id():
-                        self.mido_api.delete_route(r.get_id())
-                p.unlink()
-                self.mido_api.delete_port(p.get_id())
-
-        # delete bridge port
-        for port in bridge_ports_to_delete:
-            self.mido_api.delete_port(port.get_id())
-
-    @handle_api_error
-    def set_router_external_gateway(self, id, provider_router, snat_ip):
-        """Set router external gateway
-
-        :param ID: ID of the tenant router
-        :param provider_router: provider router
-        :param snat_ip: SNAT IP address
-        """
-        LOG.debug(_("MidoClient.set_router_external_gateway called: "
-                    "id=%(id)s, provider_router=%(provider_router)s, "
-                    "snat_ip=%(snat_ip)s)"),
-                  {'id': id, 'provider_router': provider_router,
-                   'snat_ip': snat_ip})
-        tenant_router = self.get_router(id)
-
-        # Create a interior port in the provider router
-        in_port = 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
-        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.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()
+    def link(self, port, peer_id):
+        """Link a port to a given peerId."""
+        self.mido_api.link(port, peer_id)
 
     @handle_api_error
-    def clear_router_external_gateway(self, id):
-        """Clear router external gateway
+    def delete_port_routes(self, routes, port_id):
+        """Remove routes whose next hop port is the given port ID."""
+        for route in routes:
+            if route.get_next_hop_port() == port_id:
+                self.mido_api.delete_route(route.get_id())
 
-        :param ID: ID of the tenant router
-        """
-        LOG.debug(_("MidoClient.clear_router_external_gateway called: "
-                    "id=%(id)s"), {'id': id})
-        tenant_router = self.get_router(id)
-
-        # 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.delete_port(peer_port_id)
-                self.mido_api.delete_port(p.get_id())
-
-        # 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):
-                self.mido_api.delete_route(r.get_id())
-
-        # delete SNAT(masquerade) rules
-        chains = self.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:
-                    self.mido_api.delete_rule(r.get_id())
-
-        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:
-                    self.mido_api.delete_rule(r.get_id())
+    @handle_api_error
+    def get_router_routes(self, router_id):
+        """Get all routes for the given router."""
+        return self.mido_api.get_router_routes(router_id)
 
     @handle_api_error
-    def get_router_chains(self, tenant_id, router_id):
-        """Get router chains.
+    def unlink(self, port):
+        """Unlink a port
 
-        Returns a dictionary that has in/out chain resources key'ed with 'in'
-        and 'out' respectively, given the tenant_id and the router_id passed
-        in in the arguments.
+        :param port: port object
         """
-        LOG.debug(_("MidoClient.get_router_chains called: "
-                    "tenant_id=%(tenant_id)s router_id=%(router_id)s"),
-                  {'tenant_id': tenant_id, 'router_id': router_id})
-
-        chain_names = router_chain_names(router_id)
-        chains = {}
-        for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
-            if c.get_name() == chain_names['in']:
-                chains['in'] = c
-            elif c.get_name() == chain_names['out']:
-                chains['out'] = c
-        return chains
+        LOG.debug(_("MidoClient.unlink called: port=%(port)s"),
+                  {'port': port})
+        if port.get_peer_id():
+            self.mido_api.unlink(port)
+        else:
+            LOG.warn(_("Attempted to unlink a port that was not linked. %s"),
+                     port.get_id())
+
+    @handle_api_error
+    def remove_rules_by_property(self, tenant_id, chain_name, key, value):
+        """Remove all the rules that match the provided key and value."""
+        LOG.debug(_("MidoClient.remove_rules_by_property called: "
+                    "tenant_id=%(tenant_id)s, chain_name=%(chain_name)s"
+                    "key=%(key)s, value=%(value)s"),
+                  {'tenant_id': tenant_id, 'chain_name': chain_name,
+                   'key': key, 'value': value})
+        chain = self.get_chain_by_name(tenant_id, chain_name)
+        if chain is None:
+            raise MidonetResourceNotFound(resource_type='Chain',
+                                          id=chain_name)
+
+        for r in chain.get_rules():
+            if key in r.get_properties():
+                if r.get_properties()[key] == value:
+                    self.mido_api.delete_rule(r.get_id())
 
     @handle_api_error
-    def create_router_chains(self, router):
+    def add_router_chains(self, router, inbound_chain_name,
+                          outbound_chain_name):
         """Create chains for a new router.
 
-        Creates chains for the router and returns the same dictionary as
-        get_router_chains() returns.
+        Creates inbound and outbound chains for the router with the given
+        names, and the new chains are set on the router.
 
         :param router: router to set chains for
+        :param inbound_chain_name: Name of the inbound chain
+        :param outbound_chain_name: Name of the outbound chain
         """
         LOG.debug(_("MidoClient.create_router_chains called: "
-                    "router=%(router)s"), {'router': router})
-        chains = {}
-        router_id = router.get_id()
+                    "router=%(router)s, inbound_chain_name=%(in_chain)s, "
+                    "outbound_chain_name=%(out_chain)s"),
+                  {"router": router, "in_chain": inbound_chain_name,
+                   "out_chain": outbound_chain_name})
         tenant_id = router.get_tenant_id()
-        chain_names = router_chain_names(router_id)
-        chains['in'] = self.mido_api.add_chain().tenant_id(tenant_id).name(
-            chain_names['in']).create()
 
-        chains['out'] = self.mido_api.add_chain().tenant_id(tenant_id).name(
-            chain_names['out']).create()
+        inbound_chain = self.mido_api.add_chain().tenant_id(tenant_id).name(
+            inbound_chain_name,).create()
+        outbound_chain = self.mido_api.add_chain().tenant_id(tenant_id).name(
+            outbound_chain_name).create()
 
         # set chains to in/out filters
-        router.inbound_filter_id(
-            chains['in'].get_id()).outbound_filter_id(
-                chains['out'].get_id()).update()
-        return chains
+        router.inbound_filter_id(inbound_chain.get_id()).outbound_filter_id(
+            outbound_chain.get_id()).update()
+        return inbound_chain, outbound_chain
 
     @handle_api_error
-    def destroy_router_chains(self, id):
+    def delete_router_chains(self, id):
         """Deletes chains of a router.
 
         :param id: router ID to delete chains of
         """
-        LOG.debug(_("MidoClient.destroy_router_chains called: "
+        LOG.debug(_("MidoClient.delete_router_chains called: "
                     "id=%(id)s"), {'id': id})
-        # delete corresponding chains
         router = self.get_router(id)
-        chains = self.get_router_chains(router.get_tenant_id(), id)
-        self.mido_api.delete_chain(chains['in'].get_id())
-        self.mido_api.delete_chain(chains['out'].get_id())
+        if (router.get_inbound_filter_id()):
+            self.mido_api.delete_chain(router.get_inbound_filter_id())
+
+        if (router.get_outbound_filter_id()):
+            self.mido_api.delete_chain(router.get_outbound_filter_id())
 
     @handle_api_error
-    def link_router_to_metadata_router(self, router, metadata_router):
-        """Link a router to the metadata router
+    def delete_port_chains(self, id):
+        """Deletes chains of a port.
 
-        :param router: router to link
-        :param metadata_router: metadata router
-        """
-        LOG.debug(_("MidoClient.link_router_to_metadata_router called: "
-                    "router=%(router)s, metadata_router=%(metadata_router)s"),
-                  {'router': router, 'metadata_router': metadata_router})
-        # link to metadata router
-        in_port = 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 = router.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
-        router.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()
-
-    @handle_api_error
-    def unlink_router_from_metadata_router(self, id, metadata_router):
-        """Unlink a router from the metadata router
-
-        :param id: ID of router
-        :param metadata_router: metadata router
+        :param id: port ID to delete chains of
         """
-        LOG.debug(_("MidoClient.unlink_router_from_metadata_router called: "
-                    "id=%(id)s, metadata_router=%(metadata_router)s"),
-                  {'id': id, 'metadata_router': metadata_router})
-        # unlink from metadata router and delete the interior ports
-        # that connect metadata router and this router.
-        for pp in metadata_router.get_peer_ports():
-            if pp.get_device_id() == id:
-                mdr_port = self.get_port(pp.get_peer_id())
-                pp.unlink()
-                self.mido_api.delete_port(pp.get_id())
-                self.mido_api.delete_port(mdr_port.get_id())
-
-    @handle_api_error
-    def setup_floating_ip(self, router_id, provider_router, floating_ip,
-                          fixed_ip, identifier):
-        """Setup MidoNet for floating IP
-
-        :param router_id: router_id
-        :param provider_router: provider router
-        :param floating_ip: floating IP address
-        :param fixed_ip: fixed IP address
-        :param identifier: identifier to use to map to MidoNet
-        """
-        LOG.debug(_("MidoClient.setup_floating_ip called: "
-                    "router_id=%(router_id)s, "
-                    "provider_router=%(provider_router)s"
-                    "floating_ip=%(floating_ip)s, fixed_ip=%(fixed_ip)s"
-                    "identifier=%(identifier)s"),
-                  {'router_id': router_id, 'provider_router': provider_router,
-                   'floating_ip': floating_ip, 'fixed_ip': fixed_ip,
-                   'identifier': identifier})
-        # unlink from metadata router and delete the interior ports
-        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 router.get_peer_ports():
-            if p.get_device_id() == 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
-        provider_router.add_route().type(
-            'Normal').src_network_addr('0.0.0.0').src_network_length(
-                0).dst_network_addr(
-                    floating_ip).dst_network_length(
-                        32).weight(100).next_hop_port(
-                            pr_port.get_id()).create()
+        LOG.debug(_("MidoClient.delete_port_chains called: "
+                    "id=%(id)s"), {'id': id})
+        port = self.get_port(id)
+        if (port.get_inbound_filter_id()):
+            self.mido_api.delete_chain(port.get_inbound_filter_id())
 
-        chains = self.get_router_chains(router.get_tenant_id(), router_id)
+        if (port.get_outbound_filter_id()):
+            self.mido_api.delete_chain(port.get_outbound_filter_id())
 
-        # add dnat/snat rule pair for the floating ip
+    @handle_api_error
+    def get_link_port(self, router, peer_router_id):
+        """Setup a route on the router to the next hop router."""
+        LOG.debug(_("MidoClient.get_link_port called: "
+                    "router=%(router)s, peer_router_id=%(peer_router_id)s"),
+                  {'router': router, 'peer_router_id': peer_router_id})
+        # Find the port linked between the two routers
+        link_port = None
+        for p in router.get_peer_ports():
+            if p.get_device_id() == peer_router_id:
+                link_port = p
+                break
+        return link_port
+
+    @handle_api_error
+    def add_router_route(self, router, type='Normal',
+                         src_network_addr=None, src_network_length=None,
+                         dst_network_addr=None, dst_network_length=None,
+                         next_hop_port=None, next_hop_gateway=None,
+                         weight=100):
+        """Setup a route on the router."""
+        return self.mido_api.add_router_route(
+            router, type=type, src_network_addr=src_network_addr,
+            src_network_length=src_network_length,
+            dst_network_addr=dst_network_addr,
+            dst_network_length=dst_network_length,
+            next_hop_port=next_hop_port, next_hop_gateway=next_hop_gateway,
+            weight=weight)
+
+    @handle_api_error
+    def add_static_nat(self, tenant_id, chain_name, from_ip, to_ip, port_id,
+                       nat_type='dnat', **kwargs):
+        """Add a static NAT entry
+
+        :param tenant_id: owner fo the chain to add a NAT to
+        :param chain_name: name of the chain to add a NAT to
+        :param from_ip: IP to translate from
+        :param from_ip: IP to translate from
+        :param to_ip: IP to translate to
+        :param port_id: port to match on
+        :param nat_type: 'dnat' or 'snat'
+        """
+        LOG.debug(_("MidoClient.add_static_nat called: "
+                    "tenant_id=%(tenant_id)s, chain_name=%(chain_name)s, "
+                    "from_ip=%(from_ip)s, to_ip=%(to_ip)s, "
+                    "port_id=%(port_id)s, nat_type=%(nat_type)s"),
+                  {'tenant_id': tenant_id, 'chain_name': chain_name,
+                   'from_ip': from_ip, 'to_ip': to_ip,
+                   'portid': port_id, 'nat_type': nat_type})
+        if nat_type not in ['dnat', 'snat']:
+            raise ValueError(_("Invalid NAT type passed in %s") % nat_type)
+
+        chain = self.get_chain_by_name(tenant_id, chain_name)
         nat_targets = []
         nat_targets.append(
-            {'addressFrom': fixed_ip, 'addressTo': fixed_ip,
+            {'addressFrom': to_ip, 'addressTo': to_ip,
              'portFrom': 0, 'portTo': 0})
 
-        floating_property = {OS_FLOATING_IP_RULE_KEY: identifier}
-        chains['in'].add_rule().nw_dst_address(
-            floating_ip).nw_dst_length(32).type(
-                'dnat').flow_action('accept').nat_targets(
-                    nat_targets).in_ports([tr_port_id]).position(
-                        1).properties(floating_property).create()
+        rule = chain.add_rule().type(nat_type).flow_action('accept').position(
+            1).nat_targets(nat_targets).properties(kwargs)
 
-        nat_targets = []
-        nat_targets.append(
-            {'addressFrom': floating_ip, 'addressTo': floating_ip,
-             'portFrom': 0, 'portTo': 0})
+        if nat_type == 'dnat':
+            rule = rule.nw_dst_address(from_ip).nw_dst_length(32).in_ports(
+                [port_id])
+        else:
+            rule = rule.nw_src_address(from_ip).nw_src_length(32).out_ports(
+                [port_id])
 
-        chains['out'].add_rule().nw_src_address(
-            fixed_ip).nw_src_length(32).type(
-                'snat').flow_action('accept').nat_targets(
-                    nat_targets).out_ports(
-                        [tr_port_id]).position(1).properties(
-                            floating_property).create()
+        return rule.create()
 
     @handle_api_error
-    def clear_floating_ip(self, router_id, provider_router, floating_ip,
-                          identifier):
-        """Remove floating IP
+    def add_dynamic_snat(self, tenant_id, pre_chain_name, post_chain_name,
+                         snat_ip, port_id, **kwargs):
+        """Add SNAT masquerading rule
 
-        :param router_id: router_id
-        :param provider_router: provider router
-        :param floating_ip: floating IP address
-        :param identifier: identifier to use to map to MidoNet
+        MidoNet requires two rules on the router, one to do NAT to a range of
+        ports, and another to retrieve back the original IP in the return
+        flow.
         """
-        LOG.debug(_("MidoClient.clear_floating_ip called: "
-                    "router_id=%(router_id)s, "
-                    "provider_router=%(provider_router)s"
-                    "floating_ip=%(floating_ip)s, identifier=%(identifier)s"),
-                  {'router_id': router_id, 'provider_router': provider_router,
-                   'floating_ip': floating_ip, 'identifier': identifier})
-        router = self.mido_api.get_router(router_id)
-
-        # find the provider router port that is connected to the tenant
-        # delete the route for this floating ip
-        for r in provider_router.get_routes():
-            if (r.get_dst_network_addr() == floating_ip and
-                    r.get_dst_network_length() == 32):
-                self.mido_api.delete_route(r.get_id())
+        pre_chain = self.get_chain_by_name(tenant_id, pre_chain_name)
+        post_chain = self.get_chain_by_name(tenant_id, post_chain_name)
 
-        # delete snat/dnat rule pair for this floating ip
-        chains = self.get_router_chains(router.get_tenant_id(), router_id)
+        pre_chain.add_rule().nw_dst_address(snat_ip).nw_dst_length(
+            32).type('rev_snat').flow_action('accept').in_ports(
+                [port_id]).properties(kwargs).position(1).create()
 
-        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] == identifier:
-                    LOG.debug(_('deleting rule=%r'), r)
-                    self.mido_api.delete_rule(r.get_id())
-                    break
+        nat_targets = []
+        nat_targets.append(
+            {'addressFrom': snat_ip, 'addressTo': snat_ip,
+             'portFrom': 1, 'portTo': 65535})
 
-        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] == identifier:
-                    LOG.debug(_('deleting rule=%r'), r)
-                    self.mido_api.delete_rule(r.get_id())
-                    break
+        post_chain.add_rule().type('snat').flow_action(
+            'accept').nat_targets(nat_targets).out_ports(
+                [port_id]).properties(kwargs).position(1).create()
 
     @handle_api_error
-    def create_for_sg(self, tenant_id, sg_id, sg_name):
-        """Create a new chain for security group.
+    def remove_static_route(self, router, ip):
+        """Remove static route for the IP
 
-        Creating a security group creates a pair of chains in MidoNet, one for
-        inbound and the other for outbound.
+        :param router: next hop router to remove the routes to
+        :param ip: IP address of the route to remove
         """
-        LOG.debug(_("MidoClient.create_for_sg called: "
-                    "tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
-                    "sg_name=%(sg_name)s "),
-                  {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
-
-        cnames = chain_names(sg_id, sg_name)
-        self.mido_api.add_chain().tenant_id(tenant_id).name(
-            cnames['in']).create()
-        self.mido_api.add_chain().tenant_id(tenant_id).name(
-            cnames['out']).create()
+        LOG.debug(_("MidoClient.remote_static_route called: "
+                    "router=%(router)s, ip=%(ip)s"),
+                  {'router': router, 'ip': ip})
+        for r in router.get_routes():
+            if (r.get_dst_network_addr() == ip and
+                    r.get_dst_network_length() == 32):
+                self.mido_api.delete_route(r.get_id())
 
-        pg_name = port_group_name(sg_id, sg_name)
-        self.mido_api.add_port_group().tenant_id(tenant_id).name(
-            pg_name).create()
+    @handle_api_error
+    def update_port_chains(self, port, inbound_chain_id, outbound_chain_id):
+        """Bind inbound and outbound chains to the port."""
+        LOG.debug(_("MidoClient.update_port_chains called: port=%(port)s"
+                    "inbound_chain_id=%(inbound_chain_id)s, "
+                    "outbound_chain_id=%(outbound_chain_id)s"),
+                  {"port": port, "inbound_chain_id": inbound_chain_id,
+                   "outbound_chain_id": outbound_chain_id})
+        port.inbound_filter_id(inbound_chain_id).outbound_filter_id(
+            outbound_chain_id).update()
 
     @handle_api_error
-    def delete_for_sg(self, tenant_id, sg_id, sg_name):
-        """Delete a chain mapped to a security group.
+    def create_chain(self, tenant_id, name):
+        """Create a new chain."""
+        LOG.debug(_("MidoClient.create_chain called: tenant_id=%(tenant_id)s "
+                    " name=%(name)s"), {"tenant_id": tenant_id, "name": name})
+        return self.mido_api.add_chain().tenant_id(tenant_id).name(
+            name).create()
 
-        Delete a SG means deleting all the chains (inbound and outbound)
-        associated with the SG in MidoNet.
-        """
-        LOG.debug(_("MidoClient.delete_for_sg called: "
-                    "tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
-                    "sg_name=%(sg_name)s "),
-                  {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
+    @handle_api_error
+    def delete_chain(self, id):
+        """Delete chain matching the ID."""
+        LOG.debug(_("MidoClient.delete_chain called: id=%(id)s"), {"id": id})
+        self.mido_api.delete_chain(id)
 
-        cnames = chain_names(sg_id, sg_name)
+    @handle_api_error
+    def delete_chains_by_names(self, tenant_id, names):
+        """Delete chains matching the names given for a tenant."""
+        LOG.debug(_("MidoClient.delete_chains_by_names called: "
+                    "tenant_id=%(tenant_id)s names=%(names)s "),
+                  {"tenant_id": tenant_id, "names": names})
         chains = self.mido_api.get_chains({'tenant_id': tenant_id})
         for c in chains:
-            if c.get_name() == cnames['in'] or c.get_name() == cnames['out']:
-                LOG.debug(_('MidoClient.delete_for_sg: deleting chain=%r'),
-                          c.get_id())
+            if c.get_name() in names:
                 self.mido_api.delete_chain(c.get_id())
 
-        pg_name = port_group_name(sg_id, sg_name)
+    @handle_api_error
+    def get_chain_by_name(self, tenant_id, name):
+        """Get the chain by its name."""
+        LOG.debug(_("MidoClient.get_chain_by_name called: "
+                    "tenant_id=%(tenant_id)s name=%(name)s "),
+                  {"tenant_id": tenant_id, "name": name})
+        for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
+            if c.get_name() == name:
+                return c
+        return None
+
+    @handle_api_error
+    def get_port_group_by_name(self, tenant_id, name):
+        """Get the port group by name."""
+        LOG.debug(_("MidoClient.get_port_group_by_name called: "
+                    "tenant_id=%(tenant_id)s name=%(name)s "),
+                  {"tenant_id": tenant_id, "name": name})
+        for p in self.mido_api.get_port_groups({'tenant_id': tenant_id}):
+            if p.get_name() == name:
+                return p
+        return None
+
+    @handle_api_error
+    def create_port_group(self, tenant_id, name):
+        """Create a port group
+
+        Create a new port group for a given name and ID.
+        """
+        LOG.debug(_("MidoClient.create_port_group called: "
+                    "tenant_id=%(tenant_id)s name=%(name)s"),
+                  {"tenant_id": tenant_id, "name": name})
+        return self.mido_api.add_port_group().tenant_id(tenant_id).name(
+            name).create()
+
+    @handle_api_error
+    def delete_port_group_by_name(self, tenant_id, name):
+        """Delete port group matching the name given for a tenant."""
+        LOG.debug(_("MidoClient.delete_port_group_by_name called: "
+                    "tenant_id=%(tenant_id)s name=%(name)s "),
+                  {"tenant_id": tenant_id, "name": name})
         pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id})
         for pg in pgs:
-            if pg.get_name() == pg_name:
-                LOG.debug(_("MidoClient.delete_for_sg: deleting pg=%r"),
-                          pg)
+            if pg.get_name() == name:
+                LOG.debug(_("Deleting pg %(id)s"), {"id": pg.get_id()})
                 self.mido_api.delete_port_group(pg.get_id())
 
     @handle_api_error
-    def get_sg_chains(self, tenant_id, sg_id):
-        """Get a list of chains mapped to a security group."""
-        LOG.debug(_("MidoClient.get_sg_chains called: "
-                    "tenant_id=%(tenant_id)s sg_id=%(sg_id)s"),
-                  {'tenant_id': tenant_id, 'sg_id': sg_id})
+    def add_port_to_port_group_by_name(self, tenant_id, name, port_id):
+        """Add a port to a port group with the given name."""
+        LOG.debug(_("MidoClient.add_port_to_port_group_by_name called: "
+                    "tenant_id=%(tenant_id)s name=%(name)s "
+                    "port_id=%(port_id)s"),
+                  {"tenant_id": tenant_id, "name": name, "port_id": port_id})
+        pg = self.get_port_group_by_name(tenant_id, name)
+        if pg is None:
+            raise MidonetResourceNotFound(resource_type='PortGroup', id=name)
 
-        cnames = chain_names(sg_id, sg_name='')
-        chain_name_prefix_for_id = cnames['in'][:NAME_IDENTIFIABLE_PREFIX_LEN]
-        chains = {}
+        pg = pg.add_port_group_port().port_id(port_id).create()
+        return pg
 
-        for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
-            if c.get_name().startswith(chain_name_prefix_for_id):
-                if c.get_name().endswith(SUFFIX_IN):
-                    chains['in'] = c
-                if c.get_name().endswith(SUFFIX_OUT):
-                    chains['out'] = c
-        assert 'in' in chains
-        assert 'out' in chains
-        return chains
-
-    @handle_api_error
-    def get_port_groups_for_sg(self, tenant_id, sg_id):
-        LOG.debug(_("MidoClient.get_port_groups_for_sg called: "
-                    "tenant_id=%(tenant_id)s sg_id=%(sg_id)s"),
-                  {'tenant_id': tenant_id, 'sg_id': sg_id})
-
-        pg_name_prefix = port_group_name(
-            sg_id, sg_name='')[:NAME_IDENTIFIABLE_PREFIX_LEN]
-        port_groups = self.mido_api.get_port_groups({'tenant_id': tenant_id})
-        for pg in port_groups:
-            if pg.get_name().startswith(pg_name_prefix):
-                LOG.debug(_(
-                    "MidoClient.get_port_groups_for_sg exiting: pg=%r"), pg)
-                return pg
-        return None
+    @handle_api_error
+    def remove_port_from_port_groups(self, port_id):
+        """Remove a port binding from all the port groups."""
+        LOG.debug(_("MidoClient.remove_port_from_port_groups called: "
+                    "port_id=%(port_id)s"), {"port_id": port_id})
+        port = self.get_port(port_id)
+        for pg in port.get_port_groups():
+            pg.delete()
 
     @handle_api_error
-    def create_for_sg_rule(self, rule):
-        LOG.debug(_("MidoClient.create_for_sg_rule called: rule=%r"), rule)
-
-        direction = rule['direction']
-        protocol = rule['protocol']
-        port_range_max = rule['port_range_max']
-        rule_id = rule['id']
-        security_group_id = rule['security_group_id']
-        remote_group_id = rule['remote_group_id']
-        remote_ip_prefix = rule['remote_ip_prefix']  # watch out. not validated
-        tenant_id = rule['tenant_id']
-        port_range_min = rule['port_range_min']
-
-        # construct a corresponding rule
-        tp_src_start = tp_src_end = None
-        tp_dst_start = tp_dst_end = None
-        nw_src_address = None
-        nw_src_length = None
-        port_group_id = None
-
-        # handle source
-        if remote_ip_prefix is not None:
-            nw_src_address, nw_src_length = remote_ip_prefix.split('/')
-        elif remote_group_id is not None:  # security group as a source
-            source_pg = self.pg_manager.get_for_sg(tenant_id, remote_group_id)
-            port_group_id = source_pg.get_id()
-        else:
-            raise Exception(_("Don't know what to do with rule=%r"), rule)
-
-        # dst ports
-        tp_dst_start, tp_dst_end = port_range_min, port_range_max
-
-        # protocol
-        if protocol == 'tcp':
-            nw_proto = 6
-        elif protocol == 'udp':
-            nw_proto = 17
-        elif protocol == 'icmp':
-            nw_proto = 1
-            # extract type and code from reporposed fields
-            icmp_type = rule['from_port']
-            icmp_code = rule['to_port']
-
-            # translate -1(wildcard in OS) to midonet wildcard
-            if icmp_type == -1:
-                icmp_type = None
-            if icmp_code == -1:
-                icmp_code = None
-
-            # set data for midonet rule
-            tp_src_start = tp_src_end = icmp_type
-            tp_dst_start = tp_dst_end = icmp_code
-
-        chains = self.get_sg_chains(tenant_id, security_group_id)
-        chain = None
-        if direction == 'egress':
-            chain = chains['in']
-        elif direction == 'ingress':
-            chain = chains['out']
-        else:
-            raise Exception(_("Don't know what to do with rule=%r"), rule)
-
-        # create an accept rule
-        properties = sg_rule_properties(rule_id)
-        LOG.debug(_("MidoClient.create_for_sg_rule: adding accept rule "
-                    "%(rule_id)s in portgroup %(port_group_id)s"),
-                  {'rule_id': rule_id, 'port_group_id': port_group_id})
-        chain.add_rule().port_group(port_group_id).type('accept').nw_proto(
-            nw_proto).nw_src_address(nw_src_address).nw_src_length(
-                nw_src_length).tp_src_start(tp_src_start).tp_src_end(
-                    tp_src_end).tp_dst_start(tp_dst_start).tp_dst_end(
-                        tp_dst_end).properties(properties).create()
-
-    @handle_api_error
-    def delete_for_sg_rule(self, rule):
-        LOG.debug(_("MidoClient.delete_for_sg_rule called: rule=%r"), rule)
-
-        tenant_id = rule['tenant_id']
-        security_group_id = rule['security_group_id']
-        rule_id = rule['id']
-
-        properties = sg_rule_properties(rule_id)
-        # search for the chains to find the rule to delete
-        chains = self.get_sg_chains(tenant_id, security_group_id)
-        for c in chains['in'], chains['out']:
-            rules = c.get_rules()
-            for r in rules:
-                if r.get_properties() == properties:
-                    LOG.debug(_("MidoClient.delete_for_sg_rule: deleting "
-                                "rule %r"), r)
-                    self.mido_api.delete_rule(r.get_id())
+    def add_chain_rule(self, chain, action='accept', **kwargs):
+        """Create a new accept chain rule."""
+        self.mido_api.add_chain_rule(chain, action, **kwargs)
index 05b9ef698bbacba9559f649472f4a422c1322b0e..4dd15d8ddf788ca23417007479fa8aaa8b02bd58 100644 (file)
 # @author: Takaaki Suzuki, Midokura Japan KK
 # @author: Tomoe Sugihara, Midokura Japan KK
 # @author: Ryu Ishimoto, Midokura Japan KK
+# @author: Rossella Sblendido, Midokura Japan KK
 
 from midonetclient import api
 from oslo.config import cfg
 
-from neutron.common import exceptions as q_exc
+from neutron.common import constants
+from neutron.common import exceptions as n_exc
+from neutron.common import rpc as n_rpc
+from neutron.common import topics
+from neutron.db import agents_db
+from neutron.db import agentschedulers_db
 from neutron.db import api as db
 from neutron.db import db_base_plugin_v2
+from neutron.db import dhcp_rpc_base
 from neutron.db import l3_db
 from neutron.db import models_v2
 from neutron.db import securitygroups_db
 from neutron.extensions import securitygroup as ext_sg
+from neutron.openstack.common import excutils
 from neutron.openstack.common import log as logging
-from neutron.plugins.midonet import config  # noqa
+from neutron.openstack.common import rpc
+from neutron.plugins.midonet.common import config  # noqa
+from neutron.plugins.midonet.common import net_util
 from neutron.plugins.midonet import midonet_lib
 
-
 LOG = logging.getLogger(__name__)
 
+METADATA_DEFAULT_IP = "169.254.169.254/32"
+OS_FLOATING_IP_RULE_KEY = 'OS_FLOATING_IP'
+OS_SG_RULE_KEY = 'OS_SG_RULE_ID'
+OS_TENANT_ROUTER_RULE_KEY = 'OS_TENANT_ROUTER_RULE'
+PRE_ROUTING_CHAIN_NAME = "OS_PRE_ROUTING_%s"
+PORT_INBOUND_CHAIN_NAME = "OS_PORT_%s_INBOUND"
+PORT_OUTBOUND_CHAIN_NAME = "OS_PORT_%s_OUTBOUND"
+POST_ROUTING_CHAIN_NAME = "OS_POST_ROUTING_%s"
+SG_INGRESS_CHAIN_NAME = "OS_SG_%s_INGRESS"
+SG_EGRESS_CHAIN_NAME = "OS_SG_%s_EGRESS"
+SG_PORT_GROUP_NAME = "OS_PG_%s"
+SNAT_RULE = 'SNAT'
+
+
+def _get_nat_ips(type, fip):
+    """Get NAT IP address information.
+
+    From the route type given, determine the source and target IP addresses
+    from the provided floating IP DB object.
+    """
+    if type == 'pre-routing':
+        return fip["floating_ip_address"], fip["fixed_ip_address"]
+    elif type == 'post-routing':
+        return fip["fixed_ip_address"], fip["floating_ip_address"]
+    else:
+        raise ValueError(_("Invalid nat_type %s") % type)
+
+
+def _nat_chain_names(router_id):
+    """Get the chain names for NAT.
+
+    These names are used to associate MidoNet chains to the NAT rules
+    applied to the router.  For each of these, there are two NAT types,
+    'dnat' and 'snat' that are returned as keys, and the corresponding
+    chain names as their values.
+    """
+    pre_routing_name = PRE_ROUTING_CHAIN_NAME % router_id
+    post_routing_name = POST_ROUTING_CHAIN_NAME % router_id
+    return {'pre-routing': pre_routing_name, 'post-routing': post_routing_name}
+
+
+def _sg_chain_names(sg_id):
+    """Get the chain names for security group.
+
+    These names are used to associate a security group to MidoNet chains.
+    There are two names for ingress and egress security group directions.
+    """
+    ingress = SG_INGRESS_CHAIN_NAME % sg_id
+    egress = SG_EGRESS_CHAIN_NAME % sg_id
+    return {'ingress': ingress, 'egress': egress}
+
+
+def _port_chain_names(port_id):
+    """Get the chain names for a port.
+
+    These are chains to hold security group chains.
+    """
+    inbound = PORT_INBOUND_CHAIN_NAME % port_id
+    outbound = PORT_OUTBOUND_CHAIN_NAME % port_id
+    return {'inbound': inbound, 'outbound': outbound}
+
+
+def _sg_port_group_name(sg_id):
+    """Get the port group name for security group..
+
+    This name is used to associate a security group to MidoNet  port groups.
+    """
+    return SG_PORT_GROUP_NAME % sg_id
+
+
+def _rule_direction(sg_direction):
+    """Convert the SG direction to MidoNet direction
+
+    MidoNet terms them 'inbound' and 'outbound' instead of 'ingress' and
+    'egress'.  Also, the direction is reversed since MidoNet sees it
+    from the network port's point of view, not the VM's.
+    """
+    if sg_direction == 'ingress':
+        return 'outbound'
+    elif sg_direction == 'egress':
+        return 'inbound'
+    else:
+        raise ValueError(_("Unrecognized direction %s") % sg_direction)
+
+
+def _is_router_interface_port(port):
+    """Check whether the given port is a router interface port."""
+    device_owner = port['device_owner']
+    return (device_owner in l3_db.DEVICE_OWNER_ROUTER_INTF)
+
+
+def _is_router_gw_port(port):
+    """Check whether the given port is a router gateway port."""
+    device_owner = port['device_owner']
+    return (device_owner in l3_db.DEVICE_OWNER_ROUTER_GW)
+
+
+def _is_vif_port(port):
+    """Check whether the given port is a standard VIF port."""
+    device_owner = port['device_owner']
+    return (not _is_dhcp_port(port) and
+            device_owner not in (l3_db.DEVICE_OWNER_ROUTER_GW,
+                                 l3_db.DEVICE_OWNER_ROUTER_INTF))
+
+
+def _is_dhcp_port(port):
+    """Check whether the given port is a DHCP port."""
+    device_owner = port['device_owner']
+    return device_owner.startswith('network:dhcp')
+
+
+def _check_resource_exists(func, id, name, raise_exc=False):
+    """Check whether the given resource exists in MidoNet data store."""
+    try:
+        func(id)
+    except midonet_lib.MidonetResourceNotFound as exc:
+        LOG.error(_("There is no %(name)s with ID %(id)s in MidoNet."),
+                  {"name": name, "id": id})
+        if raise_exc:
+            raise MidonetPluginException(msg=exc)
+
+
+class MidoRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
+    RPC_API_VERSION = '1.1'
+
+    def create_rpc_dispatcher(self):
+        """Get the rpc dispatcher for this manager.
+
+        This a basic implementation that will call the plugin like get_ports
+        and handle basic events
+        If a manager would like to set an rpc API version, or support more than
+        one class as the target of rpc messages, override this method.
+        """
+        return n_rpc.PluginRpcDispatcher([self,
+                                          agents_db.AgentExtRpcCallback()])
+
 
-class MidonetPluginException(q_exc.NeutronException):
+class MidonetPluginException(n_exc.NeutronException):
     message = _("%(msg)s")
 
 
 class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                       l3_db.L3_NAT_db_mixin,
+                      agentschedulers_db.AgentSchedulerDbMixin,
                       securitygroups_db.SecurityGroupDbMixin):
 
-    supported_extension_aliases = ['router', 'security-group']
+    supported_extension_aliases = ['router', 'security-group', 'agent'
+                                   'dhcp_agent_scheduler']
     __native_bulk_support = False
 
     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.provider_router_id = midonet_conf.provider_router_id
+        self.provider_router = None
 
         self.mido_api = api.MidonetApi(midonet_uri, admin_user,
                                        admin_pass,
                                        project_id=admin_project_id)
         self.client = midonet_lib.MidoClient(self.mido_api)
 
-        if provider_router_id and metadata_router_id:
-            # get MidoNet provider router and metadata router
-            self.provider_router = self.client.get_router(provider_router_id)
-            self.metadata_router = self.client.get_router(metadata_router_id)
-
-        elif not provider_router_id or not metadata_router_id:
-            if 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.provider_router_id should have been set.
+        if self.provider_router_id is None:
+            msg = _('provider_router_id should be configured in the plugin '
+                    'config file')
+            LOG.exception(msg)
+            raise MidonetPluginException(msg=msg)
 
+        self.setup_rpc()
         db.configure_db()
 
+    def _get_provider_router(self):
+        if self.provider_router is None:
+            self.provider_router = self.client.get_router(
+                self.provider_router_id)
+        return self.provider_router
+
+    def _dhcp_mappings(self, context, fixed_ips, mac):
+        for fixed_ip in fixed_ips:
+            subnet = self._get_subnet(context, fixed_ip["subnet_id"])
+            if subnet["ip_version"] == 6:
+                # TODO(ryu) handle IPv6
+                continue
+            yield subnet['cidr'], fixed_ip["ip_address"], mac
+
+    def _metadata_subnets(self, context, fixed_ips):
+        for fixed_ip in fixed_ips:
+            subnet = self._get_subnet(context, fixed_ip["subnet_id"])
+            if subnet["ip_version"] == 6:
+                continue
+            yield subnet['cidr'], fixed_ip["ip_address"]
+
+    def _initialize_port_chains(self, port, in_chain, out_chain, sg_ids):
+
+        tenant_id = port["tenant_id"]
+
+        position = 1
+        # mac spoofing protection
+        self._add_chain_rule(in_chain, action='drop',
+                             dl_src=port["mac_address"], inv_dl_src=True,
+                             position=position)
+
+        # ip spoofing protection
+        for fixed_ip in port["fixed_ips"]:
+            position += 1
+            self._add_chain_rule(in_chain, action="drop",
+                                 src_addr=fixed_ip["ip_address"] + "/32",
+                                 inv_nw_src=True, dl_type=0x0800,  # IPv4
+                                 position=position)
+
+        # conntrack
+        position += 1
+        self._add_chain_rule(in_chain, action='accept',
+                             match_forward_flow=True,
+                             position=position)
+
+        # Reset the position to process egress
+        position = 1
+
+        # Add rule for SGs
+        if sg_ids:
+            for sg_id in sg_ids:
+                chain_name = _sg_chain_names(sg_id)["ingress"]
+                chain = self.client.get_chain_by_name(tenant_id, chain_name)
+                self._add_chain_rule(out_chain, action='jump',
+                                     jump_chain_id=chain.get_id(),
+                                     jump_chain_name=chain_name,
+                                     position=position)
+                position += 1
+
+        # add reverse flow matching at the end
+        self._add_chain_rule(out_chain, action='accept',
+                             match_return_flow=True,
+                             position=position)
+        position += 1
+
+        # fall back DROP rule at the end except for ARP
+        self._add_chain_rule(out_chain, action='drop',
+                             dl_type=0x0806,  # ARP
+                             inv_dl_type=True, position=position)
+
+    def _bind_port_to_sgs(self, context, port, sg_ids):
+        self._process_port_create_security_group(context, port, sg_ids)
+        for sg_id in sg_ids:
+            pg_name = _sg_port_group_name(sg_id)
+            self.client.add_port_to_port_group_by_name(port["tenant_id"],
+                                                       pg_name, port["id"])
+
+    def _unbind_port_from_sgs(self, context, port_id):
+        self._delete_port_security_group_bindings(context, port_id)
+        self.client.remove_port_from_port_groups(port_id)
+
+    def _create_accept_chain_rule(self, context, sg_rule, chain=None):
+        direction = sg_rule["direction"]
+        tenant_id = sg_rule["tenant_id"]
+        sg_id = sg_rule["security_group_id"]
+        chain_name = _sg_chain_names(sg_id)[direction]
+
+        if chain is None:
+            chain = self.client.get_chain_by_name(tenant_id, chain_name)
+
+        pg_id = None
+        if sg_rule["remote_group_id"] is not None:
+            pg_name = _sg_port_group_name(sg_id)
+            pg = self.client.get_port_group_by_name(tenant_id, pg_name)
+            pg_id = pg.get_id()
+
+        props = {OS_SG_RULE_KEY: str(sg_rule["id"])}
+
+        # Determine source or destination address by looking at direction
+        src_pg_id = dst_pg_id = None
+        src_addr = dst_addr = None
+        src_port_to = dst_port_to = None
+        src_port_from = dst_port_from = None
+        if direction == "egress":
+            dst_pg_id = pg_id
+            dst_addr = sg_rule["remote_ip_prefix"]
+            dst_port_from = sg_rule["port_range_min"]
+            dst_port_to = sg_rule["port_range_max"]
+        else:
+            src_pg_id = pg_id
+            src_addr = sg_rule["remote_ip_prefix"]
+            src_port_from = sg_rule["port_range_min"]
+            src_port_to = sg_rule["port_range_max"]
+
+        return self._add_chain_rule(
+            chain, action='accept', port_group_src=src_pg_id,
+            port_group_dst=dst_pg_id,
+            src_addr=src_addr, src_port_from=src_port_from,
+            src_port_to=src_port_to,
+            dst_addr=dst_addr, dst_port_from=dst_port_from,
+            dst_port_to=dst_port_to,
+            nw_proto=net_util.get_protocol_value(sg_rule["protocol"]),
+            dl_type=net_util.get_ethertype_value(sg_rule["ethertype"]),
+            properties=props)
+
+    def setup_rpc(self):
+        # RPC support
+        self.topic = topics.PLUGIN
+        self.conn = rpc.create_connection(new=True)
+        self.callbacks = MidoRpcCallbacks()
+        self.dispatcher = self.callbacks.create_rpc_dispatcher()
+        self.conn.create_consumer(self.topic, self.dispatcher,
+                                  fanout=False)
+        # Consume from all consumers in a thread
+        self.conn.consume_in_thread()
+
     def create_subnet(self, context, subnet):
         """Create Neutron subnet.
 
@@ -91,16 +364,9 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         """
         LOG.debug(_("MidonetPluginV2.create_subnet called: subnet=%r"), subnet)
 
-        if subnet['subnet']['ip_version'] == 6:
-            raise q_exc.NotImplementedError(
-                _("MidoNet doesn't support IPv6."))
-
+        subnet_data = subnet["subnet"]
         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):
@@ -108,19 +374,14 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                                                                   subnet)
             bridge = self.client.get_bridge(sn_entry['network_id'])
 
-            gateway_ip = subnet['subnet']['gateway_ip']
-            network_address, prefix = subnet['subnet']['cidr'].split('/')
-            self.client.create_dhcp(bridge, gateway_ip, network_address,
-                                    prefix)
+            gateway_ip = subnet_data['gateway_ip']
+            cidr = subnet_data['cidr']
+            self.client.create_dhcp(bridge, gateway_ip, cidr)
 
             # For external network, link the bridge to the provider router.
             if net['router:external']:
-                gateway_ip = sn_entry['gateway_ip']
-                network_address, length = sn_entry['cidr'].split('/')
-
-                self.client.link_bridge_to_provider_router(
-                    bridge, self.provider_router, gateway_ip, network_address,
-                    length)
+                self._link_bridge_to_gw_router(
+                    bridge, self._get_provider_router(), gateway_ip, cidr)
 
         LOG.debug(_("MidonetPluginV2.create_subnet exiting: sn_entry=%r"),
                   sn_entry)
@@ -142,8 +403,8 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
 
         # If the network is external, clean up routes, links, ports.
         if net['router:external']:
-            self.client.unlink_bridge_from_provider_router(
-                bridge, self.provider_router)
+            self._unlink_bridge_from_gw_router(bridge,
+                                               self._get_provider_router())
 
         super(MidonetPluginV2, self).delete_subnet(context, id)
         LOG.debug(_("MidonetPluginV2.delete_subnet exiting"))
@@ -155,26 +416,18 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         """
         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 '
-                          'because it is not yet supported'), network)
-
         tenant_id = self._get_tenant_id_for_create(context, network['network'])
-
         self._ensure_default_security_group(context, tenant_id)
 
+        bridge = self.client.create_bridge(tenant_id,
+                                           network['network']['name'])
+        network['network']['id'] = bridge.get_id()
+
         session = context.session
         with session.begin(subtransactions=True):
-            bridge = self.client.create_bridge(tenant_id,
-                                               network['network']['name'])
-
-            # Set MidoNet bridge ID to the neutron 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, net, network['network'])
+
         LOG.debug(_("MidonetPluginV2.create_network exiting: net=%r"), net)
         return net
 
@@ -186,14 +439,6 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         """
         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(
@@ -210,7 +455,6 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         """
         LOG.debug(_("MidonetPluginV2.get_network called: id=%(id)r, "
                     "fields=%(fields)r"), {'id': id, 'fields': fields})
-
         qnet = super(MidonetPluginV2, self).get_network(context, id, fields)
         self.client.get_bridge(id)
 
@@ -232,77 +476,89 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         """Create a L2 port in Neutron/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.
-        bridge = self.client.get_bridge(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 = self.client.create_exterior_bridge_port(bridge)
-        elif device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
-            bridge_port = self.client.create_interior_bridge_port(bridge)
-        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 = self.client.create_interior_bridge_port(bridge)
 
-        if bridge_port:
-            # set midonet port id to neutron 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.
-                    self.client.create_dhcp_hosts(bridge, fixed_ip, mac)
-        LOG.debug(_("MidonetPluginV2.create_port exiting: port_db_entry=%r"),
-                  port_db_entry)
-        return port_db_entry
+        # Create a bridge port in MidoNet and set the bridge port ID as the
+        # port ID in Neutron.
+        bridge = self.client.get_bridge(port_data["network_id"])
+        tenant_id = bridge.get_tenant_id()
+        bridge_port = self.client.add_bridge_port(bridge)
+        port_data["id"] = bridge_port.get_id()
+        try:
+            session = context.session
+            with session.begin(subtransactions=True):
+                # Create a Neutron port
+                new_port = super(MidonetPluginV2, self).create_port(context,
+                                                                    port)
+                port_data.update(new_port)
+
+                # Bind security groups to the port
+                sg_ids = self._get_security_groups_on_port(context, port)
+                if sg_ids:
+                    self._bind_port_to_sgs(context, port_data, sg_ids)
+                port_data[ext_sg.SECURITYGROUPS] = sg_ids
+
+                # Create port chains
+                port_chains = {}
+                for d, name in _port_chain_names(new_port["id"]).iteritems():
+                    port_chains[d] = self.client.create_chain(tenant_id, name)
+
+                self._initialize_port_chains(port_data, port_chains['inbound'],
+                                             port_chains['outbound'], sg_ids)
+
+                # Update the port with the chain
+                self.client.update_port_chains(
+                    bridge_port, port_chains["inbound"].get_id(),
+                    port_chains["outbound"].get_id())
+
+                if _is_dhcp_port(port_data):
+                    # For DHCP port, add a metadata route
+                    for cidr, ip in self._metadata_subnets(
+                        context, port_data["fixed_ips"]):
+                        self.client.add_dhcp_route_option(bridge, cidr, ip,
+                                                          METADATA_DEFAULT_IP)
+                elif _is_vif_port(port_data):
+                    # DHCP mapping is only for VIF ports
+                    for cidr, ip, mac in self._dhcp_mappings(
+                            context, port_data["fixed_ips"],
+                            port_data["mac_address"]):
+                        self.client.add_dhcp_host(bridge, cidr, ip, mac)
+
+        except Exception as ex:
+            # Try removing the MidoNet port before raising an exception.
+            with excutils.save_and_reraise_exception():
+                LOG.error(_("Failed to create a port on network %(net_id)s: "
+                            "%(err)s"),
+                          {"net_id": port_data["network_id"], "err": ex})
+                self.client.delete_port(bridge_port.get_id())
+
+        LOG.debug(_("MidonetPluginV2.create_port exiting: port=%r"), port_data)
+        return port_data
 
     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 neutron port from DB.
-        port_db_entry = super(MidonetPluginV2, self).get_port(context,
-                                                              id, fields)
-        # verify that corresponding port exists in MidoNet.
-        self.client.get_port(id)
-
-        LOG.debug(_("MidonetPluginV2.get_port exiting: port_db_entry=%r"),
-                  port_db_entry)
-        return port_db_entry
+        port = super(MidonetPluginV2, self).get_port(context, id, fields)
+        "Check if the port exists in MidoNet DB"""
+        try:
+            self.client.get_port(id)
+        except midonet_lib.MidonetResourceNotFound as exc:
+            LOG.error(_("There is no port with ID %(id)s in MidoNet."),
+                      {"id": id})
+            port['status'] = constants.PORT_STATUS_ERROR
+            raise exc
+        LOG.debug(_("MidonetPluginV2.get_port exiting: port=%r"), port)
+        return port
 
     def get_ports(self, context, filters=None, fields=None):
         """List neutron 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:
-            for port in ports_db_entry:
-                if 'security_gorups' in port:
-                    self._extend_port_dict_security_group(context, port)
-        return ports_db_entry
+        ports = super(MidonetPluginV2, self).get_ports(context, filters,
+                                                       fields)
+        return ports
 
     def delete_port(self, context, id, l3_port_check=True):
         """Delete a neutron port and corresponding MidoNet bridge port."""
@@ -314,202 +570,382 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         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)
-            # 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']
+        port = self.get_port(context, id)
+        device_id = port['device_id']
+        # If this port is for router interface/gw, unlink and delete.
+        if _is_router_interface_port(port):
+            self._unlink_bridge_from_router(device_id, id)
+        elif _is_router_gw_port(port):
+            # Gateway removed
+            # Remove all the SNAT rules that are tagged.
+            router = self._get_router(context, device_id)
+            tenant_id = router["tenant_id"]
+            chain_names = _nat_chain_names(device_id)
+            for _type, name in chain_names.iteritems():
+                self.client.remove_rules_by_property(
+                    tenant_id, name, OS_TENANT_ROUTER_RULE_KEY,
+                    SNAT_RULE)
+            # Remove the default routes and unlink
+            self._remove_router_gateway(port['device_id'])
+
+        self.client.delete_port(id, delete_chains=True)
+        try:
+            for cidr, ip, mac in self._dhcp_mappings(
+                    context, port["fixed_ips"], port["mac_address"]):
+                self.client.delete_dhcp_host(port["network_id"], cidr, ip,
+                                             mac)
+        except Exception:
+            LOG.error(_("Failed to delete DHCP mapping for port %(id)s"),
+                      {"id": id})
 
-                # create dhcp host entry under the bridge.
-                self.client.delete_dhcp_hosts(port_db_entry['network_id'], ip,
-                                              mac)
+        super(MidonetPluginV2, self).delete_port(context, id)
 
-            self.client.delete_port(id)
-            return super(MidonetPluginV2, self).delete_port(context, id)
+    def update_port(self, context, id, port):
+        """Handle port update, including security groups and fixed IPs."""
+        with context.session.begin(subtransactions=True):
 
-    #
-    # L3 APIs.
-    #
+            # Get the port and save the fixed IPs
+            old_port = self._get_port(context, id)
+            net_id = old_port["network_id"]
+            mac = old_port["mac_address"]
+            old_fixed_ips = old_port.get('fixed_ips')
+
+            # update the port DB
+            p = super(MidonetPluginV2, self).update_port(context, id, port)
+
+            if "fixed_ips" in p:
+                # IPs have changed.  Re-map the DHCP entries
+                bridge = self.client.get_bridge(net_id)
+                for cidr, ip, mac in self._dhcp_mappings(
+                        context, old_fixed_ips, mac):
+                    self.client.remove_dhcp_host(bridge, cidr, ip, mac)
+                for cidr, ip, mac in self._dhcp_mappings(context,
+                                                         p["fixed_ips"], mac):
+                    self.client.add_dhcp_host(bridge, cidr, ip, mac)
+
+            if (self._check_update_deletes_security_groups(port) or
+                    self._check_update_has_security_groups(port)):
+                self._unbind_port_from_sgs(context, p["id"])
+                sg_ids = self._get_security_groups_on_port(context, port)
+                if sg_ids:
+                    self._bind_port_to_sgs(context, p, sg_ids)
+        return p
 
     def create_router(self, context, router):
-        LOG.debug(_("MidonetPluginV2.create_router called: router=%r"), router)
+        """Handle router creation.
 
-        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
+        When a new Neutron router is created, its corresponding MidoNet router
+        is also created.  In MidoNet, this router is initialized with chains
+        for inbuond and outbound traffic, which will be used to hold other
+        chains that include various rules, such as NAT.
 
+        :param router: Router information provided to create a new router.
+        """
+        LOG.debug(_("MidonetPluginV2.create_router called: router=%(router)s"),
+                  {"router": router})
         tenant_id = self._get_tenant_id_for_create(context, router['router'])
-        session = context.session
-        with session.begin(subtransactions=True):
-            mrouter = self.client.create_tenant_router(
-                tenant_id, router['router']['name'], self.metadata_router)
+        mido_router = self.client.create_router(tenant_id,
+                                                router['router']['name'])
+        mido_router_id = mido_router.get_id()
 
-            qrouter = super(MidonetPluginV2, self).create_router(context,
-                                                                 router)
+        try:
+            with context.session.begin(subtransactions=True):
 
-            # 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)
+                router_data = super(MidonetPluginV2, self).create_router(
+                    context, router)
 
-            LOG.debug(_("MidonetPluginV2.create_router exiting: qrouter=%r"),
-                      qrouter)
-            return qrouter
+                # get entry from the DB and update 'id' with MidoNet router id.
+                router_db = self._get_router(context, router_data['id'])
+                router_data['id'] = mido_router_id
+                router_db.update(router_data)
+        except Exception:
+            # Try removing the midonet router
+            with excutils.save_and_reraise_exception():
+                self.client.delete_router(mido_router_id)
+
+        # Create router chains
+        chain_names = _nat_chain_names(mido_router_id)
+        try:
+            self.client.add_router_chains(mido_router,
+                                          chain_names["pre-routing"],
+                                          chain_names["post-routing"])
+        except Exception:
+            # Set the router status to Error
+            with context.session.begin(subtransactions=True):
+                r = self._get_router(context, router_data["id"])
+                router_data['status'] = constants.NET_STATUS_ERROR
+                r['status'] = router_data['status']
+                context.session.add(r)
+
+        LOG.debug(_("MidonetPluginV2.create_router exiting: "
+                    "router_data=%(router_data)s."),
+                  {"router_data": router_data})
+        return router_data
+
+    def _set_router_gateway(self, id, gw_router, gw_ip):
+        """Set router uplink gateway
+
+        :param ID: ID of the router
+        :param gw_router: gateway router to link to
+        :param gw_ip: gateway IP address
+        """
+        LOG.debug(_("MidonetPluginV2.set_router_gateway called: id=%(id)s, "
+                    "gw_router=%(gw_router)s, gw_ip=%(gw_ip)s"),
+                  {'id': id, 'gw_router': gw_router, 'gw_ip': gw_ip}),
+
+        router = self.client.get_router(id)
+
+        # Create a port in the gw router
+        gw_port = self.client.add_router_port(gw_router,
+                                              port_address='169.254.255.1',
+                                              network_address='169.254.255.0',
+                                              network_length=30)
+
+        # Create a port in the router
+        port = self.client.add_router_port(router,
+                                           port_address='169.254.255.2',
+                                           network_address='169.254.255.0',
+                                           network_length=30)
+
+        # Link them
+        self.client.link(gw_port, port.get_id())
+
+        # Add a route for gw_ip to bring it down to the router
+        self.client.add_router_route(gw_router, type='Normal',
+                                     src_network_addr='0.0.0.0',
+                                     src_network_length=0,
+                                     dst_network_addr=gw_ip,
+                                     dst_network_length=32,
+                                     next_hop_port=gw_port.get_id(),
+                                     weight=100)
+
+        # Add default route to uplink in the router
+        self.client.add_router_route(router, type='Normal',
+                                     src_network_addr='0.0.0.0',
+                                     src_network_length=0,
+                                     dst_network_addr='0.0.0.0',
+                                     dst_network_length=0,
+                                     next_hop_port=port.get_id(),
+                                     weight=100)
+
+    def _remove_router_gateway(self, id):
+        """Clear router gateway
+
+        :param ID: ID of the router
+        """
+        LOG.debug(_("MidonetPluginV2.remove_router_gateway called: "
+                    "id=%(id)s"), {'id': id})
+        router = self.client.get_router(id)
+
+        # delete the port that is connected to the gateway router
+        for p in router.get_ports():
+            if p.get_port_address() == '169.254.255.2':
+                peer_port_id = p.get_peer_id()
+                if peer_port_id is not None:
+                    self.client.unlink(p)
+                    self.client.delete_port(peer_port_id)
+
+        # delete default route
+        for r in router.get_routes():
+            if (r.get_dst_network_addr() == '0.0.0.0' and
+                    r.get_dst_network_length() == 0):
+                self.client.delete_route(r.get_id())
 
     def update_router(self, context, id, router):
+        """Handle router updates."""
         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
+                    "router=%(router)r"), {"id": id, "router": 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']
-            qport['network_id']
-
-        session = context.session
-        with session.begin(subtransactions=True):
+        router_data = router["router"]
 
-            qrouter = super(MidonetPluginV2, self).update_router(context, id,
-                                                                 router)
+        # Check if the update included changes to the gateway.
+        gw_updated = l3_db.EXTERNAL_GW_INFO in router_data
+        with context.session.begin(subtransactions=True):
 
-            changed_name = router['router'].get('name')
+            # Update the Neutron DB
+            r = super(MidonetPluginV2, self).update_router(context, id,
+                                                           router)
+            tenant_id = r["tenant_id"]
+            if gw_updated:
+                if (l3_db.EXTERNAL_GW_INFO in r and
+                    r[l3_db.EXTERNAL_GW_INFO] is not None):
+                    # Gateway created
+                    gw_port = self._get_port(context, r["gw_port_id"])
+                    gw_ip = gw_port['fixed_ips'][0]['ip_address']
+
+                    # First link routers and set up the routes
+                    self._set_router_gateway(r["id"],
+                                             self._get_provider_router(),
+                                             gw_ip)
+
+                    # Get the NAT chains and add dynamic SNAT rules.
+                    chain_names = _nat_chain_names(r["id"])
+                    props = {OS_TENANT_ROUTER_RULE_KEY: SNAT_RULE}
+                    self.client.add_dynamic_snat(tenant_id,
+                                                 chain_names['pre-routing'],
+                                                 chain_names['post-routing'],
+                                                 gw_ip, gw_port["id"], **props)
+
+            # Update the name if changed
+            changed_name = router_data.get('name')
             if changed_name:
                 self.client.update_router(id, changed_name)
 
-            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']
-
-                self.client.set_router_external_gateway(id,
-                                                        self.provider_router,
-                                                        snat_ip)
+        LOG.debug(_("MidonetPluginV2.update_router exiting: router=%r"), r)
+        return r
 
-            if op_gateway_clear:
-                self.client.clear_router_external_gateway(id)
+    def delete_router(self, context, id):
+        """Handler for router deletion.
 
-        LOG.debug(_("MidonetPluginV2.update_router exiting: qrouter=%r"),
-                  qrouter)
-        return qrouter
+        Deleting a router on Neutron simply means deleting its corresponding
+        router in MidoNet.
 
-    def delete_router(self, context, id):
+        :param id: router ID to remove
+        """
         LOG.debug(_("MidonetPluginV2.delete_router called: id=%s"), id)
 
-        self.client.delete_tenant_router(id)
+        self.client.delete_router_chains(id)
+        self.client.delete_router(id)
+
+        super(MidonetPluginV2, self).delete_router(context, id)
 
-        result = super(MidonetPluginV2, self).delete_router(context, id)
-        LOG.debug(_("MidonetPluginV2.delete_router exiting: result=%s"),
-                  result)
-        return result
+    def _link_bridge_to_gw_router(self, bridge, gw_router, gw_ip, cidr):
+        """Link a bridge to the gateway router
+
+        :param bridge:  bridge
+        :param gw_router: gateway router to link to
+        :param gw_ip: IP address of gateway
+        :param cidr: network CIDR
+        """
+        net_addr, net_len = net_util.net_addr(cidr)
+
+        # create a port on the gateway router
+        gw_port = self.client.add_router_port(gw_router, port_address=gw_ip,
+                                              network_address=net_addr,
+                                              network_length=net_len)
+
+        # create a bridge port, then link it to the router.
+        port = self.client.add_bridge_port(bridge)
+        self.client.link(gw_port, port.get_id())
+
+        # add a route for the subnet in the gateway router
+        self.client.add_router_route(gw_router, type='Normal',
+                                     src_network_addr='0.0.0.0',
+                                     src_network_length=0,
+                                     dst_network_addr=net_addr,
+                                     dst_network_length=net_len,
+                                     next_hop_port=gw_port.get_id(),
+                                     weight=100)
+
+    def _unlink_bridge_from_gw_router(self, bridge, gw_router):
+        """Unlink a bridge from the gateway router
+
+        :param bridge: bridge to unlink
+        :param gw_router: gateway router to unlink from
+        """
+        # Delete routes and unlink the router and the bridge.
+        routes = self.client.get_router_routes(gw_router.get_id())
+
+        bridge_ports_to_delete = [
+            p for p in gw_router.get_peer_ports()
+            if p.get_device_id() == bridge.get_id()]
+
+        for p in bridge.get_peer_ports():
+            if p.get_device_id() == gw_router.get_id():
+                # delete the routes going to the bridge
+                for r in routes:
+                    if r.get_next_hop_port() == p.get_id():
+                        self.client.delete_route(r.get_id())
+                self.client.unlink(p)
+                self.client.delete_port(p.get_id())
+
+        # delete bridge port
+        for port in bridge_ports_to_delete:
+            self.client.delete_port(port.get_id())
+
+    def _link_bridge_to_router(self, router, bridge_port_id, net_addr, net_len,
+                               gw_ip, metadata_gw_ip):
+        router_port = self.client.add_router_port(
+            router, port_address=gw_ip, network_address=net_addr,
+            network_length=net_len)
+        self.client.link(router_port, bridge_port_id)
+        self.client.add_router_route(router, type='Normal',
+                                     src_network_addr='0.0.0.0',
+                                     src_network_length=0,
+                                     dst_network_addr=net_addr,
+                                     dst_network_length=net_len,
+                                     next_hop_port=router_port.get_id(),
+                                     weight=100)
+
+        if metadata_gw_ip:
+            # Add a route for the metadata server.
+            # Not all VM images supports DHCP option 121.  Add a route for the
+            # Metadata server in the router to forward the packet to the bridge
+            # that will send them to the Metadata Proxy.
+            net_addr, net_len = net_util.net_addr(METADATA_DEFAULT_IP)
+            self.client.add_router_route(
+                router, type='Normal', src_network_addr=net_addr,
+                src_network_length=net_len,
+                dst_network_addr=net_addr,
+                dst_network_length=32,
+                next_hop_port=router_port.get_id(),
+                next_hop_gateway=metadata_gw_ip)
+
+    def _unlink_bridge_from_router(self, router_id, bridge_port_id):
+        """Unlink a bridge from a router."""
+
+        # Remove the routes to the port and unlink the port
+        bridge_port = self.client.get_port(bridge_port_id)
+        routes = self.client.get_router_routes(router_id)
+        self.client.delete_port_routes(routes, bridge_port.get_peer_id())
+        self.client.unlink(bridge_port)
 
     def add_router_interface(self, context, router_id, interface_info):
+        """Handle router linking with network."""
         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)
+        with context.session.begin(subtransactions=True):
+            info = super(MidonetPluginV2, self).add_router_interface(
+                context, router_id, interface_info)
 
-            gateway_ip = subnet['gateway_ip']
-            network_address, length = subnet['cidr'].split('/')
+        try:
+            subnet = self._get_subnet(context, info["subnet_id"])
+            cidr = subnet["cidr"]
+            net_addr, net_len = net_util.net_addr(cidr)
+            router = self.client.get_router(router_id)
 
-            # Link the router and the bridge port.
-            self.client.link_bridge_port_to_router(qport['port_id'], router_id,
-                                                   gateway_ip, network_address,
-                                                   length,
-                                                   self.metadata_router)
+            # Get the metadatat GW IP
+            metadata_gw_ip = None
+            rport_qry = context.session.query(models_v2.Port)
+            dhcp_ports = rport_qry.filter_by(
+                network_id=subnet["network_id"],
+                device_owner='network:dhcp').all()
+            if dhcp_ports and dhcp_ports[0].fixed_ips:
+                metadata_gw_ip = dhcp_ports[0].fixed_ips[0].ip_address
+            else:
+                LOG.warn(_("DHCP agent is not working correctly. No port "
+                           "to reach the Metadata server on this network"))
+            # Link the router and the bridge
+            self._link_bridge_to_router(router, info["port_id"], net_addr,
+                                        net_len, subnet["gateway_ip"],
+                                        metadata_gw_ip)
+        except Exception:
+            LOG.error(_("Failed to create MidoNet 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, info)
 
         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})
-        port_id = None
-        if 'port_id' in interface_info:
-
-            port_id = 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 neutron 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
-            port_id = network_port['id']
-
-        assert 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.
-        self.client.unlink_bridge_port_from_router(port_id, network_addr,
-                                                   network_length,
-                                                   self.metadata_router)
-
-        info = super(MidonetPluginV2, self).remove_router_interface(
-            context, router_id, interface_info)
-        LOG.debug(_("MidonetPluginV2.remove_router_interface exiting"))
+                    "info=%r"), info)
         return info
 
     def update_floatingip(self, context, id, floatingip):
+        """Handle floating IP assocation and disassociation."""
         LOG.debug(_("MidonetPluginV2.update_floatingip called: id=%(id)s "
                     "floatingip=%(floatingip)s "),
                   {'id': id, 'floatingip': floatingip})
@@ -520,29 +956,60 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                 fip = super(MidonetPluginV2, self).update_floatingip(
                     context, id, floatingip)
 
-                self.client.setup_floating_ip(fip['router_id'],
-                                              self.provider_router,
-                                              fip['floating_ip_address'],
-                                              fip['fixed_ip_address'], id)
+                # Add a route for the floating IP on the provider router.
+                router = self.client.get_router(fip["router_id"])
+                link_port = self.client.get_link_port(
+                    self._get_provider_router(), router.get_id())
+                self.client.add_router_route(
+                    self._get_provider_router(),
+                    src_network_addr='0.0.0.0',
+                    src_network_length=0,
+                    dst_network_addr=fip["floating_ip_address"],
+                    dst_network_length=32,
+                    next_hop_port=link_port.get_peer_id())
+
+                # Add static SNAT and DNAT rules on the tenant router.
+                props = {OS_FLOATING_IP_RULE_KEY: id}
+                tenant_id = router.get_tenant_id()
+                chain_names = _nat_chain_names(router.get_id())
+                for chain_type, name in chain_names.iteritems():
+                    src_ip, target_ip = _get_nat_ips(chain_type, fip)
+                    if chain_type == 'pre-routing':
+                        nat_type = 'dnat'
+                    else:
+                        nat_type = 'snat'
+                    self.client.add_static_nat(tenant_id, name, src_ip,
+                                               target_ip,
+                                               link_port.get_id(),
+                                               nat_type, **props)
+
             # disassociate floating IP
             elif floatingip['floatingip']['port_id'] is None:
 
                 fip = super(MidonetPluginV2, self).get_floatingip(context, id)
-                self.client.clear_floating_ip(fip['router_id'],
-                                              self.provider_router,
-                                              fip['floating_ip_address'], id)
+                router = self.client.get_router(fip["router_id"])
+                self.client.remove_static_route(self._get_provider_router(),
+                                                fip["floating_ip_address"])
+
+                chain_names = _nat_chain_names(router.get_id())
+                for _type, name in chain_names.iteritems():
+                    self.client.remove_rules_by_property(
+                        router.get_tenant_id(), name, OS_FLOATING_IP_RULE_KEY,
+                        id)
+
                 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 Neutron security group."""
+        """Create security group.
+
+        Create a new security group, including the default security group.
+        In MidoNet, this means creating a pair of chains, inbound and outbound,
+        as well as a new port group.
+        """
         LOG.debug(_("MidonetPluginV2.create_security_group called: "
                     "security_group=%(security_group)s "
                     "default_sg=%(default_sg)s "),
@@ -550,103 +1017,137 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
 
         sg = security_group.get('security_group')
         tenant_id = self._get_tenant_id_for_create(context, sg)
+        if not default_sg:
+            self._ensure_default_security_group(context, tenant_id)
 
-        with context.session.begin(subtransactions=True):
-            sg_db_entry = super(MidonetPluginV2, self).create_security_group(
-                context, security_group, default_sg)
+        # Create the Neutron sg first
+        sg = super(MidonetPluginV2, self).create_security_group(
+            context, security_group, default_sg)
 
-            # Create MidoNet chains and portgroup for the SG
-            self.client.create_for_sg(tenant_id, sg_db_entry['id'],
-                                      sg_db_entry['name'])
+        try:
+            # Process the MidoNet side
+            self.client.create_port_group(tenant_id,
+                                          _sg_port_group_name(sg["id"]))
+            chain_names = _sg_chain_names(sg["id"])
+            chains = {}
+            for direction, chain_name in chain_names.iteritems():
+                c = self.client.create_chain(tenant_id, chain_name)
+                chains[direction] = c
+
+            # Create all the rules for this SG.  Only accept rules are created
+            for r in sg['security_group_rules']:
+                self._create_accept_chain_rule(context, r,
+                                               chain=chains[r['direction']])
+        except Exception:
+            LOG.error(_("Failed to create MidoNet resources for sg %(sg)r"),
+                      {"sg": sg})
+            with excutils.save_and_reraise_exception():
+                with context.session.begin(subtransactions=True):
+                    sg = self._get_security_group(context, sg["id"])
+                    context.session.delete(sg)
 
-            LOG.debug(_("MidonetPluginV2.create_security_group exiting: "
-                        "sg_db_entry=%r"), sg_db_entry)
-            return sg_db_entry
+        LOG.debug(_("MidonetPluginV2.create_security_group exiting: sg=%r"),
+                  sg)
+        return sg
 
     def delete_security_group(self, context, id):
         """Delete chains for Neutron 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:
+            sg = super(MidonetPluginV2, self).get_security_group(context, id)
+            if not sg:
                 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:
+            if sg["name"] == 'default' and not context.is_admin:
                 raise ext_sg.SecurityGroupCannotRemoveDefault()
 
+            sg_id = sg['id']
             filters = {'security_group_id': [sg_id]}
             if super(MidonetPluginV2, self)._get_port_security_group_bindings(
-                context, filters):
+                    context, filters):
                 raise ext_sg.SecurityGroupInUse(id=sg_id)
 
             # Delete MidoNet Chains and portgroup for the SG
-            self.client.delete_for_sg(tenant_id, sg_id, sg_name)
+            tenant_id = sg['tenant_id']
+            self.client.delete_chains_by_names(
+                tenant_id, _sg_chain_names(sg["id"]).values())
 
-            return super(MidonetPluginV2, self).delete_security_group(
-                context, id)
+            self.client.delete_port_group_by_name(
+                tenant_id, _sg_port_group_name(sg["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)
+            super(MidonetPluginV2, self).delete_security_group(context, id)
 
     def create_security_group_rule(self, context, security_group_rule):
+        """Create a security group rule
+
+        Create a security group rule in the Neutron DB and corresponding
+        MidoNet resources in its data store.
+        """
         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)
+            rule = super(MidonetPluginV2, self).create_security_group_rule(
+                context, security_group_rule)
+
+            self._create_accept_chain_rule(context, rule)
 
-            self.client.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
+                        "rule=%r"), rule)
+            return rule
 
-    def delete_security_group_rule(self, context, sgrid):
-        LOG.debug(_("MidonetPluginV2.delete_security_group_rule called: "
-                    "sgrid=%s"), sgrid)
+    def delete_security_group_rule(self, context, sg_rule_id):
+        """Delete a security group rule
 
+        Delete a security group rule from the Neutron DB and corresponding
+        MidoNet resources from its data store.
+        """
+        LOG.debug(_("MidonetPluginV2.delete_security_group_rule called: "
+                    "sg_rule_id=%s"), sg_rule_id)
         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.client.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)
+            rule = super(MidonetPluginV2, self).get_security_group_rule(
+                context, sg_rule_id)
+
+            if not rule:
+                raise ext_sg.SecurityGroupRuleNotFound(id=sg_rule_id)
+
+            sg = self._get_security_group(context,
+                                          rule["security_group_id"])
+            chain_name = _sg_chain_names(sg["id"])[rule["direction"]]
+            self.client.remove_rules_by_property(rule["tenant_id"], chain_name,
+                                                 OS_SG_RULE_KEY,
+                                                 str(rule["id"]))
+            super(MidonetPluginV2, self).delete_security_group_rule(
+                context, sg_rule_id)
+
+    def _add_chain_rule(self, chain, action, **kwargs):
+
+        nw_proto = kwargs.get("nw_proto")
+        src_addr = kwargs.pop("src_addr", None)
+        dst_addr = kwargs.pop("dst_addr", None)
+        src_port_from = kwargs.pop("src_port_from", None)
+        src_port_to = kwargs.pop("src_port_to", None)
+        dst_port_from = kwargs.pop("dst_port_from", None)
+        dst_port_to = kwargs.pop("dst_port_to", None)
+
+        # Convert to the keys and values that midonet client understands
+        if src_addr:
+            kwargs["nw_src_addr"], kwargs["nw_src_length"] = net_util.net_addr(
+                src_addr)
+
+        if dst_addr:
+            kwargs["nw_dst_addr"], kwargs["nw_dst_length"] = net_util.net_addr(
+                dst_addr)
+
+        kwargs["tp_src"] = {"start": src_port_from, "end": src_port_to}
+
+        kwargs["tp_dst"] = {"start": dst_port_from, "end": dst_port_to}
+
+        if nw_proto == 1:  # ICMP
+            # Overwrite port fields regardless of the direction
+            kwargs["tp_src"] = {"start": src_port_from, "end": src_port_from}
+            kwargs["tp_dst"] = {"start": dst_port_to, "end": dst_port_to}
+
+        return self.client.add_chain_rule(chain, action=action, **kwargs)
index 8b9224ee49d35b275d10d268f6675ce432525471..6628b50f0c376b2403b6ccacef2cf6a5cd4ada41 100644 (file)
@@ -62,26 +62,6 @@ def get_chain_mock(id=None, tenant_id='test-tenant', name='chain',
     return chain
 
 
-def get_exterior_bridge_port_mock(id=None, bridge_id=None):
-    if id is None:
-        id = str(uuid.uuid4())
-    if bridge_id is None:
-        bridge_id = str(uuid.uuid4())
-
-    return get_bridge_port_mock(id=id, bridge_id=bridge_id,
-                                type='ExteriorBridge')
-
-
-def get_interior_bridge_port_mock(id=None, bridge_id=None):
-    if id is None:
-        id = str(uuid.uuid4())
-    if bridge_id is None:
-        bridge_id = str(uuid.uuid4())
-
-    return get_bridge_port_mock(id=id, bridge_id=bridge_id,
-                                type='InteriorBridge')
-
-
 def get_port_group_mock(id=None, tenant_id='test-tenant', name='pg'):
     if id is None:
         id = str(uuid.uuid4())
@@ -143,22 +123,19 @@ class MidonetLibMockConfig():
     def _create_bridge(self, tenant_id, name):
         return get_bridge_mock(tenant_id=tenant_id, name=name)
 
-    def _create_exterior_bridge_port(self, bridge):
-        return get_exterior_bridge_port_mock(bridge_id=bridge.get_id())
-
-    def _create_interior_bridge_port(self, bridge):
-        return get_interior_bridge_port_mock(bridge_id=bridge.get_id())
-
     def _create_subnet(self, bridge, gateway_ip, subnet_prefix, subnet_len):
         return get_subnet_mock(bridge.get_id(), gateway_ip=gateway_ip,
                                subnet_prefix=subnet_prefix,
                                subnet_len=subnet_len)
 
+    def _add_bridge_port(self, bridge):
+        return get_bridge_port_mock(bridge_id=bridge.get_id())
+
     def _get_bridge(self, id):
         return get_bridge_mock(id=id)
 
     def _get_port(self, id):
-        return get_exterior_bridge_port_mock(id=id)
+        return get_bridge_port_mock(id=id)
 
     def _get_router(self, id):
         return get_router_mock(id=id)
@@ -176,10 +153,8 @@ class MidonetLibMockConfig():
         self.inst.create_subnet.side_effect = self._create_subnet
 
         # Port methods side effects
-        ex_bp = self.inst.create_exterior_bridge_port
-        ex_bp.side_effect = self._create_exterior_bridge_port
-        in_bp = self.inst.create_interior_bridge_port
-        in_bp.side_effect = self._create_interior_bridge_port
+        ex_bp = self.inst.add_bridge_port
+        ex_bp.side_effect = self._add_bridge_port
         self.inst.get_port.side_effect = self._get_port
 
         # Router methods side effects
@@ -206,6 +181,26 @@ class MidoClientMockConfig():
     def _get_bridge(self, id):
         return get_bridge_mock(id=id)
 
+    def _get_chain(self, id, query=None):
+        if not self.chains_in:
+            return []
+
+        tenant_id = self._get_query_tenant_id(query)
+        for chain in self.chains_in:
+            chain_id = chain['id']
+            if chain_id is id:
+                rule_mocks = []
+                if 'rules' in chain:
+                    for rule in chain['rules']:
+                        rule_mocks.append(
+                            get_rule_mock(id=rule['id'],
+                                          chain_id=id,
+                                          properties=rule['properties']))
+
+                return get_chain_mock(id=chain_id, name=chain['name'],
+                                      tenant_id=tenant_id, rules=rule_mocks)
+        return None
+
     def _get_chains(self, query=None):
         if not self.chains_in:
             return []
@@ -249,5 +244,6 @@ class MidoClientMockConfig():
     def setup(self):
         self.inst.get_bridge.side_effect = self._get_bridge
         self.inst.get_chains.side_effect = self._get_chains
+        self.inst.get_chain.side_effect = self._get_chain
         self.inst.get_port_groups.side_effect = self._get_port_groups
         self.inst.get_router.side_effect = self._get_router
diff --git a/neutron/tests/unit/midonet/test_midonet_driver.py b/neutron/tests/unit/midonet/test_midonet_driver.py
new file mode 100644 (file)
index 0000000..4bae517
--- /dev/null
@@ -0,0 +1,116 @@
+# 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: Rossella Sblendido, Midokura Japan KK
+
+import mock
+from oslo.config import cfg
+import sys
+sys.modules["midonetclient"] = mock.Mock()
+
+from neutron.agent.common import config
+from neutron.agent.linux import interface
+from neutron.agent.linux import ip_lib
+from neutron.agent.linux import utils
+from neutron.openstack.common import uuidutils
+import neutron.plugins.midonet.agent.midonet_driver as driver
+from neutron.tests import base
+
+
+class MidoInterfaceDriverTestCase(base.BaseTestCase):
+    def setUp(self):
+        self.conf = config.setup_conf()
+        self.conf.register_opts(interface.OPTS)
+        config.register_root_helper(self.conf)
+        self.ip_dev_p = mock.patch.object(ip_lib, 'IPDevice')
+        self.ip_dev = self.ip_dev_p.start()
+        self.ip_p = mock.patch.object(ip_lib, 'IPWrapper')
+        self.ip = self.ip_p.start()
+        self.device_exists_p = mock.patch.object(ip_lib, 'device_exists')
+        self.device_exists = self.device_exists_p.start()
+
+        self.api_p = mock.patch.object(sys.modules["midonetclient"].api,
+                                       'MidonetApi')
+        self.api = self.api_p.start()
+        self.addCleanup(mock.patch.stopall)
+        midonet_opts = [
+            cfg.StrOpt('midonet_uri',
+                       default='http://localhost:8080/midonet-api',
+                       help=_('MidoNet API server URI.')),
+            cfg.StrOpt('username', default='admin',
+                       help=_('MidoNet admin username.')),
+            cfg.StrOpt('password', default='passw0rd',
+                       secret=True,
+                       help=_('MidoNet admin password.')),
+            cfg.StrOpt('project_id',
+                       default='77777777-7777-7777-7777-777777777777',
+                       help=_('ID of the project that MidoNet admin user'
+                              'belongs to.'))
+        ]
+        self.conf.register_opts(midonet_opts, "MIDONET")
+        self.driver = driver.MidonetInterfaceDriver(self.conf)
+        self.network_id = uuidutils.generate_uuid()
+        self.port_id = uuidutils.generate_uuid()
+        self.device_name = "tap0"
+        self.mac_address = "aa:bb:cc:dd:ee:ff"
+        self.bridge = "br-test"
+        self.namespace = "ns-test"
+        super(MidoInterfaceDriverTestCase, self).setUp()
+
+    def test_plug(self):
+        def device_exists(dev, root_helper=None, namespace=None):
+            return False
+
+        self.device_exists.side_effect = device_exists
+        root_dev = mock.Mock()
+        ns_dev = mock.Mock()
+        self.ip().add_veth = mock.Mock(return_value=(root_dev, ns_dev))
+        self.driver._get_host_uuid = mock.Mock(
+            return_value=uuidutils.generate_uuid())
+        with mock.patch.object(utils, 'execute'):
+            self.driver.plug(
+                self.network_id, self.port_id,
+                self.device_name, self.mac_address,
+                self.bridge, self.namespace)
+
+        expected = [mock.call(), mock.call('sudo'),
+                    mock.call().add_veth(self.device_name,
+                                         self.device_name,
+                                         namespace2=self.namespace),
+                    mock.call().ensure_namespace(self.namespace),
+                    mock.call().ensure_namespace().add_device_to_namespace(
+                        mock.ANY)]
+        ns_dev.assert_has_calls(
+            [mock.call.link.set_address(self.mac_address)])
+
+        root_dev.assert_has_calls([mock.call.link.set_up()])
+        ns_dev.assert_has_calls([mock.call.link.set_up()])
+        self.ip.assert_has_calls(expected, True)
+        host = mock.Mock()
+        self.api().get_host = mock.Mock(return_value=host)
+        self.api.assert_has_calls([mock.call().add_host_interface_port])
+
+    def test_unplug(self):
+        with mock.patch.object(utils, 'execute'):
+            self.driver.unplug(self.device_name, self.bridge, self.namespace)
+
+        self.ip_dev.assert_has_calls([
+            mock.call(self.device_name, self.driver.root_helper,
+                      self.namespace),
+            mock.call().link.delete()])
+        self.ip.assert_has_calls(mock.call().garbage_collect_namespace())
index db1b461f2e8bd98e0c861f2bb4f16423559939de..1dd6b3a0e3ee851bc97180c7fd4dc8ab085e6486 100644 (file)
@@ -19,7 +19,6 @@
 # @author: Ryu Ishimoto, Midokura Japan KK
 # @author: Tomoe Sugihara, Midokura Japan KK
 
-
 import mock
 import testtools
 import webob.exc as w_exc
@@ -33,55 +32,8 @@ def _create_test_chain(id, name, tenant_id):
     return {'id': id, 'name': name, 'tenant_id': tenant_id}
 
 
-def _create_test_port_group(sg_id, sg_name, id, tenant_id):
-    return {"id": id, "name": "OS_SG_%s_%s" % (sg_id, sg_name),
-            "tenant_id": tenant_id}
-
-
-def _create_test_router_in_chain(router_id, id, tenant_id):
-    name = "OS_ROUTER_IN_%s" % router_id
-    return _create_test_chain(id, name, tenant_id)
-
-
-def _create_test_router_out_chain(router_id, id, tenant_id):
-    name = "OS_ROUTER_OUT_%s" % router_id
-    return _create_test_chain(id, name, tenant_id)
-
-
-def _create_test_rule(id, chain_id, properties):
-    return {"id": id, "chain_id": chain_id, "properties": properties}
-
-
-def _create_test_sg_in_chain(sg_id, sg_name, id, tenant_id):
-    if sg_name:
-        name = "OS_SG_%s_%s_IN" % (sg_id, sg_name)
-    else:
-        name = "OS_SG_%s_IN" % sg_id
-    return _create_test_chain(id, name, tenant_id)
-
-
-def _create_test_sg_out_chain(sg_id, sg_name, id, tenant_id):
-    if sg_name:
-        name = "OS_SG_%s_%s_OUT" % (sg_id, sg_name)
-    else:
-        name = "OS_SG_%s_OUT" % sg_id
-    return _create_test_chain(id, name, tenant_id)
-
-
-def _create_test_sg_rule(tenant_id, sg_id, id,
-                         direction="egress", protocol="tcp", port_min=1,
-                         port_max=65535, src_ip='192.168.1.0/24',
-                         src_group_id=None, ethertype=0x0800, properties=None):
-    return {"tenant_id": tenant_id, "security_group_id": sg_id,
-            "id": id, "direction": direction, "protocol": protocol,
-            "remote_ip_prefix": src_ip, "remote_group_id": src_group_id,
-            "port_range_min": port_min, "port_range_max": port_max,
-            "ethertype": ethertype, "external_id": None}
-
-
-def _create_test_sg_chain_rule(id, chain_id, sg_rule_id):
-    props = {"os_sg_rule_id": sg_rule_id}
-    return _create_test_rule(id, chain_id, props)
+def _create_test_port_group(id, name, tenant_id):
+    return {"id": id, "name": name, "tenant_id": tenant_id}
 
 
 class MidoClientTestCase(testtools.TestCase):
@@ -94,137 +46,59 @@ class MidoClientTestCase(testtools.TestCase):
         self.mock_api_cfg.setup()
         self.client = midonet_lib.MidoClient(self.mock_api)
 
-    def test_create_for_sg(self):
-        sg_id = uuidutils.generate_uuid()
-        sg_name = 'test-sg'
-        calls = [mock.call.add_chain().tenant_id(self._tenant_id),
-                 mock.call.add_port_group().tenant_id(self._tenant_id)]
+    def test_delete_chains_by_names(self):
 
-        self.client.create_for_sg(self._tenant_id, sg_id, sg_name)
+        tenant_id = uuidutils.generate_uuid()
+        chain1_id = uuidutils.generate_uuid()
+        chain1 = _create_test_chain(chain1_id, "chain1", tenant_id)
+
+        chain2_id = uuidutils.generate_uuid()
+        chain2 = _create_test_chain(chain2_id, "chain2", tenant_id)
+
+        calls = [mock.call.delete_chain(chain1_id),
+                 mock.call.delete_chain(chain2_id)]
+        self.mock_api_cfg.chains_in = [chain2, chain1]
+        self.client.delete_chains_by_names(tenant_id, ["chain1", "chain2"])
 
         self.mock_api.assert_has_calls(calls, any_order=True)
 
-    def test_create_for_sg_rule(self):
-        sg_id = uuidutils.generate_uuid()
-        sg_name = 'test-sg'
-        in_chain_id = uuidutils.generate_uuid()
-        out_chain_id = uuidutils.generate_uuid()
-        self.mock_api_cfg.chains_in = [
-            _create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
-                                     self._tenant_id),
-            _create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
-                                      self._tenant_id)]
-
-        sg_rule_id = uuidutils.generate_uuid()
-        sg_rule = _create_test_sg_rule(self._tenant_id, sg_id, sg_rule_id)
-
-        props = {"os_sg_rule_id": sg_rule_id}
-        calls = [mock.call.add_rule().port_group(None).type(
-            'accept').nw_proto(6).nw_src_address(
-                '192.168.1.0').nw_src_length(24).tp_src_start(
-                    None).tp_src_end(None).tp_dst_start(1).tp_dst_end(
-                        65535).properties(props).create()]
-
-        self.client.create_for_sg_rule(sg_rule)
-
-        # Egress chain rule added
-        self.mock_api_cfg.chains_out[0].assert_has_calls(calls)
-
-    def test_create_router_chains(self):
-        router = mock_lib.get_router_mock(tenant_id=self._tenant_id)
-        api_calls = [mock.call.add_chain().tenant_id(self._tenant_id)]
-        router_calls = [
-            mock.call.inbound_filter_id().outbound_filter_id().update()]
-
-        self.client.create_router_chains(router)
-
-        self.mock_api.assert_has_calls(api_calls)
-        router.assert_has_calls(router_calls)
-
-    def test_delete_for_sg(self):
-        sg_id = uuidutils.generate_uuid()
-        sg_name = 'test-sg'
-        in_chain_id = uuidutils.generate_uuid()
-        out_chain_id = uuidutils.generate_uuid()
-        pg_id = uuidutils.generate_uuid()
-        self.mock_api_cfg.chains_in = [
-            _create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
-                                     self._tenant_id),
-            _create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
-                                      self._tenant_id)]
-        self.mock_api_cfg.port_groups_in = [
-            _create_test_port_group(sg_id, sg_name, pg_id, self._tenant_id)]
-
-        calls = [mock.call.get_chains({"tenant_id": self._tenant_id}),
-                 mock.call.delete_chain(in_chain_id),
-                 mock.call.delete_chain(out_chain_id),
-                 mock.call.get_port_groups({"tenant_id": self._tenant_id}),
-                 mock.call.delete_port_group(pg_id)]
-
-        self.client.delete_for_sg(self._tenant_id, sg_id, sg_name)
-
-        self.mock_api.assert_has_calls(calls)
-
-    def test_delete_for_sg_rule(self):
-        sg_id = uuidutils.generate_uuid()
-        sg_name = 'test-sg'
-        in_chain_id = uuidutils.generate_uuid()
-        out_chain_id = uuidutils.generate_uuid()
-        self.mock_api_cfg.chains_in = [
-            _create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
-                                     self._tenant_id),
-            _create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
-                                      self._tenant_id)]
-
-        rule_id = uuidutils.generate_uuid()
-        sg_rule_id = uuidutils.generate_uuid()
-        rule = _create_test_sg_chain_rule(rule_id, in_chain_id, sg_rule_id)
-        self.mock_api_cfg.chains_in[0]['rules'] = [rule]
-        sg_rule = _create_test_sg_rule(self._tenant_id, sg_id, sg_rule_id)
-
-        self.client.delete_for_sg_rule(sg_rule)
-
-        self.mock_api.delete_rule.assert_called_once_with(rule_id)
+    def test_delete_port_group_by_name(self):
 
-    def test_get_bridge(self):
-        bridge_id = uuidutils.generate_uuid()
+        tenant_id = uuidutils.generate_uuid()
+        pg1_id = uuidutils.generate_uuid()
+        pg1 = _create_test_port_group(pg1_id, "pg1", tenant_id)
+        pg2_id = uuidutils.generate_uuid()
+        pg2 = _create_test_port_group(pg2_id, "pg2", tenant_id)
 
-        bridge = self.client.get_bridge(bridge_id)
+        self.mock_api_cfg.port_groups_in = [pg1, pg2]
+        self.client.delete_port_group_by_name(tenant_id, "pg1")
+        self.mock_api.delete_port_group.assert_called_once_with(pg1_id)
 
-        self.assertIsNotNone(bridge)
-        self.assertEqual(bridge.get_id(), bridge_id)
+    def test_create_dhcp(self):
 
-    def test_get_bridge_error(self):
-        self.mock_api.get_bridge.side_effect = w_exc.HTTPInternalServerError()
-        self.assertRaises(midonet_lib.MidonetApiException,
-                          self.client.get_bridge, uuidutils.generate_uuid())
+        bridge = mock.Mock()
+        gw_call = mock.call.add_dhcp_subnet().default_gateway("192.168.1.1")
+        subnet_prefix_call = gw_call.subnet_prefix("192.168.1.0")
+        subnet_length_call = subnet_prefix_call.subnet_length(24)
 
-    def test_get_bridge_not_found(self):
-        self.mock_api.get_bridge.side_effect = w_exc.HTTPNotFound()
-        self.assertRaises(midonet_lib.MidonetResourceNotFound,
-                          self.client.get_bridge, uuidutils.generate_uuid())
+        calls = [gw_call, subnet_prefix_call, subnet_length_call,
+                 subnet_length_call.create()]
 
-    def test_get_port_groups_for_sg(self):
-        sg_id = uuidutils.generate_uuid()
-        pg_id = uuidutils.generate_uuid()
-        self.mock_api_cfg.port_groups_in = [
-            _create_test_port_group(sg_id, 'test-sg', pg_id, self._tenant_id)]
+        self.client.create_dhcp(bridge, "192.168.1.1", "192.168.1.0/24")
+        bridge.assert_has_calls(calls, any_order=True)
 
-        pg = self.client.get_port_groups_for_sg(self._tenant_id, sg_id)
+    def test_add_dhcp_host(self):
 
-        self.assertIsNotNone(pg)
-        self.assertEqual(pg.get_id(), pg_id)
+        bridge = mock.Mock()
+        dhcp_subnet_call = mock.call.get_dhcp_subnet("10.0.0.0_24")
+        ip_addr_call = dhcp_subnet_call.add_dhcp_host().ip_addr("10.0.0.10")
+        mac_addr_call = ip_addr_call.mac_addr("2A:DB:6B:8C:19:99")
+        calls = [dhcp_subnet_call, ip_addr_call, mac_addr_call,
+                 mac_addr_call.create()]
 
-    def _create_test_rule(self, tenant_id, sg_id, rule_id, direction="egress",
-                          protocol="tcp", port_min=1, port_max=65535,
-                          src_ip='192.168.1.0/24', src_group_id=None,
-                          ethertype=0x0800):
-        return {"tenant_id": tenant_id, "security_group_id": sg_id,
-                "rule_id": rule_id, "direction": direction,
-                "protocol": protocol,
-                "remote_ip_prefix": src_ip, "remote_group_id": src_group_id,
-                "port_range_min": port_min, "port_range_max": port_max,
-                "ethertype": ethertype, "id": rule_id, "external_id": None}
+        self.client.add_dhcp_host(bridge, "10.0.0.0/24", "10.0.0.10",
+                                  "2A:DB:6B:8C:19:99")
+        bridge.assert_has_calls(calls, any_order=True)
 
     def test_get_router_error(self):
         self.mock_api.get_router.side_effect = w_exc.HTTPInternalServerError()
@@ -236,43 +110,20 @@ class MidoClientTestCase(testtools.TestCase):
         self.assertRaises(midonet_lib.MidonetResourceNotFound,
                           self.client.get_router, uuidutils.generate_uuid())
 
-    def test_get_router_chains(self):
-        router_id = uuidutils.generate_uuid()
-        in_chain_id = uuidutils.generate_uuid()
-        out_chain_id = uuidutils.generate_uuid()
-        self.mock_api_cfg.chains_in = [
-            _create_test_router_in_chain(router_id, in_chain_id,
-                                         self._tenant_id),
-            _create_test_router_out_chain(router_id, out_chain_id,
-                                          self._tenant_id)]
-
-        chains = self.client.get_router_chains(self._tenant_id, router_id)
-
-        self.mock_api.assert_has_calls(mock.call.get_chains(
-            {"tenant_id": self._tenant_id}))
-        self.assertEqual(len(chains), 2)
-        self.assertIn('in', chains)
-        self.assertIn('out', chains)
-        self.assertEqual(chains['in'].get_id(), in_chain_id)
-        self.assertEqual(chains['out'].get_id(), out_chain_id)
-
-    def test_get_sg_chains(self):
-        sg_id = uuidutils.generate_uuid()
-        sg_name = 'test-sg'
-        in_chain_id = uuidutils.generate_uuid()
-        out_chain_id = uuidutils.generate_uuid()
-        self.mock_api_cfg.chains_in = [
-            _create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
-                                     self._tenant_id),
-            _create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
-                                      self._tenant_id)]
-
-        chains = self.client.get_sg_chains(self._tenant_id, sg_id)
-
-        self.mock_api.assert_has_calls(mock.call.get_chains(
-            {"tenant_id": self._tenant_id}))
-        self.assertEqual(len(chains), 2)
-        self.assertIn('in', chains)
-        self.assertIn('out', chains)
-        self.assertEqual(chains['in'].get_id(), in_chain_id)
-        self.assertEqual(chains['out'].get_id(), out_chain_id)
+    def test_get_bridge_error(self):
+        self.mock_api.get_bridge.side_effect = w_exc.HTTPInternalServerError()
+        self.assertRaises(midonet_lib.MidonetApiException,
+                          self.client.get_bridge, uuidutils.generate_uuid())
+
+    def test_get_bridge_not_found(self):
+        self.mock_api.get_bridge.side_effect = w_exc.HTTPNotFound()
+        self.assertRaises(midonet_lib.MidonetResourceNotFound,
+                          self.client.get_bridge, uuidutils.generate_uuid())
+
+    def test_get_bridge(self):
+        bridge_id = uuidutils.generate_uuid()
+
+        bridge = self.client.get_bridge(bridge_id)
+
+        self.assertIsNotNone(bridge)
+        self.assertEqual(bridge.get_id(), bridge_id)