NSX|P: Basic router interface & GW support

Additional actions will be added with nsxlib support

Depends-on: I10a3f691b33e37e1cd8ec8094f4bfa89d7a96f35
Change-Id: I92fff433646202a0245c1cef9630173fe245a296
This commit is contained in:
Adit Sarfaty 2018-11-07 10:39:39 +02:00
parent 5e4a33e3d5
commit 183b86b1f0
4 changed files with 269 additions and 112 deletions

View File

@ -150,7 +150,10 @@ class NSXClient(object):
segments = self.get_os_nsx_segments()
print("Number of OS segments to be deleted: %s" % len(segments))
for s in segments:
# Delete all the ports
self.cleanup_segment_ports(s['id'])
# Disassociate from a tier1 router
self.nsxlib.segment.update(s['id'], tier1_id=None)
self.nsxlib.segment.delete(s['id'])
def get_os_nsx_segment_ports(self, segment_id):

View File

@ -14,6 +14,7 @@
# under the License.
import netaddr
from oslo_config import cfg
from oslo_log import log as logging
from six import moves
@ -789,3 +790,110 @@ class NsxPluginV3Base(plugin.NsxPluginBase,
self.update_security_group_on_port(
context, port_id, {'port': original_port},
updated_port, original_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 n_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 _validate_router_gw(self, context, router_id, info, org_enable_snat):
# Ensure that a router cannot have SNAT disabled if there are
# floating IP's assigned
if (info and 'enable_snat' in info and
org_enable_snat != info.get('enable_snat') and
info.get('enable_snat') is False and
self.router_gw_port_has_floating_ips(context, router_id)):
msg = _("Unable to set SNAT disabled. Floating IPs assigned")
raise n_exc.InvalidInput(error_message=msg)
def _get_update_router_gw_actions(
self,
org_tier0_uuid, orgaddr, org_enable_snat,
new_tier0_uuid, newaddr, new_enable_snat):
"""Return a dictionary of flags indicating which actions should be
performed on this router GW update.
"""
actions = {}
# Remove router link port between tier1 and tier0 if tier0 router link
# is removed or changed
actions['remove_router_link_port'] = (
org_tier0_uuid and
(not new_tier0_uuid or org_tier0_uuid != new_tier0_uuid))
# Remove SNAT rules for gw ip if gw ip is deleted/changed or
# enable_snat is updated from True to False
actions['remove_snat_rules'] = (
org_enable_snat and orgaddr and
(newaddr != orgaddr or not new_enable_snat))
# Remove No-DNAT rules if GW was removed or snat was disabled
actions['remove_no_dnat_rules'] = (
orgaddr and org_enable_snat and
(not newaddr or not new_enable_snat))
# Revocate bgp announce for nonat subnets if tier0 router link is
# changed or enable_snat is updated from False to True
actions['revocate_bgp_announce'] = (
not org_enable_snat and org_tier0_uuid and
(new_tier0_uuid != org_tier0_uuid or new_enable_snat))
# Add router link port between tier1 and tier0 if tier0 router link is
# added or changed to a new one
actions['add_router_link_port'] = (
new_tier0_uuid and
(not org_tier0_uuid or org_tier0_uuid != new_tier0_uuid))
# Add SNAT rules for gw ip if gw ip is add/changed or
# enable_snat is updated from False to True
actions['add_snat_rules'] = (
new_enable_snat and newaddr and
(newaddr != orgaddr or not org_enable_snat))
# Add No-DNAT rules if GW was added, and the router has SNAT enabled,
# or if SNAT was enabled
actions['add_no_dnat_rules'] = (
new_enable_snat and newaddr and
(not orgaddr or not org_enable_snat))
# Bgp announce for nonat subnets if tier0 router link is changed or
# enable_snat is updated from True to False
actions['bgp_announce'] = (
not new_enable_snat and new_tier0_uuid and
(new_tier0_uuid != org_tier0_uuid or not org_enable_snat))
# Advertise NAT routes if enable SNAT to support FIP. In the NoNAT
# use case, only NSX connected routes need to be advertised.
actions['advertise_route_nat_flag'] = (
True if new_enable_snat else False)
actions['advertise_route_connected_flag'] = (
True if not new_enable_snat else False)
# TODO(asarfaty): calculate flags for add/remove service router
actions['remove_service_router'] = (
actions['remove_router_link_port'] and
not actions['add_router_link_port'])
actions['add_service_router'] = (
actions['add_router_link_port'] and
not actions['remove_router_link_port'])
return actions

View File

@ -16,6 +16,7 @@
import netaddr
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log
from oslo_utils import excutils
from oslo_utils import uuidutils
@ -45,6 +46,7 @@ from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
from neutron_lib.api.definitions import external_net
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import port_security as psec
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.api.definitions import vlantransparent as vlan_apidef
from neutron_lib.api import faults
from neutron_lib.api import validators
@ -76,6 +78,7 @@ from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
from vmware_nsxlib.v3 import policy_constants
from vmware_nsxlib.v3 import policy_defs
from vmware_nsxlib.v3 import utils as nsxlib_utils
LOG = log.getLogger(__name__)
@ -793,11 +796,100 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return (ports if not fields else
[db_utils.resource_fields(port, fields) for port in ports])
def _get_tier0_uuid_by_net_id(self, context, network_id):
if not network_id:
return
network = self.get_network(context, network_id)
if not network.get(pnet.PHYSICAL_NETWORK):
return self.default_tier0_router
else:
return network.get(pnet.PHYSICAL_NETWORK)
def _get_tier0_uuid_by_router(self, context, router):
network_id = router.gw_port_id and router.gw_port.network_id
return self._get_tier0_uuid_by_net_id(context, network_id)
def _update_router_gw_info(self, context, router_id, info):
# Get the original data of the router GW
router = self._get_router(context, router_id)
org_tier0_uuid = self._get_tier0_uuid_by_router(context, router)
org_enable_snat = router.enable_snat
orgaddr, orgmask, _orgnexthop = (
self._get_external_attachment_info(
context, router))
self._validate_router_gw(context, router_id, info, org_enable_snat)
# First update the neutron DB
super(NsxPolicyPlugin, self)._update_router_gw_info(
context, router_id, info, router=router)
#TODO(asarfaty): Update the NSX
# Get the new tier0 of the updated router (or None if GW was removed)
new_tier0_uuid = self._get_tier0_uuid_by_router(context, router)
new_enable_snat = router.enable_snat
newaddr, newmask, _newnexthop = self._get_external_attachment_info(
context, router)
router_name = utils.get_name_and_uuid(router['name'] or 'router',
router['id'])
router_subnets = self._find_router_subnets(
context.elevated(), router_id)
actions = self._get_update_router_gw_actions(
org_tier0_uuid, orgaddr, org_enable_snat,
new_tier0_uuid, newaddr, new_enable_snat)
if actions['add_service_router']:
edge_cluster = self.nsxpolicy.tier0.get_edge_cluster_path(
new_tier0_uuid)
if edge_cluster:
self.nsxpolicy.tier1.set_edge_cluster_path(
router_id, edge_cluster)
if actions['remove_snat_rules']:
#self.nsxpolicy.tier1.delete_gw_snat_rules(nsx_router_id, orgaddr)
pass
if actions['remove_no_dnat_rules']:
for subnet in router_subnets:
#self._del_subnet_no_dnat_rule(context, nsx_router_id, subnet)
pass
if (actions['remove_router_link_port'] or
actions['add_router_link_port']):
# GW was changed
#TODO(asarfaty): adding the router name even though it was not
# changed because otherwise the NSX will set it to default.
# This code should be removed once NSX supports it.
self.nsxpolicy.tier1.update(router_id, name=router_name,
tier0=new_tier0_uuid)
# Set/Unset the router TZ to allow vlan switches traffic
#TODO(asarfaty) no api for this yet
if actions['add_snat_rules']:
# Add SNAT rules for all the subnets which are in different scope
# than the GW
#gw_address_scope = self._get_network_address_scope(
# context, router.gw_port.network_id)
for subnet in router_subnets:
#self._add_subnet_snat_rule(context, router_id, nsx_router_id,
# subnet, gw_address_scope, newaddr)
pass
if actions['add_no_dnat_rules']:
for subnet in router_subnets:
#self._add_subnet_no_dnat_rule(context, nsx_router_id, subnet)
pass
#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.
if actions['remove_service_router']:
# disable edge firewall before removing the service router
#TODO(asarfaty) no api for this yet
# remove the edge cluster
self.nsxpolicy.tier1.remove_edge_cluster(router_id)
def create_router(self, context, router):
r = router['router']
@ -812,11 +904,10 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
router['id'])
tags = self.nsxpolicy.build_v3_api_version_project_tag(
context.tenant_name)
#TODO(annak): handle GW
try:
self.nsxpolicy.tier1.create_or_overwrite(
router_name, router['id'],
tier0=self.default_tier0_router,
tier0=None,
tags=tags)
#TODO(annak): narrow down the exception
except Exception as ex:
@ -826,8 +917,19 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
{'id': router['id'], 'e': ex})
self.delete_router(context, router['id'])
LOG.debug("Created router %s: %s. GW info %s",
router['id'], r, gw_info)
if gw_info and gw_info != const.ATTR_NOT_SPECIFIED:
try:
self._update_router_gw_info(context, router['id'], gw_info)
except (db_exc.DBError, nsx_lib_exc.ManagerError):
with excutils.save_and_reraise_exception():
LOG.error("Failed to set gateway info for router "
"being created: %s - removing router",
router['id'])
self.delete_router(context, router['id'])
LOG.info("Create router failed while setting external "
"gateway. Router:%s has been removed from "
"DB and backend",
router['id'])
return self.get_router(context, router['id'])
def delete_router(self, context, router_id):
@ -859,10 +961,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
network_id = self._get_interface_network(context, interface_info)
extern_net = self._network_is_external(context, network_id)
router_db = self._get_router(context, router_id)
gw_network_id = (router_db.gw_port.network_id if router_db.gw_port
else None)
LOG.debug("Adding router %s interface %s with GW %s",
router_id, network_id, gw_network_id)
# A router interface cannot be an external network
if extern_net:
msg = _("An external network cannot be attached as "
@ -874,29 +973,54 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
context, router_id, interface_info)
self._validate_interface_address_scope(context, router_db, info)
subnet = self.get_subnet(context, info['subnet_ids'][0])
segment_id = self._get_network_nsx_segment_id(context, network_id)
# TODO(annak): Validate TZ
try:
# This is always an overwrite call
# NOTE: Connecting network to multiple routers is not supported
self.nsxpolicy.segment.create_or_overwrite(segment_id,
tier1_id=router_id)
#TODO(asarfaty): adding the segment name even though it was not
# changed because otherwise the NSX will set it to default.
# This code should be removed once NSX supports it.
net = self._get_network(context, network_id)
net_name = utils.get_name_and_uuid(
net['name'] or 'network', network_id)
segment_id = self._get_network_nsx_id(context, network_id)
subnet = self.get_subnet(context, info['subnet_ids'][0])
pol_subnet = policy_defs.Subnet(
gateway_address=("%s/32" % subnet.get('gateway_ip')))
self.nsxpolicy.segment.update(segment_id,
name=net_name,
tier1_id=router_id,
subnets=[pol_subnet])
except Exception as ex:
with excutils.save_and_reraise_exception():
LOG.error('Failed to create router interface for subnet '
LOG.error('Failed to create router interface for network '
'%(id)s on NSX backend. Exception: %(e)s',
{'id': subnet['id'], 'e': ex})
{'id': network_id, 'e': ex})
self.remove_router_interface(
context, router_id, interface_info)
return info
def remove_router_interface(self, context, router_id, interface_info):
#TODO(asarfaty) Update the NSX logical router ports
# Update the neutron router first
info = super(NsxPolicyPlugin, self).remove_router_interface(
context, router_id, interface_info)
network_id = info['network_id']
# Remove the tier1 router from this segment on the nSX
try:
#TODO(asarfaty): adding the segment name even though it was not
# changed because otherwise the NSX will set it to default.
# This code should be removed once NSX supports it.
net = self._get_network(context, network_id)
net_name = utils.get_name_and_uuid(
net['name'] or 'network', network_id)
segment_id = self._get_network_nsx_id(context, network_id)
self.nsxpolicy.segment.update(segment_id, name=net_name,
tier1_id=None)
except Exception as ex:
# do not fail the neutron action
LOG.error('Failed to remove router interface for network '
'%(id)s on NSX backend. Exception: %(e)s',
{'id': network_id, 'e': ex})
return info
def create_floatingip(self, context, floatingip):

