From e9b4d3484ace6addefa1e4fa8748874401295fa1 Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Wed, 10 Jan 2018 13:11:49 +0200 Subject: [PATCH] TVD: Add VPNaaS wrapper driver Change-Id: I39746cf7dc251ad8a6cc632e2c2627c80a41564a --- doc/source/devstack.rst | 9 ++ vmware_nsx/plugins/nsx_v/plugin.py | 3 +- vmware_nsx/plugins/nsx_v3/plugin.py | 2 +- .../services/vpnaas/nsx_tvd/__init__.py | 0 .../services/vpnaas/nsx_tvd/ipsec_driver.py | 100 ++++++++++++++++ .../vpnaas/nsx_tvd/ipsec_validator.py | 112 ++++++++++++++++++ .../services/vpnaas/nsxv/ipsec_driver.py | 7 +- .../services/vpnaas/nsxv3/ipsec_driver.py | 7 +- .../services/vpnaas/nsxv3/ipsec_validator.py | 26 ++-- 9 files changed, 253 insertions(+), 13 deletions(-) create mode 100644 vmware_nsx/services/vpnaas/nsx_tvd/__init__.py create mode 100644 vmware_nsx/services/vpnaas/nsx_tvd/ipsec_driver.py create mode 100644 vmware_nsx/services/vpnaas/nsx_tvd/ipsec_validator.py diff --git a/doc/source/devstack.rst b/doc/source/devstack.rst index 16feaedd58..a54b5ddc3c 100644 --- a/doc/source/devstack.rst +++ b/doc/source/devstack.rst @@ -302,3 +302,12 @@ Add neutron-dynamic-routing repo as an external repository and configure followi [[post-config|$NEUTRON_CONF]] [DEFAULT] api_extensions_path = $DEST/neutron-dynamic-routing/neutron_dynamic_routing/extensions + +Neutron VPNaaS +~~~~~~~~~~~~~~ + +Add neutron-vpnaas repo as an external repository and configure following flags in ``local.conf``:: + + [[local|localrc]] + enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas + NEUTRON_VPNAAS_SERVICE_PROVIDER=VPN:vmware:vmware_nsx.services.vpnaas.nsx_tvd.ipsec_driver.NSXIPsecVpnDriver:default diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 7158f729ca..69eac467e4 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -3894,7 +3894,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if vpn_plugin: vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider] vpn_rules = ( - vpn_driver._generate_ipsecvpn_firewall_rules(edge_id)) + vpn_driver._generate_ipsecvpn_firewall_rules( + self.plugin_type(), context, edge_id=edge_id)) fw_rules.extend(vpn_rules) # Get the load balancer rules in case they are refreshed diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 6cac134969..e79d38e7d6 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -3658,7 +3658,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider] vpn_rules = ( vpn_driver._generate_ipsecvpn_firewall_rules( - context, router_id)) + self.plugin_type(), context, router_id=router_id)) if vpn_rules: extra_rules.extend(vpn_rules) diff --git a/vmware_nsx/services/vpnaas/nsx_tvd/__init__.py b/vmware_nsx/services/vpnaas/nsx_tvd/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/services/vpnaas/nsx_tvd/ipsec_driver.py b/vmware_nsx/services/vpnaas/nsx_tvd/ipsec_driver.py new file mode 100644 index 0000000000..917b9b7db8 --- /dev/null +++ b/vmware_nsx/services/vpnaas/nsx_tvd/ipsec_driver.py @@ -0,0 +1,100 @@ +# Copyright 2018 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log as logging + +from neutron_lib.plugins import directory +from neutron_vpnaas.services.vpn import service_drivers + +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.extensions import projectpluginmap +from vmware_nsx.plugins.nsx import utils as tvd_utils +from vmware_nsx.services.vpnaas.nsx_tvd import ipsec_validator +from vmware_nsx.services.vpnaas.nsxv import ipsec_driver as v_driver +from vmware_nsx.services.vpnaas.nsxv3 import ipsec_driver as t_driver + +LOG = logging.getLogger(__name__) +IPSEC = 'ipsec' + + +class NSXIPsecVpnDriver(service_drivers.VpnDriver): + """Wrapper driver to select the relevant driver for each VPNaaS request""" + def __init__(self, service_plugin): + self.vpn_plugin = service_plugin + self._core_plugin = directory.get_plugin() + validator = ipsec_validator.IPsecValidator(service_plugin) + super(NSXIPsecVpnDriver, self).__init__(service_plugin, validator) + + # supported drivers: + self.drivers = {} + try: + self.drivers[projectpluginmap.NsxPlugins.NSX_T] = ( + t_driver.NSXv3IPsecVpnDriver(service_plugin)) + except Exception as e: + LOG.error("NSXIPsecVpnDriver failed to initialize the NSX-T " + "driver: %s", e) + self.drivers[projectpluginmap.NsxPlugins.NSX_T] = None + try: + self.drivers[projectpluginmap.NsxPlugins.NSX_V] = ( + v_driver.NSXvIPsecVpnDriver(service_plugin)) + except Exception as e: + LOG.error("NSXIPsecVpnDriver failed to initialize the NSX-V " + "driver: %s", e) + self.drivers[projectpluginmap.NsxPlugins.NSX_V] = None + + @property + def service_type(self): + return IPSEC + + def _get_driver_for_project(self, project): + plugin_type = tvd_utils.get_tvd_plugin_type_for_project(project) + if not self.drivers.get(plugin_type): + msg = (_("Project %(project)s with plugin %(plugin)s has no " + "support for VPNaaS"), {'project': project, + 'plugin': plugin_type}) + raise nsx_exc.NsxPluginException(err_msg=msg) + return self.drivers[plugin_type] + + def create_ipsec_site_connection(self, context, ipsec_site_conn): + d = self._get_driver_for_project(ipsec_site_conn['tenant_id']) + return d.create_ipsec_site_connection(context, ipsec_site_conn) + + def delete_ipsec_site_connection(self, context, ipsec_site_conn): + d = self._get_driver_for_project(ipsec_site_conn['tenant_id']) + return d.delete_ipsec_site_connection(context, ipsec_site_conn) + + def update_ipsec_site_connection(self, context, old_ipsec_conn, + ipsec_site_conn): + d = self._get_driver_for_project(old_ipsec_conn['tenant_id']) + return d.update_ipsec_site_connection(context, old_ipsec_conn, + ipsec_site_conn) + + def create_vpnservice(self, context, vpnservice): + d = self._get_driver_for_project(vpnservice['tenant_id']) + return d.create_vpnservice(context, vpnservice) + + def update_vpnservice(self, context, old_vpnservice, vpnservice): + pass + + def delete_vpnservice(self, context, vpnservice): + pass + + def _generate_ipsecvpn_firewall_rules(self, plugin_type, context, + **kargs): + d = self.drivers.get(plugin_type) + if d: + return d._generate_ipsecvpn_firewall_rules( + plugin_type, context, **kargs) + return [] diff --git a/vmware_nsx/services/vpnaas/nsx_tvd/ipsec_validator.py b/vmware_nsx/services/vpnaas/nsx_tvd/ipsec_validator.py new file mode 100644 index 0000000000..f3c0fcaf39 --- /dev/null +++ b/vmware_nsx/services/vpnaas/nsx_tvd/ipsec_validator.py @@ -0,0 +1,112 @@ +# Copyright 2018 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log as logging + +from neutron_vpnaas.db.vpn import vpn_validator + +from vmware_nsx._i18n import _ +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.extensions import projectpluginmap +from vmware_nsx.plugins.nsx import utils as tvd_utils +from vmware_nsx.services.vpnaas.nsxv import ipsec_validator as v_validator +from vmware_nsx.services.vpnaas.nsxv3 import ipsec_validator as t_validator + +LOG = logging.getLogger(__name__) + + +class IPsecValidator(vpn_validator.VpnReferenceValidator): + """Wrapper validator for selecting the V/T validator to use""" + def __init__(self, service_plugin): + super(IPsecValidator, self).__init__() + self.vpn_plugin = service_plugin + + # supported validatorss: + self.validators = {} + try: + self.validators[projectpluginmap.NsxPlugins.NSX_T] = ( + t_validator.IPsecV3Validator(service_plugin)) + except Exception as e: + LOG.error("IPsecValidator failed to initialize the NSX-T " + "validator: %s", e) + self.validators[projectpluginmap.NsxPlugins.NSX_T] = None + try: + self.validators[projectpluginmap.NsxPlugins.NSX_V] = ( + v_validator.IPsecValidator(service_plugin)) + except Exception as e: + LOG.error("IPsecValidator failed to initialize the NSX-V " + "validator: %s", e) + self.validators[projectpluginmap.NsxPlugins.NSX_V] = None + + def _get_validator_for_project(self, project): + plugin_type = tvd_utils.get_tvd_plugin_type_for_project(project) + if not self.validators.get(plugin_type): + msg = (_("Project %(project)s with plugin %(plugin)s has no " + "support for VPNaaS"), {'project': project, + 'plugin': plugin_type}) + raise nsx_exc.NsxPluginException(err_msg=msg) + return self.validators[plugin_type] + + def validate_ipsec_site_connection(self, context, ipsec_site_conn): + if not ipsec_site_conn.get('tenant_id'): + # nothing we can do here. + return + + v = self._get_validator_for_project(ipsec_site_conn['tenant_id']) + + # first make sure the plugin is the same as the one of the vpnservice + srv_id = ipsec_site_conn.get('vpnservice_id') + srv = self.vpn_plugin._get_vpnservice(context, srv_id) + srv_validator = self._get_validator_for_project(srv['tenant_id']) + if v != srv_validator: + err_msg = _('VPN service should belong to the same plugin ' + 'as the connection') + raise nsx_exc.NsxVpnValidationError(details=err_msg) + + return v.validate_ipsec_site_connection(context, ipsec_site_conn) + + def validate_vpnservice(self, context, vpnservice): + if not vpnservice.get('tenant_id'): + # This will happen during update. + # nothing significant like router or subnet can be changes + # so we can skip it. + return + + v = self._get_validator_for_project(vpnservice['tenant_id']) + + # first make sure the router&subnet plugin matches the vpnservice + router_id = vpnservice['router_id'] + p = self.core_plugin._get_plugin_from_router_id(context, router_id) + if self.validators.get(p.plugin_type()) != v: + err_msg = _('Router & subnet should belong to the same plugin ' + 'as the VPN service') + raise nsx_exc.NsxVpnValidationError(details=err_msg) + return v.validate_vpnservice(context, vpnservice) + + def validate_ipsec_policy(self, context, ipsec_policy): + if not ipsec_policy.get('tenant_id'): + # nothing we can do here + return + + v = self._get_validator_for_project(ipsec_policy['tenant_id']) + return v.validate_ipsec_policy(context, ipsec_policy) + + def validate_ike_policy(self, context, ike_policy): + if not ike_policy.get('tenant_id'): + # nothing we can do here + return + + v = self._get_validator_for_project(ike_policy['tenant_id']) + return v.validate_ike_policy(context, ike_policy) diff --git a/vmware_nsx/services/vpnaas/nsxv/ipsec_driver.py b/vmware_nsx/services/vpnaas/nsxv/ipsec_driver.py index 81baa86916..4ba4b996a4 100644 --- a/vmware_nsx/services/vpnaas/nsxv/ipsec_driver.py +++ b/vmware_nsx/services/vpnaas/nsxv/ipsec_driver.py @@ -24,6 +24,7 @@ from vmware_nsx.common import exceptions as nsxv_exc from vmware_nsx.common import locking from vmware_nsx.common import nsxv_constants from vmware_nsx.db import nsxv_db +from vmware_nsx.extensions import projectpluginmap from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as vcns_exc from vmware_nsx.services.vpnaas.nsxv import ipsec_validator @@ -35,6 +36,9 @@ class NSXvIPsecVpnDriver(service_drivers.VpnDriver): def __init__(self, service_plugin): self._core_plugin = directory.get_plugin() + if self._core_plugin.is_tvd_plugin(): + self._core_plugin = self._core_plugin.get_plugin_by_type( + projectpluginmap.NsxPlugins.NSX_V) self._vcns = self._core_plugin.nsx_v.vcns validator = ipsec_validator.IPsecValidator(service_plugin) super(NSXvIPsecVpnDriver, self).__init__(service_plugin, validator) @@ -111,7 +115,8 @@ class NSXvIPsecVpnDriver(service_drivers.VpnDriver): vse_sites.append(ipsec_site_conn) return vse_sites - def _generate_ipsecvpn_firewall_rules(self, edge_id): + def _generate_ipsecvpn_firewall_rules(self, plugin_type, context, + edge_id=None): ipsecvpn_configs = self._get_ipsec_config(edge_id) ipsec_vpn_fw_rules = [] if ipsecvpn_configs[1]['enabled']: diff --git a/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py b/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py index 61aa98d7d6..2c46d71bbf 100644 --- a/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py +++ b/vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py @@ -26,6 +26,7 @@ from neutron_vpnaas.services.vpn import service_drivers from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.db import db +from vmware_nsx.extensions import projectpluginmap from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils from vmware_nsx.services.vpnaas.nsxv3 import ipsec_validator from vmware_nsxlib.v3 import exceptions as nsx_lib_exc @@ -41,6 +42,9 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver): def __init__(self, service_plugin): self.vpn_plugin = service_plugin self._core_plugin = directory.get_plugin() + if self._core_plugin.is_tvd_plugin(): + self._core_plugin = self._core_plugin.get_plugin_by_type( + projectpluginmap.NsxPlugins.NSX_T) self._nsxlib = self._core_plugin.nsxlib self._nsx_vpn = self._nsxlib.vpn_ipsec validator = ipsec_validator.IPsecV3Validator(service_plugin) @@ -66,7 +70,8 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver): def _translate_addresses_to_target(self, cidrs): return [self._translate_cidr(ip) for ip in cidrs] - def _generate_ipsecvpn_firewall_rules(self, context, router_id): + def _generate_ipsecvpn_firewall_rules(self, plugin_type, context, + router_id=None): """Return the firewall rules needed to allow vpn traffic""" fw_rules = [] # get all the active services of this router diff --git a/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py b/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py index f318af4314..d9fe3ad038 100644 --- a/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py +++ b/vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py @@ -21,6 +21,7 @@ from neutron_vpnaas.db.vpn import vpn_validator from vmware_nsx._i18n import _ from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.extensions import projectpluginmap from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils from vmware_nsxlib.v3 import nsx_constants as consts from vmware_nsxlib.v3 import vpn_ipsec @@ -34,7 +35,13 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator): def __init__(self, service_plugin): super(IPsecV3Validator, self).__init__() self.vpn_plugin = service_plugin - self.nsxlib = self.core_plugin.nsxlib + + self._core_plugin = self.core_plugin + if self._core_plugin.is_tvd_plugin(): + self._core_plugin = self._core_plugin.get_plugin_by_type( + projectpluginmap.NsxPlugins.NSX_T) + self.nsxlib = self._core_plugin.nsxlib + self.check_backend_version() def check_backend_version(self): @@ -219,7 +226,7 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator): this_cidr = srv['subnet']['cidr'] # get all subnets of no-snat routers - all_routers = self.core_plugin.get_routers(admin_con) + all_routers = self._core_plugin.get_routers(admin_con) nosnat_routers = [rtr for rtr in all_routers if (rtr['id'] != this_router and rtr.get('external_gateway_info') and @@ -229,7 +236,7 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator): if rtr['id'] == this_router: continue # go over the subnets of this router - subnets = self.core_plugin._find_router_subnets_cidrs( + subnets = self._core_plugin._find_router_subnets_cidrs( admin_con, rtr['id']) if subnets and netaddr.IPSet(subnets) & netaddr.IPSet([this_cidr]): msg = (_("Cannot create connection with overlapping local " @@ -283,9 +290,10 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator): ipsec_policy_id) self.validate_ipsec_policy(context, ipsecpolicy) - self._check_advertisment_overlap(context, ipsec_site_conn) - self._check_unique_addresses(context, ipsec_site_conn) - self._check_policy_rules_overlap(context, ipsec_site_conn) + if ipsec_site_conn.get('vpnservice_id'): + self._check_advertisment_overlap(context, ipsec_site_conn) + self._check_unique_addresses(context, ipsec_site_conn) + self._check_policy_rules_overlap(context, ipsec_site_conn) #TODO(asarfaty): IPv6 is not yet supported. add validation @@ -293,15 +301,15 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator): vpnservice = self.vpn_plugin._get_vpnservice(context, vpnservice_id) router_id = vpnservice['router_id'] - router_db = self.core_plugin.get_router(context, router_id) + router_db = self._core_plugin.get_router(context, router_id) gw = router_db['external_gateway_info'] return gw['external_fixed_ips'][0]['ip_address'] def _validate_router(self, context, router_id): # Verify that the router gw network is connected to an active-standby # Tier0 router - router_db = self.core_plugin._get_router(context, router_id) - tier0_uuid = self.core_plugin._get_tier0_uuid_by_router(context, + router_db = self._core_plugin._get_router(context, router_id) + tier0_uuid = self._core_plugin._get_tier0_uuid_by_router(context, router_db) # TODO(asarfaty): cache this result tier0_router = self.nsxlib.logical_router.get(tier0_uuid)