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:
parent
77a9571925
commit
28c1c21b7b
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user