NSX|P: Initial connectivity support

Not covered yet: DHCP, Switching Profiles, TZs,
provider networks, GW, NAT

Change-Id: I9f4163d8a233b0d3914363556ddf6ec0f375f99a
This commit is contained in:
Anna Khmelnitsky 2018-09-21 18:30:02 -07:00 committed by Adit Sarfaty
parent 30e1395302
commit 4f6730911c
5 changed files with 223 additions and 64 deletions

View File

@ -482,7 +482,15 @@ nsx_v3_opts = nsx_v3_and_p + [
]
nsx_p_opts = nsx_v3_and_p + []
nsx_p_opts = nsx_v3_and_p + [
cfg.StrOpt('default_tier0_router',
help=_("Name or UUID of the default tier0 router that will be "
"used for connecting to tier1 logical routers and "
"configuring external networks. If only one tier0 "
" router is present on backend, it will be assumed "
"as default unless this value is provided")),
]
DEFAULT_STATUS_CHECK_INTERVAL = 2000
DEFAULT_MINIMUM_POOLED_EDGES = 1

View File

@ -24,6 +24,7 @@ from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import models_v2
from neutron_lib.api.definitions import address_scope as ext_address_scope
from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
from neutron_lib.api.definitions import availability_zone as az_def
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import network as net_def
@ -38,12 +39,14 @@ from neutron_lib.callbacks import resources
from neutron_lib import constants
from neutron_lib import context as n_context
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import allowedaddresspairs as addr_exc
from neutron_lib.plugins import directory
from neutron_lib.services.qos import constants as qos_consts
from neutron_lib.utils import net
from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import utils
from vmware_nsx.extensions import maclearning as mac_ext
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as sg_prefix
from vmware_nsx.services.qos.common import utils as qos_com_utils
@ -567,6 +570,24 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
self._assert_on_vpn_port_change(original_port)
self._assert_on_lb_port_fixed_ip_change(port_data, orig_dev_owner)
def _build_port_name(self, context, port_data):
device_owner = port_data.get('device_owner')
device_id = port_data.get('device_id')
if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF and device_id:
router = self._get_router(context, device_id)
name = utils.get_name_and_uuid(
router['name'] or 'router', port_data['id'], tag='port')
elif device_owner == constants.DEVICE_OWNER_DHCP:
network = self.get_network(context, port_data['network_id'])
name = self._get_dhcp_port_name(network['name'],
network['id'])
elif device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX):
name = utils.get_name_and_uuid(
port_data['name'] or 'instance-port', port_data['id'])
else:
name = port_data['name']
return name
def _process_extra_attr_router_create(self, context, router_db, r):
for extra_attr in l3_attrs_db.get_attr_info().keys():
if (extra_attr in r and
@ -595,6 +616,38 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
sg_rule[sg_prefix.LOCAL_IP_PREFIX].startswith('0.0.0.0/')):
sg_rule[sg_prefix.LOCAL_IP_PREFIX] = None
def _validate_interface_address_scope(self, context,
router_db, interface_info):
gw_network_id = (router_db.gw_port.network_id if router_db.gw_port
else None)
subnet = self.get_subnet(context, interface_info['subnet_ids'][0])
if not router_db.enable_snat and gw_network_id:
self._validate_address_scope_for_router_interface(
context.elevated(), router_db.id, gw_network_id, subnet['id'])
def _validate_ipv4_address_pairs(self, address_pairs):
for pair in address_pairs:
ip = pair.get('ip_address')
if not utils.is_ipv4_ip_address(ip):
raise nsx_exc.InvalidIPAddress(ip_address=ip)
# NSXv3 and Policy only
def _create_port_address_pairs(self, context, port_data):
(port_security, has_ip) = self._determine_port_security_and_has_ip(
context, port_data)
address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS)
if validators.is_attr_set(address_pairs):
if not port_security:
raise addr_exc.AddressPairAndPortSecurityRequired()
else:
self._validate_ipv4_address_pairs(address_pairs)
self._process_create_allowed_address_pairs(context, port_data,
address_pairs)
else:
port_data[addr_apidef.ADDRESS_PAIRS] = []
def get_housekeeper(self, context, name, fields=None):
# run the job in readonly mode and get the results
self.housekeeper.run(context, name, readonly=True)

View File

