# Copyright 2012 Nicira, 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.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Aaron Rosen, Nicira Networks, Inc.


import ConfigParser
import json
import hashlib
import logging
import netaddr
import os
import sys
import traceback
import urllib
import uuid


from common import config
from quantum.plugins.nicira.nicira_nvp_plugin.api_client import client_eventlet
import NvpApiClient
import nvplib
from nvp_plugin_version import PLUGIN_VERSION

from quantum.api.v2 import attributes
from quantum.common import constants
from quantum.common import exceptions as exception
from quantum.db import api as db
from quantum.db import db_base_plugin_v2
from quantum.db import models_v2
from quantum.openstack.common import cfg


CONFIG_FILE = "nvp.ini"
CONFIG_FILE_PATHS = []
if os.environ.get('QUANTUM_HOME', None):
    CONFIG_FILE_PATHS.append('%s/etc' % os.environ['QUANTUM_HOME'])
CONFIG_FILE_PATHS.append("/etc/quantum/plugins/nicira")
LOG = logging.getLogger("QuantumPlugin")


def parse_config():
    """Parse the supplied plugin configuration.
`
    :param config: a ConfigParser() object encapsulating nvp.ini.
    :returns: A tuple: (clusters, plugin_config). 'clusters' is a list of
        NVPCluster objects, 'plugin_config' is a dictionary with plugin
        parameters (currently only 'max_lp_per_bridged_ls').
    """
    db_options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
    db_options.update({'base': models_v2.model_base.BASEV2})
    sql_max_retries = cfg.CONF.DATABASE.sql_max_retries
    db_options.update({"sql_max_retries": sql_max_retries})
    reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
    db_options.update({"reconnect_interval": reconnect_interval})
    nvp_options = {'max_lp_per_bridged_ls': cfg.CONF.NVP.max_lp_per_bridged_ls}
    nvp_options.update({'failover_time': cfg.CONF.NVP.failover_time})
    nvp_options.update({'concurrent_connections':
                        cfg.CONF.NVP.concurrent_connections})

    nvp_conf = config.ClusterConfigOptions(cfg.CONF)
    cluster_names = config.register_cluster_groups(nvp_conf)
    nvp_conf.log_opt_values(LOG, logging.DEBUG)

    clusters_options = []
    for cluster_name in cluster_names:
        clusters_options.append(
            {'name': cluster_name,
             'default_tz_uuid':
             nvp_conf[cluster_name].default_tz_uuid,
             'nvp_cluster_uuid':
             nvp_conf[cluster_name].nvp_cluster_uuid,
             'nova_zone_id':
             nvp_conf[cluster_name].nova_zone_id,
             'nvp_controller_connection':
             nvp_conf[cluster_name].nvp_controller_connection, })
    LOG.debug("cluster options:%s", clusters_options)
    return db_options, nvp_options, clusters_options


