NSX|P: Add router advertisement & static routes

Support router advertisment and setting static routes for the
policy plugin.

Change-Id: Ifb68b62ef3088ce602735cbc93200bbf8906a77b
This commit is contained in:
Adit Sarfaty 2018-12-09 15:18:09 +02:00
parent 77a9571925
commit 28c1c21b7b
3 changed files with 168 additions and 48 deletions

View File

@ -35,6 +35,7 @@ from neutron_lib.exceptions import allowedaddresspairs as addr_exc
from neutron_lib.exceptions import port_security as psec_exc
from neutron_lib.plugins import utils as plugin_utils
from neutron_lib.services.qos import constants as qos_consts
from neutron_lib.utils import helpers
from neutron_lib.utils import net as nl_net_utils
from vmware_nsx.common import exceptions as nsx_exc
@ -905,3 +906,67 @@ class NsxPluginV3Base(plugin.NsxPluginBase,
orgaddr and not newaddr)) and not (fw_exist or lb_exist)
return actions
def _is_overlay_network(self):
"""Should be implemented by each plugin"""
pass
def _validate_update_router_gw(self, context, router_id, gw_info):
router_ports = self._get_router_interfaces(context, router_id)
for port in router_ports:
# if setting this router as no-snat, make sure gw address scope
# match those of the subnets
if not gw_info.get('enable_snat',
cfg.CONF.enable_snat_by_default):
for fip in port['fixed_ips']:
self._validate_address_scope_for_router_interface(
context.elevated(), router_id,
gw_info['network_id'], fip['subnet_id'])
# If the network attached to a router is a VLAN backed network
# then it must be attached to an edge cluster
if (not gw_info and
not self._is_overlay_network(context, port['network_id'])):
msg = _("A router attached to a VLAN backed network "
"must have an external network assigned")
raise n_exc.InvalidInput(error_message=msg)
def _validate_ext_routes(self, context, router_id, gw_info, new_routes):
ext_net_id = (gw_info['network_id']
if validators.is_attr_set(gw_info) and gw_info else None)
if not ext_net_id:
port_filters = {'device_id': [router_id],
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_GW]}
gw_ports = self.get_ports(context, filters=port_filters)
if gw_ports:
ext_net_id = gw_ports[0]['network_id']
if ext_net_id:
subnets = self._get_subnets_by_network(context, ext_net_id)
ext_cidrs = [subnet['cidr'] for subnet in subnets]
for route in new_routes:
if netaddr.all_matching_cidrs(
route['nexthop'], ext_cidrs):
error_message = (_("route with destination %(dest)s have "
"an external nexthop %(nexthop)s which "
"can't be supported") %
{'dest': route['destination'],
'nexthop': route['nexthop']})
LOG.error(error_message)
raise n_exc.InvalidInput(error_message=error_message)
def _get_static_routes_diff(self, context, router_id, gw_info,
router_data):
new_routes = router_data['routes']
self._validate_ext_routes(context, router_id, gw_info,
new_routes)
self._validate_routes(context, router_id, new_routes)
old_routes = self._get_extra_routes_by_router_id(
context, router_id)
routes_added, routes_removed = helpers.diff_list_of_dict(
old_routes, new_routes)
return routes_added, routes_removed
def _assert_on_router_admin_state(self, router_data):
if router_data.get("admin_state_up") is False:
err_msg = _("admin_state_up=False routers are not supported")
LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)

View File

