#m Copyright 2012 VMware, 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.

import uuid

from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import port_security as psec
from neutron_lib.api import faults
from neutron_lib.api import validators
from neutron_lib import constants
from neutron_lib import context as q_context
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import allowedaddresspairs as addr_exc
from neutron_lib.exceptions import l3 as l3_exc
from neutron_lib.exceptions import port_security as psec_exc
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import excutils
import six
from sqlalchemy import exc as sql_exc
from sqlalchemy.orm import exc as sa_exc
import webob.exc

from neutron.api import extensions as neutron_extensions
from neutron.api.v2 import attributes as attr
from neutron.db import _model_query as model_query
from neutron.db import _resource_extend as resource_extend
from neutron.db import _utils as db_utils
from neutron.db import agentschedulers_db
from neutron.db import allowedaddresspairs_db as addr_pair_db
from neutron.db import api as db_api
from neutron.db import db_base_plugin_v2
from neutron.db import dns_db
from neutron.db import external_net_db
from neutron.db import extradhcpopt_db
from neutron.db import extraroute_db
from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import l3_dvr_db
from neutron.db import l3_gwmode_db
from neutron.db.models import l3 as l3_db_models
from neutron.db.models import securitygroup as securitygroup_model  # noqa
from neutron.db import models_v2
from neutron.db import portbindings_db
from neutron.db import portsecurity_db
from neutron.db import quota_db  # noqa
from neutron.db import securitygroups_db
from neutron.extensions import providernet
from neutron.extensions import securitygroup as ext_sg
from neutron.plugins.common import utils
from neutron.quota import resource_registry
from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext
from neutron_lib.api.definitions import extraroute as xroute_apidef
from neutron_lib.api.definitions import multiprovidernet as mpnet_apidef
from neutron_lib.api.definitions import portbindings as pbin
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.exceptions import extraroute as xroute_exc
from neutron_lib.exceptions import multiprovidernet as mpnet_exc

import vmware_nsx
from vmware_nsx._i18n import _
from vmware_nsx.api_client import exception as api_exc
from vmware_nsx.common import config  # noqa
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import nsx_utils
from vmware_nsx.common import securitygroups as sg_utils
from vmware_nsx.common import sync
from vmware_nsx.common import utils as c_utils
from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import maclearning as mac_db
from vmware_nsx.db import networkgw_db
from vmware_nsx.db import nsx_models
from vmware_nsx.db import qos_db
from vmware_nsx.dhcp_meta import modes as dhcpmeta_modes
from vmware_nsx.extensions import maclearning as mac_ext
from vmware_nsx.extensions import networkgw
from vmware_nsx.extensions import qos_queue as qos
from vmware_nsx.nsxlib.mh import l2gateway as l2gwlib
from vmware_nsx.nsxlib.mh import queue as queuelib
from vmware_nsx.nsxlib.mh import router as routerlib
from vmware_nsx.nsxlib.mh import secgroup as secgrouplib
from vmware_nsx.nsxlib.mh import switch as switchlib

LOG = logging.getLogger(__name__)

NSX_NOSNAT_RULES_ORDER = 10
NSX_FLOATINGIP_NAT_RULES_ORDER = 224
NSX_EXTGW_NAT_RULES_ORDER = 255
NSX_DEFAULT_NEXTHOP = '1.1.1.1'