class NVPCluster(object):
    """Encapsulates controller connection and api_client for a cluster.

    Accessed within the NvpPluginV2 class.

    Each element in the self.controllers list is a dictionary that
    contains the following keys:
        ip, port, user, password, default_tz_uuid, uuid, zone

    There may be some redundancy here, but that has been done to provide
    future flexibility.
    """
    def __init__(self, name):
        self._name = name
        self.controllers = []
        self.api_client = None

    def __repr__(self):
        ss = ['{ "NVPCluster": [']
        ss.append('{ "name" : "%s" }' % self.name)
        ss.append(',')
        for c in self.controllers:
            ss.append(str(c))
            ss.append(',')
        ss.append('] }')
        return ''.join(ss)

    def add_controller(self, ip, port, user, password, request_timeout,
                       http_timeout, retries, redirects,
                       default_tz_uuid, uuid=None, zone=None):
        """Add a new set of controller parameters.

        :param ip: IP address of controller.
        :param port: port controller is listening on.
        :param user: user name.
        :param password: user password.
        :param request_timeout: timeout for an entire API request.
        :param http_timeout: timeout for a connect to a controller.
        :param retries: maximum number of request retries.
        :param redirects: maximum number of server redirect responses to
            follow.
        :param default_tz_uuid: default transport zone uuid.
        :param uuid: UUID of this cluster (used in MDI configs).
        :param zone: Zone of this cluster (used in MDI configs).
        """

        keys = [
            'ip', 'user', 'password', 'default_tz_uuid', 'uuid', 'zone']
        controller_dict = dict([(k, locals()[k]) for k in keys])

        int_keys = [
            'port', 'request_timeout', 'http_timeout', 'retries', 'redirects']
        for k in int_keys:
            controller_dict[k] = int(locals()[k])

        self.controllers.append(controller_dict)

    def get_controller(self, idx):
        return self.controllers[idx]

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, val=None):
        self._name = val

    @property
    def host(self):
        return self.controllers[0]['ip']

    @property
    def port(self):
        return self.controllers[0]['port']

    @property
    def user(self):
        return self.controllers[0]['user']

    @property
    def password(self):
        return self.controllers[0]['password']

    @property
    def request_timeout(self):
        return self.controllers[0]['request_timeout']

    @property
    def http_timeout(self):
        return self.controllers[0]['http_timeout']

    @property
    def retries(self):
        return self.controllers[0]['retries']

    @property
    def redirects(self):
        return self.controllers[0]['redirects']

    @property
    def default_tz_uuid(self):
        return self.controllers[0]['default_tz_uuid']

    @property
    def zone(self):
        return self.controllers[0]['zone']

    @property
    def uuid(self):
        return self.controllers[0]['uuid']