@ -994,12 +994,10 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
for subnet in router_subnets:
self._add_subnet_no_dnat_rule(context, router_id, subnet)
#self.nsxpolicy.tier1.update_route_advertisement(
# router_id,
# actions['advertise_route_nat_flag'],
# actions['advertise_route_connected_flag'])
# TODO(asarfaty): handle enable/disable snat, router adv flags, etc.
self.nsxpolicy.tier1.update_route_advertisement(
router_id,
nat=actions['advertise_route_nat_flag'],
subnets=actions['advertise_route_connected_flag'])
if actions['remove_service_router']:
# disable edge firewall before removing the service router
@ -1064,16 +1062,82 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return ret_val
def _get_static_route_id(self, route):
return "%s-%s" % (route['destination'].replace('/', '_'),
route['nexthop'])
def _add_static_routes(self, router_id, routes):
for route in routes:
dest = route['destination']
self.nsxpolicy.tier1_static_route.create_or_overwrite(
'Static route for %s' % dest,
router_id,
static_route_id=self._get_static_route_id(route),
network=dest,
next_hop=route['nexthop'])
def _delete_static_routes(self, router_id, routes):
for route in routes:
self.nsxpolicy.tier1_static_route.delete(
router_id,
static_route_id=self._get_static_route_id(route))
def update_router(self, context, router_id, router):
gw_info = self._extract_external_gw(context, router, is_extract=False)
router_data = router['router']
LOG.debug("Updating router %s: %s. GW info %s",
router_id, router_data, gw_info)
#TODO(asarfaty) update the NSX logical router & interfaces
self._assert_on_router_admin_state(router_data)
return super(NsxPolicyPlugin, self).update_router(
if validators.is_attr_set(gw_info):
self._validate_update_router_gw(context, router_id, gw_info)
routes_added = []
routes_removed = []
if 'routes' in router_data:
routes_added, routes_removed = self._get_static_routes_diff(
context, router_id, gw_info, router_data)
# Update the neutron router
updated_router = super(NsxPolicyPlugin, self).update_router(
context, router_id, router)
# Update the policy backend
try:
added_routes = removed_routes = False
# Updating name & description
if 'name' in router_data or 'description' in router_data:
router_name = utils.get_name_and_uuid(
updated_router.get('name') or 'router',
router_id)
self.nsxpolicy.tier1.update(
router_id, name=router_name,
description=updated_router.get('description'))
# Updating static routes
self._delete_static_routes(router_id, routes_removed)
removed_routes = True
self._add_static_routes(router_id, routes_added)
added_routes = True
except (nsx_lib_exc.ResourceNotFound, nsx_lib_exc.ManagerError):
with excutils.save_and_reraise_exception():
with db_api.CONTEXT_WRITER.using(context):
router_db = self._get_router(context, router_id)
router_db['status'] = const.NET_STATUS_ERROR
# return the static routes to the old state
if added_routes:
try:
self._delete_static_routes(router_id, routes_added)
except Exception as e:
LOG.error("Rollback router %s changes failed to "
"delete static routes: %s", router_id, e)
if removed_routes:
try:
self._add_static_routes(router_id, routes_removed)
except Exception as e:
LOG.error("Rollback router %s changes failed to add "
"static routes: %s", router_id, e)
return updated_router
def add_router_interface(self, context, router_id, interface_info):
LOG.info("Adding router %s interface %s", router_id, interface_info)
network_id = self._get_interface_network(context, interface_info)
@ -1739,3 +1803,29 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
super(NsxPolicyPlugin, self).delete_security_group_rule(
context, rule_id)
def _is_overlay_network(self, context, network_id):
"""Return True if this is an overlay network
1. No binding ("normal" overlay networks will have no binding)
2. Geneve network
3. nsx network where the backend network is connected to an overlay TZ
"""
bindings = nsx_db.get_network_bindings(context.session, network_id)
# With NSX plugin, "normal" overlay networks will have no binding
if not bindings:
# using the default /AZ overlay_tz
return True
binding = bindings[0]
if binding.binding_type == utils.NsxV3NetworkTypes.GENEVE:
return True
if binding.binding_type == utils.NsxV3NetworkTypes.NSX_NETWORK:
# check the backend network
segment = self.nsxpolicy.segments.get(binding.phy_uuid)
tz = self._get_nsx_net_tz_id(segment)
if tz:
type = self.nsxpolicy.transport_zone.get_transport_type(
tz)
return type == nsxlib_consts.TRANSPORT_TYPE_OVERLAY
return False

View File

@ -71,7 +71,6 @@ from neutron_lib.callbacks import resources
from neutron_lib import constants as const
from neutron_lib import context as q_context
from neutron_lib import exceptions as n_exc
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log
@ -3369,12 +3368,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
action="NO_DNAT",
match_destination_network=subnet['cidr'])
def _assert_on_router_admin_state(self, router_data):
if router_data.get("admin_state_up") is False:
err_msg = _("admin_state_up=False routers are not supported")
LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def validate_router_dhcp_relay(self, context):
"""Fail router creation dhcp relay is configured without IPAM"""
if (self._availability_zones_data.dhcp_relay_configured() and
@ -3495,29 +3488,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# get the availability zones from the hints
return [self.get_router_az(router).name]
def _validate_ext_routes(self, context, router_id, gw_info, new_routes):
ext_net_id = (gw_info['network_id']
if validators.is_attr_set(gw_info) and gw_info else None)
if not ext_net_id:
port_filters = {'device_id': [router_id],
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_GW]}
gw_ports = self.get_ports(context, filters=port_filters)
if gw_ports:
ext_net_id = gw_ports[0]['network_id']
if ext_net_id:
subnets = self._get_subnets_by_network(context, ext_net_id)
ext_cidrs = [subnet['cidr'] for subnet in subnets]
for route in new_routes:
if netaddr.all_matching_cidrs(
route['nexthop'], ext_cidrs):
error_message = (_("route with destination %(dest)s have "
"an external nexthop %(nexthop)s which "
"can't be supported") %
{'dest': route['destination'],
'nexthop': route['nexthop']})
LOG.error(error_message)
raise n_exc.InvalidInput(error_message=error_message)
def _update_router_wrapper(self, context, router_id, router):
if cfg.CONF.api_replay_mode:
# NOTE(arosen): the mock.patch here is needed for api_replay_mode
@ -3535,6 +3505,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._assert_on_router_admin_state(router_data)
if validators.is_attr_set(gw_info):
self._validate_update_router_gw(context, router_id, gw_info)
router_ports = self._get_router_interfaces(context, router_id)
for port in router_ports:
# if setting this router as no-snat, make sure gw address scope
@ -3565,14 +3536,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
routes_removed = []
try:
if 'routes' in router_data:
new_routes = router_data['routes']
self._validate_ext_routes(context, router_id, gw_info,
new_routes)
self._validate_routes(context, router_id, new_routes)
old_routes = self._get_extra_routes_by_router_id(
context, router_id)
routes_added, routes_removed = helpers.diff_list_of_dict(
old_routes, new_routes)
routes_added, routes_removed = self._get_static_routes_diff(
context, router_id, gw_info, router_data)
nsx_router_id = nsx_db.get_nsx_router_id(context.session,
router_id)
for route in routes_removed: