]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
L3 API support for BigSwitch-FloodLight Plugin
authorSumit Naiksatam <sumitnaiksatam@gmail.com>
Wed, 6 Feb 2013 04:25:46 +0000 (20:25 -0800)
committerSumit Naiksatam <sumitnaiksatam@gmail.com>
Fri, 15 Feb 2013 21:05:15 +0000 (13:05 -0800)
In keeping with the philosophy  of the RESTProxy plugin, L3 extension calls
are processed (CRUD of logical resources) and the state changes are proxied
to a backend controller.

A configuration variable specific to the RESTProxy plugin is being added
to identify that particular Quantum server's ID.

blueprint quantum-floodlight-bigswitch-l3

Change-Id: I24be02cd836352497fe5b1e7622d138e0c816377

etc/quantum/plugins/bigswitch/restproxy.ini
quantum/plugins/bigswitch/plugin.py
quantum/plugins/bigswitch/vcsversion.py [new file with mode: 0644]
quantum/plugins/bigswitch/version.py
quantum/tests/unit/bigswitch/test_router_db.py [new file with mode: 0644]

index 957179795b2eb8a514ab7cec19b2dba999916414..afe71f466606d44a28321ca2b158bba3d583dd62 100644 (file)
@@ -28,13 +28,13 @@ reconnect_interval = 2
 #
 # The following parameters are supported:
 #   servers     :   <host:port>[,<host:port>]*  (Error if not set)
-#   serverauth  :   <username:password>         (default: no auth)
-#   serverssl   :   True | False                (default: False)
-#   syncdata   :   True | False                (default: False)
-#   servertimeout   :  10                       (default: 10 seconds)
+#   server_auth  :   <username:password>         (default: no auth)
+#   server_ssl   :   True | False                (default: False)
+#   sync_data   :   True | False                (default: False)
+#   server_timeout   :  10                       (default: 10 seconds)
 #
 servers=localhost:8080
-#serverauth=username:password
-#serverssl=True
-#syncdata=True
-#servertimeout=10
+#server_auth=username:password
+#server_ssl=True
+#sync_data=True
+#server_timeout=10
index 5105da748ed124f5c3649b1abfd424eb2b464401..8566e72099c9f9b10af65e2d85b013081f515ca6 100644 (file)
@@ -45,18 +45,24 @@ on port-attach) on an additional PUT to do a bulk dump of all persistent data.
 """
 
 import base64
+import copy
 import httplib
 import json
 import socket
 
+from quantum.common import constants as const
 from quantum.common import exceptions
 from quantum.common import rpc as q_rpc
 from quantum.common import topics
+from quantum.common import utils
 from quantum import context as qcontext
 from quantum.db import api as db
 from quantum.db import db_base_plugin_v2
 from quantum.db import dhcp_rpc_base
+from quantum.db import l3_db
+from quantum.extensions import l3
 from quantum.openstack.common import cfg
+from quantum.openstack.common import lockutils
 from quantum.openstack.common import log as logging
 from quantum.openstack.common import rpc
 from quantum.plugins.bigswitch.version import version_string_with_vcs
@@ -69,16 +75,20 @@ restproxy_opts = [
     cfg.StrOpt('servers', default='localhost:8800',
                help=_("A comma separated list of servers and port numbers "
                       "to proxy request to.")),
-    cfg.StrOpt('serverauth', default='username:password',
-               help=_("Server authentication"),
-               secret=True),
-    cfg.BoolOpt('serverssl', default=False,
+    cfg.StrOpt('server_auth', default='username:password', secret=True,
+               help=_("Server authentication")),
+    cfg.BoolOpt('server_ssl', default=False,
                 help=_("Use SSL to connect")),
-    cfg.BoolOpt('syncdata', default=False,
+    cfg.BoolOpt('sync_data', default=False,
                 help=_("Sync data on connect")),
-    cfg.IntOpt('servertimeout', default=10,
+    cfg.IntOpt('server_timeout', default=10,
                help=_("Maximum number of seconds to wait for proxy request "
                       "to connect and complete.")),
+    cfg.StrOpt('quantum_id', default='Quantum-' + utils.get_hostname(),
+               help=_("User defined identifier for this Quantum deployment")),
+    cfg.BoolOpt('add_meta_server_route', default=True,
+                help=_("Flag to decide if a route to the metadata server "
+                       "should be injected into the VM")),
 ]
 
 
@@ -88,13 +98,20 @@ cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
 # The following are used to invoke the API on the external controller
 NET_RESOURCE_PATH = "/tenants/%s/networks"
 PORT_RESOURCE_PATH = "/tenants/%s/networks/%s/ports"
+ROUTER_RESOURCE_PATH = "/tenants/%s/routers"
+ROUTER_INTF_OP_PATH = "/tenants/%s/routers/%s/interfaces"
 NETWORKS_PATH = "/tenants/%s/networks/%s"
 PORTS_PATH = "/tenants/%s/networks/%s/ports/%s"
 ATTACHMENT_PATH = "/tenants/%s/networks/%s/ports/%s/attachment"
+ROUTERS_PATH = "/tenants/%s/routers/%s"
+ROUTER_INTF_PATH = "/tenants/%s/routers/%s/interfaces/%s"
 SUCCESS_CODES = range(200, 207)
 FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503,
                  504, 505]
 SYNTAX_ERROR_MESSAGE = 'Syntax error in server config file, aborting plugin'
+BASE_URI = '/networkService/v1.1'
+ORCHESTRATION_SERVICE_ID = 'Quantum v2.0'
+METADATA_SERVER_IP = '169.254.169.254'
 
 
 class RemoteRestError(exceptions.QuantumException):
@@ -109,7 +126,8 @@ class RemoteRestError(exceptions.QuantumException):
 class ServerProxy(object):
     """REST server proxy to a network controller."""
 
-    def __init__(self, server, port, ssl, auth, timeout, base_uri, name):
+    def __init__(self, server, port, ssl, auth, quantum_id, timeout,
+                 base_uri, name):
         self.server = server
         self.port = port
         self.ssl = ssl
@@ -118,9 +136,11 @@ class ServerProxy(object):
         self.name = name
         self.success_codes = SUCCESS_CODES
         self.auth = None
+        self.quantum_id = quantum_id
         if auth:
             self.auth = 'Basic ' + base64.encodestring(auth).strip()
 
+    @lockutils.synchronized('rest_call', 'bsn-', external=True)
     def rest_call(self, action, resource, data, headers):
         uri = self.base_uri + resource
         body = json.dumps(data)
@@ -129,6 +149,8 @@ class ServerProxy(object):
         headers['Content-type'] = 'application/json'
         headers['Accept'] = 'application/json'
         headers['QuantumProxy-Agent'] = self.name
+        headers['Instance-ID'] = self.quantum_id
+        headers['Orchestration-Service-ID'] = ORCHESTRATION_SERVICE_ID
         if self.auth:
             headers['Authorization'] = self.auth
 
@@ -180,20 +202,21 @@ class ServerProxy(object):
 
 
 class ServerPool(object):
-    def __init__(self, servers, ssl, auth, timeout=10,
+    def __init__(self, servers, ssl, auth, quantum_id, timeout=10,
                  base_uri='/quantum/v1.0', name='QuantumRestProxy'):
         self.base_uri = base_uri
         self.timeout = timeout
         self.name = name
         self.auth = auth
         self.ssl = ssl
+        self.quantum_id = quantum_id
         self.servers = []
         for server_port in servers:
             self.servers.append(self.server_proxy_for(*server_port))
 
     def server_proxy_for(self, server, port):
-        return ServerProxy(server, port, self.ssl, self.auth, self.timeout,
-                           self.base_uri, self.name)
+        return ServerProxy(server, port, self.ssl, self.auth, self.quantum_id,
+                           self.timeout, self.base_uri, self.name)
 
     def server_failure(self, resp):
         """Define failure codes as required.