@ -15,6 +15,8 @@
import sys
import netaddr
from oslo_config import cfg
from oslo_log import log
from oslo_utils import excutils
@ -30,6 +32,7 @@ from neutron.db import external_net_db
from neutron.db import extradhcpopt_db
from neutron.db import extraroute_db
from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import l3_gwmode_db
from neutron.db.models import l3 as l3_db_models
from neutron.db.models import securitygroup as securitygroup_model # noqa
@ -40,6 +43,7 @@ from neutron.db import securitygroups_db
from neutron.db import vlantransparent_db
from neutron.extensions import providernet
from neutron.quota import resource_registry
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
@ -161,6 +165,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self.cfg_group = 'nsx_p' # group name for nsx_p section in nsx.ini
self._init_default_config()
self._prepare_default_rules()
# subscribe the init complete method last, so it will be called only
@ -169,6 +174,38 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
resources.PROCESS,
events.AFTER_INIT)
# NOTE(annak): we may need to generalize this for API calls
# requiring path ids
def _init_default_resource(self, resource_api, name_or_id):
if not name_or_id:
# If not specified, the system will auto-configure
# in case only single resource is present
resources = resource_api.list()
if len(resources) == 1:
return resources[0]['id']
else:
return None
try:
resource_api.get(name_or_id)
return name_or_id
except nsx_lib_exc.ResourceNotFound:
try:
resource = resource_api.get_by_name(name_or_id)
if resource:
return resource['id']
except nsx_lib_exc.ResourceNotFound:
return None
def _init_default_config(self):
self.default_tier0_router = self._init_default_resource(
self.nsxpolicy.tier0,
cfg.CONF.nsx_p.default_tier0_router)
if not self.default_tier0_router:
raise cfg.RequiredOptError("default_tier0_router",
group=cfg.OptGroup('nsx_p'))
def _validate_nsx_policy_version(self):
self._nsx_version = self.nsxpolicy.get_version()
LOG.info("NSX Version: %s", self._nsx_version)
@ -219,8 +256,25 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
})
def _create_network_at_the_backend(self, context, net_data):
#TODO(asarfaty): implement, using nsx-id the same as the neutron id
pass
# TODO(annak): provider network
net_data['id'] = net_data.get('id') or uuidutils.generate_uuid()
net_name = utils.get_name_and_uuid(net_data['name'] or 'network',
net_data['id'])
tags = self.nsxpolicy.build_v3_tags_payload(
net_data, resource_type='os-neutron-net-id',
project_name=context.tenant_name)
# TODO(annak): admin state config is missing on policy
# should we not create networks that are down?
# alternative - configure status on manager for time being
# admin_state = net_data.get('admin_state_up', True)
self.nsxpolicy.segment.create_or_overwrite(
net_name,
segment_id=net_data['id'],
description=net_data.get('description'),
tags=tags)
def _validate_external_net_create(self, net_data):
#TODO(asarfaty): implement
@ -255,7 +309,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# Create the backend NSX network
if not is_external_net:
try:
self._create_network_at_the_backend(context, net_data)
self._create_network_at_the_backend(context, created_net)
except Exception as e:
LOG.exception("Failed to create NSX network network: %s", e)
with excutils.save_and_reraise_exception():
@ -272,11 +326,10 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
def delete_network(self, context, network_id):
with db_api.context_manager.writer.using(context):
self._process_l3_delete(context, network_id)
return super(NsxPolicyPlugin, self).delete_network(
super(NsxPolicyPlugin, self).delete_network(
context, network_id)
if not self._network_is_external(context, network_id):
# TODO(asarfaty) delete the NSX logical network
pass
self.nsxpolicy.segment.delete(network_id)
def update_network(self, context, id, network):
original_net = super(NsxPolicyPlugin, self).get_network(context, id)
@ -352,13 +405,51 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return updated_subnet
def _build_address_bindings(self, port):
address_bindings = []
for fixed_ip in port['fixed_ips']:
if netaddr.IPNetwork(fixed_ip['ip_address']).version != 4:
#TODO(annak): enable when IPv6 is supported
continue
binding = self.nsxpolicy.segment_port.build_address_binding(
fixed_ip['ip_address'], port['mac_address'])
address_bindings.append(binding)
for pair in port.get(addr_apidef.ADDRESS_PAIRS):
binding = self.nsxpolicy.segment_port.build_address_binding(
pair['ip_address'], pair['mac_address'])
address_bindings.append(binding)
return address_bindings
def _create_port_at_the_backend(self, context, port_data):
#TODO(asarfaty): implement
pass
# TODO(annak): admin_state not supported by policy
# TODO(annak): handle exclude list
# TODO(annak): switching profiles when supported
name = self._build_port_name(context, port_data)
psec, has_ip = self._determine_port_security_and_has_ip(context,
port_data)
address_bindings = (self._build_address_bindings(port_data)
if psec else None)
device_owner = port_data.get('device_owner')
vif_id = None
if device_owner and device_owner != l3_db.DEVICE_OWNER_ROUTER_INTF:
vif_id = port_data['id']
self.nsxpolicy.segment_port.create_or_overwrite(
port_data['network_id'],
port_data['id'],
name,
description=port_data.get('description'),
address_bindings=address_bindings,
vif_id=vif_id)
def _cleanup_port(self, context, port_id, lport_id):
super(NsxPolicyPlugin, self).delete_port(context, port_id)
#TODO(asarfaty): Delete the NSX logical port
port_data = self.get_port(context, port_id)
self.nsxpolicy.segment_port.delete(
port_data['network_id'], port_data['id'])
def base_create_port(self, context, port):
neutron_db = super(NsxPolicyPlugin, self).create_port(context, port)
@ -380,6 +471,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
neutron_db = self.base_create_port(context, port)
port["port"].update(neutron_db)
self._create_port_address_pairs(context, port_data)
if not is_external_net:
try:
self._create_port_at_the_backend(context, port_data)
@ -403,10 +496,15 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
l3_port_check=True, l2gw_port_check=True,
force_delete_dhcp=False,
force_delete_vpn=False):
port = self.get_port(context, port_id)
if not self._network_is_external(context, port['network_id']):
#TODO(asarfaty): Delete the NSX logical port
pass
port_data = self.get_port(context, port_id)
if not self._network_is_external(context, port_data['network_id']):
try:
self.nsxpolicy.segment_port.delete(
port_data['network_id'], port_data['id'])
except Exception as ex:
LOG.error("Failed to delete port %(id)s on NSX backend "
"due to %(e)s",
{'id': port_data['id'], 'e': ex})
self.disassociate_floatingips(context, port_id)
@ -510,7 +608,22 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
context, router)
router_db = self._get_router(context, router['id'])
self._process_extra_attr_router_create(context, router_db, r)
#TODO(asarfaty): Create the NSX logical router and add DB mapping
router_name = utils.get_name_and_uuid(router['name'] or 'router',
router['id'])
#TODO(annak): handle GW
try:
self.nsxpolicy.tier1.create_or_overwrite(
router_name, router['id'],
tier0=self.default_tier0_router)
#TODO(annak): narrow down the exception
except Exception as ex:
with excutils.save_and_reraise_exception():
LOG.error('Failed to create router %(id)s '
'on NSX backend. Exception: %(e)s',
{'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)
return self.get_router(context, router['id'])
@ -519,13 +632,14 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
router = self.get_router(context, router_id)
if router.get(l3_apidef.EXTERNAL_GW_INFO):
self._update_router_gw_info(context, router_id, {})
nsx_router_id = nsx_db.get_nsx_router_id(
context.session, router_id)
ret_val = super(NsxPolicyPlugin, self).delete_router(
context, router_id)
if nsx_router_id:
#TODO(asarfaty): delete the NSX logical router
pass
try:
self.nsxpolicy.tier1.delete(router_id)
except Exception as ex:
LOG.error("Failed to delete NSX T1 router %(id)s: %(e)s", {
'e': ex, 'id': router_id})
return ret_val
@ -557,7 +671,23 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
info = super(NsxPolicyPlugin, self).add_router_interface(
context, router_id, interface_info)
#TODO(asarfaty) Update the NSX logical router ports
self._validate_interface_address_scope(context, router_db, info)
subnet = self.get_subnet(context, info['subnet_ids'][0])
# 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(network_id,
tier1_id=router_id)
except Exception as ex:
with excutils.save_and_reraise_exception():
LOG.error('Failed to create router interface for subnet '
'%(id)s on NSX backend. Exception: %(e)s',
{'id': subnet['id'], 'e': ex})
self.remove_router_interface(
context, router_id, interface_info)
return info
def remove_router_interface(self, context, router_id, interface_info):

View File

@ -1727,7 +1727,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
LOG.debug("Created logical DHCP server %(server)s for network "
"%(network)s",
{'server': dhcp_server['id'], 'network': network['id']})
name = self._get_port_name(context, port_data)
name = self._build_port_name(context, port_data)
nsx_port = self.nsxlib.logical_port.create(
nsx_net_id, dhcp_server['id'], tags=port_tags, name=name,
attachment_type=nsxlib_consts.ATTACHMENT_DHCP,
@ -2226,24 +2226,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
net_name or 'network'),
net_id)
def _get_port_name(self, context, port_data):
device_owner = port_data.get('device_owner')
device_id = port_data.get('device_id')
if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF and device_id:
router = self._get_router(context, device_id)
name = utils.get_name_and_uuid(
router['name'] or 'router', port_data['id'], tag='port')
elif device_owner == const.DEVICE_OWNER_DHCP:
network = self.get_network(context, port_data['network_id'])
name = self._get_dhcp_port_name(network['name'],
network['id'])
elif device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX):
name = utils.get_name_and_uuid(
port_data['name'] or 'instance-port', port_data['id'])
else:
name = port_data['name']
return name
def _get_qos_profile_id(self, context, policy_id):
switch_profile_id = nsx_db.get_switch_profile_by_qos_policy(
context.session, policy_id)
@ -2378,7 +2360,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
profiles.append(self._mac_learning_profile)
profiles.append(self._no_switch_security)
name = self._get_port_name(context, port_data)
name = self._build_port_name(context, port_data)
nsx_net_id = self._get_network_nsx_id(context, port_data['network_id'])
try:
result = self.nsxlib.logical_port.create(
@ -2412,12 +2394,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return result
def _validate_address_pairs(self, address_pairs):
for pair in address_pairs:
ip = pair.get('ip_address')
if not utils.is_ipv4_ip_address(ip):
raise nsx_exc.InvalidIPAddress(ip_address=ip)
def _provider_sgs_specified(self, port_data):
# checks if security groups were updated adding/modifying
# security groups, port security is set and port has ip
@ -2462,18 +2438,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
context, port_data, neutron_db)
# allowed address pair checks
address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS)
if validators.is_attr_set(address_pairs):
if not port_security:
raise addr_exc.AddressPairAndPortSecurityRequired()
else:
self._validate_address_pairs(address_pairs)
self._process_create_allowed_address_pairs(
context, neutron_db,
address_pairs)
else:
# remove ATTR_NOT_SPECIFIED
port_data[addr_apidef.ADDRESS_PAIRS] = []
self._create_port_address_pairs(context, port_data)
if port_security and has_ip:
self._ensure_default_security_group_on_port(context, port)
@ -3147,7 +3112,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
raise addr_exc.AddressPairAndPortSecurityRequired()
if delete_addr_pairs or has_addr_pairs:
self._validate_address_pairs(
self._validate_ipv4_address_pairs(
updated_port[addr_apidef.ADDRESS_PAIRS])
# delete address pairs and read them in
self._delete_allowed_address_pairs(context, id)
@ -3245,7 +3210,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
attachment_type = None
vif_uuid = None
name = self._get_port_name(context, updated_port)
name = self._build_port_name(context, updated_port)
# Update exclude list if necessary
updated_ps = updated_port.get('port_security_enabled')
@ -4333,9 +4298,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# If it is a no-snat router, interface address scope must be the
# same as the gateways
if not router_db.enable_snat and gw_network_id:
self._validate_address_scope_for_router_interface(
context.elevated(), router_id, gw_network_id, subnet['id'])
self._validate_interface_address_scope(context, router_db, info)
nsx_router_id = nsx_db.get_nsx_router_id(context.session,
router_id)

View File

@ -37,11 +37,16 @@ class NsxPPluginTestCaseMixin(
ext_mgr=ext_mgr)
def _mock_nsx_policy_backend_calls(self):
resource_list_result = {'results': [{'id': 'test',
'display_name': 'test'}]}
mock.patch(
"vmware_nsxlib.v3.NsxPolicyLib.get_version",
return_value=nsx_constants.NSX_VERSION_2_4_0).start()
mock.patch(
"vmware_nsxlib.v3.client.RESTClient.get").start()
mock.patch(
"vmware_nsxlib.v3.client.RESTClient.list",
return_value=resource_list_result).start()
mock.patch(
"vmware_nsxlib.v3.client.RESTClient.patch").start()
mock.patch(