# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 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: Mandeep Dhami, Big Switch Networks, Inc.
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.

"""
Neutron REST Proxy Plug-in for Big Switch and FloodLight Controllers.

NeutronRestProxy provides a generic neutron plugin that translates all plugin
function calls to equivalent authenticated REST calls to a set of redundant
external network controllers. It also keeps persistent store for all neutron
state to allow for re-sync of the external controller(s), if required.

The local state on the plugin also allows for local response and fast-fail
semantics where it can be determined based on the local persistent store.

Network controller specific code is decoupled from this plugin and expected
to reside on the controller itself (via the REST interface).

This allows for:
 - independent authentication and redundancy schemes between neutron and the
   network controller
 - independent upgrade/development cycles between neutron and the controller
   as it limits the proxy code upgrade requirement to neutron release cycle
   and the controller specific code upgrade requirement to controller code
 - ability to sync the controller with neutron for independent recovery/reset

External REST API used by proxy is the same API as defined for neutron (JSON
subset) with some additional parameters (gateway on network-create and macaddr
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 os
import socket

from oslo.config import cfg

from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.common import constants as const
from neutron.common import exceptions
from neutron.common import rpc as q_rpc
from neutron.common import topics
from neutron.common import utils
from neutron import context as qcontext
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.extensions import l3
from neutron.extensions import portbindings
from neutron.openstack.common import log as logging
from neutron.openstack.common import rpc
from neutron.plugins.bigswitch.db import porttracker_db
from neutron.plugins.bigswitch import routerrule_db
from neutron.plugins.bigswitch.version import version_string_with_vcs

LOG = logging.getLogger(__name__)

# Include the BigSwitch Extensions path in the api_extensions
EXTENSIONS_PATH = os.path.join(os.path.dirname(__file__), 'extensions')
if not cfg.CONF.api_extensions_path:
    cfg.CONF.set_override('api_extensions_path',
                          EXTENSIONS_PATH)

restproxy_opts = [
    cfg.StrOpt('servers', default='localhost:8800',
               help=_("A comma separated list of BigSwitch or Floodlight "
                      "servers and port numbers. The plugin proxies the "
                      "requests to the BigSwitch/Floodlight server, "
                      "which performs the networking configuration. Note that "
                      "only one server is needed per deployment, but you may "
                      "wish to deploy multiple servers to support failover.")),
    cfg.StrOpt('server_auth', default='username:password', secret=True,
               help=_("The username and password for authenticating against "
                      " the BigSwitch or Floodlight controller.")),
    cfg.BoolOpt('server_ssl', default=False,
                help=_("If True, Use SSL when connecting to the BigSwitch or "
                       "Floodlight controller.")),
    cfg.BoolOpt('sync_data', default=False,
                help=_("Sync data on connect")),
    cfg.IntOpt('server_timeout', default=10,
               help=_("Maximum number of seconds to wait for proxy request "
                      "to connect and complete.")),
    cfg.StrOpt('neutron_id', default='neutron-' + utils.get_hostname(),
               deprecated_name='quantum_id',
               help=_("User defined identifier for this Neutron 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")),
]


cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")

router_opts = [
    cfg.MultiStrOpt('tenant_default_router_rule', default=['*:any:any:permit'],
                    help=_("The default router rules installed in new tenant "
                           "routers. Repeat the config option for each rule. "
                           "Format is <tenant>:<source>:<destination>:<action>"
                           " Use an * to specify default for all tenants.")),
    cfg.IntOpt('max_router_rules', default=200,
               help=_("Maximum number of router rules")),
]

cfg.CONF.register_opts(router_opts, "ROUTER")

nova_opts = [
    cfg.StrOpt('vif_type', default='ovs',
               help=_("Virtual interface type to configure on "
                      "Nova compute nodes")),
]

# Each VIF Type can have a list of nova host IDs that are fixed to that type
for i in portbindings.VIF_TYPES:
    opt = cfg.ListOpt('node_override_vif_' + i, default=[],
                      help=_("Nova compute nodes to manually set VIF "
                             "type to %s") % i)
    nova_opts.append(opt)

# Add the vif types for reference later
nova_opts.append(cfg.ListOpt('vif_types', default=portbindings.VIF_TYPES))

cfg.CONF.register_opts(nova_opts, "NOVA")


# 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 = 'Neutron v2.0'
METADATA_SERVER_IP = '169.254.169.254'


class RemoteRestError(exceptions.NeutronException):

    def __init__(self, message):
        if message is None:
            message = "None"
        self.message = _("Error in REST call to remote network "
                         "controller") + ": " + message
        super(RemoteRestError, self).__init__()


class ServerProxy(object):
    """REST server proxy to a network controller."""

    def __init__(self, server, port, ssl, auth, neutron_id, timeout,
                 base_uri, name):
        self.server = server
        self.port = port
        self.ssl = ssl
        self.base_uri = base_uri
        self.timeout = timeout
        self.name = name
        self.success_codes = SUCCESS_CODES
        self.auth = None
        self.neutron_id = neutron_id
        if auth:
            self.auth = 'Basic ' + base64.encodestring(auth).strip()

    @utils.synchronized('bsn-rest-call', external=True)
    def rest_call(self, action, resource, data, headers):
        uri = self.base_uri + resource
        body = json.dumps(data)
        if not headers:
            headers = {}
        headers['Content-type'] = 'application/json'
        headers['Accept'] = 'application/json'
        headers['NeutronProxy-Agent'] = self.name
        headers['Instance-ID'] = self.neutron_id
        headers['Orchestration-Service-ID'] = ORCHESTRATION_SERVICE_ID
        if self.auth:
            headers['Authorization'] = self.auth

        LOG.debug(_("ServerProxy: server=%(server)s, port=%(port)d, "
                    "ssl=%(ssl)r, action=%(action)s"),
                  {'server': self.server, 'port': self.port, 'ssl': self.ssl,
                   'action': action})
        LOG.debug(_("ServerProxy: resource=%(resource)s, data=%(data)r, "
                    "headers=%(headers)r"),
                  {'resource': resource, 'data': data, 'headers': headers})

        conn = None
        if self.ssl:
            conn = httplib.HTTPSConnection(
                self.server, self.port, timeout=self.timeout)
            if conn is None:
                LOG.error(_('ServerProxy: Could not establish HTTPS '
                            'connection'))
                return 0, None, None, None
        else:
            conn = httplib.HTTPConnection(
                self.server, self.port, timeout=self.timeout)
            if conn is None:
                LOG.error(_('ServerProxy: Could not establish HTTP '
                            'connection'))
                return 0, None, None, None

        try:
            conn.request(action, uri, body, headers)
            response = conn.getresponse()
            respstr = response.read()
            respdata = respstr
            if response.status in self.success_codes:
                try:
                    respdata = json.loads(respstr)
                except ValueError:
                    # response was not JSON, ignore the exception
                    pass
            ret = (response.status, response.reason, respstr, respdata)
        except (socket.timeout, socket.error) as e:
            LOG.error(_('ServerProxy: %(action)s failure, %(e)r'),
                      {'action': action, 'e': e})
            ret = 0, None, None, None
        conn.close()
        LOG.debug(_("ServerProxy: status=%(status)d, reason=%(reason)r, "
                    "ret=%(ret)s, data=%(data)r"), {'status': ret[0],
                                                    'reason': ret[1],
                                                    'ret': ret[2],
                                                    'data': ret[3]})
        return ret


class ServerPool(object):

    def __init__(self, servers, ssl, auth, neutron_id, timeout=10,
                 base_uri='/quantum/v1.0', name='NeutronRestProxy'):
        self.base_uri = base_uri
        self.timeout = timeout
        self.name = name
        self.auth = auth
        self.ssl = ssl
        self.neutron_id = neutron_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.neutron_id,
                           self.timeout, self.base_uri, self.name)

    def server_failure(self, resp):
        """Define failure codes as required.

        Note: We assume 301-303 is a failure, and try the next server in
        the server pool.
        """
        return resp[0] in FAILURE_CODES

    def action_success(self, resp):
        """Defining success codes as required.

        Note: We assume any valid 2xx as being successful response.
        """
        return resp[0] in SUCCESS_CODES

    def rest_call(self, action, resource, data, headers):
        failed_servers = []
        while self.servers:
            active_server = self.servers[0]
            ret = active_server.rest_call(action, resource, data, headers)
            if not self.server_failure(ret):
                self.servers.extend(failed_servers)
                return ret
            else:
                LOG.error(_('ServerProxy: %(action)s failure for servers: '
                            '%(server)r'),
                          {'action': action,
                           'server': (active_server.server,
                                      active_server.port)})
                failed_servers.append(self.servers.pop(0))

        # All servers failed, reset server list and try again next time
        LOG.error(_('ServerProxy: %(action)s failure for all servers: '
                    '%(server)r'),
                  {'action': action,
                   'server': tuple((s.server,
                                    s.port) for s in failed_servers)})
        self.servers.extend(failed_servers)
        return (0, None, None, None)

    def get(self, resource, data='', headers=None):
        return self.rest_call('GET', resource, data, headers)

    def put(self, resource, data, headers=None):
        return self.rest_call('PUT', resource, data, headers)

    def post(self, resource, data, headers=None):
        return self.rest_call('POST', resource, data, headers)

    def delete(self, resource, data='', headers=None):
        return self.rest_call('DELETE', resource, data, headers)


class RpcProxy(dhcp_rpc_base.DhcpRpcCallbackMixin):

    RPC_API_VERSION = '1.0'

    def create_rpc_dispatcher(self):
        return q_rpc.PluginRpcDispatcher([self])


class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2,
                         routerrule_db.RouterRule_db_mixin):

    supported_extension_aliases = ["router", "binding", "router_rules"]

    def __init__(self, server_timeout=None):
        LOG.info(_('NeutronRestProxy: Starting plugin. Version=%s'),
                 version_string_with_vcs())

        # init DB, proxy's persistent store defaults to in-memory sql-lite DB
        db.configure_db()

        # '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 'server_auth' to encode api-key
        servers = cfg.CONF.RESTPROXY.servers
        server_auth = cfg.CONF.RESTPROXY.server_auth
        server_ssl = cfg.CONF.RESTPROXY.server_ssl
        sync_data = cfg.CONF.RESTPROXY.sync_data
        neutron_id = cfg.CONF.RESTPROXY.neutron_id
        self.add_meta_server_route = cfg.CONF.RESTPROXY.add_meta_server_route
        timeout = cfg.CONF.RESTPROXY.server_timeout
        if server_timeout is not None:
            timeout = server_timeout

        # validate config
        assert servers is not None, 'Servers not defined. Aborting plugin'
        servers = tuple(s.rsplit(':', 1) for s in servers.split(','))
        servers = tuple((server, int(port)) for server, port in servers)
        assert all(len(s) == 2 for s in servers), SYNTAX_ERROR_MESSAGE

        # init network ctrl connections
        self.servers = ServerPool(servers, server_ssl, server_auth, neutron_id,
                                  timeout, BASE_URI)

        # init dhcp support
        self.topic = topics.PLUGIN
        self.conn = rpc.create_connection(new=True)
        self.callbacks = RpcProxy()
        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()
        if sync_data:
            self._send_all_data()

        self._dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
        LOG.debug(_("NeutronRestProxyV2: initialization done"))

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

        Network represents an L2 network segment which can have a set of
        subnets and ports associated with it.

        :param context: neutron api request context
        :param network: dictionary describing the network

        :returns: a sequence of mappings with the following signature:
        {
            "id": UUID representing the network.
            "name": Human-readable name identifying the network.
            "tenant_id": Owner of network. NOTE: only admin user can specify
                         a tenant_id other than its own.
            "admin_state_up": Sets admin state of network.
                              if down, network does not forward packets.
            "status": Indicates whether network is currently operational
                      (values are "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "subnets": Subnets associated with this network.
        }

        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: create_network() called"))

        self._warn_on_state_status(network['network'])

        # Validate args
        tenant_id = self._get_tenant_id_for_create(context, network["network"])

        session = context.session
        with session.begin(subtransactions=True):
            # create network in DB
            new_net = super(NeutronRestProxyV2, self).create_network(context,
                                                                     network)
            self._process_l3_create(context, new_net, network['network'])

        # 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": mapped_network
            }
            ret = self.servers.post(resource, data)
            if not self.servers.action_success(ret):
                raise RemoteRestError(ret[2])
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2:Unable to create remote "
                        "network: %s"), e.message)
            super(NeutronRestProxyV2, self).delete_network(context,
                                                           new_net['id'])
            raise

        # return created network
        return new_net

    def update_network(self, context, net_id, network):
        """Updates the properties of a particular Virtual Network.

        :param context: neutron api request context
        :param net_id: uuid of the network to update
        :param network: dictionary describing the updates

        :returns: a sequence of mappings with the following signature:
        {
            "id": UUID representing the network.
            "name": Human-readable name identifying the network.
            "tenant_id": Owner of network. NOTE: only admin user can
                         specify a tenant_id other than its own.
            "admin_state_up": Sets admin state of network.
                              if down, network does not forward packets.
            "status": Indicates whether network is currently operational
                      (values are "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "subnets": Subnets associated with this network.
        }

        :raises: exceptions.NetworkNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2.update_network() called"))

        self._warn_on_state_status(network['network'])

        session = context.session
        with session.begin(subtransactions=True):
            orig_net = super(NeutronRestProxyV2, self).get_network(context,
                                                                   net_id)
            new_net = super(NeutronRestProxyV2, self).update_network(context,
                                                                     net_id,
                                                                     network)
            self._process_l3_update(context, new_net, network['network'])

        # update network on network controller
        try:
            self._send_update_network(new_net)
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2: Unable to update remote "
                        "network: %s"), e.message)
            # reset network to original state
            super(NeutronRestProxyV2, self).update_network(context, id,
                                                           orig_net)
            raise

        # return updated network
        return new_net

    def delete_network(self, context, net_id):
        """Delete a network.
        :param context: neutron api request context
        :param id: UUID representing the network to delete.

        :returns: None

        :raises: exceptions.NetworkInUse
        :raises: exceptions.NetworkNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: delete_network() called"))

        # Validate args
        orig_net = super(NeutronRestProxyV2, 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)
            ret = self.servers.delete(resource)
            if not self.servers.action_success(ret):
                raise RemoteRestError(ret[2])
            ret_val = super(NeutronRestProxyV2, self).delete_network(context,
                                                                     net_id)
            return ret_val
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2: 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
        (e.g., a VM NIC) to attach to a L2 Neutron network.
        :param context: neutron api request context
        :param port: dictionary describing the port

        :returns:
        {
            "id": uuid represeting the port.
            "network_id": uuid of network.
            "tenant_id": tenant_id
            "mac_address": mac address to use on this port.
            "admin_state_up": Sets admin state of port. if down, port
                              does not forward packets.
            "status": dicates whether port is currently operational
                      (limit values to "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "fixed_ips": list of subnet ID"s and IP addresses to be used on
                         this port
            "device_id": identifies the device (e.g., virtual server) using
                         this port.
        }

        :raises: exceptions.NetworkNotFound
        :raises: exceptions.StateInvalid
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: create_port() called"))

        # Update DB
        port["port"]["admin_state_up"] = False
        if (portbindings.HOST_ID in port['port']
            and 'device_id' in port['port']):
            porttracker_db.put_port_hostid(context, port['port']['device_id'],
                                           port['port'][portbindings.HOST_ID])
        new_port = super(NeutronRestProxyV2, self).create_port(context, port)
        net = super(NeutronRestProxyV2,
                    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": 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
            device_id = port["port"].get("device_id")
            if device_id:
                self._plug_interface(context,
                                     net["tenant_id"], net["id"],
                                     new_port["id"], device_id)
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2: Unable to create remote port: "
                        "%s"), e.message)
            super(NeutronRestProxyV2, self).delete_port(context,
                                                        new_port["id"])
            raise

        # Set port state up and return that port
        port_update = {"port": {"admin_state_up": True}}
        new_port = super(NeutronRestProxyV2, self).update_port(context,
                                                               new_port["id"],
                                                               port_update)
        return self._extend_port_dict_binding(context, new_port)

    def get_port(self, context, id, fields=None):
        with context.session.begin(subtransactions=True):
            port = super(NeutronRestProxyV2, self).get_port(context, id,
                                                            fields)
            self._extend_port_dict_binding(context, port)
        return self._fields(port, fields)

    def get_ports(self, context, filters=None, fields=None):
        with context.session.begin(subtransactions=True):
            ports = super(NeutronRestProxyV2, self).get_ports(context, filters,
                                                              fields)
            for port in ports:
                self._extend_port_dict_binding(context, port)
        return [self._fields(port, fields) for port in ports]

    def update_port(self, context, port_id, port):
        """Update values of a port.

        :param context: neutron api request context
        :param id: UUID representing the port to update.
        :param port: dictionary with keys indicating fields to update.

        :returns: a mapping sequence with the following signature:
        {
            "id": uuid represeting the port.
            "network_id": uuid of network.
            "tenant_id": tenant_id
            "mac_address": mac address to use on this port.
            "admin_state_up": sets admin state of port. if down, port
                               does not forward packets.
            "status": dicates whether port is currently operational
                       (limit values to "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "fixed_ips": list of subnet ID's and IP addresses to be used on
                         this port
            "device_id": identifies the device (e.g., virtual server) using
                         this port.
        }

        :raises: exceptions.StateInvalid
        :raises: exceptions.PortNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: update_port() called"))

        self._warn_on_state_status(port['port'])

        # Validate Args
        orig_port = super(NeutronRestProxyV2, self).get_port(context, port_id)

        # Update DB
        new_port = super(NeutronRestProxyV2, self).update_port(context,
                                                               port_id, port)
        if (portbindings.HOST_ID in port['port']
            and 'device_id' in port['port']):
            porttracker_db.put_port_hostid(context, port['port']['device_id'],
                                           port['port'][portbindings.HOST_ID])
        # update on networl ctrl
        try:
            resource = PORTS_PATH % (orig_port["tenant_id"],
                                     orig_port["network_id"], port_id)
            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])

            if new_port.get("device_id") != orig_port.get("device_id"):
                if orig_port.get("device_id"):
                    self._unplug_interface(context, orig_port["tenant_id"],
                                           orig_port["network_id"],
                                           orig_port["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"], device_id)

        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2: Unable to create remote port: "
                        "%s"), e.message)
            # reset port to original state
            super(NeutronRestProxyV2, self).update_port(context, port_id,
                                                        orig_port)
            raise

        # return new_port
        return self._extend_port_dict_binding(context, new_port)

    def delete_port(self, context, port_id, l3_port_check=True):
        """Delete a port.

        :param context: neutron api request context
        :param id: UUID representing the port to delete.

        :raises: exceptions.PortInUse
        :raises: exceptions.PortNotFound
        :raises: exceptions.NetworkNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: 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(NeutronRestProxyV2, self).delete_port(context, port_id)

    def _delete_port(self, context, port_id):
        # Delete from DB
        port = super(NeutronRestProxyV2, self).get_port(context, port_id)
        tenant_id = port['tenant_id']
        if tenant_id == '':
            net = super(NeutronRestProxyV2,
                        self).get_network(context, port['network_id'])
            tenant_id = net['tenant_id']

        # delete from network ctrl. Remote error on delete is ignored
        try:
            resource = PORTS_PATH % (tenant_id, port["network_id"],
                                     port_id)
            ret = self.servers.delete(resource)
            if not self.servers.action_success(ret):
                raise RemoteRestError(ret[2])

            if port.get("device_id"):
                self._unplug_interface(context, tenant_id,
                                       port["network_id"], port["id"])
            ret_val = super(NeutronRestProxyV2, self)._delete_port(context,
                                                                   port_id)
            return ret_val
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2: Unable to update remote port: "
                        "%s"), e.message)
            raise

    def _plug_interface(self, context, tenant_id, net_id, port_id,
                        remote_interface_id):
        """Plug remote interface to the network.

        Attaches a remote interface to the specified port on the specified
        Virtual Network.

        :returns: None

        :raises: exceptions.NetworkNotFound
        :raises: exceptions.PortNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: _plug_interface() called"))

        # update attachment on network controller
        try:
            port = super(NeutronRestProxyV2, self).get_port(context, port_id)
            mac = port["mac_address"]

            if mac is not None:
                resource = ATTACHMENT_PATH % (tenant_id, net_id, port_id)
                data = {"attachment":
                        {"id": remote_interface_id,
                         "mac": mac,
                         }
                        }
                ret = self.servers.put(resource, data)
                if not self.servers.action_success(ret):
                    raise RemoteRestError(ret[2])
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2:Unable to update remote network: "
                        "%s"), e.message)
            raise

    def _unplug_interface(self, context, tenant_id, net_id, port_id):
        """Detach interface from the network controller.

        Detaches a remote interface from the specified port on the network
        controller.

        :returns: None

        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: _unplug_interface() called"))

        # delete from network ctrl. Remote error on delete is ignored
        try:
            resource = ATTACHMENT_PATH % (tenant_id, net_id, port_id)
            ret = self.servers.delete(resource)
            if not self.servers.action_success(ret):
                raise RemoteRestError(ret[2])
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2: Unable to update remote port: "
                        "%s"), e.message)

    def create_subnet(self, context, subnet):
        LOG.debug(_("NeutronRestProxyV2: create_subnet() called"))

        self._warn_on_state_status(subnet['subnet'])

        # create subnet in DB
        new_subnet = super(NeutronRestProxyV2, self).create_subnet(context,
                                                                   subnet)
        net_id = new_subnet['network_id']
        orig_net = super(NeutronRestProxyV2, self).get_network(context,
                                                               net_id)
        # update network on network controller
        try:
            self._send_update_network(orig_net)
        except RemoteRestError:
            # rollback creation of subnet
            super(NeutronRestProxyV2, self).delete_subnet(context,
                                                          subnet['id'])
            raise
        return new_subnet

    def update_subnet(self, context, id, subnet):
        LOG.debug(_("NeutronRestProxyV2: update_subnet() called"))

        self._warn_on_state_status(subnet['subnet'])

        orig_subnet = super(NeutronRestProxyV2, self)._get_subnet(context, id)

        # update subnet in DB
        new_subnet = super(NeutronRestProxyV2, self).update_subnet(context, id,
                                                                   subnet)
        net_id = new_subnet['network_id']
        orig_net = super(NeutronRestProxyV2, self).get_network(context,
                                                               net_id)
        # update network on network controller
        try:
            self._send_update_network(orig_net)
        except RemoteRestError:
            # rollback updation of subnet
            super(NeutronRestProxyV2, self).update_subnet(context, id,
                                                          orig_subnet)
            raise
        return new_subnet

    def delete_subnet(self, context, id):
        LOG.debug(_("NeutronRestProxyV2: delete_subnet() called"))
        orig_subnet = super(NeutronRestProxyV2, self).get_subnet(context, id)
        net_id = orig_subnet['network_id']
        # delete subnet in DB
        super(NeutronRestProxyV2, self).delete_subnet(context, id)
        orig_net = super(NeutronRestProxyV2, self).get_network(context,
                                                               net_id)
        # update network on network controller
        try:
            self._send_update_network(orig_net)
        except RemoteRestError:
            # TODO(Sumit): rollback deletion of subnet
            raise

    def _get_tenant_default_router_rules(self, tenant):
        rules = cfg.CONF.ROUTER.tenant_default_router_rule
        defaultset = []
        tenantset = []
        for rule in rules:
            items = rule.split(':')
            if len(items) == 5:
                (tenantid, source, destination, action, nexthops) = items
            elif len(items) == 4:
                (tenantid, source, destination, action) = items
                nexthops = ''
            else:
                continue
            parsedrule = {'source': source,
                          'destination': destination, 'action': action,
                          'nexthops': nexthops.split(',')}
            if parsedrule['nexthops'][0] == '':
                parsedrule['nexthops'] = []
            if tenantid == '*':
                defaultset.append(parsedrule)
            if tenantid == tenant:
                tenantset.append(parsedrule)
        if tenantset:
            return tenantset
        return defaultset

    def create_router(self, context, router):
        LOG.debug(_("NeutronRestProxyV2: create_router() called"))

        self._warn_on_state_status(router['router'])

        tenant_id = self._get_tenant_id_for_create(context, router["router"])

        # set default router rules
        rules = self._get_tenant_default_router_rules(tenant_id)
        router['router']['router_rules'] = rules

        # create router in DB
        new_router = super(NeutronRestProxyV2, 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(_("NeutronRestProxyV2: Unable to create remote router: "
                        "%s"), e.message)
            super(NeutronRestProxyV2, self).delete_router(context,
                                                          new_router['id'])
            raise

        # return created router
        return new_router

    def update_router(self, context, router_id, router):

        LOG.debug(_("NeutronRestProxyV2.update_router() called"))

        self._warn_on_state_status(router['router'])

        orig_router = super(NeutronRestProxyV2, self).get_router(context,
                                                                 router_id)
        tenant_id = orig_router["tenant_id"]
        new_router = super(NeutronRestProxyV2, 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(_("NeutronRestProxyV2: Unable to update remote router: "
                        "%s"), e.message)
            # reset router to original state
            super(NeutronRestProxyV2, self).update_router(context,
                                                          router_id,
                                                          orig_router)
            raise

        # return updated router
        return new_router

    def delete_router(self, context, router_id):
        LOG.debug(_("NeutronRestProxyV2: 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(NeutronRestProxyV2, self).delete_router(context,
                                                                    router_id)
            return ret_val
        except RemoteRestError as e:
            LOG.error(_("NeutronRestProxyV2: Unable to delete remote router: "
                        "%s"), e.message)
            raise

    def add_router_interface(self, context, router_id, interface_info):

        LOG.debug(_("NeutronRestProxyV2: 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(NeutronRestProxyV2,
                                   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(_("NeutronRestProxyV2: Unable to create interface: "
                        "%s"), e.message)
            super(NeutronRestProxyV2,
                  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(_("NeutronRestProxyV2: 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(NeutronRestProxyV2,
                              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(_("NeutronRestProxyV2:Unable to delete remote intf: "
                        "%s"), e.message)
            raise

        # return new interface
        return del_intf_info

    def create_floatingip(self, context, floatingip):
        LOG.debug(_("NeutronRestProxyV2: create_floatingip() called"))

        # create floatingip in DB
        new_fl_ip = super(NeutronRestProxyV2,
                          self).create_floatingip(context, floatingip)

        net_id = new_fl_ip['floating_network_id']
        orig_net = super(NeutronRestProxyV2, 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(_("NeutronRestProxyV2: Unable to create remote "
                        "floatin IP: %s"), e.message)
            super(NeutronRestProxyV2, self).delete_floatingip(context,
                                                              floatingip)
            raise

        # return created floating IP
        return new_fl_ip

    def update_floatingip(self, context, id, floatingip):
        LOG.debug(_("NeutronRestProxyV2: update_floatingip() called"))

        orig_fl_ip = super(NeutronRestProxyV2, self).get_floatingip(context,
                                                                    id)

        # update floatingip in DB
        new_fl_ip = super(NeutronRestProxyV2,
                          self).update_floatingip(context, id, floatingip)

        net_id = new_fl_ip['floating_network_id']
        orig_net = super(NeutronRestProxyV2, self).get_network(context,
                                                               net_id)
        # update network on network controller
        try:
            self._send_update_network(orig_net)
        except RemoteRestError:
            # rollback updation of subnet
            super(NeutronRestProxyV2, self).update_floatingip(context, id,
                                                              orig_fl_ip)
            raise
        return new_fl_ip

    def delete_floatingip(self, context, id):
        LOG.debug(_("NeutronRestProxyV2: delete_floatingip() called"))

        orig_fl_ip = super(NeutronRestProxyV2, self).get_floatingip(context,
                                                                    id)
        # delete floating IP in DB
        net_id = orig_fl_ip['floating_network_id']
        super(NeutronRestProxyV2, self).delete_floatingip(context, id)

        orig_net = super(NeutronRestProxyV2, self).get_network(context,
                                                               net_id)
        # update network on network controller
        try:
            self._send_update_network(orig_net)
        except RemoteRestError:
            # TODO(Sumit): rollback deletion of floating IP
            raise

    def _send_all_data(self):
        """Pushes all data to network ctrl (networks/ports, ports/attachments).

        This gives the controller an option to re-sync it's persistent store
        with neutron's current view of that data.
        """
        admin_context = qcontext.get_admin_context()
        networks = []
        routers = []

        all_networks = super(NeutronRestProxyV2,
                             self).get_networks(admin_context) or []
        for net in all_networks:
            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')]}
            net_ports = super(NeutronRestProxyV2,
                              self).get_ports(admin_context,
                                              filters=net_filter) or []
            for port in net_ports:
                mapped_port = self._map_state_and_status(port)
                mapped_port['attachment'] = {
                    'id': port.get('device_id'),
                    'mac': port.get('mac_address'),
                }
                ports.append(mapped_port)
            net_fl_ips['ports'] = ports

            networks.append(net_fl_ips)

        all_routers = super(NeutronRestProxyV2,
                            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(NeutronRestProxyV2,
                                 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(_('NeutronRestProxy: 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}]
            updated_subnet = self.update_subnet(context,
                                                subnet_id,
                                                {'subnet': subnet})
            payload = {'subnet': updated_subnet}
            self._dhcp_agent_notifier.notify(context, payload,
                                             'subnet.update.end')
            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(NeutronRestProxyV2,
                       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(_("NeutronRestProxyV2: Unable to update remote "