@@ -254,7 +277,10 @@ class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):
         return q_rpc.PluginRpcDispatcher([self])
 
 
-class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
+class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2,
+                         l3_db.L3_NAT_db_mixin):
+
+    supported_extension_aliases = ["router"]
 
     def __init__(self):
         LOG.info(_('QuantumRestProxy: Starting plugin. Version=%s'),
@@ -265,12 +291,14 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
 
         # 'servers' is the list of network controller REST end-points
         # (used in order specified till one suceeds, and it is sticky
-        # till next failure). Use 'serverauth' to encode api-key
+        # till next failure). Use 'server_auth' to encode api-key
         servers = cfg.CONF.RESTPROXY.servers
-        serverauth = cfg.CONF.RESTPROXY.serverauth
-        serverssl = cfg.CONF.RESTPROXY.serverssl
-        syncdata = cfg.CONF.RESTPROXY.syncdata
-        timeout = cfg.CONF.RESTPROXY.servertimeout
+        server_auth = cfg.CONF.RESTPROXY.server_auth
+        server_ssl = cfg.CONF.RESTPROXY.server_ssl
+        sync_data = cfg.CONF.RESTPROXY.sync_data
+        timeout = cfg.CONF.RESTPROXY.server_timeout
+        quantum_id = cfg.CONF.RESTPROXY.quantum_id
+        self.add_meta_server_route = cfg.CONF.RESTPROXY.add_meta_server_route
 
         # validate config
         assert servers is not None, 'Servers not defined. Aborting plugin'
@@ -279,8 +307,8 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
         assert all(len(s) == 2 for s in servers), SYNTAX_ERROR_MESSAGE
 
         # init network ctrl connections
-        self.servers = ServerPool(servers, serverssl, serverauth,
-                                  timeout)
+        self.servers = ServerPool(servers, server_ssl, server_auth, quantum_id,
+                                  timeout, BASE_URI)
 
         # init dhcp support
         self.topic = topics.PLUGIN
@@ -291,7 +319,7 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
                                   fanout=False)
         # Consume from all consumers in a thread
         self.conn.consume_in_thread()
-        if syncdata:
+        if sync_data:
             self._send_all_data()
 
         LOG.debug(_("QuantumRestProxyV2: initialization done"))
@@ -320,26 +348,25 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
 
         LOG.debug(_("QuantumRestProxyV2: create_network() called"))
 
+        self._warn_on_state_status(network['network'])
+
         # Validate args
         tenant_id = self._get_tenant_id_for_create(context, network["network"])
-        net_name = network["network"]["name"]
-        if network["network"]["admin_state_up"] is False:
-            LOG.warning(_("Network with admin_state_up=False are not yet "
-                        "supported by this plugin. Ignoring setting for "
-                        "network %s"), net_name)
 
-        # create in DB
-        new_net = super(QuantumRestProxyV2, self).create_network(context,
-                                                                 network)
+        session = context.session
+        with session.begin(subtransactions=True):
+            # create network in DB
+            new_net = super(QuantumRestProxyV2, self).create_network(context,
+                                                                     network)
+            self._process_l3_create(context, network['network'], new_net['id'])
+            self._extend_network_dict_l3(context, new_net)
 
-        # create on networl ctrl
+        # create network on the network controller
         try:
             resource = NET_RESOURCE_PATH % tenant_id
+            mapped_network = self._get_mapped_network_with_subnets(new_net)
             data = {
-                "network": {
-                    "id": new_net["id"],
-                    "name": new_net["name"],
-                }
+                "network": mapped_network
             }
             ret = self.servers.post(resource, data)
             if not self.servers.action_success(ret):
@@ -379,36 +406,28 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
 
         LOG.debug(_("QuantumRestProxyV2.update_network() called"))
 
-        # Validate Args
-        if network["network"].get("admin_state_up"):
-            if network["network"]["admin_state_up"] is False:
-                LOG.warning(_("Network with admin_state_up=False are not yet "
-                              "supported by this plugin. Ignoring setting for "
-                              "network %s", net_name))
+        self._warn_on_state_status(network['network'])
 
-        # update DB
-        orig_net = super(QuantumRestProxyV2, self).get_network(context, net_id)
-        tenant_id = orig_net["tenant_id"]
-        new_net = super(QuantumRestProxyV2, self).update_network(
-            context, net_id, network)
+        session = context.session
+        with session.begin(subtransactions=True):
+            orig_net = super(QuantumRestProxyV2, self).get_network(context,
+                                                                   net_id)
+            new_net = super(QuantumRestProxyV2, self).update_network(context,
+                                                                     net_id,
+                                                                     network)
+            self._process_l3_update(context, network['network'], net_id)
+            self._extend_network_dict_l3(context, new_net)
 
         # update network on network controller