View File

@ -3157,31 +3157,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return (ports if not fields else
[db_utils.resource_fields(port, fields) for port in ports])
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 n_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_tier0_uuid_by_net_id(self, context, network_id):
if not network_id:
return
@ -3221,15 +3196,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
orgaddr, orgmask, _orgnexthop = (
self._get_external_attachment_info(
context, router))
# Ensure that a router cannot have SNAT disabled if there are
# floating IP's assigned
if (info and 'enable_snat' in info and
org_enable_snat != info.get('enable_snat') and
info.get('enable_snat') is False and
self.router_gw_port_has_floating_ips(context, router_id)):
msg = _("Unable to set SNAT disabled. Floating IPs assigned")
raise n_exc.InvalidInput(error_message=msg)
self._validate_router_gw(context, router_id, info, org_enable_snat)
router_subnets = self._find_router_subnets(
context.elevated(), router_id)
@ -3254,72 +3221,26 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
context, router))
nsx_router_id = nsx_db.get_nsx_router_id(context.session, router_id)
# Remove router link port between tier1 and tier0 if tier0 router link
# is removed or changed
remove_router_link_port = (org_tier0_uuid and
(not new_tier0_uuid or
org_tier0_uuid != new_tier0_uuid))
actions = self._get_update_router_gw_actions(
org_tier0_uuid, orgaddr, org_enable_snat,
new_tier0_uuid, newaddr, new_enable_snat)
# Remove SNAT rules for gw ip if gw ip is deleted/changed or
# enable_snat is updated from True to False
remove_snat_rules = (org_enable_snat and orgaddr and
(newaddr != orgaddr or
not new_enable_snat))
# Remove No-DNAT rules if GW was removed or snat was disabled
remove_no_dnat_rules = (orgaddr and org_enable_snat and
(not newaddr or not new_enable_snat))
# Revocate bgp announce for nonat subnets if tier0 router link is
# changed or enable_snat is updated from False to True
revocate_bgp_announce = (not org_enable_snat and org_tier0_uuid and
(new_tier0_uuid != org_tier0_uuid or
new_enable_snat))
# Add router link port between tier1 and tier0 if tier0 router link is
# added or changed to a new one
add_router_link_port = (new_tier0_uuid and
(not org_tier0_uuid or
org_tier0_uuid != new_tier0_uuid))
# Add SNAT rules for gw ip if gw ip is add/changed or
# enable_snat is updated from False to True
add_snat_rules = (new_enable_snat and newaddr and
(newaddr != orgaddr or
not org_enable_snat))
# Add No-DNAT rules if GW was added, and the router has SNAT enabled,
# or if SNAT was enabled
add_no_dnat_rules = (new_enable_snat and newaddr and
(not orgaddr or not org_enable_snat))
# Bgp announce for nonat subnets if tier0 router link is changed or
# enable_snat is updated from True to False
bgp_announce = (not new_enable_snat and new_tier0_uuid and
(new_tier0_uuid != org_tier0_uuid or
not org_enable_snat))
# Advertise NAT routes if enable SNAT to support FIP. In the NoNAT
# use case, only NSX connected routes need to be advertised.
advertise_route_nat_flag = True if new_enable_snat else False
advertise_route_connected_flag = True if not new_enable_snat else False
if revocate_bgp_announce:
if actions['revocate_bgp_announce']:
# TODO(berlin): revocate bgp announce on org tier0 router
pass
if remove_snat_rules:
if actions['remove_snat_rules']:
self.nsxlib.router.delete_gw_snat_rules(nsx_router_id, orgaddr)
if remove_no_dnat_rules:
if actions['remove_no_dnat_rules']:
for subnet in router_subnets:
self._del_subnet_no_dnat_rule(context, nsx_router_id, subnet)
if remove_router_link_port:
if actions['remove_router_link_port']:
# remove the link port and reset the router transport zone
self.nsxlib.router.remove_router_link_port(nsx_router_id)
if self.nsxlib.feature_supported(
nsxlib_consts.FEATURE_ROUTER_TRANSPORT_ZONE):
self.nsxlib.router.update_router_transport_zone(
nsx_router_id, None)
if add_router_link_port:
if actions['add_router_link_port']:
# First update edge cluster info for router
edge_cluster_uuid = self._get_edge_cluster(new_tier0_uuid)
self.nsxlib.router.update_router_edge_cluster(
@ -3338,7 +3259,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self.nsxlib.router.add_router_link_port(nsx_router_id,
new_tier0_uuid,
tags=tags)
if add_snat_rules:
if actions['add_snat_rules']:
# Add SNAT rules for all the subnets which are in different scope
# than the gw
gw_address_scope = self._get_network_address_scope(
@ -3346,17 +3267,18 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
for subnet in router_subnets:
self._add_subnet_snat_rule(context, router_id, nsx_router_id,
subnet, gw_address_scope, newaddr)
if add_no_dnat_rules:
if actions['add_no_dnat_rules']:
for subnet in router_subnets:
self._add_subnet_no_dnat_rule(context, nsx_router_id, subnet)
if bgp_announce:
if actions['bgp_announce']:
# TODO(berlin): bgp announce on new tier0 router
pass
self.nsxlib.router.update_advertisement(nsx_router_id,
advertise_route_nat_flag,
advertise_route_connected_flag)
self.nsxlib.router.update_advertisement(
nsx_router_id,
actions['advertise_route_nat_flag'],
actions['advertise_route_connected_flag'])
def _add_subnet_snat_rule(self, context, router_id, nsx_router_id, subnet,
gw_address_scope, gw_ip):