From: Kaiwei Fan Date: Fri, 23 Aug 2013 06:25:52 +0000 (-0700) Subject: Support for NVP advanced service router X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=f43eb49f56accab87e4d1b6defcf78d1e697f9e6;p=openstack-build%2Fneutron-build.git Support for NVP advanced service router When creating an LR: - deploy an Edge asynchronously - create a L2 switch for connecting LR and Edge - attach a router port to the L2 switch. - assign ip address 169.254.2.1/28 and nexthop 169.254.2.3 to LR When set external gateway: - configure Edge interface and default gateway - Add static routes to Edge for all logic networks attached to LR via nexthop 169.254.2.1 - configure SNAT rules for all logic networks attached to LR When add router interface: - Add static route/SNAT rule for the network attached to LR When associate floating IP address: - configure DNAT rule for the floating ip and the port Tests being done: - Verified Edge is deployed asynchronously and LR is attached to the internal created L2 switch - Manually attach Edge's vNic to the L2 switch and Edge is able to ping 169.254.2.1 - Verified router-delete deletes Edge asynchronously and remove the internal L2 switch - Verified SNAT/DNAT/static-routes rules are configured on Edge in correct order - Verified external vnic ip address/netmask and default gateway is configured Implements: blueprint nvp-service-router Change-Id: If9eff53df4d65cf4e318dedbfaafc742f6c6ab7f --- diff --git a/neutron/db/migration/alembic_migrations/versions/4a666eb208c2_service_router.py b/neutron/db/migration/alembic_migrations/versions/4a666eb208c2_service_router.py new file mode 100644 index 000000000..9d4a7f3d0 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/4a666eb208c2_service_router.py @@ -0,0 +1,69 @@ +# 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') diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index cf49b4c69..242abc464 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -54,6 +54,7 @@ from neutron.extensions import portsecurity as psec 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 @@ -78,6 +79,7 @@ NVP_NOSNAT_RULES_ORDER = 10 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 @@ -1381,48 +1383,14 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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 " @@ -1432,6 +1400,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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( @@ -1449,7 +1418,48 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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 @@ -1459,14 +1469,23 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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'] @@ -1494,8 +1513,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, "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 @@ -1525,8 +1544,11 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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): @@ -1544,7 +1566,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, # 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) @@ -1553,6 +1575,27 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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 @@ -1587,17 +1630,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, # 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, @@ -1655,12 +1688,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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( @@ -1677,7 +1705,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, "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: @@ -1720,12 +1748,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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") @@ -1748,7 +1771,6 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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 @@ -1757,9 +1779,22 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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 @@ -1797,6 +1832,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, "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. @@ -1816,7 +1852,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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) @@ -1828,7 +1865,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 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) diff --git a/neutron/plugins/nicira/NeutronServicePlugin.py b/neutron/plugins/nicira/NeutronServicePlugin.py new file mode 100644 index 000000000..211be7132 --- /dev/null +++ b/neutron/plugins/nicira/NeutronServicePlugin.py @@ -0,0 +1,808 @@ +# 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) diff --git a/neutron/plugins/nicira/dbexts/distributedrouter.py b/neutron/plugins/nicira/dbexts/distributedrouter.py index 6856e8b35..c1d3bfbc4 100644 --- a/neutron/plugins/nicira/dbexts/distributedrouter.py +++ b/neutron/plugins/nicira/dbexts/distributedrouter.py @@ -17,53 +17,15 @@ # @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 + }]) diff --git a/neutron/plugins/nicira/dbexts/nicira_models.py b/neutron/plugins/nicira/dbexts/nicira_models.py index d6a21880c..ef7b01d9c 100644 --- a/neutron/plugins/nicira/dbexts/nicira_models.py +++ b/neutron/plugins/nicira/dbexts/nicira_models.py @@ -89,6 +89,7 @@ class NSXRouterExtAttributes(model_base.BASEV2): 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( diff --git a/neutron/plugins/nicira/dbexts/nsxrouter.py b/neutron/plugins/nicira/dbexts/nsxrouter.py new file mode 100644 index 000000000..de00ec985 --- /dev/null +++ b/neutron/plugins/nicira/dbexts/nsxrouter.py @@ -0,0 +1,70 @@ +# 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']) diff --git a/neutron/plugins/nicira/dbexts/servicerouter.py b/neutron/plugins/nicira/dbexts/servicerouter.py new file mode 100644 index 000000000..9cba25362 --- /dev/null +++ b/neutron/plugins/nicira/dbexts/servicerouter.py @@ -0,0 +1,29 @@ +# 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 + }]) diff --git a/neutron/plugins/nicira/dbexts/vcns_db.py b/neutron/plugins/nicira/dbexts/vcns_db.py new file mode 100644 index 000000000..9d7daaa61 --- /dev/null +++ b/neutron/plugins/nicira/dbexts/vcns_db.py @@ -0,0 +1,50 @@ +# 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) diff --git a/neutron/plugins/nicira/dbexts/vcns_models.py b/neutron/plugins/nicira/dbexts/vcns_models.py new file mode 100644 index 000000000..fe96d245c --- /dev/null +++ b/neutron/plugins/nicira/dbexts/vcns_models.py @@ -0,0 +1,38 @@ +# 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) diff --git a/neutron/plugins/nicira/extensions/servicerouter.py b/neutron/plugins/nicira/extensions/servicerouter.py new file mode 100644 index 000000000..ed863f032 --- /dev/null +++ b/neutron/plugins/nicira/extensions/servicerouter.py @@ -0,0 +1,61 @@ +# 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 {} diff --git a/neutron/plugins/nicira/vshield/edge_appliance_driver.py b/neutron/plugins/nicira/vshield/edge_appliance_driver.py index 026744a8b..246cba587 100644 --- a/neutron/plugins/nicira/vshield/edge_appliance_driver.py +++ b/neutron/plugins/nicira/vshield/edge_appliance_driver.py @@ -506,6 +506,7 @@ class EdgeApplianceDriver(object): LOG.debug(_("VCNS: start updating nat rules: %s"), rules) nat = { + 'featureType': 'nat', 'rules': { 'natRulesDtos': rules } @@ -565,20 +566,18 @@ class EdgeApplianceDriver(object): 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 diff --git a/neutron/plugins/nicira/vshield/vcns.py b/neutron/plugins/nicira/vshield/vcns.py index 72e5e858b..b02a97c2c 100644 --- a/neutron/plugins/nicira/vshield/vcns.py +++ b/neutron/plugins/nicira/vshield/vcns.py @@ -37,9 +37,6 @@ class Vcns(object): 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')"), { @@ -100,7 +97,7 @@ class Vcns(object): 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" diff --git a/neutron/tests/unit/nicira/__init__.py b/neutron/tests/unit/nicira/__init__.py index c4a742471..62b27ecf9 100644 --- a/neutron/tests/unit/nicira/__init__.py +++ b/neutron/tests/unit/nicira/__init__.py @@ -20,10 +20,12 @@ import os 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 @@ -32,6 +34,8 @@ STUBS_PATH = os.path.join(os.path.dirname(__file__), 'etc') 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__) diff --git a/neutron/tests/unit/nicira/test_edge_router.py b/neutron/tests/unit/nicira/test_edge_router.py new file mode 100644 index 000000000..fba4c286d --- /dev/null +++ b/neutron/tests/unit/nicira/test_edge_router.py @@ -0,0 +1,205 @@ +# 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) diff --git a/neutron/tests/unit/nicira/test_nicira_plugin.py b/neutron/tests/unit/nicira/test_nicira_plugin.py index 467eb6a84..e0be1521b 100644 --- a/neutron/tests/unit/nicira/test_nicira_plugin.py +++ b/neutron/tests/unit/nicira/test_nicira_plugin.py @@ -32,6 +32,7 @@ from neutron.extensions import portbindings 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 @@ -433,15 +434,21 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, 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() @@ -487,7 +494,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, 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'}} @@ -503,8 +511,9 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, 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']) @@ -584,7 +593,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, 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: @@ -609,8 +619,10 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, ['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( @@ -635,14 +647,10 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, 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') @@ -724,7 +732,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, 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( @@ -746,7 +754,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, # 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): @@ -756,7 +764,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, # 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( @@ -817,7 +825,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, # 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): @@ -827,7 +835,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, # 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'],