diff --git a/neutron/db/l3_gwmode_db.py b/neutron/db/l3_gwmode_db.py index 74c0cf36e..c2b49b4c7 100644 --- a/neutron/db/l3_gwmode_db.py +++ b/neutron/db/l3_gwmode_db.py @@ -43,8 +43,10 @@ class L3_NAT_db_mixin(l3_db.L3_NAT_db_mixin): 'enable_snat': router.enable_snat} return self._fields(res, fields) - def _update_router_gw_info(self, context, router_id, info): - router = self._get_router(context, router_id) + def _update_router_gw_info(self, context, router_id, info, router=None): + # Load the router only if necessary + if not router: + router = self._get_router(context, router_id) # if enable_snat is not specified use the value # stored in the database (default:True) enable_snat = not info or info.get('enable_snat', router.enable_snat) @@ -54,6 +56,9 @@ class L3_NAT_db_mixin(l3_db.L3_NAT_db_mixin): # Calls superclass, pass router db object for avoiding re-loading super(L3_NAT_db_mixin, self)._update_router_gw_info( context, router_id, info, router=router) + # Returning the router might come back useful if this + # method is overriden in child classes + return router def _build_routers_list(self, routers, gw_ports): gw_port_id_gw_port_dict = {} diff --git a/neutron/db/migration/alembic_migrations/versions/128e042a2b68_ext_gw_mode.py b/neutron/db/migration/alembic_migrations/versions/128e042a2b68_ext_gw_mode.py index 20d08ae14..8770b1e24 100644 --- a/neutron/db/migration/alembic_migrations/versions/128e042a2b68_ext_gw_mode.py +++ b/neutron/db/migration/alembic_migrations/versions/128e042a2b68_ext_gw_mode.py @@ -34,6 +34,7 @@ migration_for_plugins = [ 'neutron.plugins.linuxbridge.lb_neutron_plugin.LinuxBridgePluginV2', 'neutron.plugins.metaplugin.meta_neutron_plugin.MetaPluginV2', 'neutron.plugins.nec.nec_plugin.NECPluginV2', + 'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2', 'neutron.plugins.openvswitch.ovs_neutron_plugin.OVSNeutronPluginV2', 'neutron.plugins.ryu.ryu_neutron_plugin.RyuNeutronPluginV2' ] diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index 557521e02..cf718fcac 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -44,6 +44,7 @@ from neutron.db import db_base_plugin_v2 from neutron.db import dhcp_rpc_base from neutron.db import extraroute_db from neutron.db import l3_db +from neutron.db import l3_gwmode_db from neutron.db import models_v2 from neutron.db import portsecurity_db from neutron.db import quota_db # noqa @@ -73,6 +74,7 @@ from neutron.plugins.nicira import nvplib LOG = logging.getLogger("NeutronPlugin") + NVP_NOSNAT_RULES_ORDER = 10 NVP_FLOATINGIP_NAT_RULES_ORDER = 224 NVP_EXTGW_NAT_RULES_ORDER = 255 @@ -127,6 +129,7 @@ class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin): class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, extraroute_db.ExtraRoute_db_mixin, + l3_gwmode_db.L3_NAT_db_mixin, portsecurity_db.PortSecurityDbMixin, securitygroups_db.SecurityGroupDbMixin, mac_db.MacLearningDbMixin, @@ -141,7 +144,8 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, functionality using NVP. """ - supported_extension_aliases = ["extraroute", + supported_extension_aliases = ["ext_gw_mode", + "extraroute", "mac-learning", "network-gateway", "nvp-qos", @@ -278,6 +282,58 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, attachment_vlan) return lrouter_port + def _update_router_gw_info(self, context, router_id, info): + # NOTE(salvatore-orlando): We need to worry about rollback of NVP + # configuration in case of failures in the process + # Ref. LP bug 1102301 + router = self._get_router(context, router_id) + # Check whether SNAT rule update should be triggered + # NVP also supports multiple external networks so there is also + # the possibility that NAT rules should be replaced + current_ext_net_id = router.gw_port_id and router.gw_port.network_id + new_ext_net_id = info and info.get('network_id') + # SNAT should be enabled unless info['enable_snat'] is + # explicitly set to false + enable_snat = new_ext_net_id and info.get('enable_snat', True) + # Remove if ext net removed, changed, or if snat disabled + remove_snat_rules = (current_ext_net_id and + new_ext_net_id != current_ext_net_id or + router.enable_snat and not enable_snat) + # Add rules if snat is enabled, and if either the external network + # changed or snat was previously disabled + # NOTE: enable_snat == True implies new_ext_net_id != None + add_snat_rules = (enable_snat and + (new_ext_net_id != current_ext_net_id or + not router.enable_snat)) + router = super(NvpPluginV2, self)._update_router_gw_info( + context, router_id, info, router=router) + # Add/Remove SNAT rules as needed + # Create an elevated context for dealing with metadata access + # cidrs which are created within admin context + ctx_elevated = context.elevated() + if remove_snat_rules or add_snat_rules: + cidrs = self._find_router_subnets_cidrs(ctx_elevated, router_id) + if remove_snat_rules: + # Be safe and concede NAT rules might not exist. + # Therefore use min_num_expected=0 + for cidr in cidrs: + nvplib.delete_nat_rules_by_match( + self.cluster, router_id, "SourceNatRule", + max_num_expected=1, min_num_expected=0, + source_ip_addresses=cidr) + if add_snat_rules: + ip_addresses = self._build_ip_address_list( + ctx_elevated, router.gw_port['fixed_ips']) + # Set the SNAT rule for each subnet (only first IP) + for cidr in cidrs: + cidr_prefix = int(cidr.split('/')[1]) + nvplib.create_lrouter_snat_rule( + self.cluster, router_id, + ip_addresses[0].split('/')[0], + ip_addresses[0].split('/')[0], + order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix, + match_criteria={'source_ip_addresses': cidr}) + def _update_router_port_attachment(self, cluster, context, router_id, port_data, nvp_router_port_id, @@ -526,15 +582,6 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, "L3GatewayAttachment", ext_network[pnet.PHYSICAL_NETWORK], ext_network[pnet.SEGMENTATION_ID]) - # Set the SNAT rule for each subnet (only first IP) - for cidr in self._find_router_subnets_cidrs(context, router_id): - cidr_prefix = int(cidr.split('/')[1]) - nvplib.create_lrouter_snat_rule( - self.cluster, router_id, - ip_addresses[0].split('/')[0], - ip_addresses[0].split('/')[0], - order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix, - match_criteria={'source_ip_addresses': cidr}) LOG.debug(_("_nvp_create_ext_gw_port completed on external network " "%(ext_net_id)s, attached to router:%(router_id)s. " @@ -559,13 +606,6 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, port_data['name'], True, ['0.0.0.0/31']) - # Delete the SNAT rule for each subnet, keep in mind - # that the rule might have already been removed from NVP - for cidr in self._find_router_subnets_cidrs(context, router_id): - nvplib.delete_nat_rules_by_match( - self.cluster, router_id, "SourceNatRule", - max_num_expected=1, min_num_expected=0, - source_ip_addresses=cidr) # Reset attachment self._update_router_port_attachment( self.cluster, context, router_id, port_data, @@ -1654,7 +1694,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, # Fetch router from DB router = self._get_router(context, router_id) gw_port = router.gw_port - if 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'): @@ -1869,7 +1909,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, match_criteria={'destination_ip_addresses': floating_ip}) # setup snat rule such that src ip of a IP packet when - # using floating is the floating ip itself. + # using floating is the floating ip itself. nvplib.create_lrouter_snat_rule( self.cluster, router_id, floating_ip, floating_ip, order=NVP_FLOATINGIP_NAT_RULES_ORDER, diff --git a/neutron/tests/unit/nicira/test_nicira_plugin.py b/neutron/tests/unit/nicira/test_nicira_plugin.py index f95da9e15..bf7c6cc92 100644 --- a/neutron/tests/unit/nicira/test_nicira_plugin.py +++ b/neutron/tests/unit/nicira/test_nicira_plugin.py @@ -39,6 +39,7 @@ from neutron.plugins.nicira import nvplib from neutron.tests.unit.nicira import fake_nvpapiclient import neutron.tests.unit.nicira.test_networkgw as test_l2_gw import neutron.tests.unit.test_db_plugin as test_plugin +import neutron.tests.unit.test_extension_ext_gw_mode as test_ext_gw_mode import neutron.tests.unit.test_extension_portsecurity as psec import neutron.tests.unit.test_extension_security_group as ext_sg from neutron.tests.unit import test_extensions @@ -830,6 +831,11 @@ class TestNiciraQoSQueue(NiciraPluginV2TestCase): self.assertEqual(queue['qos_queue']['max'], 20) +class NiciraExtGwModeTestCase(test_ext_gw_mode.ExtGwModeTestCase, + NiciraPluginV2TestCase): + pass + + class NiciraNeutronNVPOutOfSync(test_l3_plugin.L3NatTestCaseBase, NiciraPluginV2TestCase): diff --git a/neutron/tests/unit/test_extension_ext_gw_mode.py b/neutron/tests/unit/test_extension_ext_gw_mode.py index ac8ac0ccb..5da43518f 100644 --- a/neutron/tests/unit/test_extension_ext_gw_mode.py +++ b/neutron/tests/unit/test_extension_ext_gw_mode.py @@ -302,7 +302,7 @@ class ExtGwModeTestCase(test_db_plugin.NeutronDbPluginV2TestCase, test_l3_plugin.L3NatTestCaseMixin): def setUp(self): - # Store l3 resource attribute map as it's will be updated + # Store l3 resource attribute map as it will be updated self._l3_attribute_map_bk = {} for item in l3.RESOURCE_ATTRIBUTE_MAP: self._l3_attribute_map_bk[item] = (