-        if new_net["name"] != orig_net["name"]:
-            try:
-                resource = NETWORKS_PATH % (tenant_id, net_id)
-                data = {
-                    "network": new_net,
-                }
-                ret = self.servers.put(resource, data)
-                if not self.servers.action_success(ret):
-                    raise RemoteRestError(ret[2])
-            except RemoteRestError as e:
-                LOG.error(_("QuantumRestProxyV2: Unable to update remote "
-                            "network: %s"), e.message)
-                # reset network to original state
-                super(QuantumRestProxyV2, self).update_network(
-                    context, id, orig_net)
-                raise
+        try:
+            self._send_update_network(new_net)
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2: Unable to update remote "
+                        "network: %s"), e.message)
+            # reset network to original state
+            super(QuantumRestProxyV2, self).update_network(context, id,
+                                                           orig_net)
+            raise
 
         # return updated network
         return new_net
@@ -430,6 +449,17 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
         orig_net = super(QuantumRestProxyV2, self).get_network(context, net_id)
         tenant_id = orig_net["tenant_id"]
 
+        filter = {'network_id': [net_id]}
+        ports = self.get_ports(context, filters=filter)
+
+        # check if there are any tenant owned ports in-use
+        auto_delete_port_owners = db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS
+        only_auto_del = all(p['device_owner'] in auto_delete_port_owners
+                            for p in ports)
+
+        if not only_auto_del:
+            raise exceptions.NetworkInUse(net_id=net_id)
+
         # delete from network ctrl. Remote error on delete is ignored
         try:
             resource = NETWORKS_PATH % (tenant_id, net_id)
@@ -442,6 +472,7 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
         except RemoteRestError as e:
             LOG.error(_("QuantumRestProxyV2: Unable to update remote "
                         "network: %s"), e.message)
+            raise
 
     def create_port(self, context, port):
         """Create a port, which is a connection point of a device
@@ -477,24 +508,28 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
         net = super(QuantumRestProxyV2,
                     self).get_network(context, new_port["network_id"])
 
+        if self.add_meta_server_route:
+            if new_port['device_owner'] == 'network:dhcp':
+                destination = METADATA_SERVER_IP + '/32'
+                self._add_host_route(context, destination, new_port)
+
         # create on networl ctrl
         try:
             resource = PORT_RESOURCE_PATH % (net["tenant_id"], net["id"])
+            mapped_port = self._map_state_and_status(new_port)
             data = {
-                "port": {
-                    "id": new_port["id"],
-                    "state": "ACTIVE",
-                }
+                "port": mapped_port
             }
             ret = self.servers.post(resource, data)
             if not self.servers.action_success(ret):
                 raise RemoteRestError(ret[2])
 
             # connect device to network, if present
-            if port["port"].get("device_id"):
+            device_id = port["port"].get("device_id")
+            if device_id:
                 self._plug_interface(context,
                                      net["tenant_id"], net["id"],
-                                     new_port["id"], new_port["id"] + "00")
+                                     new_port["id"], device_id)
         except RemoteRestError as e:
             LOG.error(_("QuantumRestProxyV2: Unable to create remote port: "
                         "%s"), e.message)
@@ -536,6 +571,8 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
         """
         LOG.debug(_("QuantumRestProxyV2: update_port() called"))
 
+        self._warn_on_state_status(port['port'])
+
         # Validate Args
         orig_port = super(QuantumRestProxyV2, self).get_port(context, port_id)
 
@@ -547,7 +584,8 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
         try:
             resource = PORTS_PATH % (orig_port["tenant_id"],
                                      orig_port["network_id"], port_id)
-            data = {"port": new_port, }
+            mapped_port = self._map_state_and_status(new_port)
+            data = {"port": mapped_port}
             ret = self.servers.put(resource, data)
             if not self.servers.action_success(ret):
                 raise RemoteRestError(ret[2])
@@ -557,10 +595,11 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
                     self._unplug_interface(context, orig_port["tenant_id"],
                                            orig_port["network_id"],
                                            orig_port["id"])