class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
    """
    NvpPluginV2 is a Quantum plugin that provides L2 Virtual Network
    functionality using NVP.
    """

    def __init__(self, loglevel=None):
        if loglevel:
            logging.basicConfig(level=loglevel)
            nvplib.LOG.setLevel(loglevel)
            NvpApiClient.LOG.setLevel(loglevel)

        self.db_opts, self.nvp_opts, self.clusters_opts = parse_config()
        self.clusters = []
        for c_opts in self.clusters_opts:
            # Password is guaranteed to be the same across all controllers
            # in the same NVP cluster.
            cluster = NVPCluster(c_opts['name'])
            for controller_connection in c_opts['nvp_controller_connection']:
                args = controller_connection.split(':')
                try:
                    args.extend([c_opts['default_tz_uuid'],
                                 c_opts['nvp_cluster_uuid'],
                                 c_opts['nova_zone_id']])
                    cluster.add_controller(*args)
                except Exception:
                    LOG.exception("Invalid connection parameters for "
                                  "controller %s in cluster %s",
                                  controller_connection,
                                  c_opts['name'])
                    raise

            api_providers = [(x['ip'], x['port'], True)
                             for x in cluster.controllers]
            cluster.api_client = NvpApiClient.NVPApiHelper(
                api_providers, cluster.user, cluster.password,
                request_timeout=cluster.request_timeout,
                http_timeout=cluster.http_timeout,
                retries=cluster.retries,
                redirects=cluster.redirects,
                failover_time=self.nvp_opts['failover_time'],
                concurrent_connections=self.nvp_opts['concurrent_connections'])

            # TODO(salvatore-orlando): do login at first request,
            # not when plugin, is instantiated
            cluster.api_client.login()

            # TODO(pjb): What if the cluster isn't reachable this
            # instant?  It isn't good to fall back to invalid cluster
            # strings.
            # Default for future-versions
            self.clusters.append(cluster)

        # Connect and configure ovs_quantum db
        options = {
            'sql_connection': self.db_opts['sql_connection'],
            'sql_max_retries': self.db_opts['sql_max_retries'],
            'reconnect_interval': self.db_opts['reconnect_interval'],
            'base': models_v2.model_base.BASEV2,
        }
        db.configure_db(options)

    @property
    def cluster(self):
        if len(self.clusters):
            return self.clusters[0]
        return None

    def clear_state(self):
        nvplib.clear_state(self.clusters[0])

    def get_all_networks(self, tenant_id, **kwargs):
        networks = []
        for c in self.clusters:
            networks.extend(nvplib.get_all_networks(c, tenant_id, networks))
        LOG.debug("get_all_networks() completed for tenant %s: %s" % (
            tenant_id, networks))
        return networks

    def create_network(self, context, 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. 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 (limit values to "ACTIVE", "DOWN",
                               "BUILD", and "ERROR"?
                     'subnets': Subnets associated with this network. Plan
                                to allow fully specified subnets as part of
                                network create.
                   }
        :raises: exception.NoImplementedError
        """
        # FIXME(arosen) implement admin_state_up = False in NVP
        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",
                        network['network'].get('name', '<unknown>'))

        tenant_id = self._get_tenant_id_for_create(context, network)
        # TODO(salvatore-orlando): if the network is shared this should be
        # probably stored into the lswitch with a tag
        # TODO(salvatore-orlando): Important - provider networks support
        # (might require a bridged TZ)
        net = nvplib.create_network(network['network']['tenant_id'],
                                    network['network']['name'],
                                    clusters=self.clusters)

        network['network']['id'] = net['net-id']
        return super(NvpPluginV2, self).create_network(context, network)

    def delete_network(self, context, id):
        """
        Deletes the network with the specified network identifier
        belonging to the specified tenant.

        :returns: None
        :raises: exception.NetworkInUse
        :raises: exception.NetworkNotFound
        """

        super(NvpPluginV2, self).delete_network(context, id)
        pairs = self._get_lswitch_cluster_pairs(id, context.tenant_id)
        for (cluster, switches) in pairs:
            nvplib.delete_networks(cluster, id, switches)

        LOG.debug("delete_network() completed for tenant: %s" %
                  context.tenant_id)

    def _get_lswitch_cluster_pairs(self, netw_id, tenant_id):
        """Figure out the set of lswitches on each cluster that maps to this
           network id"""
        pairs = []
        for c in self.clusters:
            lswitches = []
            try:
                ls = nvplib.get_network(c, netw_id)
                lswitches.append(ls['uuid'])
            except exception.NetworkNotFound:
                continue
            pairs.append((c, lswitches))
        if len(pairs) == 0:
            raise exception.NetworkNotFound(net_id=netw_id)
        LOG.debug("Returning pairs for network: %s" % (pairs))
        return pairs

    def get_network(self, context, id, fields=None, verbose=None):
        """
        Retrieves all attributes of the network, NOT including
        the ports of that 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. 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 (limit values to "ACTIVE", "DOWN",
                               "BUILD", and "ERROR"?
                     'subnets': Subnets associated with this network. Plan
                                to allow fully specified subnets as part of
                                network create.
                   }

        :raises: exception.NetworkNotFound
        :raises: exception.QuantumException
        """
        result = {}
        lswitch_query = "&uuid=%s" % id
        # always look for the tenant_id in the resource itself rather than
        # the context, as with shared networks context.tenant_id and
        # network['tenant_id'] might differ on GETs
        # goto to the plugin DB and fecth the network
        network = self._get_network(context, id, verbose)
        # TODO(salvatore-orlando): verify whether the query on os_tid is
        # redundant or not.
        if context.is_admin is False:
            tenant_query = ("&tag=%s&tag_scope=os_tid"
                            % network['tenant_id'])
        else:
            tenant_query = ""
        # Then fetch the correspondiong logical switch in NVP as well
        # TODO(salvatore-orlando): verify whether the step on NVP
        # can be completely avoided
        lswitch_url_path = (
            "/ws.v1/lswitch?"
            "fields=uuid,display_name%s%s"
            % (tenant_query, lswitch_query))
        try:
            for c in self.clusters:
                lswitch_results = nvplib.get_all_query_pages(
                    lswitch_url_path, c)
                if lswitch_results:
                    result['lswitch-display-name'] = (
                        lswitch_results[0]['display_name'])
                    break
        except Exception:
            LOG.error("Unable to get switches: %s" % traceback.format_exc())
            raise exception.QuantumException()

        if 'lswitch-display-name' not in result:
            raise exception.NetworkNotFound(net_id=id)

        d = {'id': id,
             'name': result['lswitch-display-name'],
             'tenant_id': network['tenant_id'],
             'admin_state_up': True,
             'status': constants.NET_STATUS_ACTIVE,
             'shared': network['shared'],
             'subnets': []}

        LOG.debug("get_network() completed for tenant %s: %s" % (
                  context.tenant_id, d))
        return d

    def get_networks(self, context, filters=None, fields=None, verbose=None):
        """
        Retrieves all attributes of the network, NOT including
        the ports of that 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. 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 (limit values to "ACTIVE", "DOWN",
                               "BUILD", and "ERROR"?
                     'subnets': Subnets associated with this network. Plan
                                to allow fully specified subnets as part of
                                network create.
                   }

        :raises: exception.NetworkNotFound
        :raises: exception.QuantumException
        """
        result = {}
        nvp_lswitches = []
        quantum_lswitches = (
            super(NvpPluginV2, self).get_networks(context, filters))

        if context.is_admin and not filters.get("tenant_id"):
            tenant_filter = ""
        elif filters.get("tenant_id"):
            tenant_filter = ""
            for tenant in filters.get("tenant_id"):
                tenant_filter += "&tag=%s&tag_scope=os_tid" % tenant
        else:
            tenant_filter = "&tag=%s&tag_scope=os_tid" % context.tenant_id

        lswitch_filters = "uuid,display_name,fabric_status"
        lswitch_url_path = (
            "/ws.v1/lswitch?fields=%s&relations=LogicalSwitchStatus%s"
            % (lswitch_filters, tenant_filter))
        try:
            for c in self.clusters:
                res = nvplib.get_all_query_pages(
                    lswitch_url_path, c)

                nvp_lswitches.extend(res)
        except Exception:
            LOG.error("Unable to get switches: %s" % traceback.format_exc())
            raise exception.QuantumException()

        # TODO (Aaron) This can be optimized
        if filters.get("id"):
            filtered_lswitches = []
            for nvp_lswitch in nvp_lswitches:
                for id in filters.get("id"):
                    if id == nvp_lswitch['uuid']:
                        filtered_lswitches.append(nvp_lswitch)
            nvp_lswitches = filtered_lswitches

        for quantum_lswitch in quantum_lswitches:
            Found = False
            for nvp_lswitch in nvp_lswitches:
                if nvp_lswitch["uuid"] == quantum_lswitch["id"]:
                    if (nvp_lswitch["_relations"]["LogicalSwitchStatus"]
                            ["fabric_status"]):
                        quantum_lswitch["status"] = constants.NET_STATUS_ACTIVE
                    else:
                        quantum_lswitch["status"] = constants.NET_STATUS_DOWN
                    quantum_lswitch["name"] = nvp_lswitch["display_name"]
                    nvp_lswitches.remove(nvp_lswitch)
                    Found = True
                    break

            if not Found:
                raise Exception("Quantum and NVP Databases are out of Sync!")
        # do not make the case in which switches are found in NVP
        # but not in Quantum catastrophic.
        if len(nvp_lswitches):
            LOG.warning("Found %s logical switches not bound "
                        "to Quantum networks. Quantum and NVP are "
                        "potentially out of sync", len(nvp_lswitches))

        LOG.debug("get_networks() completed for tenant %s" % context.tenant_id)

        if fields:
            ret_fields = []
            for quantum_lswitch in quantum_lswitches:
                row = {}
                for field in fields:
                    row[field] = quantum_lswitch[field]
                ret_fields.append(row)
            return ret_fields

        return quantum_lswitches

    def update_network(self, context, id, network):
        """
        Updates the properties of a particular Virtual 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. 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 (limit values to "ACTIVE", "DOWN",
                               "BUILD", and "ERROR"?
        'subnets': Subnets associated with this network. Plan
                   to allow fully specified subnets as part of
                   network create.
                   }

        :raises: exception.NetworkNotFound
        :raises: exception.NoImplementedError
        """

        if network["network"].get("admin_state_up"):
            if network['network']["admin_state_up"] is False:
                raise exception.NotImplementedError("admin_state_up=False "
                                                    "networks are not "
                                                    "supported.")
        params = {}
        params["network"] = network["network"]
        pairs = self._get_lswitch_cluster_pairs(id, context.tenant_id)

        #Only field to update in NVP is name
        if network['network'].get("name"):
            for (cluster, switches) in pairs:
                for switch in switches:
                    result = nvplib.update_network(cluster, switch, **params)

        LOG.debug("update_network() completed for tenant: %s" %
                  context.tenant_id)
        return super(NvpPluginV2, self).update_network(context, id, network)

    def get_ports(self, context, filters=None, fields=None, verbose=None):
        """
        Returns all ports from given tenant

        :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. 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 (limit values to "ACTIVE", "DOWN",
                               "BUILD", and "ERROR"?
        'subnets': Subnets associated with this network. Plan
                   to allow fully specified subnets as part of
                   network create.
                   }

        :raises: exception.NetworkNotFound
        """
        quantum_lports = super(NvpPluginV2, self).get_ports(context, filters)
        vm_filter = ""
        tenant_filter = ""
        # This is used when calling delete_network. Quantum checks to see if
        # the network has any ports.
        if filters.get("network_id"):
            # FIXME (Aaron) If we get more than one network_id this won't work
            lswitch = filters["network_id"][0]
        else:
            lswitch = "*"

        if filters.get("device_id"):
            for vm_id in filters.get("device_id"):
                vm_filter = ("%stag_scope=vm_id&tag=%s&" % (vm_filter,
                             hashlib.sha1(vm_id).hexdigest()))
        else:
            vm_id = ""

        if filters.get("tenant_id"):
            for tenant in filters.get("tenant_id"):
                tenant_filter = ("%stag_scope=os_tid&tag=%s&" %
                                 (tenant_filter, tenant))

        nvp_lports = {}

        lport_fields_str = ("tags,admin_status_enabled,display_name,"
                            "fabric_status_up")
        try:
            for c in self.clusters:
                lport_query_path = (
                    "/ws.v1/lswitch/%s/lport?fields=%s&%s%stag_scope=q_port_id"
                    "&relations=LogicalPortStatus" %
                    (lswitch, lport_fields_str, vm_filter, tenant_filter))

                ports = nvplib.get_all_query_pages(lport_query_path, c)
                if ports:
                    for port in ports:
                        for tag in port["tags"]:
                            if tag["scope"] == "q_port_id":
                                nvp_lports[tag["tag"]] = port

        except Exception:
            LOG.error("Unable to get ports: %s" % traceback.format_exc())
            raise exception.QuantumException()

        lports = []
        for quantum_lport in quantum_lports:
            try:
                quantum_lport["admin_state_up"] = (
                    nvp_lports[quantum_lport["id"]]["admin_status_enabled"])

                quantum_lport["name"] = (
                    nvp_lports[quantum_lport["id"]]["display_name"])

                if (nvp_lports[quantum_lport["id"]]
                        ["_relations"]
                        ["LogicalPortStatus"]
                        ["fabric_status_up"]):
                    quantum_lport["status"] = constants.PORT_STATUS_ACTIVE
                else:
                    quantum_lport["status"] = constants.PORT_STATUS_DOWN

                del nvp_lports[quantum_lport["id"]]
                lports.append(quantum_lport)
            except KeyError:
                raise Exception("Quantum and NVP Databases are out of Sync!")
        # do not make the case in which ports are found in NVP
        # but not in Quantum catastrophic.
        if len(nvp_lports):
            LOG.warning("Found %s logical ports not bound "
                        "to Quantum ports. Quantum and NVP are "
                        "potentially out of sync", len(nvp_lports))

        if fields:
            ret_fields = []
            for lport in lports:
                row = {}
                for field in fields:
                    row[field] = lport[field]
                ret_fields.append(row)
            return ret_fields
        return lports

    def create_port(self, context, port):
        """
        Creates a port on the specified Virtual Network.
        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: exception.NetworkNotFound
        :raises: exception.StateInvalid
        """

        # Set admin_state_up False since not created in NVP set
        port["port"]["admin_state_up"] = False

        # First we allocate port in quantum database
        try:
            quantum_db = super(NvpPluginV2, self).create_port(context, port)
        except Exception as e:
            raise e

        # Update fields obtained from quantum db
        port["port"].update(quantum_db)

        # We want port to be up in NVP
        port["port"]["admin_state_up"] = True
        params = {}
        params["max_lp_per_bridged_ls"] = \
            self.nvp_opts["max_lp_per_bridged_ls"]
        params["port"] = port["port"]
        params["clusters"] = self.clusters
        tenant_id = self._get_tenant_id_for_create(context, port["port"])

        try:
            port["port"], nvp_port_id = nvplib.create_port(tenant_id,
                                                           **params)
            nvplib.plug_interface(self.clusters, port["port"]["network_id"],
                                  nvp_port_id, "VifAttachment",
                                  port["port"]["id"])
        except Exception as e:
            # failed to create port in NVP delete port from quantum_db
            super(NvpPluginV2, self).delete_port(context, port["port"]["id"])
            raise e

        d = {"port-id": port["port"]["id"],
             "port-op-status": port["port"]["status"]}

        LOG.debug("create_port() completed for tenant %s: %s" %
                  (tenant_id, d))

        # update port with admin_state_up True
        port_update = {"port": {"admin_state_up": True}}
        return super(NvpPluginV2, self).update_port(context,
                                                    port["port"]["id"],
                                                    port_update)

    def update_port(self, context, id, port):
        """
        Updates the properties of a specific port on the
        specified Virtual Network.
        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: exception.StateInvalid
        :raises: exception.PortNotFound
        """
        params = {}

        quantum_db = super(NvpPluginV2, self).get_port(context, id)

        port_nvp, cluster = (
            nvplib.get_port_by_quantum_tag(self.clusters,
                                           quantum_db["network_id"], id))

        LOG.debug("Update port request: %s" % (params))

        params["cluster"] = cluster
        params["port"] = port["port"]
        result = nvplib.update_port(quantum_db["network_id"],
                                    port_nvp["uuid"], **params)
        LOG.debug("update_port() completed for tenant: %s" % context.tenant_id)

        return super(NvpPluginV2, self).update_port(context, id, port)

    def delete_port(self, context, id):
        """
        Deletes a port on a specified Virtual Network,
        if the port contains a remote interface attachment,
        the remote interface is first un-plugged and then the port
        is deleted.

        :returns: None
        :raises: exception.PortInUse
        :raises: exception.PortNotFound
        :raises: exception.NetworkNotFound
        """

        port, cluster = nvplib.get_port_by_quantum_tag(self.clusters,
                                                       '*', id)
        if port is None:
            raise exception.PortNotFound(port_id=id)
        # TODO(bgh): if this is a bridged network and the lswitch we just got
        # back will have zero ports after the delete we should garbage collect
        # the lswitch.
        nvplib.delete_port(cluster, port)

        LOG.debug("delete_port() completed for tenant: %s" % context.tenant_id)
        return  super(NvpPluginV2, self).delete_port(context, id)

    def get_port(self, context, id, fields=None, verbose=None):
        """
        This method allows the user to retrieve a remote interface
        that is attached to this particular port.

        :returns: a mapping sequence with the following signature:
                    {'port-id': uuid representing the port on
                                 specified quantum network
                     'attachment': uuid of the virtual interface
                                   bound to the port, None otherwise
                     'port-op-status': operational status of the port
                     'port-state': admin status of the port
                    }
        :raises: exception.PortNotFound
        :raises: exception.NetworkNotFound
        """

        quantum_db = super(NvpPluginV2, self).get_port(context, id, fields,
                                                       verbose)

        port, cluster = (
            nvplib.get_port_by_quantum_tag(self.clusters,
                                           quantum_db["network_id"], id))

        quantum_db["admin_state_up"] = port["admin_status_enabled"]
        if port["_relations"]["LogicalPortStatus"]["fabric_status_up"]:
            quantum_db["status"] = constants.PORT_STATUS_ACTIVE
        else:
            quantum_db["status"] = constants.PORT_STATUS_DOWN

        LOG.debug("Port details for tenant %s: %s" %
                  (context.tenant_id, quantum_db))
        return quantum_db

    def get_plugin_version(self):
        return PLUGIN_VERSION
