--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 OpenStack Foundation
+#
+# 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.
+#
+
+"""service router
+
+Revision ID: 4a666eb208c2
+Revises: 38fc1f6789f8
+Create Date: 2013-09-03 01:55:57.799217
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '4a666eb208c2'
+down_revision = '38fc1f6789f8'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+ 'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+
+
+def upgrade(active_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.create_table(
+ 'vcns_router_bindings',
+ sa.Column('status', sa.String(length=16), nullable=False),
+ sa.Column('status_description', sa.String(length=255), nullable=True),
+ sa.Column('router_id', sa.String(length=36), nullable=False),
+ sa.Column('edge_id', sa.String(length=16), nullable=True),
+ sa.Column('lswitch_id', sa.String(length=36), nullable=False),
+ sa.PrimaryKeyConstraint('router_id'),
+ mysql_engine='InnoDB'
+ )
+ op.add_column(
+ u'nsxrouterextattributess',
+ sa.Column('service_router',
+ sa.Boolean(),
+ nullable=False))
+ op.execute("UPDATE nsxrouterextattributess set service_router=False")
+
+
+def downgrade(active_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.drop_column(u'nsxrouterextattributess', 'service_router')
+ op.drop_table('vcns_router_bindings')
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as ext_sg
from neutron.openstack.common import excutils
+from neutron.plugins.common import constants as plugin_const
from neutron.plugins.nicira.common import config
from neutron.plugins.nicira.common import exceptions as nvp_exc
from neutron.plugins.nicira.common import securitygroups as nvp_sec
NVP_FLOATINGIP_NAT_RULES_ORDER = 224
NVP_EXTGW_NAT_RULES_ORDER = 255
NVP_EXT_PATH = os.path.join(os.path.dirname(__file__), 'extensions')
+NVP_DEFAULT_NEXTHOP = '1.1.1.1'
# Provider network extension - allowed network types for the NVP Plugin
else:
return super(NvpPluginV2, self).get_router(context, id, fields)
- def create_router(self, context, router):
- # NOTE(salvatore-orlando): We completely override this method in
- # order to be able to use the NVP 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 = self._get_tenant_id_for_create(context, r)
- # default value to set - nvp wants it (even if we don't have it)
- nexthop = '1.1.1.1'
+ def _create_lrouter(self, context, router, nexthop):
+ tenant_id = self._get_tenant_id_for_create(context, router)
+ name = router['name']
+ distributed = router.get('distributed')
try:
- # 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 NVP 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 q_exc.BadRequest(resource='router', msg=msg)
- if ext_net.subnets:
- ext_subnet = ext_net.subnets[0]
- nexthop = ext_subnet.gateway_ip
- distributed = r.get('distributed')
lrouter = nvplib.create_lrouter(
- self.cluster, tenant_id, router['router']['name'], nexthop,
+ self.cluster, tenant_id, name, nexthop,
distributed=attr.is_attr_set(distributed) and distributed)
- # Use NVP identfier for Neutron resource
- r['id'] = lrouter['uuid']
- # Update 'distributed' with value returned from NVP
- # This will be useful for setting the value if the API request
- # did not specify any value for the 'distributed' attribute.
- # Platforms older than 3.x do not support the attribute
- r['distributed'] = lrouter.get('distributed', False)
except nvp_exc.NvpInvalidVersion:
msg = _("Cannot create a distributed router with the NVP "
"platform currently in execution. Please, try "
except NvpApiClient.NvpApiException:
raise nvp_exc.NvpPluginException(
err_msg=_("Unable to create logical router on NVP Platform"))
+
# Create the port here - and update it later if we have gw_info
try:
self._create_and_attach_router_port(
nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
# Return user a 500 with an apter message
raise nvp_exc.NvpPluginException(
- err_msg=_("Unable to create router %s") % r['name'])
+ err_msg=_("Unable to create router %s") % router['name'])
+ lrouter['status'] = plugin_const.ACTIVE
+ return lrouter
+
+ def create_router(self, context, router):
+ # NOTE(salvatore-orlando): We completely override this method in
+ # order to be able to use the NVP 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 = self._get_tenant_id_for_create(context, r)
+ # default value to set - nvp wants it (even if we don't have it)
+ nexthop = NVP_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 NVP 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 q_exc.BadRequest(resource='router', msg=msg)
+ if ext_net.subnets:
+ ext_subnet = ext_net.subnets[0]
+ nexthop = ext_subnet.gateway_ip
+ lrouter = self._create_lrouter(context, r, nexthop)
+ # Use NVP identfier for Neutron resource
+ r['id'] = lrouter['uuid']
+ # Update 'distributed' with value returned from NVP
+ # This will be useful for setting the value if the API request
+ # did not specify any value for the 'distributed' attribute
+ # Platforms older than 3.x do not support the attribute
+ r['distributed'] = lrouter.get('distributed', False)
with context.session.begin(subtransactions=True):
# Transaction nesting is needed to avoid foreign key violations
tenant_id=tenant_id,
name=r['name'],
admin_state_up=r['admin_state_up'],
- status="ACTIVE")
- self._process_distributed_router_create(context, router_db, r)
+ status=lrouter['status'])
+ self._process_nsx_router_create(context, router_db, r)
context.session.add(router_db)
if has_gw_info:
self._update_router_gw_info(context, router_db['id'], gw_info)
router = self._make_router_dict(router_db)
return router
+ def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
+ return nvplib.update_lrouter(
+ self.cluster, router_id, name,
+ nexthop, routes=routes)
+
+ def _update_lrouter_routes(self, router_id, routes):
+ nvplib.update_explicit_routes_lrouter(
+ self.cluster, 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']
"this must be updated through the default "
"gateway attribute")
raise q_exc.BadRequest(resource='router', msg=msg)
- previous_routes = nvplib.update_lrouter(
- self.cluster, router_id, r.get('name'),
+ 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 nvplib raises a neutron notfound exception when an
extraroute.RoutesExhausted):
with excutils.save_and_reraise_exception():
# revert changes made to NVP
- nvplib.update_explicit_routes_lrouter(
- self.cluster, router_id, previous_routes)
+ self._update_lrouter_routes(
+ router_id, previous_routes)
+
+ def _delete_lrouter(self, context, id):
+ nvplib.delete_lrouter(self.cluster, id)
def delete_router(self, context, router_id):
with context.session.begin(subtransactions=True):
# allow an extra field for storing the cluster information
# together with the resource
try:
- nvplib.delete_lrouter(self.cluster, router_id)
+ self._delete_lrouter(context, router_id)
except q_exc.NotFound:
LOG.warning(_("Logical router '%s' not found "
"on NVP Platform"), router_id)
err_msg=(_("Unable to delete logical router '%s' "
"on NVP Platform") % router_id))
+ def _add_subnet_snat_rule(self, 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])
+ nvplib.create_lrouter_snat_rule(
+ self.cluster, router['id'], snat_ip, snat_ip,
+ order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix,
+ match_criteria={'source_ip_addresses': subnet['cidr']})
+
+ def _delete_subnet_snat_rule(self, router, subnet):
+ # Remove SNAT rule if external gateway is configured
+ if router.gw_port:
+ nvplib.delete_nat_rules_by_match(
+ self.cluster, router['id'], "SourceNatRule",
+ max_num_expected=1, min_num_expected=1,
+ 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 nvp logical router in this routine
# If there is an external gateway we need to configure the SNAT rule.
# Fetch router from DB
router = self._get_router(context, router_id)
- 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])
- nvplib.create_lrouter_snat_rule(
- self.cluster, router_id, snat_ip, snat_ip,
- order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix,
- match_criteria={'source_ip_addresses': subnet['cidr']})
+ self._add_subnet_snat_rule(router, subnet)
nvplib.create_lrouter_nosnat_rule(
self.cluster, router_id,
order=NVP_NOSNAT_RULES_ORDER,
if not subnet:
subnet = self._get_subnet(context, subnet_id)
router = self._get_router(context, router_id)
- # Remove SNAT rule if external gateway is configured
- if router.gw_port:
- nvplib.delete_nat_rules_by_match(
- self.cluster, router_id, "SourceNatRule",
- max_num_expected=1, min_num_expected=1,
- source_ip_addresses=subnet['cidr'])
+ self._delete_subnet_snat_rule(router, subnet)
# Relax the minimum expected number as the nosnat rules
# do not exist in 2.x deployments
nvplib.delete_nat_rules_by_match(
"on NVP Platform")))
return info
- def _retrieve_and_delete_nat_rules(self, floating_ip_address,
+ def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
internal_ip, router_id,
min_num_rules_expected=0):
try:
ips_to_add=[],
ips_to_remove=nvp_floating_ips)
- 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.
- """
+ def _get_fip_assoc_data(self, context, fip, floatingip_db):
if (('fixed_ip_address' in fip and fip['fixed_ip_address']) and
not ('port_id' in fip and fip['port_id'])):
msg = _("fixed_ip_address cannot be specified without a port_id")
fip,
floatingip_db['floating_network_id'])
- floating_ip = floatingip_db['floating_ip_address']
# Retrieve and delete existing NAT rules, if any
if not router_id and floatingip_db.get('fixed_port_id'):
# This happens if we're disassociating. Need to explicitly
tmp_fip['port_id'] = floatingip_db['fixed_port_id']
_pid, internal_ip, router_id = self.get_assoc_data(
context, tmp_fip, floatingip_db['floating_network_id'])
+
+ return (port_id, internal_ip, router_id)
+
+ 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.
+ """
+ port_id, internal_ip, router_id = self._get_fip_assoc_data(
+ context, fip, floatingip_db)
+ floating_ip = floatingip_db['floating_ip_address']
# If there's no association router_id will be None
if router_id:
- self._retrieve_and_delete_nat_rules(floating_ip,
+ self._retrieve_and_delete_nat_rules(context,
+ floating_ip,
internal_ip,
router_id)
# Fetch logical port of router's external gateway
"internal ip:%(internal_ip)s"),
{'floating_ip': floating_ip,
'internal_ip': internal_ip})
+ msg = _("Failed to update NAT rules for floatingip update")
raise nvp_exc.NvpPluginException(err_msg=msg)
elif floatingip_db['fixed_port_id']:
# This is a disassociation.
fip_db = self._get_floatingip(context, id)
# Check whether the floating ip is associated or not
if fip_db.fixed_port_id:
- self._retrieve_and_delete_nat_rules(fip_db.floating_ip_address,
+ self._retrieve_and_delete_nat_rules(context,
+ fip_db.floating_ip_address,
fip_db.fixed_ip_address,
fip_db.router_id,
min_num_rules_expected=1)
try:
fip_qry = context.session.query(l3_db.FloatingIP)
fip_db = fip_qry.filter_by(fixed_port_id=port_id).one()
- self._retrieve_and_delete_nat_rules(fip_db.floating_ip_address,
+ self._retrieve_and_delete_nat_rules(context,
+ fip_db.floating_ip_address,
fip_db.fixed_ip_address,
fip_db.router_id,
min_num_rules_expected=1)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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 netaddr
+from oslo.config import cfg
+from sqlalchemy.orm import exc as sa_exc
+
+from neutron.common import exceptions as q_exc
+from neutron.db import l3_db
+from neutron.openstack.common import log as logging
+from neutron.plugins.common import constants as service_constants
+from neutron.plugins.nicira.common import config # noqa
+from neutron.plugins.nicira.dbexts import servicerouter as sr_db
+from neutron.plugins.nicira.dbexts import vcns_db
+from neutron.plugins.nicira.dbexts import vcns_models
+from neutron.plugins.nicira.extensions import servicerouter as sr
+from neutron.plugins.nicira import NeutronPlugin
+from neutron.plugins.nicira import NvpApiClient
+from neutron.plugins.nicira import nvplib
+from neutron.plugins.nicira.vshield.common import (
+ constants as vcns_const)
+from neutron.plugins.nicira.vshield.common.constants import RouterStatus
+from neutron.plugins.nicira.vshield.common import exceptions
+from neutron.plugins.nicira.vshield.tasks.constants import TaskStatus
+from neutron.plugins.nicira.vshield import vcns_driver
+
+LOG = logging.getLogger(__name__)
+
+ROUTER_TYPE_BASIC = 1
+ROUTER_TYPE_ADVANCED = 2
+
+ROUTER_STATUS = [
+ service_constants.ACTIVE,
+ service_constants.DOWN,
+ service_constants.PENDING_CREATE,
+ service_constants.PENDING_DELETE,
+ service_constants.ERROR
+]
+
+ROUTER_STATUS_LEVEL = {
+ service_constants.ACTIVE: RouterStatus.ROUTER_STATUS_ACTIVE,
+ service_constants.DOWN: RouterStatus.ROUTER_STATUS_DOWN,
+ service_constants.PENDING_CREATE: (
+ RouterStatus.ROUTER_STATUS_PENDING_CREATE
+ ),
+ service_constants.PENDING_DELETE: (
+ RouterStatus.ROUTER_STATUS_PENDING_DELETE
+ ),
+ service_constants.ERROR: RouterStatus.ROUTER_STATUS_ERROR
+}
+
+
+class NvpAdvancedPlugin(sr_db.ServiceRouter_mixin,
+ NeutronPlugin.NvpPluginV2):
+
+ supported_extension_aliases = (
+ NeutronPlugin.NvpPluginV2.supported_extension_aliases + [
+ 'service-router'
+ ])
+
+ def __init__(self):
+ super(NvpAdvancedPlugin, self).__init__()
+
+ self._super_create_ext_gw_port = (
+ self._port_drivers['create'][l3_db.DEVICE_OWNER_ROUTER_GW])
+ self._super_delete_ext_gw_port = (
+ self._port_drivers['delete'][l3_db.DEVICE_OWNER_ROUTER_GW])
+
+ self._port_drivers['create'][l3_db.DEVICE_OWNER_ROUTER_GW] = (
+ self._vcns_create_ext_gw_port)
+ self._port_drivers['delete'][l3_db.DEVICE_OWNER_ROUTER_GW] = (
+ self._vcns_delete_ext_gw_port)
+
+ # cache router type based on router id
+ self._router_type = {}
+ self.callbacks = VcnsCallbacks(self)
+
+ # load the vCNS driver
+ self._load_vcns_drivers()
+
+ def _load_vcns_drivers(self):
+ self.vcns_driver = vcns_driver.VcnsDriver(self.callbacks)
+
+ def _set_router_type(self, router_id, router_type):
+ self._router_type[router_id] = router_type
+
+ def _get_router_type(self, context=None, router_id=None, router=None):
+ if not router:
+ if router_id in self._router_type:
+ return self._router_type[router_id]
+ router = self._get_router(context, router_id)
+
+ LOG.debug(_("EDGE: router = %s"), router)
+ if router['nsx_attributes']['service_router']:
+ router_type = ROUTER_TYPE_ADVANCED
+ else:
+ router_type = ROUTER_TYPE_BASIC
+ self._set_router_type(router['id'], router_type)
+ return router_type
+
+ def _find_router_type(self, router):
+ is_service_router = router.get(sr.SERVICE_ROUTER, False)
+ if is_service_router:
+ return ROUTER_TYPE_ADVANCED
+ else:
+ return ROUTER_TYPE_BASIC
+
+ def _is_advanced_service_router(self, context=None, router_id=None,
+ router=None):
+ if router:
+ router_type = self._get_router_type(router=router)
+ else:
+ router_type = self._get_router_type(context, router_id)
+ return (router_type == ROUTER_TYPE_ADVANCED)
+
+ def _vcns_create_ext_gw_port(self, context, port_data):
+ router_id = port_data['device_id']
+ if not self._is_advanced_service_router(context, router_id):
+ self._super_create_ext_gw_port(context, port_data)
+ return
+
+ # NOP for Edge because currently the port will be create internally
+ # by VSM
+ LOG.debug(_("EDGE: _vcns_create_ext_gw_port"))
+
+ def _vcns_delete_ext_gw_port(self, context, port_data):
+ router_id = port_data['device_id']
+ if not self._is_advanced_service_router(context, router_id):
+ self._super_delete_ext_gw_port(context, port_data)
+ return
+
+ # NOP for Edge
+ LOG.debug(_("EDGE: _vcns_delete_ext_gw_port"))
+
+ def _get_external_attachment_info(self, context, router):
+ gw_port = router.gw_port
+ ipaddress = None
+ netmask = None
+ nexthop = None
+
+ if gw_port:
+ # gw_port may have multiple IPs, only configure the first one
+ if gw_port.get('fixed_ips'):
+ ipaddress = gw_port['fixed_ips'][0]['ip_address']
+
+ network_id = gw_port.get('network_id')
+ 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 q_exc.BadRequest(resource='router', msg=msg)
+ if ext_net.subnets:
+ ext_subnet = ext_net.subnets[0]
+ netmask = str(netaddr.IPNetwork(ext_subnet.cidr).netmask)
+ nexthop = ext_subnet.gateway_ip
+
+ return (ipaddress, netmask, nexthop)
+
+ def _get_external_gateway_address(self, context, router):
+ ipaddress, netmask, nexthop = self._get_external_attachment_info(
+ context, router)
+ return nexthop
+
+ def _vcns_update_static_routes(self, context, **kwargs):
+ router = kwargs.get('router')
+ if router is None:
+ router = self._get_router(context, kwargs['router_id'])
+
+ edge_id = kwargs.get('edge_id')
+ if edge_id is None:
+ binding = vcns_db.get_vcns_router_binding(context.session,
+ router['id'])
+ edge_id = binding['edge_id']
+
+ skippable = True
+ if 'nexthop' in kwargs:
+ nexthop = kwargs['nexthop']
+ # The default gateway and vnic config has dependencies, if we
+ # explicitly specify nexthop to change, tell the driver not to
+ # skip this route update
+ skippable = False
+ else:
+ nexthop = self._get_external_gateway_address(context,
+ router)
+
+ if 'subnets' in kwargs:
+ subnets = kwargs['subnets']
+ else:
+ subnets = self._find_router_subnets_cidrs(context.elevated(),
+ router['id'])
+
+ routes = []
+ for subnet in subnets:
+ routes.append({
+ 'cidr': subnet,
+ 'nexthop': vcns_const.INTEGRATION_LR_IPADDRESS.split('/')[0]
+ })
+ self.vcns_driver.update_routes(router['id'], edge_id, nexthop, routes,
+ skippable)
+
+ def _get_nat_rules(self, context, router):
+ fip_qry = context.session.query(l3_db.FloatingIP)
+ fip_db = fip_qry.filter_by(router_id=router['id']).all()
+
+ dnat = []
+ snat = []
+ for fip in fip_db:
+ if fip.fixed_port_id:
+ dnat.append({
+ 'dst': fip.floating_ip_address,
+ 'translated': fip.fixed_ip_address
+ })
+
+ gw_port = router.gw_port
+ if gw_port and router.enable_snat:
+ if gw_port.get('fixed_ips'):
+ snat_ip = gw_port['fixed_ips'][0]['ip_address']
+ subnets = self._find_router_subnets_cidrs(context.elevated(),
+ router['id'])
+ for subnet in subnets:
+ snat.append({
+ 'src': subnet,
+ 'translated': snat_ip
+ })
+
+ return (snat, dnat)
+
+ def _update_nat_rules(self, context, router):
+ snat, dnat = self._get_nat_rules(context, router)
+ binding = vcns_db.get_vcns_router_binding(context.session,
+ router['id'])
+ self.vcns_driver.update_nat_rules(router['id'],
+ binding['edge_id'],
+ snat, dnat)
+
+ def _update_interface(self, context, router):
+ addr, mask, nexthop = self._get_external_attachment_info(
+ context, router)
+
+ secondary = []
+ fip_qry = context.session.query(l3_db.FloatingIP)
+ fip_db = fip_qry.filter_by(router_id=router['id']).all()
+ for fip in fip_db:
+ if fip.fixed_port_id:
+ secondary.append(fip.floating_ip_address)
+
+ binding = vcns_db.get_vcns_router_binding(context.session,
+ router['id'])
+ self.vcns_driver.update_interface(
+ router['id'], binding['edge_id'],
+ vcns_const.EXTERNAL_VNIC_INDEX,
+ self.vcns_driver.external_network,
+ addr, mask, secondary=secondary)
+
+ def _update_router_gw_info(self, context, router_id, info):
+ if not self._is_advanced_service_router(context, router_id):
+ super(NvpAdvancedPlugin, self)._update_router_gw_info(
+ context, router_id, info)
+ return
+
+ # get original gw_port config
+ router = self._get_router(context, router_id)
+ org_ext_net_id = router.gw_port_id and router.gw_port.network_id
+ org_enable_snat = router.enable_snat
+ orgaddr, orgmask, orgnexthop = self._get_external_attachment_info(
+ context, router)
+
+ super(NeutronPlugin.NvpPluginV2, self)._update_router_gw_info(
+ context, router_id, info, router=router)
+
+ new_ext_net_id = router.gw_port_id and router.gw_port.network_id
+ new_enable_snat = router.enable_snat
+ newaddr, newmask, newnexthop = self._get_external_attachment_info(
+ context, router)
+
+ binding = vcns_db.get_vcns_router_binding(context.session, router_id)
+
+ if new_ext_net_id != org_ext_net_id and orgnexthop:
+ # network changed, need to remove default gateway before vnic
+ # can be configured
+ LOG.debug(_("VCNS: delete default gateway %s"), orgnexthop)
+ self._vcns_update_static_routes(context,
+ router=router,
+ edge_id=binding['edge_id'],
+ nexthop=None)
+
+ if orgaddr != newaddr or orgmask != newmask:
+ self.vcns_driver.update_interface(
+ router_id, binding['edge_id'],
+ vcns_const.EXTERNAL_VNIC_INDEX,
+ self.vcns_driver.external_network,
+ newaddr, newmask)
+
+ if orgnexthop != newnexthop:
+ self._vcns_update_static_routes(context,
+ router=router,
+ edge_id=binding['edge_id'],
+ nexthop=newnexthop)
+
+ if (new_ext_net_id == org_ext_net_id and
+ org_enable_snat == new_enable_snat):
+ return
+
+ self._update_nat_rules(context, router)
+
+ def _add_subnet_snat_rule(self, router, subnet):
+ # NOP for service router
+ if not self._is_advanced_service_router(router=router):
+ super(NvpAdvancedPlugin, self)._add_subnet_snat_rule(
+ router, subnet)
+
+ def _delete_subnet_snat_rule(self, router, subnet):
+ # NOP for service router
+ if not self._is_advanced_service_router(router=router):
+ super(NvpAdvancedPlugin, self)._delete_subnet_snat_rule(
+ router, subnet)
+
+ def _remove_floatingip_address(self, context, fip_db):
+ # NOP for service router
+ router_id = fip_db.router_id
+ if not self._is_advanced_service_router(context, router_id):
+ super(NvpAdvancedPlugin, self)._remove_floatingip_address(
+ context, fip_db)
+
+ def _create_advanced_service_router(self, context, name, lrouter, lswitch):
+
+ # store binding
+ binding = vcns_db.add_vcns_router_binding(
+ context.session, lrouter['uuid'], None, lswitch['uuid'],
+ service_constants.PENDING_CREATE)
+
+ # deploy edge
+ jobdata = {
+ 'lrouter': lrouter,
+ 'lswitch': lswitch,
+ 'context': context
+ }
+
+ # deploy and wait until the deploy requeste has been requested
+ # so we will have edge_id ready. The wait here should be fine
+ # as we're not in a database transaction now
+ self.vcns_driver.deploy_edge(
+ lrouter['uuid'], name, lswitch['uuid'], jobdata=jobdata,
+ wait_for_exec=True)
+
+ return binding
+
+ def _create_integration_lswitch(self, tenant_id, name):
+ # use defautl transport zone
+ transport_zone_config = [{
+ "zone_uuid": self.cluster.default_tz_uuid,
+ "transport_type": cfg.CONF.NVP.default_transport_type
+ }]
+ return self.vcns_driver.create_lswitch(name, transport_zone_config)
+
+ def _add_router_integration_interface(self, tenant_id, name,
+ lrouter, lswitch):
+ # create logic switch port
+ try:
+ ls_port = nvplib.create_lport(
+ self.cluster, lswitch['uuid'], tenant_id,
+ '', '', lrouter['uuid'], True)
+ except NvpApiClient.NvpApiException:
+ msg = (_("An exception occured while creating a port "
+ "on lswitch %s") % lswitch['uuid'])
+ LOG.exception(msg)
+ raise q_exc.NeutronException(message=msg)
+
+ # create logic router port
+ try:
+ neutron_port_id = ''
+ pname = name[:36] + '-lp'
+ admin_status_enabled = True
+ lr_port = nvplib.create_router_lport(
+ self.cluster, lrouter['uuid'], tenant_id,
+ neutron_port_id, pname, admin_status_enabled,
+ [vcns_const.INTEGRATION_LR_IPADDRESS])
+ except NvpApiClient.NvpApiException:
+ msg = (_("Unable to create port on NVP logical router %s") % name)
+ LOG.exception(msg)
+ nvplib.delete_port(self.cluster, lswitch['uuid'], ls_port['uuid'])
+ raise q_exc.NeutronException(message=msg)
+
+ # attach logic router port to switch port
+ try:
+ self._update_router_port_attachment(
+ self.cluster, None, lrouter['uuid'], {}, lr_port['uuid'],
+ 'PatchAttachment', ls_port['uuid'], None)
+ except NvpApiClient.NvpApiException as e:
+ # lr_port should have been deleted
+ nvplib.delete_port(self.cluster, lswitch['uuid'], ls_port['uuid'])
+ raise e
+
+ def _create_lrouter(self, context, router, nexthop):
+ lrouter = super(NvpAdvancedPlugin, self)._create_lrouter(
+ context, router, vcns_const.INTEGRATION_EDGE_IPADDRESS)
+
+ router_type = self._find_router_type(router)
+ self._set_router_type(lrouter['uuid'], router_type)
+ if router_type == ROUTER_TYPE_BASIC:
+ return lrouter
+
+ tenant_id = self._get_tenant_id_for_create(context, router)
+ name = router['name']
+ try:
+ lsname = name[:36] + '-ls'
+ lswitch = self._create_integration_lswitch(
+ tenant_id, lsname)
+ except Exception:
+ msg = _("Unable to create integration logic switch "
+ "for router %s") % name
+ LOG.exception(msg)
+ nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
+ raise q_exc.NeutronException(message=msg)
+
+ try:
+ self._add_router_integration_interface(tenant_id, name,
+ lrouter, lswitch)
+ except Exception:
+ msg = _("Unable to add router interface to integration lswitch "
+ "for router %s") % name
+ LOG.exception(msg)
+ nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
+ raise q_exc.NeutronException(message=msg)
+
+ try:
+ self._create_advanced_service_router(
+ context, name, lrouter, lswitch)
+ except Exception:
+ msg = (_("Unable to create advance service router for %s") % name)
+ LOG.exception(msg)
+ self.vcns_driver.delete_lswitch(lswitch('uuid'))
+ nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
+ raise q_exc.NeutronException(message=msg)
+
+ lrouter['status'] = service_constants.PENDING_CREATE
+ return lrouter
+
+ def _delete_lrouter(self, context, id):
+ if not self._is_advanced_service_router(context, id):
+ super(NvpAdvancedPlugin, self)._delete_lrouter(context, id)
+ if id in self._router_type:
+ del self._router_type[id]
+ return
+
+ binding = vcns_db.get_vcns_router_binding(context.session, id)
+ if binding:
+ vcns_db.update_vcns_router_binding(
+ context.session, id, status=service_constants.PENDING_DELETE)
+
+ lswitch_id = binding['lswitch_id']
+ edge_id = binding['edge_id']
+
+ # delete lswitch
+ try:
+ self.vcns_driver.delete_lswitch(lswitch_id)
+ except exceptions.ResourceNotFound:
+ LOG.warning(_("Did not found lswitch %s in NVP"), lswitch_id)
+
+ # delete edge
+ jobdata = {
+ 'context': context
+ }
+ self.vcns_driver.delete_edge(id, edge_id, jobdata=jobdata)
+
+ # delete LR
+ nvplib.delete_lrouter(self.cluster, id)
+ if id in self._router_type:
+ del self._router_type[id]
+
+ def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
+ if not self._is_advanced_service_router(context, router_id):
+ return super(NvpAdvancedPlugin, self)._update_lrouter(
+ context, router_id, name, nexthop, routes=routes)
+
+ previous_routes = super(NvpAdvancedPlugin, self)._update_lrouter(
+ context, router_id, name,
+ vcns_const.INTEGRATION_EDGE_IPADDRESS, routes=routes)
+
+ # TODO(fank): Theoretically users can specify extra routes for
+ # physical network, and routes for phyiscal network needs to be
+ # configured on Edge. This can be done by checking if nexthop is in
+ # external network. But for now we only handle routes for logic
+ # space and leave it for future enhancement.
+
+ # Let _update_router_gw_info handle nexthop change
+ #self._vcns_update_static_routes(context, router_id=router_id)
+
+ return previous_routes
+
+ def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
+ internal_ip, router_id,
+ min_num_rules_expected=0):
+ # NOP for advanced service router
+ if not self._is_advanced_service_router(context, router_id):
+ super(NvpAdvancedPlugin, self)._retrieve_and_delete_nat_rules(
+ context, floating_ip_address, internal_ip, router_id,
+ min_num_rules_expected=min_num_rules_expected)
+
+ def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
+ # Update DB model only for advanced service router
+ router_id = self._get_fip_assoc_data(context, fip, floatingip_db)[2]
+ if (router_id and
+ not self._is_advanced_service_router(context, router_id)):
+ super(NvpAdvancedPlugin, self)._update_fip_assoc(
+ context, fip, floatingip_db, external_port)
+ else:
+ super(NeutronPlugin.NvpPluginV2, self)._update_fip_assoc(
+ context, fip, floatingip_db, external_port)
+
+ def _get_nvp_lrouter_status(self, id):
+ try:
+ lrouter = nvplib.get_lrouter(self.cluster, id)
+ lr_status = lrouter["_relations"]["LogicalRouterStatus"]
+ if lr_status["fabric_status"]:
+ nvp_status = RouterStatus.ROUTER_STATUS_ACTIVE
+ else:
+ nvp_status = RouterStatus.ROUTER_STATUS_DOWN
+ except q_exc.NotFound:
+ nvp_status = RouterStatus.ROUTER_STATUS_ERROR
+
+ return nvp_status
+
+ def _get_vse_status(self, context, id):
+ binding = vcns_db.get_vcns_router_binding(context.session, id)
+
+ edge_status_level = self.vcns_driver.get_edge_status(
+ binding['edge_id'])
+ edge_db_status_level = ROUTER_STATUS_LEVEL[binding.status]
+
+ if edge_status_level > edge_db_status_level:
+ return edge_status_level
+ else:
+ return edge_db_status_level
+
+ def _get_all_nvp_lrouters_statuses(self, tenant_id, fields):
+ # get nvp lrouters status
+ nvp_lrouters = nvplib.get_lrouters(self.cluster,
+ tenant_id,
+ fields)
+
+ nvp_status = {}
+ for nvp_lrouter in nvp_lrouters:
+ if (nvp_lrouter["_relations"]["LogicalRouterStatus"]
+ ["fabric_status"]):
+ nvp_status[nvp_lrouter['uuid']] = (
+ RouterStatus.ROUTER_STATUS_ACTIVE
+ )
+ else:
+ nvp_status[nvp_lrouter['uuid']] = (
+ RouterStatus.ROUTER_STATUS_DOWN
+ )
+
+ return nvp_status
+
+ def _get_all_vse_statuses(self, context):
+ bindings = self._model_query(
+ context, vcns_models.VcnsRouterBinding)
+
+ vse_db_status_level = {}
+ edge_id_to_router_id = {}
+ router_ids = []
+ for binding in bindings:
+ if not binding['edge_id']:
+ continue
+ router_id = binding['router_id']
+ router_ids.append(router_id)
+ edge_id_to_router_id[binding['edge_id']] = router_id
+ vse_db_status_level[router_id] = (
+ ROUTER_STATUS_LEVEL[binding['status']])
+
+ if not vse_db_status_level:
+ # no advanced service router, no need to query
+ return {}
+
+ vse_status_level = {}
+ edges_status_level = self.vcns_driver.get_edges_statuses()
+ for edge_id, status_level in edges_status_level.iteritems():
+ if edge_id in edge_id_to_router_id:
+ router_id = edge_id_to_router_id[edge_id]
+ db_status_level = vse_db_status_level[router_id]
+ if status_level > db_status_level:
+ vse_status_level[router_id] = status_level
+ else:
+ vse_status_level[router_id] = db_status_level
+
+ return vse_status_level
+
+ def get_router(self, context, id, fields=None):
+ if fields and 'status' not in fields:
+ return super(NvpAdvancedPlugin, self).get_router(
+ context, id, fields=fields)
+
+ router = super(NvpAdvancedPlugin, self).get_router(context, id)
+
+ router_type = self._find_router_type(router)
+ if router_type == ROUTER_TYPE_ADVANCED:
+ vse_status_level = self._get_vse_status(context, id)
+ if vse_status_level > ROUTER_STATUS_LEVEL[router['status']]:
+ router['status'] = ROUTER_STATUS[vse_status_level]
+
+ return self._fields(router, fields)
+
+ def get_routers(self, context, filters=None, fields=None, **kwargs):
+ routers = super(NvpAdvancedPlugin, self).get_routers(
+ context, filters=filters, **kwargs)
+
+ if fields and 'status' not in fields:
+ # no status checking, just return regular get_routers
+ return [self._fields(router, fields) for router in routers]
+
+ for router in routers:
+ router_type = self._find_router_type(router)
+ if router_type == ROUTER_TYPE_ADVANCED:
+ break
+ else:
+ # no advanced service router, return here
+ return [self._fields(router, fields) for router in routers]
+
+ vse_status_all = self._get_all_vse_statuses(context)
+ for router in routers:
+ router_type = self._find_router_type(router)
+ if router_type == ROUTER_TYPE_ADVANCED:
+ vse_status_level = vse_status_all.get(router['id'])
+ if vse_status_level is None:
+ vse_status_level = RouterStatus.ROUTER_STATUS_ERROR
+ if vse_status_level > ROUTER_STATUS_LEVEL[router['status']]:
+ router['status'] = ROUTER_STATUS[vse_status_level]
+
+ return [self._fields(router, fields) for router in routers]
+
+ def add_router_interface(self, context, router_id, interface_info):
+ info = super(NvpAdvancedPlugin, self).add_router_interface(
+ context, router_id, interface_info)
+ if self._is_advanced_service_router(context, router_id):
+ router = self._get_router(context, router_id)
+ if router.enable_snat:
+ self._update_nat_rules(context, router)
+ # TODO(fank): do rollback if error, or have a dedicated thread
+ # do sync work (rollback, re-configure, or make router down)
+ self._vcns_update_static_routes(context, router=router)
+ return info
+
+ def remove_router_interface(self, context, router_id, interface_info):
+ info = super(NvpAdvancedPlugin, self).remove_router_interface(
+ context, router_id, interface_info)
+ if self._is_advanced_service_router(context, router_id):
+ router = self._get_router(context, router_id)
+ if router.enable_snat:
+ self._update_nat_rules(context, router)
+ # TODO(fank): do rollback if error, or have a dedicated thread
+ # do sync work (rollback, re-configure, or make router down)
+ self._vcns_update_static_routes(context, router=router)
+ return info
+
+ def create_floatingip(self, context, floatingip):
+ fip = super(NvpAdvancedPlugin, self).create_floatingip(
+ context, floatingip)
+ router_id = fip.get('router_id')
+ if router_id and self._is_advanced_service_router(context, router_id):
+ router = self._get_router(context, router_id)
+ # TODO(fank): do rollback if error, or have a dedicated thread
+ # do sync work (rollback, re-configure, or make router down)
+ self._update_interface(context, router)
+ self._update_nat_rules(context, router)
+ return fip
+
+ def update_floatingip(self, context, id, floatingip):
+ fip = super(NvpAdvancedPlugin, self).update_floatingip(
+ context, id, floatingip)
+ router_id = fip.get('router_id')
+ if router_id and self._is_advanced_service_router(context, router_id):
+ router = self._get_router(context, router_id)
+ # TODO(fank): do rollback if error, or have a dedicated thread
+ # do sync work (rollback, re-configure, or make router down)
+ self._update_interface(context, router)
+ self._update_nat_rules(context, router)
+ return fip
+
+ def delete_floatingip(self, context, id):
+ fip_db = self._get_floatingip(context, id)
+ router_id = None
+ if fip_db.fixed_port_id:
+ router_id = fip_db.router_id
+ super(NvpAdvancedPlugin, self).delete_floatingip(context, id)
+ if router_id and self._is_advanced_service_router(context, router_id):
+ router = self._get_router(context, router_id)
+ # TODO(fank): do rollback if error, or have a dedicated thread
+ # do sync work (rollback, re-configure, or make router down)
+ self._update_interface(context, router)
+ self._update_nat_rules(context, router)
+
+ def disassociate_floatingips(self, context, port_id):
+ try:
+ fip_qry = context.session.query(l3_db.FloatingIP)
+ fip_db = fip_qry.filter_by(fixed_port_id=port_id).one()
+ router_id = fip_db.router_id
+ except sa_exc.NoResultFound:
+ router_id = None
+ super(NvpAdvancedPlugin, self).disassociate_floatingips(context,
+ port_id)
+ if router_id and self._is_advanced_service_router(context, router_id):
+ router = self._get_router(context, router_id)
+ # TODO(fank): do rollback if error, or have a dedicated thread
+ # do sync work (rollback, re-configure, or make router down)
+ self._update_interface(context, router)
+ self._update_nat_rules(context, router)
+
+
+class VcnsCallbacks(object):
+ """Edge callback implementation
+
+ Callback functions for asynchronous tasks
+ """
+ def __init__(self, plugin):
+ self.plugin = plugin
+
+ def edge_deploy_started(self, task):
+ """callback when deployment task started."""
+ jobdata = task.userdata['jobdata']
+ lrouter = jobdata['lrouter']
+ context = jobdata['context']
+ edge_id = task.userdata.get('edge_id')
+ name = task.userdata['router_name']
+ if edge_id:
+ LOG.debug(_("Start deploying %(edge_id)s for router %(name)s"), {
+ 'edge_id': edge_id,
+ 'name': name})
+ vcns_db.update_vcns_router_binding(
+ context.session, lrouter['uuid'], edge_id=edge_id)
+ else:
+ LOG.debug(_("Failed to deploy Edge for router %s"), name)
+ vcns_db.update_vcns_router_binding(
+ context.session, lrouter['uuid'],
+ status=service_constants.ERROR)
+
+ def edge_deploy_result(self, task):
+ """callback when deployment task finished."""
+ jobdata = task.userdata['jobdata']
+ lrouter = jobdata['lrouter']
+ context = jobdata['context']
+ name = task.userdata['router_name']
+ router_db = self.plugin._get_router(context, lrouter['uuid'])
+ if task.status == TaskStatus.COMPLETED:
+ LOG.debug(_("Successfully deployed %(edge_id)s for "
+ "router %(name)s"), {
+ 'edge_id': task.userdata['edge_id'],
+ 'name': name})
+ if router_db['status'] == service_constants.PENDING_CREATE:
+ router_db['status'] = service_constants.ACTIVE
+ binding = vcns_db.get_vcns_router_binding(
+ context.session, lrouter['uuid'])
+ # only update status to active if its status is pending create
+ if binding['status'] == service_constants.PENDING_CREATE:
+ vcns_db.update_vcns_router_binding(
+ context.session, lrouter['uuid'],
+ status=service_constants.ACTIVE)
+ else:
+ LOG.debug(_("Failed to deploy Edge for router %s"), name)
+ router_db['status'] = service_constants.ERROR
+ vcns_db.update_vcns_router_binding(
+ context.session, lrouter['uuid'],
+ status=service_constants.ERROR)
+
+ def edge_delete_result(self, task):
+ jobdata = task.userdata['jobdata']
+ router_id = task.userdata['router_id']
+ context = jobdata['context']
+ if task.status == TaskStatus.COMPLETED:
+ vcns_db.delete_vcns_router_binding(context.session,
+ router_id)
+
+ def interface_update_result(self, task):
+ LOG.debug(_("interface_update_result %d"), task.status)
+
+ def snat_create_result(self, task):
+ LOG.debug(_("snat_create_result %d"), task.status)
+
+ def snat_delete_result(self, task):
+ LOG.debug(_("snat_delete_result %d"), task.status)
+
+ def dnat_create_result(self, task):
+ LOG.debug(_("dnat_create_result %d"), task.status)
+
+ def dnat_delete_result(self, task):
+ LOG.debug(_("dnat_delete_result %d"), task.status)
+
+ def routes_update_result(self, task):
+ LOG.debug(_("routes_update_result %d"), task.status)
+
+ def nat_update_result(self, task):
+ LOG.debug(_("nat_update_result %d"), task.status)
# @author: Salvatore Orlando, Nicira, Inc
#
-from neutron.db import db_base_plugin_v2
-from neutron.extensions import l3
-from neutron.openstack.common import log as logging
-from neutron.plugins.nicira.dbexts import nicira_models
+from neutron.plugins.nicira.dbexts import nsxrouter
from neutron.plugins.nicira.extensions import distributedrouter as dist_rtr
-LOG = logging.getLogger(__name__)
-
-class DistributedRouter_mixin(object):
+class DistributedRouter_mixin(nsxrouter.NsxRouterMixin):
"""Mixin class to enable distributed router support."""
- def _extend_router_dict_distributed(self, router_res, router_db):
- # Avoid setting attribute to None for routers already existing before
- # the data model was extended with the distributed attribute
- nsx_attrs = router_db['nsx_attributes']
- # Return False if nsx attributes are not definied for this
- # neutron router
- router_res[dist_rtr.DISTRIBUTED] = (
- nsx_attrs and nsx_attrs['distributed'] or False)
-
- def _process_distributed_router_create(
- self, context, router_db, router_req):
- """Ensures persistency for the 'distributed' attribute.
-
- Either creates or fetches the nicira extended attributes
- record for this router and stores the 'distributed'
- attribute value.
- This method should be called from within a transaction, as
- it does not start a new one.
- """
- if not router_db['nsx_attributes']:
- nsx_attributes = nicira_models.NSXRouterExtAttributes(
- router_id=router_db['id'],
- distributed=router_req['distributed'])
- context.session.add(nsx_attributes)
- router_db['nsx_attributes'] = nsx_attributes
- else:
- # The situation where the record already exists will
- # be likely once the NSXRouterExtAttributes model
- # will allow for defining several attributes pertaining
- # to different extensions
- router_db['nsx_attributes']['distributed'] = (
- router_req['distributed'])
- LOG.debug(_("Distributed router extension successfully processed "
- "for router:%s"), router_db['id'])
-
- # Register dict extend functions for ports
- db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
- l3.ROUTERS, ['_extend_router_dict_distributed'])
+ nsx_attributes = (
+ nsxrouter.NsxRouterMixin.nsx_attributes + [{
+ 'name': dist_rtr.DISTRIBUTED,
+ 'default': False
+ }])
ForeignKey('routers.id', ondelete="CASCADE"),
primary_key=True)
distributed = Column(Boolean, default=False, nullable=False)
+ service_router = Column(Boolean, default=False, nullable=False)
# Add a relationship to the Router model in order to instruct
# SQLAlchemy to eagerly load this association
router = orm.relationship(
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Nicira Networks, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Salvatore Orlando, Nicira, Inc
+#
+
+from neutron.db import db_base_plugin_v2
+from neutron.extensions import l3
+from neutron.openstack.common import log as logging
+from neutron.plugins.nicira.dbexts import nicira_models
+
+LOG = logging.getLogger(__name__)
+
+
+class NsxRouterMixin(object):
+ """Mixin class to enable nsx router support."""
+
+ nsx_attributes = []
+
+ def _extend_nsx_router_dict(self, router_res, router_db):
+ nsx_attrs = router_db['nsx_attributes']
+ # Return False if nsx attributes are not definied for this
+ # neutron router
+ for attr in self.nsx_attributes:
+ name = attr['name']
+ default = attr['default']
+ router_res[name] = (
+ nsx_attrs and nsx_attrs[name] or default)
+
+ def _process_nsx_router_create(
+ self, context, router_db, router_req):
+ if not router_db['nsx_attributes']:
+ kwargs = {}
+ for attr in self.nsx_attributes:
+ name = attr['name']
+ default = attr['default']
+ kwargs[name] = router_req.get(name, default)
+ nsx_attributes = nicira_models.NSXRouterExtAttributes(
+ router_id=router_db['id'], **kwargs)
+ context.session.add(nsx_attributes)
+ router_db['nsx_attributes'] = nsx_attributes
+ else:
+ # The situation where the record already exists will
+ # be likely once the NSXRouterExtAttributes model
+ # will allow for defining several attributes pertaining
+ # to different extensions
+ for attr in self.nsx_attributes:
+ name = attr['name']
+ default = attr['default']
+ router_db['nsx_attributes'][name] = router_req.get(
+ name, default)
+ LOG.debug(_("Nsx router extension successfully processed "
+ "for router:%s"), router_db['id'])
+
+ # Register dict extend functions for ports
+ db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+ l3.ROUTERS, ['_extend_nsx_router_dict'])
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.
+#
+
+from neutron.plugins.nicira.dbexts import distributedrouter as dist_rtr
+from neutron.plugins.nicira.extensions import servicerouter
+
+
+class ServiceRouter_mixin(dist_rtr.DistributedRouter_mixin):
+ """Mixin class to enable service router support."""
+
+ nsx_attributes = (
+ dist_rtr.DistributedRouter_mixin.nsx_attributes + [{
+ 'name': servicerouter.SERVICE_ROUTER,
+ 'default': False
+ }])
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Nicira, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from neutron.plugins.nicira.dbexts import vcns_models
+
+
+def add_vcns_router_binding(session, router_id, vse_id, lswitch_id, status):
+ with session.begin(subtransactions=True):
+ binding = vcns_models.VcnsRouterBinding(
+ router_id=router_id,
+ edge_id=vse_id,
+ lswitch_id=lswitch_id,
+ status=status)
+ session.add(binding)
+ return binding
+
+
+def get_vcns_router_binding(session, router_id):
+ with session.begin(subtransactions=True):
+ return (session.query(vcns_models.VcnsRouterBinding).
+ filter_by(router_id=router_id).first())
+
+
+def update_vcns_router_binding(session, router_id, **kwargs):
+ with session.begin(subtransactions=True):
+ binding = (session.query(vcns_models.VcnsRouterBinding).
+ filter_by(router_id=router_id).one())
+ for key, value in kwargs.iteritems():
+ binding[key] = value
+
+
+def delete_vcns_router_binding(session, router_id):
+ with session.begin(subtransactions=True):
+ binding = (session.query(vcns_models.VcnsRouterBinding).
+ filter_by(router_id=router_id).one())
+ session.delete(binding)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Nicira, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import sqlalchemy as sa
+
+from neutron.db import model_base
+from neutron.db import models_v2
+
+
+class VcnsRouterBinding(model_base.BASEV2, models_v2.HasStatusDescription):
+ """Represents the mapping between neutron router and vShield Edge."""
+
+ __tablename__ = 'vcns_router_bindings'
+
+ # no ForeignKey to routers.id because for now, a router can be removed
+ # from routers when delete_router is executed, but the binding is only
+ # removed after the Edge is deleted
+ router_id = sa.Column(sa.String(36),
+ primary_key=True)
+ edge_id = sa.Column(sa.String(16),
+ nullable=True)
+ lswitch_id = sa.Column(sa.String(36),
+ nullable=False)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 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.
+#
+# @author: Kaiwei Fan, VMware, Inc
+
+
+from neutron.api import extensions
+from neutron.api.v2 import attributes
+
+
+SERVICE_ROUTER = 'service_router'
+EXTENDED_ATTRIBUTES_2_0 = {
+ 'routers': {
+ SERVICE_ROUTER: {'allow_post': True, 'allow_put': False,
+ 'convert_to': attributes.convert_to_boolean,
+ 'default': False, 'is_visible': True},
+ }
+}
+
+
+class Servicerouter(extensions.ExtensionDescriptor):
+ """Extension class supporting advanced service router."""
+
+ @classmethod
+ def get_name(cls):
+ return "Service Router"
+
+ @classmethod
+ def get_alias(cls):
+ return "service-router"
+
+ @classmethod
+ def get_description(cls):
+ return "Provides service router"
+
+ @classmethod
+ def get_namespace(cls):
+ return ""
+
+ @classmethod
+ def get_updated(cls):
+ return "2013-08-08T00:00:00-00:00"
+
+ def get_extended_resources(self, version):
+ if version == "2.0":
+ return EXTENDED_ATTRIBUTES_2_0
+ else:
+ return {}
LOG.debug(_("VCNS: start updating nat rules: %s"), rules)
nat = {
+ 'featureType': 'nat',
'rules': {
'natRulesDtos': rules
}
static_routes = []
for route in routes:
static_routes.append({
- "route": {
- "description": "",
- "vnic": vcns_const.INTERNAL_VNIC_INDEX,
- "network": route['cidr'],
- "nextHop": route['nexthop']
- }
+ "description": "",
+ "vnic": vcns_const.INTERNAL_VNIC_INDEX,
+ "network": route['cidr'],
+ "nextHop": route['nexthop']
})
request = {
- "staticRouting": {
- "staticRoutes": static_routes,
+ "staticRoutes": {
+ "staticRoutes": static_routes
}
}
if gateway:
- request["staticRouting"]["defaultRoute"] = {
+ request["defaultRoute"] = {
"description": "default-gateway",
"gatewayAddress": gateway,
"vnic": vcns_const.EXTERNAL_VNIC_INDEX
self.password = password
self.jsonapi_client = VcnsApiClient.VcnsApiHelper(address, user,
password, 'json')
- # TODO(fank): remove this after json syntax is fixed on VSM
- self.xmlapi_client = VcnsApiClient.VcnsApiHelper(address, user,
- password, 'xml')
def do_request(self, method, uri, params=None, format='json', **kwargs):
LOG.debug(_("VcnsApiHelper('%(method)s', '%(uri)s', '%(body)s')"), {
def update_routes(self, edge_id, routes):
uri = "%s/%s/routing/config/static" % (URI_PREFIX, edge_id)
- return self.do_request(HTTP_PUT, uri, routes, format='xml')
+ return self.do_request(HTTP_PUT, uri, routes)
def create_lswitch(self, lsconfig):
uri = "/api/ws.v1/lswitch"
import neutron.plugins.nicira.api_client.client_eventlet as client
from neutron.plugins.nicira import extensions
import neutron.plugins.nicira.NeutronPlugin as plugin
+import neutron.plugins.nicira.NeutronServicePlugin as service_plugin
import neutron.plugins.nicira.NvpApiClient as nvpapi
from neutron.plugins.nicira.vshield import vcns
nvp_plugin = plugin.NvpPluginV2
+nvp_service_plugin = service_plugin.NvpAdvancedPlugin
api_helper = nvpapi.NVPApiHelper
nvp_client = client.NvpApiClientEventlet
vcns_class = vcns.Vcns
NVPEXT_PATH = os.path.dirname(extensions.__file__)
NVPAPI_NAME = '%s.%s' % (api_helper.__module__, api_helper.__name__)
PLUGIN_NAME = '%s.%s' % (nvp_plugin.__module__, nvp_plugin.__name__)
+SERVICE_PLUGIN_NAME = '%s.%s' % (nvp_service_plugin.__module__,
+ nvp_service_plugin.__name__)
CLIENT_NAME = '%s.%s' % (nvp_client.__module__, nvp_client.__name__)
VCNS_NAME = '%s.%s' % (vcns_class.__module__, vcns_class.__name__)
--- /dev/null
+# Copyright (c) 2013 OpenStack Foundation.
+#
+# 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 copy
+
+from eventlet import greenthread
+import mock
+from oslo.config import cfg
+
+from neutron.api.v2 import attributes
+from neutron import context
+from neutron.extensions import l3
+from neutron.manager import NeutronManager
+from neutron.openstack.common import uuidutils
+from neutron.tests.unit.nicira import NVPEXT_PATH
+from neutron.tests.unit.nicira import SERVICE_PLUGIN_NAME
+from neutron.tests.unit.nicira import test_nicira_plugin
+from neutron.tests.unit.nicira import VCNS_NAME
+from neutron.tests.unit.nicira.vshield import fake_vcns
+
+_uuid = uuidutils.generate_uuid
+
+
+class ServiceRouterTestExtensionManager(object):
+
+ def get_resources(self):
+ # If l3 resources have been loaded and updated by main API
+ # router, update the map in the l3 extension so it will load
+ # the same attributes as the API router
+ l3_attr_map = copy.deepcopy(l3.RESOURCE_ATTRIBUTE_MAP)
+ for res in l3.RESOURCE_ATTRIBUTE_MAP.keys():
+ attr_info = attributes.RESOURCE_ATTRIBUTE_MAP.get(res)
+ if attr_info:
+ l3.RESOURCE_ATTRIBUTE_MAP[res] = attr_info
+ resources = l3.L3.get_resources()
+ # restore the original resources once the controllers are created
+ l3.RESOURCE_ATTRIBUTE_MAP = l3_attr_map
+
+ return resources
+
+ def get_actions(self):
+ return []
+
+ def get_request_extensions(self):
+ return []
+
+
+class NvpRouterTestCase(test_nicira_plugin.TestNiciraL3NatTestCase):
+
+ def setUp(self, plugin=None, ext_mgr=None):
+ plugin = plugin or SERVICE_PLUGIN_NAME
+ super(NvpRouterTestCase, self).setUp(plugin=plugin, ext_mgr=ext_mgr)
+
+
+class ServiceRouterTestCase(NvpRouterTestCase):
+
+ def vcns_patch(self):
+ instance = self.mock_vcns.start()
+ instance.return_value.deploy_edge.side_effect = self.fc2.deploy_edge
+ instance.return_value.get_edge_id.side_effect = self.fc2.get_edge_id
+ instance.return_value.get_edge_deploy_status.side_effect = (
+ self.fc2.get_edge_deploy_status)
+ instance.return_value.delete_edge.side_effect = self.fc2.delete_edge
+ instance.return_value.update_interface.side_effect = (
+ self.fc2.update_interface)
+ instance.return_value.get_nat_config.side_effect = (
+ self.fc2.get_nat_config)
+ instance.return_value.update_nat_config.side_effect = (
+ self.fc2.update_nat_config)
+ instance.return_value.delete_nat_rule.side_effect = (
+ self.fc2.delete_nat_rule)
+ instance.return_value.get_edge_status.side_effect = (
+ self.fc2.get_edge_status)
+ instance.return_value.get_edges.side_effect = self.fc2.get_edges
+ instance.return_value.update_routes.side_effect = (
+ self.fc2.update_routes)
+ instance.return_value.create_lswitch.side_effect = (
+ self.fc2.create_lswitch)
+ instance.return_value.delete_lswitch.side_effect = (
+ self.fc2.delete_lswitch)
+
+ def setUp(self):
+ cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
+ cfg.CONF.set_override('task_status_check_interval', 100, group="vcns")
+
+ # vcns does not support duplicated router name, ignore router name
+ # validation for unit-test cases
+ self.fc2 = fake_vcns.FakeVcns(unique_router_name=False)
+ self.mock_vcns = mock.patch(VCNS_NAME, autospec=True)
+ self.vcns_patch()
+
+ super(ServiceRouterTestCase, self).setUp(
+ ext_mgr=ServiceRouterTestExtensionManager())
+
+ self.fc2.set_fake_nvpapi(self.fc)
+ self.addCleanup(self.fc2.reset_all)
+ self.addCleanup(self.mock_vcns.stop)
+
+ def tearDown(self):
+ plugin = NeutronManager.get_plugin()
+ manager = plugin.vcns_driver.task_manager
+ for i in range(20):
+ if not manager.has_pending_task():
+ break
+ greenthread.sleep(0.1)
+ if manager.has_pending_task():
+ manager.show_pending_tasks()
+ raise Exception(_("Tasks not completed"))
+ manager.stop()
+
+ super(ServiceRouterTestCase, self).tearDown()
+
+ def _create_router(self, fmt, tenant_id, name=None,
+ admin_state_up=None, set_context=False,
+ arg_list=None, **kwargs):
+ data = {'router': {'tenant_id': tenant_id}}
+ if name:
+ data['router']['name'] = name
+ if admin_state_up:
+ data['router']['admin_state_up'] = admin_state_up
+ for arg in (('admin_state_up', 'tenant_id') + (arg_list or ())):
+ # Arg must be present and not empty
+ if arg in kwargs and kwargs[arg]:
+ data['router'][arg] = kwargs[arg]
+ data['router']['service_router'] = True
+ router_req = self.new_create_request('routers', data, fmt)
+ if set_context and tenant_id:
+ # create a specific auth context for this request
+ router_req.environ['neutron.context'] = context.Context(
+ '', tenant_id)
+
+ return router_req.get_response(self.ext_api)
+
+ def test_router_create(self):
+ name = 'router1'
+ tenant_id = _uuid()
+ expected_value = [('name', name), ('tenant_id', tenant_id),
+ ('admin_state_up', True),
+ ('external_gateway_info', None),
+ ('service_router', True)]
+ with self.router(name='router1', admin_state_up=True,
+ tenant_id=tenant_id) as router:
+ expected_value_1 = expected_value + [('status', 'PENDING_CREATE')]
+ for k, v in expected_value_1:
+ self.assertEqual(router['router'][k], v)
+
+ # wait ~1 seconds for router status update
+ for i in range(2):
+ greenthread.sleep(0.5)
+ res = self._show('routers', router['router']['id'])
+ if res['router']['status'] == 'ACTIVE':
+ break
+ expected_value_2 = expected_value + [('status', 'ACTIVE')]
+ for k, v in expected_value_2:
+ self.assertEqual(res['router'][k], v)
+
+ # check an integration lswitch is created
+ lswitch_name = "%s-ls" % name
+ for lswitch_id, lswitch in self.fc2._lswitches.iteritems():
+ if lswitch['display_name'] == lswitch_name:
+ break
+ else:
+ self.fail("Integration lswitch not found")
+
+ # check an integration lswitch is deleted
+ lswitch_name = "%s-ls" % name
+ for lswitch_id, lswitch in self.fc2._lswitches.iteritems():
+ if lswitch['display_name'] == lswitch_name:
+ self.fail("Integration switch is not deleted")
+
+ def test_router_show(self):
+ name = 'router1'
+ tenant_id = _uuid()
+ expected_value = [('name', name), ('tenant_id', tenant_id),
+ ('admin_state_up', True),
+ ('status', 'PENDING_CREATE'),
+ ('external_gateway_info', None),
+ ('service_router', True)]
+ with self.router(name='router1', admin_state_up=True,
+ tenant_id=tenant_id) as router:
+ res = self._show('routers', router['router']['id'])
+ for k, v in expected_value:
+ self.assertEqual(res['router'][k], v)
+
+ def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None):
+ super(ServiceRouterTestCase,
+ self)._test_router_create_with_gwinfo_and_l3_ext_net(
+ vlan_id, validate_ext_gw=False)
+
+ def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None):
+ super(ServiceRouterTestCase,
+ self)._test_router_update_gateway_on_l3_ext_net(
+ vlan_id, validate_ext_gw=False)
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as secgrp
from neutron import manager
+from neutron.manager import NeutronManager
from neutron.openstack.common import uuidutils
from neutron.plugins.nicira.common import exceptions as nvp_exc
from neutron.plugins.nicira.common import sync
def _restore_l3_attribute_map(self):
l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk
- def setUp(self):
+ def setUp(self, plugin=None, ext_mgr=None):
self._l3_attribute_map_bk = {}
for item in l3.RESOURCE_ATTRIBUTE_MAP:
self._l3_attribute_map_bk[item] = (
l3.RESOURCE_ATTRIBUTE_MAP[item].copy())
cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
self.addCleanup(self._restore_l3_attribute_map)
+ ext_mgr = ext_mgr or TestNiciraL3ExtensionManager()
super(TestNiciraL3NatTestCase, self).setUp(
- ext_mgr=TestNiciraL3ExtensionManager())
+ plugin=plugin, ext_mgr=ext_mgr)
+ plugin_instance = NeutronManager.get_plugin()
+ self._plugin_name = "%s.%s" % (
+ plugin_instance.__module__,
+ plugin_instance.__class__.__name__)
+ self._plugin_class = plugin_instance.__class__
def tearDown(self):
super(TestNiciraL3NatTestCase, self).tearDown()
def test_create_l3_ext_network_without_vlan(self):
self._test_create_l3_ext_network()
- def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None):
+ def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None,
+ validate_ext_gw=True):
with self._create_l3_ext_network(vlan_id) as net:
with self.subnet(network=net) as s:
data = {'router': {'tenant_id': 'whatever'}}
s['subnet']['network_id'],
(router['router']['external_gateway_info']
['network_id']))
- self._nvp_validate_ext_gw(router['router']['id'],
- 'l3_gw_uuid', vlan_id)
+ if validate_ext_gw:
+ self._nvp_validate_ext_gw(router['router']['id'],
+ 'l3_gw_uuid', vlan_id)
finally:
self._delete('routers', router['router']['id'])
uuidutils.generate_uuid(),
expected_code=webob.exc.HTTPNotFound.code)
- def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None):
+ def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None,
+ validate_ext_gw=True):
with self.router() as r:
with self.subnet() as s1:
with self._create_l3_ext_network(vlan_id) as net:
['external_gateway_info']['network_id'])
self.assertEqual(net_id,
s2['subnet']['network_id'])
- self._nvp_validate_ext_gw(body['router']['id'],
- 'l3_gw_uuid', vlan_id)
+ if validate_ext_gw:
+ self._nvp_validate_ext_gw(
+ body['router']['id'],
+ 'l3_gw_uuid', vlan_id)
finally:
# Cleanup
self._remove_external_gateway_from_router(
self._test_create_l3_ext_network(666)
def test_floatingip_with_assoc_fails(self):
- self._test_floatingip_with_assoc_fails(
- 'neutron.plugins.nicira.'
- 'NeutronPlugin.NvpPluginV2')
+ self._test_floatingip_with_assoc_fails(self._plugin_name)
def test_floatingip_with_invalid_create_port(self):
- self._test_floatingip_with_invalid_create_port(
- 'neutron.plugins.nicira.'
- 'NeutronPlugin.NvpPluginV2')
+ self._test_floatingip_with_invalid_create_port(self._plugin_name)
def _nvp_metadata_setup(self):
cfg.CONF.set_override('metadata_mode', 'access_network', 'NVP')
with self.router() as r:
with self.subnet() as s:
# Raise a NeutronException (eg: NotFound)
- with mock.patch.object(NeutronPlugin.NvpPluginV2,
+ with mock.patch.object(self._plugin_class,
'create_subnet',
side_effect=ntn_exc.NotFound):
self._router_interface_action(
# Raise a NeutronException when adding metadata subnet
# to router
# save function being mocked
- real_func = NeutronPlugin.NvpPluginV2.add_router_interface
+ real_func = self._plugin_class.add_router_interface
plugin_instance = manager.NeutronManager.get_plugin()
def side_effect(*args):
# otherwise raise
raise NvpApiClient.NvpApiException()
- with mock.patch.object(NeutronPlugin.NvpPluginV2,
+ with mock.patch.object(self._plugin_class,
'add_router_interface',
side_effect=side_effect):
self._router_interface_action(
# Raise a NeutronException when removing
# metadata subnet from router
# save function being mocked
- real_func = NeutronPlugin.NvpPluginV2.remove_router_interface
+ real_func = self._plugin_class.remove_router_interface
plugin_instance = manager.NeutronManager.get_plugin()
def side_effect(*args):
# otherwise raise
raise NvpApiClient.NvpApiException()
- with mock.patch.object(NeutronPlugin.NvpPluginV2,
+ with mock.patch.object(self._plugin_class,
'remove_router_interface',
side_effect=side_effect):
self._router_interface_action('remove', r['router']['id'],