-                if new_port.get("device_id"):
+                device_id = new_port.get("device_id")
+                if device_id:
                     self._plug_interface(context, new_port["tenant_id"],
                                          new_port["network_id"],
-                                         new_port["id"], new_port["id"] + "00")
+                                         new_port["id"], device_id)
 
         except RemoteRestError as e:
             LOG.error(_("QuantumRestProxyV2: Unable to create remote port: "
@@ -573,7 +612,7 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
         # return new_port
         return new_port
 
-    def delete_port(self, context, port_id):
+    def delete_port(self, context, port_id, l3_port_check=True):
         """Delete a port.
         :param context: quantum api request context
         :param id: UUID representing the port to delete.
@@ -586,6 +625,15 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
 
         LOG.debug(_("QuantumRestProxyV2: delete_port() called"))
 
+        # if needed, check to see if this is a port owned by
+        # and l3-router.  If so, we should prevent deletion.
+        if l3_port_check:
+            self.prevent_l3_port_deletion(context, port_id)
+        self.disassociate_floatingips(context, port_id)
+
+        super(QuantumRestProxyV2, self).delete_port(context, port_id)
+
+    def _delete_port(self, context, port_id):
         # Delete from DB
         port = super(QuantumRestProxyV2, self).get_port(context, port_id)
 
@@ -600,12 +648,13 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
             if port.get("device_id"):
                 self._unplug_interface(context, port["tenant_id"],
                                        port["network_id"], port["id"])
-            ret_val = super(QuantumRestProxyV2, self).delete_port(context,
-                                                                  port_id)
+            ret_val = super(QuantumRestProxyV2, self)._delete_port(context,
+                                                                   port_id)
             return ret_val
         except RemoteRestError as e:
             LOG.error(_("QuantumRestProxyV2: Unable to update remote port: "
                         "%s"), e.message)
+            raise
 
     def _plug_interface(self, context, tenant_id, net_id, port_id,
                         remote_interface_id):
@@ -625,22 +674,6 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
             port = super(QuantumRestProxyV2, self).get_port(context, port_id)
             mac = port["mac_address"]
 
-            for ip in port["fixed_ips"]:
-                if ip.get("subnet_id") is not None:
-                    subnet = super(QuantumRestProxyV2, self).get_subnet(
-                        context, ip["subnet_id"])
-                    gateway = subnet.get("gateway_ip")
-                    if gateway is not None:
-                        resource = NETWORKS_PATH % (tenant_id, net_id)
-                        data = {"network":
-                                {"id": net_id,
-                                 "gateway": gateway,
-                                 }
-                                }
-                        ret = self.servers.put(resource, data)
-                        if not self.servers.action_success(ret):
-                            raise RemoteRestError(ret[2])
-
             if mac is not None:
                 resource = ATTACHMENT_PATH % (tenant_id, net_id, port_id)
                 data = {"attachment":
@@ -676,31 +709,326 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
             LOG.error(_("QuantumRestProxyV2: Unable to update remote port: "
                         "%s"), e.message)
 
+    def create_subnet(self, context, subnet):
+        LOG.debug(_("QuantumRestProxyV2: create_subnet() called"))
+
+        self._warn_on_state_status(subnet['subnet'])
+
+        # create subnet in DB
+        new_subnet = super(QuantumRestProxyV2, self).create_subnet(context,
+                                                                   subnet)
+        net_id = new_subnet['network_id']
+        orig_net = super(QuantumRestProxyV2, self).get_network(context,
+                                                               net_id)
+        # update network on network controller
+        try:
+            self._send_update_network(orig_net)
+        except RemoteRestError as e:
+            # rollback creation of subnet
+            super(QuantumRestProxyV2, self).delete_subnet(context,
+                                                          subnet['id'])
+            raise
+        return new_subnet
+
+    def update_subnet(self, context, id, subnet):
+        LOG.debug(_("QuantumRestProxyV2: update_subnet() called"))
+
+        self._warn_on_state_status(subnet['subnet'])
+
+        orig_subnet = super(QuantumRestProxyV2, self)._get_subnet(context, id)
+
+        # update subnet in DB
+        new_subnet = super(QuantumRestProxyV2, self).update_subnet(context, id,
+                                                                   subnet)
+        net_id = new_subnet['network_id']
+        orig_net = super(QuantumRestProxyV2, self).get_network(context,
+                                                               net_id)
+        # update network on network controller
+        try:
+            self._send_update_network(orig_net)
+        except RemoteRestError as e:
+            # rollback updation of subnet
+            super(QuantumRestProxyV2, self).update_subnet(context, id,
+                                                          orig_subnet)
+            raise
+        return new_subnet
+
+    def delete_subnet(self, context, id):
+        LOG.debug(_("QuantumRestProxyV2: delete_subnet() called"))
+        orig_subnet = super(QuantumRestProxyV2, self).get_subnet(context, id)
+        net_id = orig_subnet['network_id']
+        # delete subnet in DB
+        super(QuantumRestProxyV2, self).delete_subnet(context, id)
+        orig_net = super(QuantumRestProxyV2, self).get_network(context,
+                                                               net_id)
+        # update network on network controller
+        try:
+            self._send_update_network(orig_net)
+        except RemoteRestError as e:
+            # TODO (Sumit): rollback deletion of subnet
+            raise
+
+    def create_router(self, context, router):
+        LOG.debug(_("QuantumRestProxyV2: create_router() called"))
+
+        self._warn_on_state_status(router['router'])
+
+        tenant_id = self._get_tenant_id_for_create(context, router["router"])
+
+        # create router in DB
+        new_router = super(QuantumRestProxyV2, self).create_router(context,
+                                                                   router)
+
+        # create router on the network controller
+        try:
+            resource = ROUTER_RESOURCE_PATH % tenant_id
+            mapped_router = self._map_state_and_status(new_router)
+            data = {
+                "router": mapped_router
+            }
+            ret = self.servers.post(resource, data)
+            if not self.servers.action_success(ret):
+                raise RemoteRestError(ret[2])
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2: Unable to create remote router: "
+                        "%s"), e.message)
+            super(QuantumRestProxyV2, self).delete_router(context,
+                                                          new_router['id'])
+            raise
+
+        # return created router
+        return new_router
+
+    def update_router(self, context, router_id, router):
+
+        LOG.debug(_("QuantumRestProxyV2.update_router() called"))
+
+        self._warn_on_state_status(router['router'])
+
+        orig_router = super(QuantumRestProxyV2, self).get_router(context,
+                                                                 router_id)
+        tenant_id = orig_router["tenant_id"]
+        new_router = super(QuantumRestProxyV2, self).update_router(context,
+                                                                   router_id,
+                                                                   router)
+
+        # update router on network controller
+        try:
+            resource = ROUTERS_PATH % (tenant_id, router_id)
+            mapped_router = self._map_state_and_status(new_router)
+            data = {
+                "router": mapped_router
+            }
+            ret = self.servers.put(resource, data)
+            if not self.servers.action_success(ret):
+                raise RemoteRestError(ret[2])
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2: Unable to update remote router: "
+                        "%s"), e.message)
+            # reset router to original state
+            super(QuantumRestProxyV2, self).update_router(context,
+                                                          router_id,
+                                                          orig_router)
+            raise
+
+        # return updated router
+        return new_router
+
+    def delete_router(self, context, router_id):
+        LOG.debug(_("QuantumRestProxyV2: delete_router() called"))
+
+        with context.session.begin(subtransactions=True):
+            orig_router = self._get_router(context, router_id)
+            tenant_id = orig_router["tenant_id"]
+
+            # Ensure that the router is not used
+            router_filter = {'router_id': [router_id]}
+            fips = self.get_floatingips_count(context.elevated(),
+                                              filters=router_filter)
+            if fips:
+                raise l3.RouterInUse(router_id=router_id)
+
+            device_owner = l3_db.DEVICE_OWNER_ROUTER_INTF
+            device_filter = {'device_id': [router_id],
+                             'device_owner': [device_owner]}
+            ports = self.get_ports_count(context.elevated(),
+                                         filters=device_filter)
+            if ports:
+                raise l3.RouterInUse(router_id=router_id)
+
+        # delete from network ctrl. Remote error on delete is ignored
+        try:
+            resource = ROUTERS_PATH % (tenant_id, router_id)
+            ret = self.servers.delete(resource)
+            if not self.servers.action_success(ret):
+                raise RemoteRestError(ret[2])
+            ret_val = super(QuantumRestProxyV2, self).delete_router(context,
+                                                                    router_id)
+            return ret_val
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2: Unable to delete remote router: "
+                        "%s"), e.message)
+            raise
+
+    def add_router_interface(self, context, router_id, interface_info):
+
+        LOG.debug(_("QuantumRestProxyV2: add_router_interface() called"))
+
+        # Validate args
+        router = self._get_router(context, router_id)
+        tenant_id = router['tenant_id']
+
+        # create interface in DB
+        new_interface_info = super(QuantumRestProxyV2,
+                                   self).add_router_interface(context,
+                                                              router_id,
+                                                              interface_info)
+        port = self._get_port(context, new_interface_info['port_id'])
+        net_id = port['network_id']
+        subnet_id = new_interface_info['subnet_id']
+        # we will use the port's network id as interface's id
+        interface_id = net_id
+        intf_details = self._get_router_intf_details(context,
+                                                     interface_id,
+                                                     subnet_id)
+
+        # create interface on the network controller
+        try:
+            resource = ROUTER_INTF_OP_PATH % (tenant_id, router_id)
+            data = {"interface": intf_details}
+            ret = self.servers.post(resource, data)
+            if not self.servers.action_success(ret):
+                raise RemoteRestError(ret[2])
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2: Unable to create interface: "
+                        "%s"), e.message)
+            super(QuantumRestProxyV2,
+                  self).remove_router_interface(context, router_id,
+                                                interface_info)
+            raise
+
+        return new_interface_info
+
+    def remove_router_interface(self, context, router_id, interface_info):
+
+        LOG.debug(_("QuantumRestProxyV2: remove_router_interface() called"))
+
+        # Validate args
+        router = self._get_router(context, router_id)
+        tenant_id = router['tenant_id']
+
+        # we will first get the interface identifier before deleting in the DB
+        if not interface_info:
+            msg = "Either subnet_id or port_id must be specified"
+            raise exceptions.BadRequest(resource='router', msg=msg)
+        if 'port_id' in interface_info:
+            port = self._get_port(context, interface_info['port_id'])
+            interface_id = port['network_id']
+        elif 'subnet_id' in interface_info:
+            subnet = self._get_subnet(context, interface_info['subnet_id'])
+            interface_id = subnet['network_id']
+        else:
+            msg = "Either subnet_id or port_id must be specified"
+            raise exceptions.BadRequest(resource='router', msg=msg)
+
+        # remove router in DB
+        del_intf_info = super(QuantumRestProxyV2,
+                              self).remove_router_interface(context,
+                                                            router_id,
+                                                            interface_info)
+
+        # create router on the network controller
+        try:
+            resource = ROUTER_INTF_PATH % (tenant_id, router_id, interface_id)
+            ret = self.servers.delete(resource)
+            if not self.servers.action_success(ret):
+                raise RemoteRestError(ret[2])
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2:Unable to delete remote intf: "
+                        "%s"), e.message)
+            raise
+
+        # return new interface
+        return del_intf_info
+
+    def create_floatingip(self, context, floatingip):
+        LOG.debug(_("QuantumRestProxyV2: create_floatingip() called"))
+
+        # create floatingip in DB
+        new_fl_ip = super(QuantumRestProxyV2,
+                          self).create_floatingip(context, floatingip)
+
+        net_id = new_fl_ip['floating_network_id']
+        orig_net = super(QuantumRestProxyV2, self).get_network(context,
+                                                               net_id)
+        # create floatingip on the network controller
+        try:
+            self._send_update_network(orig_net)
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2: Unable to create remote "
+                        "floatin IP: %s"), e.message)
+            super(QuantumRestProxyV2, self).delete_floatingip(context,
+                                                              floatingip)
+            raise
+
+        # return created floating IP
+        return new_fl_ip
+
+    def update_floatingip(self, context, id, floatingip):
+        LOG.debug(_("QuantumRestProxyV2: update_floatingip() called"))
+
+        orig_fl_ip = super(QuantumRestProxyV2, self).get_floatingip(context,
+                                                                    id)
+
+        # update floatingip in DB
+        new_fl_ip = super(QuantumRestProxyV2,
+                          self).update_floatingip(context, id, floatingip)
+
+        net_id = new_fl_ip['floating_network_id']
+        orig_net = super(QuantumRestProxyV2, self).get_network(context,
+                                                               net_id)
+        # update network on network controller
+        try:
+            self._send_update_network(orig_net)
+        except RemoteRestError as e:
+            # rollback updation of subnet
+            super(QuantumRestProxyV2, self).update_floatingip(context, id,
+                                                              orig_fl_ip)
+            raise
+        return new_fl_ip
+
+    def delete_floatingip(self, context, id):
+        LOG.debug(_("QuantumRestProxyV2: delete_floatingip() called"))
+
+        orig_fl_ip = super(QuantumRestProxyV2, self).get_floatingip(context,
+                                                                    id)
+        # delete floating IP in DB
+        net_id = orig_fl_ip['floating_network_id']
+        super(QuantumRestProxyV2, self).delete_floatingip(context, id)
+
+        orig_net = super(QuantumRestProxyV2, self).get_network(context,
+                                                               net_id)
+        # update network on network controller
+        try:
+            self._send_update_network(orig_net)
+        except RemoteRestError as e:
+            # TODO(Sumit): rollback deletion of floating IP
+            raise
+
     def _send_all_data(self):
         """Pushes all data to network ctrl (networks/ports, ports/attachments)
         to give the controller an option to re-sync it's persistent store
         with quantum's current view of that data.
         """
         admin_context = qcontext.get_admin_context()
-        networks = {}
-        ports = {}
+        networks = []
+        routers = []
 
         all_networks = super(QuantumRestProxyV2,
                              self).get_networks(admin_context) or []
         for net in all_networks:
-            networks[net.get('id')] = {
-                'id': net.get('id'),
-                'name': net.get('name'),
-                'op-status': net.get('admin_state_up'),
-            }
-
-            subnets = net.get('subnets', [])
-            for subnet_id in subnets:
-                subnet = self.get_subnet(admin_context, subnet_id)
-                gateway_ip = subnet.get('gateway_ip')
-                if gateway_ip:
-                    # FIX: For backward compatibility with wire protocol
-                    networks[net.get('id')]['gateway'] = gateway_ip
+            mapped_network = self._get_mapped_network_with_subnets(net)
+            net_fl_ips = self._get_network_with_floatingips(mapped_network)
 
             ports = []
             net_filter = {'network_id': [net.get('id')]}
@@ -708,28 +1036,166 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2):
                               self).get_ports(admin_context,
                                               filters=net_filter) or []
             for port in net_ports:
-                port_details = {
-                    'id': port.get('id'),
-                    'attachment': {
-                        'id': port.get('id') + '00',
-                        'mac': port.get('mac_address'),
-                    },
-                    'state': port.get('status'),
-                    'op-status': port.get('admin_state_up'),
-                    'mac': None
+                mapped_port = self._map_state_and_status(port)
+                mapped_port['attachment'] = {
+                    'id': port.get('device_id'),
+                    'mac': port.get('mac_address'),
                 }
-                ports.append(port_details)
-            networks[net.get('id')]['ports'] = ports
+                ports.append(mapped_port)
+            net_fl_ips['ports'] = ports
+
+            networks.append(net_fl_ips)
+
+        all_routers = super(QuantumRestProxyV2,
+                            self).get_routers(admin_context) or []
+        for router in all_routers:
+            interfaces = []
+            mapped_router = self._map_state_and_status(router)
+            router_filter = {
+                'device_owner': ["network:router_interface"],
+                'device_id': [router.get('id')]
+            }
+            router_ports = super(QuantumRestProxyV2,
+                                 self).get_ports(admin_context,
+                                                 filters=router_filter) or []
+            for port in router_ports:
+                net_id = port.get('network_id')
+                subnet_id = port['fixed_ips'][0]['subnet_id']
+                intf_details = self._get_router_intf_details(admin_context,
+                                                             net_id,
+                                                             subnet_id)
+                interfaces.append(intf_details)
+            mapped_router['interfaces'] = interfaces
+
+            routers.append(mapped_router)
+
         try:
             resource = '/topology'
             data = {
                 'networks': networks,
+                'routers': routers,
             }
             ret = self.servers.put(resource, data)
             if not self.servers.action_success(ret):
                 raise RemoteRestError(ret[2])
             return ret
         except RemoteRestError as e:
-            LOG.error(_('QuantumRestProxy: Unable to update remote network: '
-                        '%s'), e.message)
+            LOG.error(_('QuantumRestProxy: Unable to update remote '
+                        'topology: %s'), e.message)
+            raise
+
+    def _add_host_route(self, context, destination, port):
+        subnet = {}
+        for fixed_ip in port['fixed_ips']:
+            subnet_id = fixed_ip['subnet_id']
+            nexthop = fixed_ip['ip_address']
+            subnet['host_routes'] = [{'destination': destination,
+                                      'nexthop': nexthop}]
+            self.update_subnet(context, subnet_id, {'subnet': subnet})
+            LOG.debug(_("Adding host route: "))
+            LOG.debug(_("destination:%s nexthop:%s"), (destination,
+                                                       nexthop))
+
+    def _get_network_with_floatingips(self, network):
+        admin_context = qcontext.get_admin_context()
+
+        net_id = network['id']
+        net_filter = {'floating_network_id': [net_id]}
+        fl_ips = super(QuantumRestProxyV2,
+                       self).get_floatingips(admin_context,
+                                             filters=net_filter) or []
+        network['floatingips'] = fl_ips
+
+        return network
+
+    def _get_all_subnets_json_for_network(self, net_id):
+        admin_context = qcontext.get_admin_context()
+        subnets = self._get_subnets_by_network(admin_context,
+                                               net_id)
+        subnets_details = []
+        if subnets:
+            for subnet in subnets:
+                subnet_dict = self._make_subnet_dict(subnet)
+                mapped_subnet = self._map_state_and_status(subnet_dict)
+                subnets_details.append(mapped_subnet)
+
+        return subnets_details
+
+    def _get_mapped_network_with_subnets(self, network):
+        admin_context = qcontext.get_admin_context()
+        network = self._map_state_and_status(network)
+        subnets = self._get_all_subnets_json_for_network(network['id'])
+        network['subnets'] = subnets
+        for subnet in (subnets or []):
+            if subnet['gateway_ip']:
+                # FIX: For backward compatibility with wire protocol
+                network['gateway'] = subnet['gateway_ip']
+                break
+        else:
+            network['gateway'] = ''
+
+        network[l3.EXTERNAL] = self._network_is_external(admin_context,
+                                                         network['id'])
+
+        return network
+
+    def _send_update_network(self, network):
+        net_id = network['id']
+        tenant_id = network['tenant_id']
+        # update network on network controller
+        try:
+            resource = NETWORKS_PATH % (tenant_id, net_id)
+            mapped_network = self._get_mapped_network_with_subnets(network)
+            net_fl_ips = self._get_network_with_floatingips(mapped_network)
+            data = {
+                "network": net_fl_ips,
+            }
+            ret = self.servers.put(resource, data)
+            if not self.servers.action_success(ret):
+                raise RemoteRestError(ret[2])
+        except RemoteRestError as e:
+            LOG.error(_("QuantumRestProxyV2: Unable to update remote "
+                        "network: %s"), e.message)
             raise
+
+    def _map_state_and_status(self, resource):
+        resource = copy.copy(resource)
+
+        resource['state'] = ('UP' if resource.pop('admin_state_up',
+                                                  True) else 'DOWN')
+
+        if 'status' in resource:
+            del resource['status']
+
+        return resource
+
+    def _warn_on_state_status(self, resource):
+        if resource.get('admin_state_up', True) is False:
+            LOG.warning(_("Setting admin_state_up=False is not supported"
+                          " in this plugin version. Ignoring setting for "
+                          "resource: %s"), resource)
+
+        if 'status' in resource:
+            if resource['status'] is not const.NET_STATUS_ACTIVE:
+                LOG.warning(_("Operational status is internally set by the"
+                              " plugin. Ignoring setting status=%s."),
+                            resource['status'])
+
+    def _get_router_intf_details(self, context, intf_id, subnet_id):
+
+        # we will use the network id as interface's id
+        net_id = intf_id
+        network = super(QuantumRestProxyV2, self).get_network(context,
+                                                              net_id)
+        subnet = super(QuantumRestProxyV2, self).get_subnet(context,
+                                                            subnet_id)
+        mapped_network = self._get_mapped_network_with_subnets(network)
+        mapped_subnet = self._map_state_and_status(subnet)
+
+        data = {
+            'id': intf_id,
+            "network": mapped_network,
+            "subnet": mapped_subnet
+        }
+
+        return data
diff --git a/quantum/plugins/bigswitch/vcsversion.py b/quantum/plugins/bigswitch/vcsversion.py
new file mode 100644 (file)
index 0000000..6515941
--- /dev/null
@@ -0,0 +1,27 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Big Switch Networks, Inc.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com
+#
+version_info = {'branch_nick': u'quantum/trunk',
+                'revision_id': u'1',
+                'revno': 0}
+
+
+QUANTUMRESTPROXY_VERSION = ['2013', '1', None]
+
+
+FINAL = False   # This becomes true at Release Candidate time
index 2a441ed1fbe36a4e2c069a4818d1739b4a2094bb..711d1052bba8ffe289ee7eaac16c7d8edf1c32cc 100755 (executable)
 
 # if vcsversion exists, use it. Else, use LOCALBRANCH:LOCALREVISION
 try:
-    from bigswitch.vcsversion import version_info
+    from quantum.plugins.bigswitch.vcsversion import version_info
 except ImportError:
     version_info = {'branch_nick': u'LOCALBRANCH',
                     'revision_id': u'LOCALREVISION',
                     'revno': 0}
+try:
+    from quantum.plugins.bigswitch.vcsversion import QUANTUMRESTPROXY_VERSION
+except ImportError:
+    QUANTUMRESTPROXY_VERSION = ['2013', '1', None]
+try:
+    from quantum.plugins.bigswitch.vcsversion import FINAL
+except ImportError:
+    FINAL = False   # This becomes true at Release Candidate time
 
 
-QUANTUMRESTPROXY_VERSION = ['2012', '1', None]
 YEAR, COUNT, REVISION = QUANTUMRESTPROXY_VERSION
-FINAL = False   # This becomes true at Release Candidate time
 
 
 def canonical_version_string():
diff --git a/quantum/tests/unit/bigswitch/test_router_db.py b/quantum/tests/unit/bigswitch/test_router_db.py
new file mode 100644 (file)
index 0000000..e829eb8
--- /dev/null
@@ -0,0 +1,317 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Big Switch Networks, Inc.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+# Adapted from quantum.tests.unit.test_l3_plugin
+# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com
+#
+
+import os
+
+from mock import patch
+from webob import exc
+
+from quantum.common.test_lib import test_config
+from quantum.extensions import l3
+from quantum.manager import QuantumManager
+from quantum.openstack.common import cfg
+from quantum.openstack.common.notifier import api as notifier_api
+from quantum.openstack.common.notifier import test_notifier
+from quantum.tests.unit import test_l3_plugin
+
+
+def new_L3_setUp(self):
+    test_config['plugin_name_v2'] = (
+        'quantum.plugins.bigswitch.plugin.QuantumRestProxyV2')
+    etc_path = os.path.join(os.path.dirname(__file__), 'etc')
+    rp_conf_file = os.path.join(etc_path, 'restproxy.ini.test')
+    test_config['config_files'] = [rp_conf_file]
+    cfg.CONF.set_default('allow_overlapping_ips', False)
+    ext_mgr = L3TestExtensionManager()
+    test_config['extension_manager'] = ext_mgr
+    super(test_l3_plugin.L3NatDBTestCase, self).setUp()
+
+    # Set to None to reload the drivers
+    notifier_api._drivers = None
+    cfg.CONF.set_override("notification_driver", [test_notifier.__name__])
+
+
+origSetUp = test_l3_plugin.L3NatDBTestCase.setUp
+
+
+class HTTPResponseMock():
+    status = 200
+    reason = 'OK'
+
+    def __init__(self, sock, debuglevel=0, strict=0, method=None,
+                 buffering=False):
+        pass
+
+    def read(self):
+        return "{'status': '200 OK'}"
+
+
+class HTTPConnectionMock():
+
+    def __init__(self, server, port, timeout):
+        pass
+
+    def request(self, action, uri, body, headers):
+        return
+
+    def getresponse(self):
+        return HTTPResponseMock(None)
+
+    def close(self):
+        pass
+
+
+class L3TestExtensionManager(object):
+
+    def get_resources(self):
+        return l3.L3.get_resources()
+
+    def get_actions(self):
+        return []
+
+    def get_request_extensions(self):
+        return []
+
+
+class RouterDBTestCase(test_l3_plugin.L3NatDBTestCase):
+
+    def setUp(self):
+        self.httpPatch = patch('httplib.HTTPConnection', create=True,
+                               new=HTTPConnectionMock)
+        self.httpPatch.start()
+        test_l3_plugin.L3NatDBTestCase.setUp = new_L3_setUp
+        super(RouterDBTestCase, self).setUp()
+        self.plugin_obj = QuantumManager.get_plugin()
+
+    def tearDown(self):
+        self.httpPatch.stop()
+        super(RouterDBTestCase, self).tearDown()
+        del test_config['plugin_name_v2']
+        del test_config['config_files']
+        cfg.CONF.reset()
+        test_l3_plugin.L3NatDBTestCase.setUp = origSetUp
+
+    def test_router_remove_router_interface_wrong_subnet_returns_409(self):
+        with self.router() as r:
+            with self.subnet() as s:
+                with self.subnet(cidr='10.0.10.0/24') as s1:
+                    with self.port(subnet=s1, no_delete=True) as p:
+                        self._router_interface_action('add',
+                                                      r['router']['id'],
+                                                      None,
+                                                      p['port']['id'])
+                        self._router_interface_action('remove',
+                                                      r['router']['id'],
+                                                      s['subnet']['id'],
+                                                      p['port']['id'],
+                                                      exc.HTTPConflict.code)
+                        #remove properly to clean-up
+                        self._router_interface_action('remove',
+                                                      r['router']['id'],
+                                                      None,
+                                                      p['port']['id'])
+
+    def test_router_remove_router_interface_wrong_port_returns_404(self):
+        with self.router() as r:
+            with self.subnet() as s:
+                with self.port(subnet=s, no_delete=True) as p:
+                    self._router_interface_action('add',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p['port']['id'])
+                    # create another port for testing failure case
+                    res = self._create_port('json', p['port']['network_id'])
+                    p2 = self.deserialize('json', res)
+                    self._router_interface_action('remove',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p2['port']['id'],
+                                                  exc.HTTPNotFound.code)
+                    # remove correct interface to cleanup
+                    self._router_interface_action('remove',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p['port']['id'])
+                    # remove extra port created
+                    self._delete('ports', p2['port']['id'])
+
+    def test_create_floatingip_no_ext_gateway_return_404(self):
+        with self.subnet(cidr='10.0.10.0/24') as public_sub:
+            self._set_net_external(public_sub['subnet']['network_id'])
+            with self.port() as private_port:
+                with self.router() as r:
+                    res = self._create_floatingip(
+                        'json',
+                        public_sub['subnet']['network_id'],
+                        port_id=private_port['port']['id'])
+                    self.assertEqual(res.status_int, exc.HTTPNotFound.code)
+
+    def test_router_update_gateway(self):
+        with self.router() as r:
+            with self.subnet() as s1:
+                with self.subnet(cidr='10.0.10.0/24') as s2:
+                    self._set_net_external(s1['subnet']['network_id'])
+                    self._add_external_gateway_to_router(
+                        r['router']['id'],
+                        s1['subnet']['network_id'])
+                    body = self._show('routers', r['router']['id'])
+                    net_id = (body['router']
+                              ['external_gateway_info']['network_id'])
+                    self.assertEquals(net_id, s1['subnet']['network_id'])
+                    self._set_net_external(s2['subnet']['network_id'])
+                    self._add_external_gateway_to_router(
+                        r['router']['id'],
+                        s2['subnet']['network_id'])
+                    body = self._show('routers', r['router']['id'])
+                    net_id = (body['router']
+                              ['external_gateway_info']['network_id'])
+                    self.assertEquals(net_id, s2['subnet']['network_id'])
+                    self._remove_external_gateway_from_router(
+                        r['router']['id'],
+                        s2['subnet']['network_id'])
+
+    def test_router_add_interface_overlapped_cidr(self):
+        self.skipTest("Plugin does not support")
+
+    def test_router_add_interface_overlapped_cidr_returns_400(self):
+        self.skipTest("Plugin does not support")
+
+    def test_list_nets_external(self):
+        self.skipTest("Plugin does not support")
+
+    def test_router_update_gateway_with_existed_floatingip(self):
+        with self.subnet(cidr='10.0.10.0/24') as subnet:
+            self._set_net_external(subnet['subnet']['network_id'])
+            with self.floatingip_with_assoc() as fip:
+                self._add_external_gateway_to_router(
+                    fip['floatingip']['router_id'],
+                    subnet['subnet']['network_id'],
+                    expected_code=exc.HTTPConflict.code)
+
+    def test_router_remove_interface_wrong_subnet_returns_409(self):
+        with self.router() as r:
+            with self.subnet(cidr='10.0.10.0/24') as s:
+                with self.port(no_delete=True) as p:
+                    self._router_interface_action('add',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p['port']['id'])
+                    self._router_interface_action('remove',
+                                                  r['router']['id'],
+                                                  s['subnet']['id'],
+                                                  p['port']['id'],
+                                                  exc.HTTPConflict.code)
+                    #remove properly to clean-up
+                    self._router_interface_action('remove',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p['port']['id'])
+
+    def test_router_remove_interface_wrong_port_returns_404(self):
+        with self.router() as r:
+            with self.subnet(cidr='10.0.10.0/24') as s:
+                with self.port(no_delete=True) as p:
+                    self._router_interface_action('add',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p['port']['id'])
+                    # create another port for testing failure case
+                    res = self._create_port('json', p['port']['network_id'])
+                    p2 = self.deserialize('json', res)
+                    self._router_interface_action('remove',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p2['port']['id'],
+                                                  exc.HTTPNotFound.code)
+                    # remove correct interface to cleanup
+                    self._router_interface_action('remove',
+                                                  r['router']['id'],
+                                                  None,
+                                                  p['port']['id'])
+                    # remove extra port created
+                    self._delete('ports', p2['port']['id'])
+
+    def test_send_data(self):
+        fmt = 'json'
+        plugin_obj = QuantumManager.get_plugin()
+
+        with self.router() as r:
+            r_id = r['router']['id']
+
+            with self.subnet(cidr='10.0.10.0/24') as s:
+                s_id = s['subnet']['id']
+
+                with self.router() as r1:
+                    r1_id = r1['router']['id']
+                    body = self._router_interface_action('add', r_id, s_id,
+                                                         None)
+                    self.assertTrue('port_id' in body)
+                    r_port_id = body['port_id']
+                    body = self._show('ports', r_port_id)
+                    self.assertEquals(body['port']['device_id'], r_id)
+
+                    with self.subnet(cidr='10.0.20.0/24') as s1:
+                        s1_id = s1['subnet']['id']
+                        body = self._router_interface_action('add', r1_id,
+                                                             s1_id, None)
+                        self.assertTrue('port_id' in body)
+                        r1_port_id = body['port_id']
+                        body = self._show('ports', r1_port_id)
+                        self.assertEquals(body['port']['device_id'], r1_id)
+
+                        with self.subnet(cidr='11.0.0.0/24') as public_sub:
+                            public_net_id = public_sub['subnet']['network_id']
+                            self._set_net_external(public_net_id)
+
+                            with self.port() as prv_port:
+                                prv_fixed_ip = prv_port['port']['fixed_ips'][0]
+                                priv_sub_id = prv_fixed_ip['subnet_id']
+                                self._add_external_gateway_to_router(
+                                    r_id, public_net_id)
+                                self._router_interface_action('add', r_id,
+                                                              priv_sub_id,
+                                                              None)
+
+                                priv_port_id = prv_port['port']['id']
+                                res = self._create_floatingip(
+                                    fmt, public_net_id,
+                                    port_id=priv_port_id)
+                                self.assertEqual(res.status_int,
+                                                 exc.HTTPCreated.code)
+                                floatingip = self.deserialize(fmt, res)
+
+                                result = plugin_obj._send_all_data()
+                                self.assertEquals(result[0], 200)
+
+                                self._delete('floatingips',
+                                             floatingip['floatingip']['id'])
+                                self._remove_external_gateway_from_router(
+                                    r_id, public_net_id)
+                                self._router_interface_action('remove', r_id,
+                                                              priv_sub_id,
+                                                              None)
+                        self._router_interface_action('remove', r_id, s_id,
+                                                      None)
+                        self._show('ports', r_port_id,
+                                   expected_code=exc.HTTPNotFound.code)
+                        self._router_interface_action('remove', r1_id, s1_id,
+                                                      None)
+                        self._show('ports', r1_port_id,
+                                   expected_code=exc.HTTPNotFound.code)