class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                  agentschedulers_db.DhcpAgentSchedulerDbMixin,
                  db_base_plugin_v2.NeutronDbPluginV2,
                  dhcpmeta_modes.DhcpMetadataAccess,
                  l3_dvr_db.L3_NAT_with_dvr_db_mixin,
                  external_net_db.External_net_db_mixin,
                  extradhcpopt_db.ExtraDhcpOptMixin,
                  extraroute_db.ExtraRoute_db_mixin,
                  l3_gwmode_db.L3_NAT_db_mixin,
                  mac_db.MacLearningDbMixin,
                  networkgw_db.NetworkGatewayMixin,
                  portbindings_db.PortBindingMixin,
                  portsecurity_db.PortSecurityDbMixin,
                  qos_db.QoSDbMixin,
                  securitygroups_db.SecurityGroupDbMixin,
                  dns_db.DNSDbMixin):

    supported_extension_aliases = ["allowed-address-pairs",
                                   "binding",
                                   "dvr",
                                   "ext-gw-mode",
                                   xroute_apidef.ALIAS,
                                   "mac-learning",
                                   "multi-provider",
                                   "network-gateway",
                                   "port-security",
                                   "provider",
                                   "qos-queue",
                                   "quotas",
                                   "external-net",
                                   "extra_dhcp_opt",
                                   "router",
                                   "security-group",
                                   constants.SUBNET_ALLOCATION_EXT_ALIAS]

    __native_bulk_support = True
    __native_pagination_support = True
    __native_sorting_support = True

    # Map nova zones to cluster for easy retrieval
    novazone_cluster_map = {}

    @resource_registry.tracked_resources(
        network=models_v2.Network,
        port=models_v2.Port,
        subnet=models_v2.Subnet,
        subnetpool=models_v2.SubnetPool,
        security_group=securitygroup_model.SecurityGroup,
        security_group_rule=securitygroup_model.SecurityGroupRule,
        router=l3_db_models.Router,
        floatingip=l3_db_models.FloatingIP)
    def __init__(self):
        LOG.warning("The NSX-MH plugin is deprecated and may be removed "
                    "in the O or the P cycle")
        super(NsxPluginV2, self).__init__()
        # TODO(salv-orlando): Replace These dicts with
        # collections.defaultdict for better handling of default values
        # Routines for managing logical ports in NSX
        self.port_special_owners = [l3_db.DEVICE_OWNER_ROUTER_GW,
                                    l3_db.DEVICE_OWNER_ROUTER_INTF]
        self._port_drivers = {
            'create': {constants.DEVICE_OWNER_ROUTER_GW:
                       self._nsx_create_ext_gw_port,
                       constants.DEVICE_OWNER_FLOATINGIP:
                       self._nsx_create_fip_port,
                       constants.DEVICE_OWNER_ROUTER_INTF:
                       self._nsx_create_router_port,
                       constants.DEVICE_OWNER_DVR_INTERFACE:
                       self._nsx_create_router_port,
                       networkgw_db.DEVICE_OWNER_NET_GW_INTF:
                       self._nsx_create_l2_gw_port,
                       'default': self._nsx_create_port},
            'delete': {constants.DEVICE_OWNER_ROUTER_GW:
                       self._nsx_delete_ext_gw_port,
                       constants.DEVICE_OWNER_ROUTER_INTF:
                       self._nsx_delete_router_port,
                       constants.DEVICE_OWNER_DVR_INTERFACE:
                       self._nsx_delete_router_port,
                       constants.DEVICE_OWNER_FLOATINGIP:
                       self._nsx_delete_fip_port,
                       networkgw_db.DEVICE_OWNER_NET_GW_INTF:
                       self._nsx_delete_port,
                       'default': self._nsx_delete_port}
        }

        neutron_extensions.append_api_extensions_path(
            [vmware_nsx.NSX_EXT_PATH])
        self.cfg_group = 'NSX'  # group name for nsx section in nsx.ini
        self.nsx_opts = cfg.CONF.NSX
        self.nsx_sync_opts = cfg.CONF.NSX_SYNC
        self.cluster = nsx_utils.create_nsx_cluster(
            cfg.CONF,
            self.nsx_opts.concurrent_connections,
            self.nsx_opts.nsx_gen_timeout)

        self.base_binding_dict = {
            pbin.VIF_TYPE: pbin.VIF_TYPE_OVS,
            pbin.VIF_DETAILS: {
                # TODO(rkukura): Replace with new VIF security details
                pbin.CAP_PORT_FILTER:
                'security-group' in self.supported_extension_aliases}}

        self._extend_fault_map()
        self.setup_dhcpmeta_access()
        # Set this flag to false as the default gateway has not
        # been yet updated from the config file
        self._is_default_net_gw_in_sync = False
        # Create a synchronizer instance for backend sync
        self._synchronizer = sync.NsxSynchronizer(
            self.safe_reference, self.cluster,
            self.nsx_sync_opts.state_sync_interval,
            self.nsx_sync_opts.min_sync_req_delay,
            self.nsx_sync_opts.min_chunk_size,
            self.nsx_sync_opts.max_random_sync_delay)

    def _ensure_default_network_gateway(self):
        if self._is_default_net_gw_in_sync:
            return
        # Add the gw in the db as default, and unset any previous default
        def_l2_gw_uuid = self.cluster.default_l2_gw_service_uuid
        try:
            ctx = q_context.get_admin_context()
            self._unset_default_network_gateways(ctx)
            if not def_l2_gw_uuid:
                return
            try:
                def_network_gw = self._get_network_gateway(ctx,
                                                           def_l2_gw_uuid)
            except networkgw_db.GatewayNotFound:
                # Create in DB only - don't go to backend
                def_gw_data = {'id': def_l2_gw_uuid,
                               'name': 'default L2 gateway service',
                               'devices': [],
                               'tenant_id': ctx.tenant_id}
                gw_res_name = networkgw.GATEWAY_RESOURCE_NAME.replace('-', '_')
                def_network_gw = super(
                    NsxPluginV2, self).create_network_gateway(
                        ctx, {gw_res_name: def_gw_data})
            # In any case set is as default
            self._set_default_network_gateway(ctx, def_network_gw['id'])
            # Ensure this method is executed only once
            self._is_default_net_gw_in_sync = True
        except Exception:
            with excutils.save_and_reraise_exception():
                LOG.exception("Unable to process default l2 gw service: "
                              "%s",
                              def_l2_gw_uuid)

    def _build_ip_address_list(self, context, fixed_ips, subnet_ids=None):
        """Build ip_addresses data structure for logical router port.

        No need to perform validation on IPs - this has already been
        done in the l3_db mixin class.
        """
        ip_addresses = []
        for ip in fixed_ips:
            if not subnet_ids or (ip['subnet_id'] in subnet_ids):
                subnet = self._get_subnet(context, ip['subnet_id'])
                ip_prefix = '%s/%s' % (ip['ip_address'],
                                       subnet['cidr'].split('/')[1])
                ip_addresses.append(ip_prefix)
        return ip_addresses

    def _create_and_attach_router_port(self, cluster, context,
                                       nsx_router_id, port_data,
                                       attachment_type, attachment,
                                       attachment_vlan=None,
                                       subnet_ids=None):
        # Use a fake IP address if gateway port is not 'real'
        ip_addresses = (port_data.get('fake_ext_gw') and
                        ['0.0.0.0/31'] or
                        self._build_ip_address_list(context,
                                                    port_data['fixed_ips'],
                                                    subnet_ids))
        try:
            lrouter_port = routerlib.create_router_lport(
                cluster, nsx_router_id, port_data.get('tenant_id', 'fake'),
                port_data.get('id', 'fake'), port_data.get('name', 'fake'),
                port_data.get('admin_state_up', True), ip_addresses,
                port_data.get('mac_address'))
            LOG.debug("Created NSX router port:%s", lrouter_port['uuid'])
        except api_exc.NsxApiException:
            LOG.exception("Unable to create port on NSX logical router "
                          "%s",
                          nsx_router_id)
            raise nsx_exc.NsxPluginException(
                err_msg=_("Unable to create logical router port for neutron "
                          "port id %(port_id)s on router %(nsx_router_id)s") %
                {'port_id': port_data.get('id'),
                 'nsx_router_id': nsx_router_id})
        self._update_router_port_attachment(cluster, context, nsx_router_id,
                                            port_data, lrouter_port['uuid'],
                                            attachment_type, attachment,
                                            attachment_vlan)
        return lrouter_port

    def _update_router_gw_info(self, context, router_id, info):
        # NOTE(salvatore-orlando): We need to worry about rollback of NSX
        # configuration in case of failures in the process
        # Ref. LP bug 1102301
        router = self._get_router(context, router_id)
        # Check whether SNAT rule update should be triggered
        # NSX also supports multiple external networks so there is also
        # the possibility that NAT rules should be replaced
        current_ext_net_id = router.gw_port_id and router.gw_port.network_id
        new_ext_net_id = info and info.get('network_id')
        # SNAT should be enabled unless info['enable_snat'] is
        # explicitly set to false
        enable_snat = new_ext_net_id and info.get('enable_snat', True)
        # Remove if ext net removed, changed, or if snat disabled
        remove_snat_rules = (current_ext_net_id and
                             new_ext_net_id != current_ext_net_id or
                             router.enable_snat and not enable_snat)
        # Add rules if snat is enabled, and if either the external network
        # changed or snat was previously disabled
        # NOTE: enable_snat == True implies new_ext_net_id != None
        add_snat_rules = (enable_snat and
                          (new_ext_net_id != current_ext_net_id or
                           not router.enable_snat))
        router = super(NsxPluginV2, self)._update_router_gw_info(
            context, router_id, info, router=router)
        # Add/Remove SNAT rules as needed
        # Create an elevated context for dealing with metadata access
        # cidrs which are created within admin context
        ctx_elevated = context.elevated()
        if remove_snat_rules or add_snat_rules:
            cidrs = self._find_router_subnets_cidrs(ctx_elevated, router_id)
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, router_id)
        if remove_snat_rules:
            # Be safe and concede NAT rules might not exist.
            # Therefore, use min_num_expected=0
            for cidr in cidrs:
                routerlib.delete_nat_rules_by_match(
                    self.cluster, nsx_router_id, "SourceNatRule",
                    max_num_expected=1, min_num_expected=0,
                    raise_on_len_mismatch=False,
                    source_ip_addresses=cidr)
        if add_snat_rules:
            ip_addresses = self._build_ip_address_list(
                ctx_elevated, router.gw_port['fixed_ips'])
            # Set the SNAT rule for each subnet (only first IP)
            for cidr in cidrs:
                cidr_prefix = int(cidr.split('/')[1])
                routerlib.create_lrouter_snat_rule(
                    self.cluster, nsx_router_id,
                    ip_addresses[0].split('/')[0],
                    ip_addresses[0].split('/')[0],
                    order=NSX_EXTGW_NAT_RULES_ORDER - cidr_prefix,
                    match_criteria={'source_ip_addresses': cidr})

    def _update_router_port_attachment(self, cluster, context,
                                       nsx_router_id, port_data,
                                       nsx_router_port_id,
                                       attachment_type,
                                       attachment,
                                       attachment_vlan=None):
        if not nsx_router_port_id:
            nsx_router_port_id = self._find_router_gw_port(context, port_data)
        try:
            routerlib.plug_router_port_attachment(cluster, nsx_router_id,
                                                  nsx_router_port_id,
                                                  attachment,
                                                  attachment_type,
                                                  attachment_vlan)
            LOG.debug("Attached %(att)s to NSX router port %(port)s",
                      {'att': attachment, 'port': nsx_router_port_id})
        except api_exc.NsxApiException:
            # Must remove NSX logical port
            routerlib.delete_router_lport(cluster, nsx_router_id,
                                          nsx_router_port_id)
            LOG.exception("Unable to plug attachment in NSX logical "
                          "router port %(r_port_id)s, associated with "
                          "Neutron %(q_port_id)s",
                          {'r_port_id': nsx_router_port_id,
                           'q_port_id': port_data.get('id')})
            raise nsx_exc.NsxPluginException(
                err_msg=(_("Unable to plug attachment in router port "
                           "%(r_port_id)s for neutron port id %(q_port_id)s "
                           "on router %(router_id)s") %
                         {'r_port_id': nsx_router_port_id,
                          'q_port_id': port_data.get('id'),
                          'router_id': nsx_router_id}))

    def _get_port_by_device_id(self, context, device_id, device_owner):
        """Retrieve ports associated with a specific device id.

        Used for retrieving all neutron ports attached to a given router.
        """
        port_qry = context.session.query(models_v2.Port)
        return port_qry.filter_by(
            device_id=device_id,
            device_owner=device_owner,).all()

    def _find_router_subnets_cidrs(self, context, router_id):
        """Retrieve subnets attached to the specified router."""
        ports = self._get_port_by_device_id(context, router_id,
                                            l3_db.DEVICE_OWNER_ROUTER_INTF)
        # No need to check for overlapping CIDRs
        cidrs = []
        for port in ports:
            for ip in port.get('fixed_ips', []):
                cidrs.append(self._get_subnet(context,
                                              ip.subnet_id).cidr)
        return cidrs

    def _nsx_find_lswitch_for_port(self, context, port_data):
        network = self._get_network(context, port_data['network_id'])
        return self._handle_lswitch_selection(
            context, self.cluster, network)

    def _nsx_create_port_helper(self, session, ls_uuid, port_data,
                                do_port_security=True):
        # Convert Neutron security groups identifiers into NSX security
        # profiles identifiers
        nsx_sec_profile_ids = [
            nsx_utils.get_nsx_security_group_id(
                session, self.cluster, neutron_sg_id) for
            neutron_sg_id in (port_data[ext_sg.SECURITYGROUPS] or [])]
        return switchlib.create_lport(self.cluster,
                                      ls_uuid,
                                      port_data['tenant_id'],
                                      port_data['id'],
                                      port_data['name'],
                                      port_data['device_id'],
                                      port_data['admin_state_up'],
                                      port_data['mac_address'],
                                      port_data['fixed_ips'],
                                      port_data[psec.PORTSECURITY],
                                      nsx_sec_profile_ids,
                                      port_data.get(qos.QUEUE),
                                      port_data.get(mac_ext.MAC_LEARNING),
                                      port_data.get(addr_apidef.ADDRESS_PAIRS))

    def _handle_create_port_exception(self, context, port_id,
                                      ls_uuid, lp_uuid):
        with excutils.save_and_reraise_exception():
            # rollback nsx logical port only if it was successfully
            # created on NSX. Should this command fail the original
            # exception will be raised.
            if lp_uuid:
                # Remove orphaned port from NSX
                switchlib.delete_port(self.cluster, ls_uuid, lp_uuid)
            # rollback the neutron-nsx port mapping
            nsx_db.delete_neutron_nsx_port_mapping(context.session,
                                                   port_id)
            LOG.exception("An exception occurred while creating the "
                          "neutron port %s on the NSX plaform", port_id)

    def _nsx_create_port(self, context, port_data):
        """Driver for creating a logical switch port on NSX platform."""
        # FIXME(salvatore-orlando): On the NSX platform we do not really have
        # external networks. So if as user tries and create a "regular" VIF
        # port on an external network we are unable to actually create.
        # However, in order to not break unit tests, we need to still create
        # the DB object and return success
        if self._network_is_external(context, port_data['network_id']):
            LOG.info("NSX plugin does not support regular VIF ports on "
                     "external networks. Port %s will be down.",
                     port_data['network_id'])
            # No need to actually update the DB state - the default is down
            return port_data
        lport = None
        selected_lswitch = None
        try:
            selected_lswitch = self._nsx_find_lswitch_for_port(context,
                                                               port_data)
            lport = self._nsx_create_port_helper(context.session,
                                                 selected_lswitch['uuid'],
                                                 port_data,
                                                 True)
            nsx_db.add_neutron_nsx_port_mapping(
                context.session, port_data['id'],
                selected_lswitch['uuid'], lport['uuid'])
            if port_data['device_owner'] not in self.port_special_owners:
                switchlib.plug_vif_interface(
                    self.cluster, selected_lswitch['uuid'],
                    lport['uuid'], "VifAttachment", port_data['id'])
            LOG.debug("_nsx_create_port completed for port %(name)s "
                      "on network %(network_id)s. The new port id is "
                      "%(id)s.", port_data)
        except (api_exc.NsxApiException, n_exc.NeutronException):
            self._handle_create_port_exception(
                context, port_data['id'],
                selected_lswitch and selected_lswitch['uuid'],
                lport and lport['uuid'])
        except db_exc.DBError as e:
            if (port_data['device_owner'] == constants.DEVICE_OWNER_DHCP and
                isinstance(e.inner_exception, sql_exc.IntegrityError)):
                LOG.warning(
                    "Concurrent network deletion detected; Back-end "
                    "Port %(nsx_id)s creation to be rolled back for "
                    "Neutron port: %(neutron_id)s",
                    {'nsx_id': lport['uuid'],
                     'neutron_id': port_data['id']})
                if selected_lswitch and lport:
                    try:
                        switchlib.delete_port(self.cluster,
                                              selected_lswitch['uuid'],
                                              lport['uuid'])
                    except n_exc.NotFound:
                        LOG.debug("NSX Port %s already gone", lport['uuid'])

    def _nsx_delete_port(self, context, port_data):
        # FIXME(salvatore-orlando): On the NSX platform we do not really have
        # external networks. So deleting regular ports from external networks
        # does not make sense. However we cannot raise as this would break
        # unit tests.
        if self._network_is_external(context, port_data['network_id']):
            LOG.info("NSX plugin does not support regular VIF ports on "
                     "external networks. Port %s will be down.",
                     port_data['network_id'])
            return
        nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
            context.session, self.cluster, port_data['id'])
        if not nsx_port_id:
            LOG.debug("Port '%s' was already deleted on NSX platform", id)
            return
        # 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.
        try:
            switchlib.delete_port(self.cluster, nsx_switch_id, nsx_port_id)
            LOG.debug("_nsx_delete_port completed for port %(port_id)s "
                      "on network %(net_id)s",
                      {'port_id': port_data['id'],
                       'net_id': port_data['network_id']})
        except n_exc.NotFound:
            LOG.warning("Port %s not found in NSX", port_data['id'])

    def _nsx_delete_router_port(self, context, port_data):
        # Delete logical router port
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, port_data['device_id'])
        nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
            context.session, self.cluster, port_data['id'])
        if not nsx_port_id:
            LOG.warning(
                "Neutron port %(port_id)s not found on NSX backend. "
                "Terminating delete operation. A dangling router port "
                "might have been left on router %(router_id)s",
                {'port_id': port_data['id'],
                 'router_id': nsx_router_id})
            return
        try:
            routerlib.delete_peer_router_lport(self.cluster,
                                               nsx_router_id,
                                               nsx_switch_id,
                                               nsx_port_id)
        except api_exc.NsxApiException:
            # Do not raise because the issue might as well be that the
            # router has already been deleted, so there would be nothing
            # to do here
            LOG.exception("Ignoring exception as this means the peer "
                          "for port '%s' has already been deleted.",
                          nsx_port_id)

        # Delete logical switch port
        self._nsx_delete_port(context, port_data)

    def _nsx_create_router_port(self, context, port_data):
        """Driver for creating a switch port to be connected to a router."""
        # No router ports on external networks!
        if self._network_is_external(context, port_data['network_id']):
            raise nsx_exc.NsxPluginException(
                err_msg=(_("It is not allowed to create router interface "
                           "ports on external networks as '%s'") %
                         port_data['network_id']))
        ls_port = None
        selected_lswitch = None
        try:
            selected_lswitch = self._nsx_find_lswitch_for_port(
                context, port_data)
            # Do not apply port security here!
            ls_port = self._nsx_create_port_helper(
                context.session, selected_lswitch['uuid'],
                port_data, False)
            # Assuming subnet being attached is on first fixed ip
            # element in port data
            subnet_id = None
            if len(port_data['fixed_ips']):
                subnet_id = port_data['fixed_ips'][0]['subnet_id']
            nsx_router_id = nsx_utils.get_nsx_router_id(
                context.session, self.cluster, port_data['device_id'])
            # Create peer port on logical router
            self._create_and_attach_router_port(
                self.cluster, context, nsx_router_id, port_data,
                "PatchAttachment", ls_port['uuid'],
                subnet_ids=[subnet_id])
            nsx_db.add_neutron_nsx_port_mapping(
                context.session, port_data['id'],
                selected_lswitch['uuid'], ls_port['uuid'])
            LOG.debug("_nsx_create_router_port completed for port "
                      "%(name)s on network %(network_id)s. The new "
                      "port id is %(id)s.",
                      port_data)
        except (api_exc.NsxApiException, n_exc.NeutronException):
            self._handle_create_port_exception(
                context, port_data['id'],
                selected_lswitch and selected_lswitch['uuid'],
                ls_port and ls_port['uuid'])

    def _find_router_gw_port(self, context, port_data):
        router_id = port_data['device_id']
        if not router_id:
            raise n_exc.BadRequest(_("device_id field must be populated in "
                                   "order to create an external gateway "
                                   "port for network %s"),
                                   port_data['network_id'])
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, router_id)
        lr_port = routerlib.find_router_gw_port(context, self.cluster,
                                                nsx_router_id)
        if not lr_port:
            raise nsx_exc.NsxPluginException(
                err_msg=(_("The gateway port for the NSX router %s "
                           "was not found on the backend")
                         % nsx_router_id))
        return lr_port

    @lockutils.synchronized('vmware', 'neutron-')
    def _nsx_create_ext_gw_port(self, context, port_data):
        """Driver for creating an external gateway port on NSX platform."""
        # TODO(salvatore-orlando): Handle NSX resource
        # rollback when something goes not quite as expected
        lr_port = self._find_router_gw_port(context, port_data)
        ip_addresses = self._build_ip_address_list(context,
                                                   port_data['fixed_ips'])
        # This operation actually always updates a NSX logical port
        # instead of creating one. This is because the gateway port
        # is created at the same time as the NSX logical router, otherwise
        # the fabric status of the NSX router will be down.
        # admin_status should always be up for the gateway port
        # regardless of what the user specifies in neutron
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, port_data['device_id'])
        routerlib.update_router_lport(self.cluster,
                                      nsx_router_id,
                                      lr_port['uuid'],
                                      port_data['tenant_id'],
                                      port_data['id'],
                                      port_data['name'],
                                      True,
                                      ip_addresses)
        ext_network = self.get_network(context, port_data['network_id'])
        if ext_network.get(pnet.NETWORK_TYPE) == c_utils.NetworkTypes.L3_EXT:
            # Update attachment
            physical_network = (ext_network[pnet.PHYSICAL_NETWORK] or
                                self.cluster.default_l3_gw_service_uuid)
            self._update_router_port_attachment(
                self.cluster, context, nsx_router_id, port_data,
                lr_port['uuid'],
                "L3GatewayAttachment",
                physical_network,
                ext_network[pnet.SEGMENTATION_ID])

        LOG.debug("_nsx_create_ext_gw_port completed on external network "
                  "%(ext_net_id)s, attached to router:%(router_id)s. "
                  "NSX port id is %(nsx_port_id)s",
                  {'ext_net_id': port_data['network_id'],
                   'router_id': nsx_router_id,
                   'nsx_port_id': lr_port['uuid']})

    @lockutils.synchronized('vmware', 'neutron-')
    def _nsx_delete_ext_gw_port(self, context, port_data):
        # TODO(salvatore-orlando): Handle NSX resource
        # rollback when something goes not quite as expected
        try:
            router_id = port_data['device_id']
            nsx_router_id = nsx_utils.get_nsx_router_id(
                context.session, self.cluster, router_id)
            if not nsx_router_id:
                LOG.debug("No object found on backend for router %s. This "
                          "that the router was already deleted and no "
                          "further action is needed for resetting the "
                          "external gateway port", router_id)
                return
            lr_port = self._find_router_gw_port(context, port_data)
            # Delete is actually never a real delete, otherwise the NSX
            # logical router will stop working
            routerlib.update_router_lport(self.cluster,
                                          nsx_router_id,
                                          lr_port['uuid'],
                                          port_data['tenant_id'],
                                          port_data['id'],
                                          port_data['name'],
                                          True,
                                          ['0.0.0.0/31'])
            # Reset attachment
            self._update_router_port_attachment(
                self.cluster, context, nsx_router_id, port_data,
                lr_port['uuid'],
                "L3GatewayAttachment",
                self.cluster.default_l3_gw_service_uuid)
            LOG.debug("_nsx_delete_ext_gw_port completed on external network "
                      "%(ext_net_id)s, attached to NSX router:%(router_id)s",
                      {'ext_net_id': port_data['network_id'],
                       'router_id': nsx_router_id})
        except n_exc.NotFound:
            LOG.debug("Logical router resource %s not found "
                      "on NSX platform : the router may have "
                      "already been deleted",
                      port_data['device_id'])
        except api_exc.NsxApiException:
            raise nsx_exc.NsxPluginException(
                err_msg=_("Unable to update logical router"
                          "on NSX Platform"))

    def _nsx_create_l2_gw_port(self, context, port_data):
        """Create a switch port, and attach it to a L2 gateway attachment."""
        # FIXME(salvatore-orlando): On the NSX platform we do not really have
        # external networks. So if as user tries and create a "regular" VIF
        # port on an external network we are unable to actually create.
        # However, in order to not break unit tests, we need to still create
        # the DB object and return success
        if self._network_is_external(context, port_data['network_id']):
            LOG.info("NSX plugin does not support regular VIF ports on "
                     "external networks. Port %s will be down.",
                     port_data['network_id'])
            # No need to actually update the DB state - the default is down
            return port_data
        lport = None
        try:
            selected_lswitch = self._nsx_find_lswitch_for_port(
                context, port_data)
            lport = self._nsx_create_port_helper(
                context.session,
                selected_lswitch['uuid'],
                port_data,
                True)
            nsx_db.add_neutron_nsx_port_mapping(
                context.session, port_data['id'],
                selected_lswitch['uuid'], lport['uuid'])
            l2gwlib.plug_l2_gw_service(
                self.cluster,
                selected_lswitch['uuid'],
                lport['uuid'],
                port_data['device_id'],
                int(port_data.get('gw:segmentation_id') or 0))
        except Exception:
            with excutils.save_and_reraise_exception():
                if lport:
                    switchlib.delete_port(self.cluster,
                                          selected_lswitch['uuid'],
                                          lport['uuid'])
        LOG.debug("_nsx_create_l2_gw_port completed for port %(name)s "
                  "on network %(network_id)s. The new port id "
                  "is %(id)s.", port_data)

    def _nsx_create_fip_port(self, context, port_data):
        # As we do not create ports for floating IPs in NSX,
        # this is a no-op driver
        pass

    def _nsx_delete_fip_port(self, context, port_data):
        # As we do not create ports for floating IPs in NSX,
        # this is a no-op driver
        pass

    def _extend_fault_map(self):
        """Extends the Neutron Fault Map.

        Exceptions specific to the NSX Plugin are mapped to standard
        HTTP Exceptions.
        """
        faults.FAULT_MAP.update({nsx_exc.InvalidNovaZone:
                                 webob.exc.HTTPBadRequest,
                                 nsx_exc.NoMorePortsException:
                                 webob.exc.HTTPBadRequest,
                                 nsx_exc.MaintenanceInProgress:
                                 webob.exc.HTTPServiceUnavailable,
                                 nsx_exc.InvalidSecurityCertificate:
                                 webob.exc.HTTPBadRequest})

    def _validate_provider_create(self, context, network):
        segments = network.get(mpnet_apidef.SEGMENTS)
        if not validators.is_attr_set(segments):
            return

        mpnet_apidef.check_duplicate_segments(segments)
        for segment in segments:
            network_type = segment.get(pnet.NETWORK_TYPE)
            physical_network = segment.get(pnet.PHYSICAL_NETWORK)
            physical_network_set = validators.is_attr_set(physical_network)
            segmentation_id = segment.get(pnet.SEGMENTATION_ID)
            network_type_set = validators.is_attr_set(network_type)
            segmentation_id_set = validators.is_attr_set(segmentation_id)

            # If the physical_network_uuid isn't passed in use the default one.
            if not physical_network_set:
                physical_network = cfg.CONF.default_tz_uuid

            err_msg = None
            if not network_type_set:
                err_msg = _("%s required") % pnet.NETWORK_TYPE
            elif network_type in (c_utils.NetworkTypes.GRE,
                                  c_utils.NetworkTypes.STT,
                                  c_utils.NetworkTypes.FLAT):
                if segmentation_id_set:
                    err_msg = _("Segmentation ID cannot be specified with "
                                "flat network type")
            elif network_type == c_utils.NetworkTypes.VLAN:
                if not segmentation_id_set:
                    err_msg = _("Segmentation ID must be specified with "
                                "vlan network type")
                elif (segmentation_id_set and
                      not utils.is_valid_vlan_tag(segmentation_id)):
                    err_msg = (_("%(segmentation_id)s out of range "
                                 "(%(min_id)s through %(max_id)s)") %
                               {'segmentation_id': segmentation_id,
                                'min_id': constants.MIN_VLAN_TAG,
                                'max_id': constants.MAX_VLAN_TAG})
                else:
                    # Verify segment is not already allocated
                    bindings = (
                        nsx_db.get_network_bindings_by_vlanid_and_physical_net(
                            context.session, segmentation_id,
                            physical_network)
                    )
                    if bindings:
                        raise n_exc.VlanIdInUse(
                            vlan_id=segmentation_id,
                            physical_network=physical_network)
            elif network_type == c_utils.NetworkTypes.L3_EXT:
                if (segmentation_id_set and
                    not utils.is_valid_vlan_tag(segmentation_id)):
                    err_msg = (_("%(segmentation_id)s out of range "
                                 "(%(min_id)s through %(max_id)s)") %
                               {'segmentation_id': segmentation_id,
                                'min_id': constants.MIN_VLAN_TAG,
                                'max_id': constants.MAX_VLAN_TAG})
                # Network must be external
                if not network.get(extnet_apidef.EXTERNAL):
                    err_msg = (_("The l3_ext provide network type can be "
                                 "used with external networks only"))
            else:
                err_msg = (_("%(net_type_param)s %(net_type_value)s not "
                             "supported") %
                           {'net_type_param': pnet.NETWORK_TYPE,
                            'net_type_value': network_type})
            if err_msg:
                raise n_exc.InvalidInput(error_message=err_msg)
            # TODO(salvatore-orlando): Validate tranport zone uuid
            # which should be specified in physical_network

    def _extend_network_dict_provider(self, context, network,
                                      multiprovider=None, bindings=None):
        if not bindings:
            bindings = nsx_db.get_network_bindings(context.session,
                                                   network['id'])
        if not multiprovider:
            multiprovider = nsx_db.is_multiprovider_network(context.session,
                                                            network['id'])
        # With NSX plugin 'normal' overlay networks will have no binding
        # TODO(salvatore-orlando) make sure users can specify a distinct
        # phy_uuid as 'provider network' for STT net type
        if bindings:
            if not multiprovider:
                # network came in through provider networks api
                network[pnet.NETWORK_TYPE] = bindings[0].binding_type
                network[pnet.PHYSICAL_NETWORK] = bindings[0].phy_uuid
                network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id
            else:
                # network come in though multiprovider networks api
                network[mpnet_apidef.SEGMENTS] = [
                    {pnet.NETWORK_TYPE: binding.binding_type,
                     pnet.PHYSICAL_NETWORK: binding.phy_uuid,
                     pnet.SEGMENTATION_ID: binding.vlan_id}
                    for binding in bindings]

    def extend_port_dict_binding(self, port_res, port_db):
        super(NsxPluginV2, self).extend_port_dict_binding(port_res, port_db)
        port_res[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL

    def _handle_lswitch_selection(self, context, cluster, network):
        # NOTE(salv-orlando): This method used to select a NSX logical switch
        # with an available port, and create a new logical switch if
        # necessary. As there is no more need to perform switch chaining in
        # NSX, the logic for creating a new logical switch has been removed.
        max_ports = self.nsx_opts.max_lp_per_overlay_ls
        network_bindings = nsx_db.get_network_bindings(
            context.session, network['id'])
        for network_binding in network_bindings:
            if network_binding.binding_type in (c_utils.NetworkTypes.FLAT,
                                                c_utils.NetworkTypes.VLAN):
                max_ports = self.nsx_opts.max_lp_per_bridged_ls
        # This is still necessary as there could be chained switches in
        # the deployment and the code needs to find the first one with
        # an available slot for a port
        lswitches = nsx_utils.fetch_nsx_switches(
            context.session, cluster, network['id'])
        try:
            return [ls for ls in lswitches
                    if (ls['_relations']['LogicalSwitchStatus']
                        ['lport_count'] < max_ports)].pop(0)
        except IndexError:
            # Too bad, no switch where a port can be created
            LOG.debug("No switch has available ports (%d checked)",
                      len(lswitches))
            raise nsx_exc.NoMorePortsException(network=network.id)

    def _convert_to_nsx_transport_zones(self, cluster, network=None,
                                        bindings=None):
        # TODO(salv-orlando): Remove this method and call nsx-utils direct
        return nsx_utils.convert_to_nsx_transport_zones(
            cluster.default_tz_uuid, network, bindings,
            default_transport_type=cfg.CONF.NSX.default_transport_type)

    def _convert_to_transport_zones_dict(self, network):
        """Converts the provider request body to multiprovider.
        Returns: True if request is multiprovider False if provider
        and None if neither.
        """
        if any(validators.is_attr_set(network.get(f))
               for f in (pnet.NETWORK_TYPE, pnet.PHYSICAL_NETWORK,
                         pnet.SEGMENTATION_ID)):
            if validators.is_attr_set(network.get(mpnet_apidef.SEGMENTS)):
                raise mpnet_exc.SegmentsSetInConjunctionWithProviders()
            # convert to transport zone list
            network[mpnet_apidef.SEGMENTS] = [
                {pnet.NETWORK_TYPE: network[pnet.NETWORK_TYPE],
                 pnet.PHYSICAL_NETWORK: network[pnet.PHYSICAL_NETWORK],
                 pnet.SEGMENTATION_ID: network[pnet.SEGMENTATION_ID]}]
            del network[pnet.NETWORK_TYPE]
            del network[pnet.PHYSICAL_NETWORK]
            del network[pnet.SEGMENTATION_ID]
            return False
        if validators.is_attr_set(mpnet_apidef.SEGMENTS):
            return True

    def create_network(self, context, network):
        net_data = network['network']
        tenant_id = net_data['tenant_id']
        self._ensure_default_security_group(context, tenant_id)
        # Process the provider network extension
        provider_type = self._convert_to_transport_zones_dict(net_data)
        self._validate_provider_create(context, net_data)
        # Replace ATTR_NOT_SPECIFIED with None before sending to NSX
        for key, value in six.iteritems(network['network']):
            if value is constants.ATTR_NOT_SPECIFIED:
                net_data[key] = None
        # FIXME(arosen) implement admin_state_up = False in NSX
        if net_data['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_data.get('name', '<unknown>'))
        transport_zone_config = self._convert_to_nsx_transport_zones(
            self.cluster, net_data)
        external = net_data.get(extnet_apidef.EXTERNAL)
        # NOTE(salv-orlando): Pre-generating uuid for Neutron
        # network. This will be removed once the network create operation
        # becomes an asynchronous task
        net_data['id'] = str(uuid.uuid4())
        if (not validators.is_attr_set(external) or
            validators.is_attr_set(external) and not external):
            lswitch = switchlib.create_lswitch(
                self.cluster, net_data['id'],
                tenant_id, net_data.get('name'),
                transport_zone_config,
                shared=net_data.get(attr.SHARED))

        with db_api.context_manager.writer.using(context):
            new_net = super(NsxPluginV2, self).create_network(context,
                                                              network)
            # Process port security extension
            self._process_network_port_security_create(
                context, net_data, new_net)
            # DB Operations for setting the network as external
            self._process_l3_create(context, new_net, net_data)
            # Process QoS queue extension
            net_queue_id = net_data.get(qos.QUEUE)
            if net_queue_id:
                # Raises if not found
                self.get_qos_queue(context, net_queue_id)
                self._process_network_queue_mapping(
                    context, new_net, net_queue_id)
            # Add mapping between neutron network and NSX switch
            if (not validators.is_attr_set(external) or
                validators.is_attr_set(external) and not external):
                nsx_db.add_neutron_nsx_network_mapping(
                    context.session, new_net['id'],
                    lswitch['uuid'])
            if (net_data.get(mpnet_apidef.SEGMENTS) and
                isinstance(provider_type, bool)):
                net_bindings = []
                for tz in net_data[mpnet_apidef.SEGMENTS]:
                    segmentation_id = tz.get(pnet.SEGMENTATION_ID, 0)
                    segmentation_id_set = validators.is_attr_set(
                        segmentation_id)
                    if not segmentation_id_set:
                        segmentation_id = 0
                    net_bindings.append(nsx_db.add_network_binding(
                        context.session, new_net['id'],
                        tz.get(pnet.NETWORK_TYPE),
                        tz.get(pnet.PHYSICAL_NETWORK),
                        segmentation_id))
                if provider_type:
                    nsx_db.set_multiprovider_network(context.session,
                                                     new_net['id'])
                self._extend_network_dict_provider(context, new_net,
                                                   provider_type,
                                                   net_bindings)

        # this extra lookup is necessary to get the
        # latest db model for the extension functions
        net_model = self._get_network(context, new_net['id'])
        resource_extend.apply_funcs('networks', new_net, net_model)
        self.handle_network_dhcp_access(context, new_net,
                                        action='create_network')
        return new_net

    def delete_network(self, context, id):
        external = self._network_is_external(context, id)
        # Before removing entry from Neutron DB, retrieve NSX switch
        # identifiers for removing them from backend
        if not external:
            lswitch_ids = nsx_utils.get_nsx_switch_ids(
                context.session, self.cluster, id)
        self._process_l3_delete(context, id)
        nsx_db.delete_network_bindings(context.session, id)
        super(NsxPluginV2, self).delete_network(context, id)

        # Do not go to NSX for external networks
        if not external:
            try:
                switchlib.delete_networks(self.cluster, id, lswitch_ids)
            except n_exc.NotFound:
                LOG.warning("The following logical switches were not "
                            "found on the NSX backend:%s", lswitch_ids)
        self.handle_network_dhcp_access(context, id, action='delete_network')
        LOG.debug("Delete network complete for network: %s", id)

    def get_network(self, context, id, fields=None):
        with db_api.context_manager.writer.using(context):
            # goto to the plugin DB and fetch the network
            network = self._get_network(context, id)
            if (self.nsx_sync_opts.always_read_status or
                fields and 'status' in fields):
                # External networks are not backed by nsx lswitches
                if not network.external:
                    # Perform explicit state synchronization
                    self._synchronizer.synchronize_network(context, network)
            # Don't do field selection here otherwise we won't be able
            # to add provider networks fields
            net_result = self._make_network_dict(network,
                                                 context=context)
            self._extend_network_dict_provider(context, net_result)
        return db_utils.resource_fields(net_result, fields)

    def get_networks(self, context, filters=None, fields=None,
                     sorts=None, limit=None, marker=None,
                     page_reverse=False):
        filters = filters or {}
        with db_api.context_manager.reader.using(context):
            networks = (
                super(NsxPluginV2, self).get_networks(
                    context, filters, fields, sorts,
                    limit, marker, page_reverse))
            for net in networks:
                self._extend_network_dict_provider(context, net)
        return (networks if not fields else
                [db_utils.resource_fields(network,
                                          fields) for network in networks])

    def update_network(self, context, id, network):
        providernet._raise_if_updates_provider_attributes(network['network'])
        if network["network"].get("admin_state_up") is False:
            raise NotImplementedError(_("admin_state_up=False networks "
                                        "are not supported."))
        with db_api.context_manager.writer.using(context):
            net = super(NsxPluginV2, self).update_network(context, id, network)
            if psec.PORTSECURITY in network['network']:
                self._process_network_port_security_update(
                    context, network['network'], net)
            net_queue_id = network['network'].get(qos.QUEUE)
            if net_queue_id:
                self._delete_network_queue_mapping(context, id)
                self._process_network_queue_mapping(context, net, net_queue_id)
            self._process_l3_update(context, net, network['network'])
            self._extend_network_dict_provider(context, net)
        # If provided, update port name on backend; treat backend failures as
        # not critical (log error, but do not raise)
        if 'name' in network['network']:
            # in case of chained switches update name only for the first one
            nsx_switch_ids = nsx_utils.get_nsx_switch_ids(
                context.session, self.cluster, id)
            if not nsx_switch_ids or len(nsx_switch_ids) < 1:
                LOG.warning("Unable to find NSX mappings for neutron "
                            "network:%s", id)
            try:
                switchlib.update_lswitch(self.cluster,
                                         nsx_switch_ids[0],
                                         network['network']['name'])
            except api_exc.NsxApiException as e:
                LOG.warning("Logical switch update on NSX backend failed. "
                            "Neutron network id:%(net_id)s; "
                            "NSX lswitch id:%(lswitch_id)s;"
                            "Error:%(error)s",
                            {'net_id': id, 'lswitch_id': nsx_switch_ids[0],
                             'error': e})

        return net

    def create_port(self, context, port):
        # If PORTSECURITY is not the default value ATTR_NOT_SPECIFIED
        # then we pass the port to the policy engine. The reason why we don't
        # pass the value to the policy engine when the port is
        # ATTR_NOT_SPECIFIED is for the case where a port is created on a
        # shared network that is not owned by the tenant.
        port_data = port['port']
        dhcp_opts = port_data.get(edo_ext.EXTRADHCPOPTS, [])
        # Set port status as 'DOWN'. This will be updated by backend sync.
        port_data['status'] = constants.PORT_STATUS_DOWN
        with db_api.context_manager.writer.using(context):
            # First we allocate port in neutron database
            neutron_db = super(NsxPluginV2, self).create_port(context, port)
            neutron_port_id = neutron_db['id']
            # Update fields obtained from neutron db (eg: MAC address)
            port["port"].update(neutron_db)
            self.handle_port_metadata_access(context, neutron_db)
            # port security extension checks
            (port_security, has_ip) = self._determine_port_security_and_has_ip(
                context, port_data)
            port_data[psec.PORTSECURITY] = port_security
            self._process_port_port_security_create(
                context, port_data, neutron_db)
            # allowed address pair checks
            if validators.is_attr_set(port_data.get(
                    addr_apidef.ADDRESS_PAIRS)):
                if not port_security:
                    raise addr_exc.AddressPairAndPortSecurityRequired()
                else:
                    self._process_create_allowed_address_pairs(
                        context, neutron_db,
                        port_data[addr_apidef.ADDRESS_PAIRS])
            else:
                # remove ATTR_NOT_SPECIFIED
                port_data[addr_apidef.ADDRESS_PAIRS] = []

            # security group extension checks
            # NOTE: check_update_has_security_groups works fine for
            # create operations as well
            if port_security and has_ip:
                self._ensure_default_security_group_on_port(context, port)
            elif self._check_update_has_security_groups(
                 {'port': port_data}):
                raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
            port_data[ext_sg.SECURITYGROUPS] = (
                self._get_security_groups_on_port(context, port))
            self._process_port_create_security_group(
                context, port_data, port_data[ext_sg.SECURITYGROUPS])
            # QoS extension checks
            port_queue_id = self._check_for_queue_and_create(
                context, port_data)
            self._process_port_queue_mapping(
                context, port_data, port_queue_id)
            if (isinstance(port_data.get(mac_ext.MAC_LEARNING), bool)):
                self._create_mac_learning_state(context, port_data)
            elif mac_ext.MAC_LEARNING in port_data:
                port_data.pop(mac_ext.MAC_LEARNING)
            self._process_portbindings_create_and_update(context,
                                                         port['port'],
                                                         port_data)
            self._process_port_create_extra_dhcp_opts(context, port_data,
                                                      dhcp_opts)
            # For some reason the port bindings DB mixin does not handle
            # the VNIC_TYPE attribute, which is required by nova for
            # setting up VIFs.
            context.session.flush()
            port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL

        # DB Operation is complete, perform NSX operation
        try:
            port_data = port['port'].copy()
            port_create_func = self._port_drivers['create'].get(
                port_data['device_owner'],
                self._port_drivers['create']['default'])
            port_create_func(context, port_data)
            LOG.debug("port created on NSX backend for tenant "
                      "%(tenant_id)s: (%(id)s)", port_data)
        except n_exc.NotFound:
            LOG.warning("Logical switch for network %s was not "
                        "found in NSX.", port_data['network_id'])
            # Put port in error on neutron DB
            with db_api.context_manager.writer.using(context):
                port = self._get_port(context, neutron_port_id)
                port_data['status'] = constants.PORT_STATUS_ERROR
                port['status'] = port_data['status']
                context.session.add(port)
        except Exception:
            # Port must be removed from neutron DB
            with excutils.save_and_reraise_exception():
                LOG.error("Unable to create port or set port "
                          "attachment in NSX.")
                with db_api.context_manager.writer.using(context):
                    self.ipam.delete_port(context, neutron_port_id)
        # this extra lookup is necessary to get the
        # latest db model for the extension functions
        port_model = self._get_port(context, neutron_port_id)
        resource_extend.apply_funcs('ports', port_data, port_model)
        self.handle_port_dhcp_access(context, port_data, action='create_port')
        return port_data

    def update_port(self, context, id, port):
        delete_security_groups = self._check_update_deletes_security_groups(
            port)
        has_security_groups = self._check_update_has_security_groups(port)
        delete_addr_pairs = self._check_update_deletes_allowed_address_pairs(
            port)
        has_addr_pairs = self._check_update_has_allowed_address_pairs(port)

        with db_api.context_manager.writer.using(context):
            ret_port = super(NsxPluginV2, self).update_port(
                context, id, port)

            # Save current mac learning state to check whether it's
            # being updated or not
            old_mac_learning_state = ret_port.get(mac_ext.MAC_LEARNING)
            # copy values over - except fixed_ips as
            # they've already been processed
            port['port'].pop('fixed_ips', None)
            ret_port.update(port['port'])
            tenant_id = ret_port['tenant_id']
            self._update_extra_dhcp_opts_on_port(context, id, port, ret_port)

            # populate port_security setting
            if psec.PORTSECURITY not in port['port']:
                ret_port[psec.PORTSECURITY] = self._get_port_security_binding(
                    context, id)
            has_ip = self._ip_on_port(ret_port)
            # validate port security and allowed address pairs
            if not ret_port[psec.PORTSECURITY]:
                #  has address pairs in request
                if has_addr_pairs:
                    raise addr_exc.AddressPairAndPortSecurityRequired()
                elif not delete_addr_pairs:
                    # check if address pairs are in db
                    ret_port[addr_apidef.ADDRESS_PAIRS] = (
                        self.get_allowed_address_pairs(context, id))
                    if ret_port[addr_apidef.ADDRESS_PAIRS]:
                        raise addr_exc.AddressPairAndPortSecurityRequired()

            if (delete_addr_pairs or has_addr_pairs):
                # delete address pairs and read them in
                self._delete_allowed_address_pairs(context, id)
                self._process_create_allowed_address_pairs(
                    context, ret_port, ret_port[addr_apidef.ADDRESS_PAIRS])
            # checks if security groups were updated adding/modifying
            # security groups, port security is set and port has ip
            if not (has_ip and ret_port[psec.PORTSECURITY]):
                if has_security_groups:
                    raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
                # Update did not have security groups passed in. Check
                # that port does not have any security groups already on it.
                filters = {'port_id': [id]}
                security_groups = (
                    super(NsxPluginV2, self)._get_port_security_group_bindings(
                        context, filters)
                )
                if security_groups and not delete_security_groups:
                    raise psec_exc.PortSecurityPortHasSecurityGroup()

            if (delete_security_groups or has_security_groups):
                # delete the port binding and read it with the new rules.
                self._delete_port_security_group_bindings(context, id)
                sgids = self._get_security_groups_on_port(context, port)
                self._process_port_create_security_group(context, ret_port,
                                                         sgids)

            if psec.PORTSECURITY in port['port']:
                self._process_port_port_security_update(
                    context, port['port'], ret_port)

            port_queue_id = self._check_for_queue_and_create(
                context, ret_port)
            # Populate the mac learning attribute
            new_mac_learning_state = port['port'].get(mac_ext.MAC_LEARNING)
            if (new_mac_learning_state is not None and
                old_mac_learning_state != new_mac_learning_state):
                self._update_mac_learning_state(context, id,
                                                new_mac_learning_state)
                ret_port[mac_ext.MAC_LEARNING] = new_mac_learning_state
            self._delete_port_queue_mapping(context, ret_port['id'])
            self._process_port_queue_mapping(context, ret_port,
                                             port_queue_id)
            self._process_portbindings_create_and_update(context,
                                                         port['port'],
                                                         ret_port)
            nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
                context.session, self.cluster, id)
            # Convert Neutron security groups identifiers into NSX security
            # profiles identifiers
            nsx_sec_profile_ids = [
                nsx_utils.get_nsx_security_group_id(
                    context.session, self.cluster, neutron_sg_id) for
                neutron_sg_id in (ret_port[ext_sg.SECURITYGROUPS] or [])]

        # Perform the NSX operation outside of the DB transaction
        LOG.debug("Updating port %s on NSX backend", ret_port['id'])
        if nsx_port_id:
            try:
                switchlib.update_port(
                    self.cluster, nsx_switch_id, nsx_port_id,
                    id, tenant_id,
                    ret_port['name'],
                    ret_port['device_id'],
                    ret_port['admin_state_up'],
                    ret_port['mac_address'],
                    ret_port['fixed_ips'],
                    ret_port[psec.PORTSECURITY],
                    nsx_sec_profile_ids,
                    ret_port[qos.QUEUE],
                    ret_port.get(mac_ext.MAC_LEARNING),
                    ret_port.get(addr_apidef.ADDRESS_PAIRS))

                # Update the port status from nsx. If we fail here hide it
                # since the port was successfully updated but we were not
                # able to retrieve the status.
                ret_port['status'] = switchlib.get_port_status(
                    self.cluster, nsx_switch_id,
                    nsx_port_id)
            # FIXME(arosen) improve exception handling.
            except Exception:
                ret_port['status'] = constants.PORT_STATUS_ERROR
                LOG.exception("Unable to update port id: %s.",
                              nsx_port_id)

        # If nsx_port_id is not in database or in nsx put in error state.
        else:
            ret_port['status'] = constants.PORT_STATUS_ERROR
        return ret_port

    def delete_port(self, context, id, l3_port_check=True,
                    nw_gw_port_check=True):
        """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
        """
        # if needed, check to see if this is a port owned by
        # a l3 router.  If so, we should prevent deletion here
        if l3_port_check:
            self.prevent_l3_port_deletion(context, id)
        neutron_db_port = self.get_port(context, id)
        # Perform the same check for ports owned by layer-2 gateways
        if nw_gw_port_check:
            self.prevent_network_gateway_port_deletion(context,
                                                       neutron_db_port)
        port_delete_func = self._port_drivers['delete'].get(
            neutron_db_port['device_owner'],
            self._port_drivers['delete']['default'])

        port_delete_func(context, neutron_db_port)
        self.disassociate_floatingips(context, id)
        with db_api.context_manager.writer.using(context):
            queue = self._get_port_queue_bindings(context, {'port_id': [id]})
            # metadata_dhcp_host_route
            self.handle_port_metadata_access(
                context, neutron_db_port, is_delete=True)
            super(NsxPluginV2, self).delete_port(context, id)
            # Delete qos queue if possible
            if queue:
                self.delete_qos_queue(context, queue[0]['queue_id'], False)
        self.handle_port_dhcp_access(
            context, neutron_db_port, action='delete_port')

    def get_port(self, context, id, fields=None):
        with db_api.context_manager.writer.using(context):
            if (self.nsx_sync_opts.always_read_status or
                fields and 'status' in fields):
                # Perform explicit state synchronization
                db_port = self._get_port(context, id)
                self._synchronizer.synchronize_port(
                    context, db_port)
                return self._make_port_dict(db_port, fields)
            else:
                return super(NsxPluginV2, self).get_port(context, id, fields)

    def get_router(self, context, id, fields=None):
        with db_api.context_manager.writer.using(context):
            if (self.nsx_sync_opts.always_read_status or
                fields and 'status' in fields):
                db_router = self._get_router(context, id)
                # Perform explicit state synchronization
                self._synchronizer.synchronize_router(
                    context, db_router)
                return self._make_router_dict(db_router, fields)
            else:
                return super(NsxPluginV2, self).get_router(context, id, fields)

    def _create_lrouter(self, context, router, nexthop):
        tenant_id = router['tenant_id']
        distributed = router.get('distributed')
        try:
            lrouter = routerlib.create_lrouter(
                self.cluster, router['id'],
                tenant_id, router['name'], nexthop,
                distributed=(validators.is_attr_set(distributed)
                             and distributed))
        except nsx_exc.InvalidVersion:
            msg = _("Cannot create a distributed router with the NSX "
                    "platform currently in execution. Please, try "
                    "without specifying the 'distributed' attribute.")
            LOG.exception(msg)
            raise n_exc.BadRequest(resource='router', msg=msg)
        except api_exc.NsxApiException:
            err_msg = _("Unable to create logical router on NSX Platform")
            LOG.exception(err_msg)
            raise nsx_exc.NsxPluginException(err_msg=err_msg)

        # Create the port here - and update it later if we have gw_info
        try:
            self._create_and_attach_router_port(
                self.cluster, context, lrouter['uuid'], {'fake_ext_gw': True},
                "L3GatewayAttachment",
                self.cluster.default_l3_gw_service_uuid)
        except nsx_exc.NsxPluginException:
            LOG.exception("Unable to create L3GW port on logical router "
                          "%(router_uuid)s. Verify Default Layer-3 "
                          "Gateway service %(def_l3_gw_svc)s id is "
                          "correct",
                          {'router_uuid': lrouter['uuid'],
                           'def_l3_gw_svc':
                           self.cluster.default_l3_gw_service_uuid})
            # Try and remove logical router from NSX
            routerlib.delete_lrouter(self.cluster, lrouter['uuid'])
            # Return user a 500 with an apter message
            raise nsx_exc.NsxPluginException(
                err_msg=(_("Unable to create router %s on NSX backend") %
                         router['id']))
        lrouter['status'] = constants.ACTIVE
        return lrouter

    def _process_extra_attr_router_create(self, context, router_db, r):
        for extra_attr in l3_attrs_db.get_attr_info().keys():
            if extra_attr in r:
                self.set_extra_attr_value(context, router_db,
                                          extra_attr, r[extra_attr])

    def create_router(self, context, router):
        # NOTE(salvatore-orlando): We completely override this method in
        # order to be able to use the NSX ID as Neutron ID
        # TODO(salvatore-orlando): Propose upstream patch for allowing
        # 3rd parties to specify IDs as we do with l2 plugin
        r = router['router']
        has_gw_info = False
        tenant_id = r['tenant_id']
        # default value to set - nsx wants it (even if we don't have it)
        nexthop = NSX_DEFAULT_NEXTHOP
        # if external gateway info are set, then configure nexthop to
        # default external gateway
        if 'external_gateway_info' in r and r.get('external_gateway_info'):
            has_gw_info = True
            gw_info = r['external_gateway_info']
            del r['external_gateway_info']
            # The following DB read will be performed again when updating
            # gateway info. This is not great, but still better than
            # creating NSX router here and updating it later
            network_id = (gw_info.get('network_id', None) if gw_info
                          else None)
            if network_id:
                ext_net = self._get_network(context, network_id)
                if not ext_net.external:
                    msg = (_("Network '%s' is not a valid external "
                             "network") % network_id)
                    raise n_exc.BadRequest(resource='router', msg=msg)
                if ext_net.subnets:
                    ext_subnet = ext_net.subnets[0]
                    nexthop = ext_subnet.gateway_ip
        # NOTE(salv-orlando): Pre-generating uuid for Neutron
        # router. This will be removed once the router create operation
        # becomes an asynchronous task
        neutron_router_id = str(uuid.uuid4())
        r['id'] = neutron_router_id
        # Populate distributed attribute in order to ensure the appropriate
        # type of router is created in the NSX backend
        r['distributed'] = l3_dvr_db.is_distributed_router(r)
        lrouter = self._create_lrouter(context, r, nexthop)
        # TODO(salv-orlando): Deal with backend object removal in case
        # of db failures
        with db_api.context_manager.writer.using(context):
            # Transaction nesting is needed to avoid foreign key violations
            # when processing the distributed router binding
            with db_api.context_manager.writer.using(context):
                router_db = l3_db_models.Router(
                    id=neutron_router_id,
                    tenant_id=tenant_id,
                    name=r['name'],
                    admin_state_up=r['admin_state_up'],
                    status=lrouter['status'])
                context.session.add(router_db)
                self._process_extra_attr_router_create(context, router_db, r)
                # Ensure neutron router is moved into the transaction's buffer
                context.session.flush()
                # Add mapping between neutron and nsx identifiers
                nsx_db.add_neutron_nsx_router_mapping(
                    context.session, router_db['id'], lrouter['uuid'])

            if has_gw_info:
                # NOTE(salv-orlando): This operation has been moved out of the
                # database transaction since it performs several NSX queries,
                # ithis ncreasing the risk of deadlocks between eventlet and
                # sqlalchemy operations.
                # Set external gateway and remove router in case of failure
                try:
                    self._update_router_gw_info(context, router_db['id'],
                                                gw_info)
                except (n_exc.NeutronException, api_exc.NsxApiException):
                    with excutils.save_and_reraise_exception():
                        # As setting gateway failed, the router must be deleted
                        # in order to ensure atomicity
                        router_id = router_db['id']
                        LOG.warning("Failed to set gateway info for router "
                                    "being created:%s - removing router",
                                    router_id)
                        self.delete_router(context, router_id)
                        LOG.info("Create router failed while setting external "
                                 "gateway. Router:%s has been removed from "
                                 "DB and backend",
                                 router_id)
        return self._make_router_dict(router_db)

    def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, router_id)
        return routerlib.update_lrouter(
            self.cluster, nsx_router_id, name,
            nexthop, routes=routes)

    def _update_lrouter_routes(self, context, router_id, routes):
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, router_id)
        routerlib.update_explicit_routes_lrouter(
            self.cluster, nsx_router_id, routes)

    def update_router(self, context, router_id, router):
        # Either nexthop is updated or should be kept as it was before
        r = router['router']
        nexthop = None
        if 'external_gateway_info' in r and r.get('external_gateway_info'):
            gw_info = r['external_gateway_info']
            # The following DB read will be performed again when updating
            # gateway info. This is not great, but still better than
            # creating NSX router here and updating it later
            network_id = (gw_info.get('network_id', None) if gw_info
                          else None)
            if network_id:
                ext_net = self._get_network(context, network_id)
                if not ext_net.external:
                    msg = (_("Network '%s' is not a valid external "
                             "network") % network_id)
                    raise n_exc.BadRequest(resource='router', msg=msg)
                if ext_net.subnets:
                    ext_subnet = ext_net.subnets[0]
                    nexthop = ext_subnet.gateway_ip
        try:
            for route in r.get('routes', []):
                if route['destination'] == '0.0.0.0/0':
                    msg = _("'routes' cannot contain route '0.0.0.0/0', "
                            "this must be updated through the default "
                            "gateway attribute")
                    raise n_exc.BadRequest(resource='router', msg=msg)
            previous_routes = self._update_lrouter(
                context, router_id, r.get('name'),
                nexthop, routes=r.get('routes'))
        # NOTE(salv-orlando): The exception handling below is not correct, but
        # unfortunately nsxlib raises a neutron notfound exception when an
        # object is not found in the underlying backend
        except n_exc.NotFound:
            # Put the router in ERROR status
            with db_api.context_manager.writer.using(context):
                router_db = self._get_router(context, router_id)
                router_db['status'] = constants.NET_STATUS_ERROR
            raise nsx_exc.NsxPluginException(
                err_msg=_("Logical router %s not found "
                          "on NSX Platform") % router_id)
        except api_exc.NsxApiException:
            raise nsx_exc.NsxPluginException(
                err_msg=_("Unable to update logical router on NSX Platform"))
        except nsx_exc.InvalidVersion:
            msg = _("Request cannot contain 'routes' with the NSX "
                    "platform currently in execution. Please, try "
                    "without specifying the static routes.")
            LOG.exception(msg)
            raise n_exc.BadRequest(resource='router', msg=msg)
        try:
            return super(NsxPluginV2, self).update_router(context,
                                                          router_id, router)
        except (xroute_exc.InvalidRoutes,
                xroute_exc.RouterInterfaceInUseByRoute,
                xroute_exc.RoutesExhausted):
            with excutils.save_and_reraise_exception():
                # revert changes made to NSX
                self._update_lrouter_routes(
                    context, router_id, previous_routes)

    def _delete_lrouter(self, context, router_id, nsx_router_id):
        # The neutron router id (router_id) is ignored in this routine,
        # but used in plugins deriving from this one
        routerlib.delete_lrouter(self.cluster, nsx_router_id)

    def delete_router(self, context, router_id):
        with db_api.context_manager.writer.using(context):
            # NOTE(salv-orlando): These checks will be repeated anyway when
            # calling the superclass. This is wasteful, but is the simplest
            # way of ensuring a consistent removal of the router both in
            # the neutron Database and in the NSX backend.
            self._ensure_router_not_in_use(context, router_id)
            # TODO(salv-orlando): This call should have no effect on delete
            # router, but if it does, it should not happen within a
            # transaction, and it should be restored on rollback
            self.handle_router_metadata_access(
                context, router_id, interface=None)

        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, router_id)
        # It is safe to remove the router from the database, so remove it
        # from the backend
        if nsx_router_id:
            try:
                self._delete_lrouter(context, router_id, nsx_router_id)
            except n_exc.NotFound:
                # This is not a fatal error, but needs to be logged
                LOG.warning("Logical router '%s' not found "
                            "on NSX Platform", router_id)
            except api_exc.NsxApiException:
                raise nsx_exc.NsxPluginException(
                    err_msg=(_("Unable to delete logical router '%s' "
                            "on NSX Platform") % nsx_router_id))
        else:
            # If no mapping is found it is likely that the logical router does
            # not exist anymore in the backend. This is not a fatal condition,
            # but will result in an exception is "None" is passed to
            # _delete_lrouter
            LOG.warning("No mapping found for logical router '%s' "
                        "on NSX Platform", router_id)

        # Remove the NSX mapping first in order to ensure a mapping to
        # a non-existent NSX router is not left in the DB in case of
        # failure while removing the router from the neutron DB
        try:
            nsx_db.delete_neutron_nsx_router_mapping(
                context.session, router_id)
        except db_exc.DBError as d_exc:
            # Do not make this error fatal
            LOG.warning("Unable to remove NSX mapping for Neutron router "
                        "%(router_id)s because of the following exception:"
                        "%(d_exc)s", {'router_id': router_id,
                                      'd_exc': str(d_exc)})
        # Perform the actual delete on the Neutron DB
        super(NsxPluginV2, self).delete_router(context, router_id)

    def _add_subnet_snat_rule(self, context, router, subnet):
        gw_port = router.gw_port
        if gw_port and router.enable_snat:
            # There is a change gw_port might have multiple IPs
            # In that case we will consider only the first one
            if gw_port.get('fixed_ips'):
                snat_ip = gw_port['fixed_ips'][0]['ip_address']
                cidr_prefix = int(subnet['cidr'].split('/')[1])
                nsx_router_id = nsx_utils.get_nsx_router_id(
                    context.session, self.cluster, router['id'])
                routerlib.create_lrouter_snat_rule(
                    self.cluster, nsx_router_id, snat_ip, snat_ip,
                    order=NSX_EXTGW_NAT_RULES_ORDER - cidr_prefix,
                    match_criteria={'source_ip_addresses': subnet['cidr']})

    def _delete_subnet_snat_rule(self, context, router, subnet):
        # Remove SNAT rule if external gateway is configured
        if router.gw_port:
            nsx_router_id = nsx_utils.get_nsx_router_id(
                context.session, self.cluster, router['id'])
            routerlib.delete_nat_rules_by_match(
                self.cluster, nsx_router_id, "SourceNatRule",
                max_num_expected=1, min_num_expected=1,
                raise_on_len_mismatch=False,
                source_ip_addresses=subnet['cidr'])

    def add_router_interface(self, context, router_id, interface_info):
        # When adding interface by port_id we need to create the
        # peer port on the nsx logical router in this routine
        port_id = interface_info.get('port_id')
        router_iface_info = super(NsxPluginV2, self).add_router_interface(
            context, router_id, interface_info)
        # router_iface_info will always have a subnet_id attribute
        subnet_id = router_iface_info['subnet_id']
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, router_id)
        if port_id:
            port_data = self.get_port(context, port_id)
            # If security groups are present we need to remove them as
            # this is a router port and disable port security.
            if port_data['security_groups']:
                self.update_port(context, port_id,
                                 {'port': {'security_groups': [],
                                           psec.PORTSECURITY: False}})
            nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
                context.session, self.cluster, port_id)
            # Unplug current attachment from lswitch port
            switchlib.plug_vif_interface(self.cluster, nsx_switch_id,
                                         nsx_port_id, "NoAttachment")
            # Create logical router port and plug patch attachment
            self._create_and_attach_router_port(
                self.cluster, context, nsx_router_id, port_data,
                "PatchAttachment", nsx_port_id, subnet_ids=[subnet_id])
        subnet = self._get_subnet(context, subnet_id)
        # If there is an external gateway we need to configure the SNAT rule.
        # Fetch router from DB
        router = self._get_router(context, router_id)
        self._add_subnet_snat_rule(context, router, subnet)
        routerlib.create_lrouter_nosnat_rule(
            self.cluster, nsx_router_id,
            order=NSX_NOSNAT_RULES_ORDER,
            match_criteria={'destination_ip_addresses': subnet['cidr']})

        # Ensure the NSX logical router has a connection to a 'metadata access'
        # network (with a proxy listening on its DHCP port), by creating it
        # if needed.
        self.handle_router_metadata_access(
            context, router_id, interface=router_iface_info)
        LOG.debug("Add_router_interface completed for subnet:%(subnet_id)s "
                  "and router:%(router_id)s",
                  {'subnet_id': subnet_id, 'router_id': router_id})
        return router_iface_info

    def get_l3_agents_hosting_routers(self, context, routers):
        # This method is just a stub added because is required by the l3 dvr
        # mixin. That's so much for a management layer which is plugin
        # agnostic
        return []

    def _create_snat_intf_ports_if_not_exists(self, context, router):
        # VMware plugins do not need SNAT interface ports
        return []

    def _add_csnat_router_interface_port(self, context, router, network_id,
                                         subnet_id, do_pop=True):
        # VMware plugins do not need SNAT interface ports
        return

    def _delete_csnat_router_interface_ports(self, context, router,
                                             subnet_id=None):
        # VMware plugins do not need SNAT interface ports
        return

    def remove_router_interface(self, context, router_id, interface_info):
        # The code below is duplicated from base class, but comes handy
        # as we need to retrieve the router port id before removing the port
        subnet = None
        subnet_id = None
        if 'port_id' in interface_info:
            port_id = interface_info['port_id']
            # find subnet_id - it is need for removing the SNAT rule
            port = self._get_port(context, port_id)
            if port.get('fixed_ips'):
                subnet_id = port['fixed_ips'][0]['subnet_id']
            if not (port['device_owner'] in constants.ROUTER_INTERFACE_OWNERS
                    and port['device_id'] == router_id):
                raise l3_exc.RouterInterfaceNotFound(
                    router_id=router_id, port_id=port_id)
        elif 'subnet_id' in interface_info:
            subnet_id = interface_info['subnet_id']
            subnet = self._get_subnet(context, subnet_id)
            rport_qry = context.session.query(models_v2.Port)
            ports = rport_qry.filter_by(
                device_id=router_id,
                network_id=subnet['network_id']).filter(
                    models_v2.Port.device_owner.in_(
                        constants.ROUTER_INTERFACE_OWNERS))
            for p in ports:
                if p['fixed_ips'][0]['subnet_id'] == subnet_id:
                    port_id = p['id']
                    break
            else:
                raise l3_exc.RouterInterfaceNotFoundForSubnet(
                    router_id=router_id, subnet_id=subnet_id)
        # Finally remove the data from the Neutron DB
        # This will also destroy the port on the logical switch
        info = super(NsxPluginV2, self).remove_router_interface(
            context, router_id, interface_info)

        try:
            # Ensure the connection to the 'metadata access network'
            # is removed  (with the network) if this the last subnet
            # on the router
            self.handle_router_metadata_access(
                context, router_id, interface=None)
            if not subnet:
                subnet = self._get_subnet(context, subnet_id)
            router = self._get_router(context, router_id)
            # If router is enabled_snat = False there are no snat rules to
            # delete.
            if router.enable_snat:
                self._delete_subnet_snat_rule(context, router, subnet)
            # Relax the minimum expected number as the nosnat rules
            # do not exist in 2.x deployments
            nsx_router_id = nsx_utils.get_nsx_router_id(
                context.session, self.cluster, router_id)
            routerlib.delete_nat_rules_by_match(
                self.cluster, nsx_router_id, "NoSourceNatRule",
                max_num_expected=1, min_num_expected=0,
                raise_on_len_mismatch=False,
                destination_ip_addresses=subnet['cidr'])
        except n_exc.NotFound:
            LOG.error("Logical router resource %s not found "
                      "on NSX platform", router_id)
        except api_exc.NsxApiException:
            raise nsx_exc.NsxPluginException(
                err_msg=(_("Unable to update logical router"
                           "on NSX Platform")))
        return info

    def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
                                       internal_ip, nsx_router_id,
                                       min_num_rules_expected=0):
        """Finds and removes NAT rules from a NSX router."""
        # NOTE(salv-orlando): The context parameter is ignored in this method
        # but used by derived classes
        try:
            # Remove DNAT rule for the floating IP
            routerlib.delete_nat_rules_by_match(
                self.cluster, nsx_router_id, "DestinationNatRule",
                max_num_expected=1,
                min_num_expected=min_num_rules_expected,
                destination_ip_addresses=floating_ip_address)

            # Remove SNAT rules for the floating IP
            routerlib.delete_nat_rules_by_match(
                self.cluster, nsx_router_id, "SourceNatRule",
                max_num_expected=1,
                min_num_expected=min_num_rules_expected,
                source_ip_addresses=internal_ip)
            routerlib.delete_nat_rules_by_match(
                self.cluster, nsx_router_id, "SourceNatRule",
                max_num_expected=1,
                min_num_expected=min_num_rules_expected,
                destination_ip_addresses=internal_ip)

        except api_exc.NsxApiException:
            with excutils.save_and_reraise_exception():
                LOG.exception("An error occurred while removing NAT rules "
                              "on the NSX platform for floating ip:%s",
                              floating_ip_address)
        except nsx_exc.NatRuleMismatch:
            # Do not surface to the user
            LOG.warning("An incorrect number of matching NAT rules "
                        "was found on the NSX platform")

    def _remove_floatingip_address(self, context, fip_db):
        # Remove floating IP address from logical router port
        # Fetch logical port of router's external gateway
        router_id = fip_db.router_id
        nsx_router_id = nsx_utils.get_nsx_router_id(
            context.session, self.cluster, router_id)
        nsx_gw_port_id = routerlib.find_router_gw_port(
            context, self.cluster, nsx_router_id)['uuid']
        ext_neutron_port_db = self._get_port(context.elevated(),
                                             fip_db.floating_port_id)
        nsx_floating_ips = self._build_ip_address_list(
            context.elevated(), ext_neutron_port_db['fixed_ips'])
        routerlib.update_lrouter_port_ips(self.cluster,
                                          nsx_router_id,
                                          nsx_gw_port_id,
                                          ips_to_add=[],
                                          ips_to_remove=nsx_floating_ips)

    def _floatingip_status(self, floatingip_db, associated):
        if (associated and
            floatingip_db['status'] != constants.FLOATINGIP_STATUS_ACTIVE):
            return constants.FLOATINGIP_STATUS_ACTIVE
        elif (not associated and
              floatingip_db['status'] != constants.FLOATINGIP_STATUS_DOWN):
            return constants.FLOATINGIP_STATUS_DOWN
        # in any case ensure the status is not reset by this method!
        return floatingip_db['status']

    def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
        """Update floating IP association data.

        Overrides method from base class.
        The method is augmented for creating NAT rules in the process.
        """
        # Store router currently serving the floating IP
        old_router_id = floatingip_db.router_id
        port_id, internal_ip, router_id = self._check_and_get_fip_assoc(
            context, fip, floatingip_db)
        floating_ip = floatingip_db['floating_ip_address']
        # If there's no association router_id will be None
        if router_id:
            nsx_router_id = nsx_utils.get_nsx_router_id(
                context.session, self.cluster, router_id)
            self._retrieve_and_delete_nat_rules(
                context, floating_ip, internal_ip, nsx_router_id)
        nsx_floating_ips = self._build_ip_address_list(
            context.elevated(), external_port['fixed_ips'])
        floating_ip = floatingip_db['floating_ip_address']
        # Retrieve and delete existing NAT rules, if any
        if old_router_id:
            nsx_old_router_id = nsx_utils.get_nsx_router_id(
                context.session, self.cluster, old_router_id)
            # Retrieve the current internal ip
            _p, _s, old_internal_ip = self._internal_fip_assoc_data(
                context, {'id': floatingip_db.id,
                          'port_id': floatingip_db.fixed_port_id,
                          'fixed_ip_address':
                              str(floatingip_db.fixed_ip_address),
                          'tenant_id': floatingip_db.tenant_id},
                floatingip_db.tenant_id)
            nsx_gw_port_id = routerlib.find_router_gw_port(
                context, self.cluster, nsx_old_router_id)['uuid']
            self._retrieve_and_delete_nat_rules(
                context, floating_ip, old_internal_ip, nsx_old_router_id)
            routerlib.update_lrouter_port_ips(
                self.cluster, nsx_old_router_id, nsx_gw_port_id,
                ips_to_add=[], ips_to_remove=nsx_floating_ips)

        if router_id:
            nsx_gw_port_id = routerlib.find_router_gw_port(
                context, self.cluster, nsx_router_id)['uuid']
            # Re-create NAT rules only if a port id is specified
            if fip.get('port_id'):
                try:
                    # Setup DNAT rules for the floating IP
                    routerlib.create_lrouter_dnat_rule(
                        self.cluster, nsx_router_id, internal_ip,
                        order=NSX_FLOATINGIP_NAT_RULES_ORDER,
                        match_criteria={'destination_ip_addresses':
                                        floating_ip})
                    # Setup SNAT rules for the floating IP
                    # Create a SNAT rule for enabling connectivity to the
                    # floating IP from the same network as the internal port
                    # Find subnet id for internal_ip from fixed_ips
                    internal_port = self._get_port(context, port_id)
                    # Cchecks not needed on statements below since otherwise
                    # _internal_fip_assoc_data would have raised
                    subnet_ids = [ip['subnet_id'] for ip in
                                  internal_port['fixed_ips'] if
                                  ip['ip_address'] == internal_ip]
                    internal_subnet_cidr = self._build_ip_address_list(
                        context, internal_port['fixed_ips'],
                        subnet_ids=subnet_ids)[0]
                    routerlib.create_lrouter_snat_rule(
                        self.cluster, nsx_router_id, floating_ip, floating_ip,
                        order=NSX_NOSNAT_RULES_ORDER - 1,
                        match_criteria={'source_ip_addresses':
                                        internal_subnet_cidr,
                                        'destination_ip_addresses':
                                        internal_ip})
                    # setup snat rule such that src ip of an IP packet when
                    # using floating is the floating ip itself.
                    routerlib.create_lrouter_snat_rule(
                        self.cluster, nsx_router_id, floating_ip, floating_ip,
                        order=NSX_FLOATINGIP_NAT_RULES_ORDER,
                        match_criteria={'source_ip_addresses': internal_ip})

                    # Add Floating IP address to router_port
                    routerlib.update_lrouter_port_ips(
                        self.cluster, nsx_router_id, nsx_gw_port_id,
                        ips_to_add=nsx_floating_ips, ips_to_remove=[])
                except api_exc.NsxApiException:
                    LOG.exception("An error occurred while creating NAT "
                                  "rules on the NSX platform for floating "
                                  "ip:%(floating_ip)s mapped to "
                                  "internal ip:%(internal_ip)s",
                                  {'floating_ip': floating_ip,
                                   'internal_ip': internal_ip})
                    msg = _("Failed to update NAT rules for floatingip update")
                    raise nsx_exc.NsxPluginException(err_msg=msg)
        # Update also floating ip status (no need to call base class method)
        new_status = self._floatingip_status(floatingip_db, router_id)
        floatingip_db.fixed_ip_address = internal_ip
        floatingip_db.fixed_port_id = port_id
        floatingip_db.router_id = router_id
        floatingip_db.status = new_status

        return {'fixed_ip_address': internal_ip,
                'fixed_port_id': port_id,
                'router_id': router_id,
                'last_known_router_id': None,
                'floating_ip_address': floatingip_db.floating_ip_address,
                'floating_network_id': floatingip_db.floating_network_id,
                'floating_ip_id': floatingip_db.id,
                'context': context}

    @lockutils.synchronized('vmware', 'neutron-')
    def create_floatingip(self, context, floatingip):
        return super(NsxPluginV2, self).create_floatingip(context, floatingip)

    @lockutils.synchronized('vmware', 'neutron-')
    def update_floatingip(self, context, floatingip_id, floatingip):
        return super(NsxPluginV2, self).update_floatingip(context,
                                                          floatingip_id,
                                                          floatingip)

    @lockutils.synchronized('vmware', 'neutron-')
    def delete_floatingip(self, context, id):
        fip_db = self._get_floatingip(context, id)
        # Check whether the floating ip is associated or not
        if fip_db.fixed_port_id:
            nsx_router_id = nsx_utils.get_nsx_router_id(
                context.session, self.cluster, fip_db.router_id)
            self._retrieve_and_delete_nat_rules(context,
                                                fip_db.floating_ip_address,
                                                fip_db.fixed_ip_address,
                                                nsx_router_id,
                                                min_num_rules_expected=1)
            # Remove floating IP address from logical router port
            self._remove_floatingip_address(context, fip_db)
        return super(NsxPluginV2, self).delete_floatingip(context, id)

    def disassociate_floatingips(self, context, port_id):
        try:
            fip_qry = context.session.query(l3_db_models.FloatingIP)
            fip_dbs = fip_qry.filter_by(fixed_port_id=port_id)

            for fip_db in fip_dbs:
                nsx_router_id = nsx_utils.get_nsx_router_id(
                    context.session, self.cluster, fip_db.router_id)
                self._retrieve_and_delete_nat_rules(context,
                                                    fip_db.floating_ip_address,
                                                    fip_db.fixed_ip_address,
                                                    nsx_router_id,
                                                    min_num_rules_expected=1)
                self._remove_floatingip_address(context, fip_db)
        except sa_exc.NoResultFound:
            LOG.debug("The port '%s' is not associated with floating IPs",
                      port_id)
        except n_exc.NotFound:
            LOG.warning("Nat rules not found in nsx for port: %s", id)

        # NOTE(ihrachys): L3 agent notifications don't make sense for
        # NSX VMWare plugin since there is no L3 agent in such setup, so
        # disabling them here.
        super(NsxPluginV2, self).disassociate_floatingips(
            context, port_id, do_notify=False)

    def create_network_gateway(self, context, network_gateway):
        """Create a layer-2 network gateway.

        Create the gateway service on NSX platform and corresponding data
        structures in Neutron datase.
        """
        gw_data = network_gateway[networkgw.GATEWAY_RESOURCE_NAME]
        tenant_id = gw_data['tenant_id']
        # Ensure the default gateway in the config file is in sync with the db
        self._ensure_default_network_gateway()
        # Validate provided gateway device list
        self._validate_device_list(context, tenant_id, gw_data)
        devices = gw_data['devices']
        # Populate default physical network where not specified
        for device in devices:
            if not device.get('interface_name'):
                device['interface_name'] = (self.cluster.
                                            nsx_default_interface_name)
        try:
            # Replace Neutron device identifiers with NSX identifiers
            dev_map = dict((dev['id'], dev['interface_name']) for
                           dev in devices)
            nsx_devices = []
            for db_device in self._query_gateway_devices(
                context, filters={'id': [device['id'] for device in devices]}):
                nsx_devices.append(
                    {'id': db_device['nsx_id'],
                     'interface_name': dev_map[db_device['id']]})
            nsx_res = l2gwlib.create_l2_gw_service(
                self.cluster, tenant_id, gw_data['name'], nsx_devices)
            nsx_uuid = nsx_res.get('uuid')
        except api_exc.Conflict:
            raise nsx_exc.L2GatewayAlreadyInUse(gateway=gw_data['name'])
        except api_exc.NsxApiException:
            err_msg = _("Unable to create l2_gw_service for: %s") % gw_data
            LOG.exception(err_msg)
            raise nsx_exc.NsxPluginException(err_msg=err_msg)
        gw_data['id'] = nsx_uuid
        return super(NsxPluginV2, self).create_network_gateway(
            context, network_gateway, validate_device_list=False)

    def delete_network_gateway(self, context, gateway_id):
        """Remove a layer-2 network gateway.

        Remove the gateway service from NSX platform and corresponding data
        structures in Neutron datase.
        """
        # Ensure the default gateway in the config file is in sync with the db
        self._ensure_default_network_gateway()
        with db_api.context_manager.writer.using(context):
            try:
                super(NsxPluginV2, self).delete_network_gateway(
                    context, gateway_id)
                l2gwlib.delete_l2_gw_service(self.cluster, gateway_id)
            except api_exc.ResourceNotFound:
                # Do not cause a 500 to be returned to the user if
                # the corresponding NSX resource does not exist
                LOG.exception("Unable to remove gateway service from "
                              "NSX plaform - the resource was not found")

    def get_network_gateway(self, context, id, fields=None):
        # Ensure the default gateway in the config file is in sync with the db
        self._ensure_default_network_gateway()
        return super(NsxPluginV2, self).get_network_gateway(context,
                                                            id, fields)

    def get_network_gateways(self, context, filters=None, fields=None,
                             sorts=None, limit=None, marker=None,
                             page_reverse=False):
        # Ensure the default gateway in the config file is in sync with the db
        self._ensure_default_network_gateway()
        # Ensure the tenant_id attribute is populated on returned gateways
        return super(NsxPluginV2, self).get_network_gateways(
            context, filters, fields, sorts, limit, marker, page_reverse)

    def update_network_gateway(self, context, id, network_gateway):
        # Ensure the default gateway in the config file is in sync with the db
        self._ensure_default_network_gateway()
        # Update gateway on backend when there's a name change
        name = network_gateway[networkgw.GATEWAY_RESOURCE_NAME].get('name')
        if name:
            try:
                l2gwlib.update_l2_gw_service(self.cluster, id, name)
            except api_exc.NsxApiException:
                # Consider backend failures as non-fatal, but still warn
                # because this might indicate something dodgy is going on
                LOG.warning("Unable to update name on NSX backend "
                            "for network gateway: %s", id)
        return super(NsxPluginV2, self).update_network_gateway(
            context, id, network_gateway)

    def connect_network(self, context, network_gateway_id,
                        network_mapping_info):
        # Ensure the default gateway in the config file is in sync with the db
        self._ensure_default_network_gateway()
        try:
            return super(NsxPluginV2, self).connect_network(
                context, network_gateway_id, network_mapping_info)
        except api_exc.Conflict:
            raise nsx_exc.L2GatewayAlreadyInUse(gateway=network_gateway_id)

    def disconnect_network(self, context, network_gateway_id,
                           network_mapping_info):
        # Ensure the default gateway in the config file is in sync with the db
        self._ensure_default_network_gateway()
        return super(NsxPluginV2, self).disconnect_network(
            context, network_gateway_id, network_mapping_info)

    def _get_nsx_device_id(self, context, device_id):
        return self._get_gateway_device(context, device_id)['nsx_id']

    def _rollback_gw_device(self, context, device_id, gw_data=None,
                            new_status=None, is_create=False):
        LOG.error("Rolling back database changes for gateway device %s "
                  "because of an error in the NSX backend", device_id)
        with db_api.context_manager.writer.using(context):
            query = model_query.query_with_hooks(
                context, nsx_models.NetworkGatewayDevice).filter(
                    nsx_models.NetworkGatewayDevice.id == device_id)
            if is_create:
                query.delete(synchronize_session=False)
            else:
                super(NsxPluginV2, self).update_gateway_device(
                    context, device_id,
                    {networkgw.DEVICE_RESOURCE_NAME: gw_data})
                if new_status:
                    query.update({'status': new_status},
                                 synchronize_session=False)

    # TODO(salv-orlando): Handlers for Gateway device operations should be
    # moved into the appropriate nsx_handlers package once the code for the
    # blueprint nsx-async-backend-communication merges
    def create_gateway_device_handler(self, context, gateway_device,
                                      client_certificate):
        neutron_id = gateway_device['id']
        try:
            nsx_res = l2gwlib.create_gateway_device(
                self.cluster,
                gateway_device['tenant_id'],
                gateway_device['name'],
                neutron_id,
                self.cluster.default_tz_uuid,
                gateway_device['connector_type'],
                gateway_device['connector_ip'],
                client_certificate)

            # Fetch status (it needs another NSX API call)
            device_status = nsx_utils.get_nsx_device_status(self.cluster,
                                                            nsx_res['uuid'])

            # set NSX GW device in neutron database and update status
            with db_api.context_manager.writer.using(context):
                query = model_query.query_with_hooks(
                    context, nsx_models.NetworkGatewayDevice).filter(
                        nsx_models.NetworkGatewayDevice.id == neutron_id)
                query.update({'status': device_status,
                              'nsx_id': nsx_res['uuid']},
                             synchronize_session=False)
            LOG.debug("Neutron gateway device: %(neutron_id)s; "
                      "NSX transport node identifier: %(nsx_id)s; "
                      "Operational status: %(status)s.",
                      {'neutron_id': neutron_id,
                       'nsx_id': nsx_res['uuid'],
                       'status': device_status})
            return device_status
        except (nsx_exc.InvalidSecurityCertificate, api_exc.NsxApiException):
            with excutils.save_and_reraise_exception():
                self._rollback_gw_device(context, neutron_id, is_create=True)

    def update_gateway_device_handler(self, context, gateway_device,
                                      old_gateway_device_data,
                                      client_certificate):
        nsx_id = gateway_device['nsx_id']
        neutron_id = gateway_device['id']
        try:
            l2gwlib.update_gateway_device(
                self.cluster,
                nsx_id,
                gateway_device['tenant_id'],
                gateway_device['name'],
                neutron_id,
                self.cluster.default_tz_uuid,
                gateway_device['connector_type'],
                gateway_device['connector_ip'],
                client_certificate)

            # Fetch status (it needs another NSX API call)
            device_status = nsx_utils.get_nsx_device_status(self.cluster,
                                                            nsx_id)
            # update status
            with db_api.context_manager.writer.using(context):
                query = model_query.query_with_hooks(
                    context, nsx_models.NetworkGatewayDevice).filter(
                        nsx_models.NetworkGatewayDevice.id == neutron_id)
                query.update({'status': device_status},
                             synchronize_session=False)
            LOG.debug("Neutron gateway device: %(neutron_id)s; "
                      "NSX transport node identifier: %(nsx_id)s; "
                      "Operational status: %(status)s.",
                      {'neutron_id': neutron_id,
                       'nsx_id': nsx_id,
                       'status': device_status})
            return device_status
        except (nsx_exc.InvalidSecurityCertificate, api_exc.NsxApiException):
            with excutils.save_and_reraise_exception():
                self._rollback_gw_device(context, neutron_id,
                                         gw_data=old_gateway_device_data)
        except n_exc.NotFound:
            # The gateway device was probably deleted in the backend.
            # The DB change should be rolled back and the status must
            # be put in error
            with excutils.save_and_reraise_exception():
                self._rollback_gw_device(context, neutron_id,
                                         gw_data=old_gateway_device_data,
                                         new_status=networkgw_db.ERROR)

    def get_gateway_device(self, context, device_id, fields=None):
        # Get device from database
        gw_device = super(NsxPluginV2, self).get_gateway_device(
            context, device_id, fields, include_nsx_id=True)
        # Fetch status from NSX
        nsx_id = gw_device['nsx_id']
        device_status = nsx_utils.get_nsx_device_status(self.cluster, nsx_id)
        # TODO(salv-orlando): Asynchronous sync for gateway device status
        # Update status in database
        with db_api.context_manager.writer.using(context):
            query = model_query.query_with_hooks(
                context, nsx_models.NetworkGatewayDevice).filter(
                    nsx_models.NetworkGatewayDevice.id == device_id)
            query.update({'status': device_status},
                         synchronize_session=False)
        gw_device['status'] = device_status
        return gw_device

    def get_gateway_devices(self, context, filters=None, fields=None,
                            sorts=None, limit=None, marker=None,
                            page_reverse=False):
        # Get devices from database
        devices = super(NsxPluginV2, self).get_gateway_devices(
            context, filters, fields, include_nsx_id=True)
        # Fetch operational status from NSX, filter by tenant tag
        # TODO(salv-orlando): Asynchronous sync for gateway device status
        tenant_id = context.tenant_id if not context.is_admin else None
        nsx_statuses = nsx_utils.get_nsx_device_statuses(self.cluster,
                                                         tenant_id)
        # Update statuses in database
        with db_api.context_manager.writer.using(context):
            for device in devices:
                new_status = nsx_statuses.get(device['nsx_id'])
                if new_status:
                    device['status'] = new_status
        return devices

    def create_gateway_device(self, context, gateway_device):
        # NOTE(salv-orlando): client-certificate will not be stored
        # in the database
        device_data = gateway_device[networkgw.DEVICE_RESOURCE_NAME]
        client_certificate = device_data.pop('client_certificate')
        gw_device = super(NsxPluginV2, self).create_gateway_device(
            context, gateway_device)
        # DB operation was successful, perform NSX operation
        gw_device['status'] = self.create_gateway_device_handler(
            context, gw_device, client_certificate)
        return gw_device

    def update_gateway_device(self, context, device_id,
                              gateway_device):
        # NOTE(salv-orlando): client-certificate will not be stored
        # in the database
        client_certificate = (
            gateway_device[networkgw.DEVICE_RESOURCE_NAME].pop(
                'client_certificate', None))
        # Retrive current state from DB in case a rollback should be needed
        old_gw_device_data = super(NsxPluginV2, self).get_gateway_device(
            context, device_id, include_nsx_id=True)
        gw_device = super(NsxPluginV2, self).update_gateway_device(
            context, device_id, gateway_device, include_nsx_id=True)
        # DB operation was successful, perform NSX operation
        gw_device['status'] = self.update_gateway_device_handler(
            context, gw_device, old_gw_device_data, client_certificate)
        gw_device.pop('nsx_id')
        return gw_device

    def delete_gateway_device(self, context, device_id):
        nsx_device_id = self._get_nsx_device_id(context, device_id)
        super(NsxPluginV2, self).delete_gateway_device(
            context, device_id)
        # DB operation was successful, perform NSX operation
        # TODO(salv-orlando): State consistency with neutron DB
        # should be ensured even in case of backend failures
        try:
            l2gwlib.delete_gateway_device(self.cluster, nsx_device_id)
        except n_exc.NotFound:
            LOG.warning("Removal of gateway device: %(neutron_id)s failed "
                        "on NSX backend (NSX id:%(nsx_id)s) because the "
                        "NSX resource was not found",
                        {'neutron_id': device_id, 'nsx_id': nsx_device_id})
        except api_exc.NsxApiException:
            with excutils.save_and_reraise_exception():
                # In this case a 500 should be returned
                LOG.exception("Removal of gateway device: %(neutron_id)s "
                              "failed on NSX backend (NSX id:%(nsx_id)s). "
                              "Neutron and NSX states have diverged.",
                              {'neutron_id': device_id,
                               'nsx_id': nsx_device_id})

    def create_security_group(self, context, security_group, default_sg=False):
        """Create security group.

        If default_sg is true that means we are creating a default security
        group and we don't need to check if one exists.
        """
        s = security_group.get('security_group')

        tenant_id = s['tenant_id']
        if not default_sg:
            self._ensure_default_security_group(context, tenant_id)
        # NOTE(salv-orlando): Pre-generating Neutron ID for security group.
        neutron_id = str(uuid.uuid4())
        nsx_secgroup = secgrouplib.create_security_profile(
            self.cluster, tenant_id, neutron_id, s)
        with db_api.context_manager.writer.using(context):
            s['id'] = neutron_id
            sec_group = super(NsxPluginV2, self).create_security_group(
                context, security_group, default_sg)
            context.session.flush()
            # Add mapping between neutron and nsx identifiers
            nsx_db.add_neutron_nsx_security_group_mapping(
                context.session, neutron_id, nsx_secgroup['uuid'])
        return sec_group

    def update_security_group(self, context, secgroup_id, security_group):
        secgroup = (super(NsxPluginV2, self).
                    update_security_group(context,
                                          secgroup_id,
                                          security_group))
        if ('name' in security_group['security_group'] and
            secgroup['name'] != 'default'):
            nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
                context.session, self.cluster, secgroup_id)
            try:
                name = security_group['security_group']['name']
                secgrouplib.update_security_profile(
                    self.cluster, nsx_sec_profile_id, name)
            except (n_exc.NotFound, api_exc.NsxApiException) as e:
                # Reverting the DB change is not really worthwhile
                # for a mismatch between names. It's the rules that
                # we care about.
                LOG.error('Error while updating security profile '
                          '%(uuid)s with name %(name)s: %(error)s.',
                          {'uuid': secgroup_id, 'name': name, 'error': e})
        return secgroup

    def delete_security_group(self, context, security_group_id):
        """Delete a security group.

        :param security_group_id: security group rule to remove.
        """
        with db_api.context_manager.writer.using(context):
            security_group = super(NsxPluginV2, self).get_security_group(
                context, security_group_id)
            if not security_group:
                raise ext_sg.SecurityGroupNotFound(id=security_group_id)

            if security_group['name'] == 'default' and not context.is_admin:
                raise ext_sg.SecurityGroupCannotRemoveDefault()

            filters = {'security_group_id': [security_group['id']]}
            if super(NsxPluginV2, self)._get_port_security_group_bindings(
                context, filters):
                raise ext_sg.SecurityGroupInUse(id=security_group['id'])
            nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
                context.session, self.cluster, security_group_id)

            try:
                secgrouplib.delete_security_profile(
                    self.cluster, nsx_sec_profile_id)
            except n_exc.NotFound:
                # The security profile was not found on the backend
                # do not fail in this case.
                LOG.warning("The NSX security profile %(sec_profile_id)s, "
                            "associated with the Neutron security group "
                            "%(sec_group_id)s was not found on the "
                            "backend",
                            {'sec_profile_id': nsx_sec_profile_id,
                             'sec_group_id': security_group_id})
            except api_exc.NsxApiException:
                # Raise and fail the operation, as there is a problem which
                # prevented the sec group from being removed from the backend
                LOG.exception("An exception occurred while removing the "
                              "NSX security profile %(sec_profile_id)s, "
                              "associated with Netron security group "
                              "%(sec_group_id)s",
                              {'sec_profile_id': nsx_sec_profile_id,
                               'sec_group_id': security_group_id})
                raise nsx_exc.NsxPluginException(
                    _("Unable to remove security group %s from backend"),
                    security_group['id'])
            return super(NsxPluginV2, self).delete_security_group(
                context, security_group_id)

    def _validate_security_group_rules(self, context, rules):
        for rule in rules['security_group_rules']:
            r = rule.get('security_group_rule')
            port_based_proto = (self._get_ip_proto_number(r['protocol'])
                                in constants.IP_PROTOCOL_MAP.values())
            if (not port_based_proto and
                (r['port_range_min'] is not None or
                 r['port_range_max'] is not None)):
                msg = (_("Port values not valid for "
                         "protocol: %s") % r['protocol'])
                raise n_exc.BadRequest(resource='security_group_rule',
                                       msg=msg)
        return super(NsxPluginV2, self)._validate_security_group_rules(context,
                                                                       rules)

    def create_security_group_rule(self, context, security_group_rule):
        """Create a single security group rule."""
        bulk_rule = {'security_group_rules': [security_group_rule]}
        return self.create_security_group_rule_bulk(context, bulk_rule)[0]

    def create_security_group_rule_bulk(self, context, security_group_rules):
        """Create security group rules.

        :param security_group_rule: list of rules to create
        """
        s = security_group_rules.get('security_group_rules')

        # TODO(arosen) is there anyway we could avoid having the update of
        # the security group rules in nsx outside of this transaction?
        with db_api.context_manager.writer.using(context):
            security_group_id = self._validate_security_group_rules(
                context, security_group_rules)
            # Check to make sure security group exists
            security_group = super(NsxPluginV2, self).get_security_group(
                context, security_group_id)

            if not security_group:
                raise ext_sg.SecurityGroupNotFound(id=security_group_id)
            # Check for duplicate rules
            self._check_for_duplicate_rules(context, s)
            # gather all the existing security group rules since we need all
            # of them to PUT to NSX.
            existing_rules = self.get_security_group_rules(
                context, {'security_group_id': [security_group['id']]})
            combined_rules = sg_utils.merge_security_group_rules_with_current(
                context.session, self.cluster, s, existing_rules)
            nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
                context.session, self.cluster, security_group_id)
            secgrouplib.update_security_group_rules(self.cluster,
                                                    nsx_sec_profile_id,
                                                    combined_rules)
            return super(
                NsxPluginV2, self).create_security_group_rule_bulk_native(
                    context, security_group_rules)

    def delete_security_group_rule(self, context, sgrid):
        """Delete a security group rule
        :param sgrid: security group id to remove.
        """
        with db_api.context_manager.writer.using(context):
            # determine security profile id
            security_group_rule = (
                super(NsxPluginV2, self).get_security_group_rule(
                    context, sgrid))
            if not security_group_rule:
                raise ext_sg.SecurityGroupRuleNotFound(id=sgrid)

            sgid = security_group_rule['security_group_id']
            current_rules = self.get_security_group_rules(
                context, {'security_group_id': [sgid]})
            current_rules_nsx = sg_utils.get_security_group_rules_nsx_format(
                context.session, self.cluster, current_rules, True)

            sg_utils.remove_security_group_with_id_and_id_field(
                current_rules_nsx, sgrid)
            nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id(
                context.session, self.cluster, sgid)
            secgrouplib.update_security_group_rules(
                self.cluster, nsx_sec_profile_id, current_rules_nsx)
            return super(NsxPluginV2, self).delete_security_group_rule(context,
                                                                       sgrid)

    def create_qos_queue(self, context, qos_queue, check_policy=True):
        q = qos_queue.get('qos_queue')
        self._validate_qos_queue(context, q)
        q['id'] = queuelib.create_lqueue(self.cluster, q)
        return super(NsxPluginV2, self).create_qos_queue(context, qos_queue)

    def delete_qos_queue(self, context, queue_id, raise_in_use=True):
        filters = {'queue_id': [queue_id]}
        queues = self._get_port_queue_bindings(context, filters)
        if queues:
            if raise_in_use:
                raise qos.QueueInUseByPort()
            else:
                return
        queuelib.delete_lqueue(self.cluster, queue_id)
        return super(NsxPluginV2, self).delete_qos_queue(context, queue_id)
