From 80a0a40e970202fa23b5a72f993c54c2264878f1 Mon Sep 17 00:00:00 2001 From: berlin Date: Tue, 5 Nov 2013 10:31:00 +0800 Subject: [PATCH] Support advanced NVP IPsec VPN Service The patch adds NVP advanced IPsec VPN Service support for NVP with VCNS: * NVP IPsec VPN is an advanced Service depending on NVP advanced service router * NVP IPsec VPN Service will finally call VCNS IPsec VPN bulk reconfiguration to map to VPN DB logic Implements: blueprint nvp-vpnaas-plugin Change-Id: Ic8a96f5defc2b868c6f18fb4966b04079d3c781a --- neutron/plugins/vmware/plugins/service.py | 158 +++++++- .../vmware/vshield/edge_ipsecvpn_driver.py | 149 +++++++ neutron/plugins/vmware/vshield/vcns.py | 15 + neutron/plugins/vmware/vshield/vcns_driver.py | 4 +- neutron/tests/unit/db/vpn/test_db_vpnaas.py | 94 ++--- neutron/tests/unit/vmware/test_nsx_plugin.py | 10 +- .../tests/unit/vmware/vshield/fake_vcns.py | 29 +- .../unit/vmware/vshield/test_vpnaas_plugin.py | 372 ++++++++++++++++++ 8 files changed, 773 insertions(+), 58 deletions(-) create mode 100644 neutron/plugins/vmware/vshield/edge_ipsecvpn_driver.py create mode 100644 neutron/tests/unit/vmware/vshield/test_vpnaas_plugin.py diff --git a/neutron/plugins/vmware/plugins/service.py b/neutron/plugins/vmware/plugins/service.py index 00b92a707..6aa4e2f78 100644 --- a/neutron/plugins/vmware/plugins/service.py +++ b/neutron/plugins/vmware/plugins/service.py @@ -24,6 +24,7 @@ from neutron.db.firewall import firewall_db from neutron.db import l3_db from neutron.db.loadbalancer import loadbalancer_db from neutron.db import routedserviceinsertion_db as rsi_db +from neutron.db.vpn import vpn_db from neutron.extensions import firewall as fw_ext from neutron.extensions import l3 from neutron.extensions import routedserviceinsertion as rsi @@ -79,7 +80,8 @@ class NsxAdvancedPlugin(sr_db.ServiceRouter_mixin, base.NsxPluginV2, rsi_db.RoutedServiceInsertionDbMixin, firewall_db.Firewall_db_mixin, - loadbalancer_db.LoadBalancerPluginDb + loadbalancer_db.LoadBalancerPluginDb, + vpn_db.VPNPluginDb ): supported_extension_aliases = ( @@ -87,7 +89,8 @@ class NsxAdvancedPlugin(sr_db.ServiceRouter_mixin, "service-router", "routed-service-insertion", "fwaas", - "lbaas" + "lbaas", + "vpnaas" ]) def __init__(self): @@ -892,8 +895,8 @@ class NsxAdvancedPlugin(sr_db.ServiceRouter_mixin, } self._process_create_resource_router_id( context, res, firewall_db.Firewall) - #Since there is only one firewall per edge, - #here would be bulk configureation operation on firewall + # Since there is only one firewall per edge, + # here would be bulk configuration operation on firewall self._vcns_update_firewall(context, fw, router_id) self._firewall_set_status( context, fw['id'], service_constants.ACTIVE, fw) @@ -1570,6 +1573,153 @@ class NsxAdvancedPlugin(sr_db.ServiceRouter_mixin, context, loadbalancer_db.Pool, pool_id, service_constants.ACTIVE) + def _vcns_update_ipsec_config( + self, context, vpnservice_id, removed_ipsec_conn_id=None): + sites = [] + vpn_service = self._get_vpnservice(context, vpnservice_id) + edge_id = self._get_edge_id_by_vcns_edge_binding( + context, vpn_service.router_id) + if not vpn_service.router.gw_port: + msg = _("Failed to update ipsec vpn configuration on edge, since " + "the router: %s does not have a gateway yet!" + ) % vpn_service.router_id + LOG.error(msg) + raise exceptions.VcnsBadRequest(resource='router', msg=msg) + + external_ip = vpn_service.router.gw_port['fixed_ips'][0]['ip_address'] + subnet = self._make_subnet_dict(vpn_service.subnet) + for ipsec_site_conn in vpn_service.ipsec_site_connections: + if ipsec_site_conn.id != removed_ipsec_conn_id: + site = self._make_ipsec_site_connection_dict(ipsec_site_conn) + ikepolicy = self._make_ikepolicy_dict( + ipsec_site_conn.ikepolicy) + ipsecpolicy = self._make_ipsecpolicy_dict( + ipsec_site_conn.ipsecpolicy) + sites.append({'site': site, + 'ikepolicy': ikepolicy, + 'ipsecpolicy': ipsecpolicy, + 'subnet': subnet, + 'external_ip': external_ip}) + try: + self.vcns_driver.update_ipsec_config( + edge_id, sites, enabled=vpn_service.admin_state_up) + except exceptions.VcnsBadRequest: + LOG.exception(_("Bad or unsupported Input request!")) + raise + except exceptions.VcnsApiException: + msg = (_("Failed to update ipsec VPN configuration " + "with vpnservice: %(vpnservice_id)s on vShield Edge: " + "%(edge_id)s") % {'vpnservice_id': vpnservice_id, + 'edge_id': edge_id}) + LOG.exception(msg) + raise + + def create_vpnservice(self, context, vpnservice): + LOG.debug(_("create_vpnservice() called")) + router_id = vpnservice['vpnservice'].get('router_id') + if not self._is_advanced_service_router(context, router_id): + msg = _("router_id:%s is not an advanced router!") % router_id + LOG.warning(msg) + raise exceptions.VcnsBadRequest(resource='router', msg=msg) + + if self.get_vpnservices(context, filters={'router_id': [router_id]}): + msg = _("a vpnservice is already associated with the router: %s" + ) % router_id + LOG.warning(msg) + raise nsx_exc.ServiceOverQuota( + overs='vpnservice', err_msg=msg) + + service = super(NvpAdvancedPlugin, self).create_vpnservice( + context, vpnservice) + self._resource_set_status( + context, vpn_db.VPNService, + service['id'], service_constants.ACTIVE, service) + return service + + def update_vpnservice(self, context, vpnservice_id, vpnservice): + vpnservice['vpnservice']['status'] = service_constants.PENDING_UPDATE + service = super(NvpAdvancedPlugin, self).update_vpnservice( + context, vpnservice_id, vpnservice) + # Only admin_state_up attribute is configurable on Edge. + if vpnservice['vpnservice'].get('admin_state_up') is None: + self._resource_set_status( + context, vpn_db.VPNService, + service['id'], service_constants.ACTIVE, service) + return service + # Test whether there is one ipsec site connection attached to + # the vpnservice. If not, just return without updating ipsec + # config on edge side. + vpn_service_db = self._get_vpnservice(context, vpnservice_id) + if not vpn_service_db.ipsec_site_connections: + self._resource_set_status( + context, vpn_db.VPNService, + service['id'], service_constants.ACTIVE, service) + return service + try: + self._vcns_update_ipsec_config(context, service['id']) + except Exception: + with excutils.save_and_reraise_exception(): + self._resource_set_status( + context, vpn_db.VPNService, + service['id'], service_constants.ERROR, service) + self._resource_set_status( + context, vpn_db.VPNService, + service['id'], service_constants.ACTIVE, service) + return service + + def create_ipsec_site_connection(self, context, ipsec_site_connection): + ipsec_site_conn = super( + NvpAdvancedPlugin, self).create_ipsec_site_connection( + context, ipsec_site_connection) + try: + self._vcns_update_ipsec_config( + context, ipsec_site_conn['vpnservice_id']) + except Exception: + with excutils.save_and_reraise_exception(): + super(NvpAdvancedPlugin, self).delete_ipsec_site_connection( + context, ipsec_site_conn['id']) + self._resource_set_status( + context, vpn_db.IPsecSiteConnection, + ipsec_site_conn['id'], service_constants.ACTIVE, ipsec_site_conn) + return ipsec_site_conn + + def update_ipsec_site_connection(self, context, ipsec_site_connection_id, + ipsec_site_connection): + ipsec_site_connection['ipsec_site_connection']['status'] = ( + service_constants.PENDING_UPDATE) + ipsec_site_conn = super( + NvpAdvancedPlugin, self).update_ipsec_site_connection( + context, ipsec_site_connection_id, ipsec_site_connection) + try: + self._vcns_update_ipsec_config( + context, ipsec_site_conn['vpnservice_id']) + except Exception: + with excutils.save_and_reraise_exception(): + self._resource_set_status( + context, vpn_db.IPsecSiteConnection, ipsec_site_conn['id'], + service_constants.ERROR, ipsec_site_conn) + self._resource_set_status( + context, vpn_db.IPsecSiteConnection, + ipsec_site_conn['id'], service_constants.ACTIVE, ipsec_site_conn) + return ipsec_site_conn + + def delete_ipsec_site_connection(self, context, ipsec_site_conn_id): + self._resource_set_status( + context, vpn_db.IPsecSiteConnection, + ipsec_site_conn_id, service_constants.PENDING_DELETE) + vpnservice_id = self.get_ipsec_site_connection( + context, ipsec_site_conn_id)['vpnservice_id'] + try: + self._vcns_update_ipsec_config( + context, vpnservice_id, ipsec_site_conn_id) + except Exception: + with excutils.save_and_reraise_exception(): + self._resource_set_status( + context, vpn_db.IPsecSiteConnection, ipsec_site_conn_id, + service_constants.ERROR) + super(NvpAdvancedPlugin, self).delete_ipsec_site_connection( + context, ipsec_site_conn_id) + class VcnsCallbacks(object): """Edge callback implementation Callback functions for diff --git a/neutron/plugins/vmware/vshield/edge_ipsecvpn_driver.py b/neutron/plugins/vmware/vshield/edge_ipsecvpn_driver.py new file mode 100644 index 000000000..ae39187ea --- /dev/null +++ b/neutron/plugins/vmware/vshield/edge_ipsecvpn_driver.py @@ -0,0 +1,149 @@ +# Copyright 2014 VMware, Inc +# +# 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 neutron.openstack.common import log as logging +from neutron.plugins.vmware.vshield.common import ( + exceptions as vcns_exc) + +LOG = logging.getLogger(__name__) + +ENCRYPTION_ALGORITHM_MAP = { + '3des': '3des', + 'aes-128': 'aes', + 'aes-256': 'aes256' +} + +PFS_MAP = { + 'group2': 'dh2', + 'group5': 'dh5'} + +TRANSFORM_PROTOCOL_ALLOWED = ('esp',) + +ENCAPSULATION_MODE_ALLOWED = ('tunnel',) + + +class EdgeIPsecVpnDriver(): + + """Driver APIs for Edge IPsec VPN bulk configuration.""" + + def _check_ikepolicy_ipsecpolicy_allowed(self, ikepolicy, ipsecpolicy): + """Check whether ikepolicy and ipsecpolicy are allowed on vshield edge. + + Some IPsec VPN configurations and features are configured by default or + not supported on vshield edge. + + """ + # Check validation of IKEPolicy. + if ikepolicy['ike_version'] != 'v1': + msg = _("Unsupported ike_version: %s! Only 'v1' ike version is " + "supported on vshield Edge!" + ) % ikepolicy['ike_version'] + LOG.warning(msg) + raise vcns_exc.VcnsBadRequest(resource='ikepolicy', + msg=msg) + + # In VSE, Phase 1 and Phase 2 share the same encryption_algorithm + # and authentication algorithms setting. At present, just record the + # discrepancy error in log and take ipsecpolicy to do configuration. + if (ikepolicy['auth_algorithm'] != ipsecpolicy['auth_algorithm'] or + ikepolicy['encryption_algorithm'] != ipsecpolicy[ + 'encryption_algorithm'] or + ikepolicy['pfs'] != ipsecpolicy['pfs']): + msg = _("IKEPolicy and IPsecPolicy should have consistent " + "auth_algorithm, encryption_algorithm and pfs for VSE!") + LOG.warning(msg) + + # Check whether encryption_algorithm is allowed. + encryption_algorithm = ENCRYPTION_ALGORITHM_MAP.get( + ipsecpolicy.get('encryption_algorithm'), None) + if not encryption_algorithm: + msg = _("Unsupported encryption_algorithm: %s! '3des', " + "'aes-128' and 'aes-256' are supported on VSE right now." + ) % ipsecpolicy['encryption_algorithm'] + LOG.warning(msg) + raise vcns_exc.VcnsBadRequest(resource='ipsecpolicy', + msg=msg) + + # Check whether pfs is allowed. + if not PFS_MAP.get(ipsecpolicy['pfs']): + msg = _("Unsupported pfs: %s! 'group2' and 'group5' " + "are supported on VSE right now.") % ipsecpolicy['pfs'] + LOG.warning(msg) + raise vcns_exc.VcnsBadRequest(resource='ipsecpolicy', + msg=msg) + + # Check whether transform protocol is allowed. + if ipsecpolicy['transform_protocol'] not in TRANSFORM_PROTOCOL_ALLOWED: + msg = _("Unsupported transform protocol: %s! 'esp' is supported " + "by default on VSE right now." + ) % ipsecpolicy['transform_protocol'] + LOG.warning(msg) + raise vcns_exc.VcnsBadRequest(resource='ipsecpolicy', + msg=msg) + + # Check whether encapsulation mode is allowed. + if ipsecpolicy['encapsulation_mode'] not in ENCAPSULATION_MODE_ALLOWED: + msg = _("Unsupported encapsulation mode: %s! 'tunnel' is " + "supported by default on VSE right now." + ) % ipsecpolicy['encapsulation_mode'] + LOG.warning(msg) + raise vcns_exc.VcnsBadRequest(resource='ipsecpolicy', + msg=msg) + + def _convert_ipsec_site(self, site, enablePfs=True): + self._check_ikepolicy_ipsecpolicy_allowed( + site['ikepolicy'], site['ipsecpolicy']) + return { + 'enabled': site['site'].get('admin_state_up'), + 'enablePfs': enablePfs, + 'dhGroup': PFS_MAP.get(site['ipsecpolicy']['pfs']), + 'name': site['site'].get('name'), + 'description': site['site'].get('description'), + 'localId': site['external_ip'], + 'localIp': site['external_ip'], + 'peerId': site['site'].get('peer_id'), + 'peerIp': site['site'].get('peer_address'), + 'localSubnets': { + 'subnets': [site['subnet'].get('cidr')]}, + 'peerSubnets': { + 'subnets': site['site'].get('peer_cidrs')}, + 'authenticationMode': site['site'].get('auth_mode'), + 'psk': site['site'].get('psk'), + 'encryptionAlgorithm': ENCRYPTION_ALGORITHM_MAP.get( + site['ipsecpolicy'].get('encryption_algorithm'))} + + def update_ipsec_config(self, edge_id, sites, enabled=True): + ipsec_config = {'featureType': "ipsec_4.0", + 'enabled': enabled} + vse_sites = [self._convert_ipsec_site(site) for site in sites] + ipsec_config['sites'] = {'sites': vse_sites} + try: + self.vcns.update_ipsec_config(edge_id, ipsec_config) + except vcns_exc.VcnsApiException: + LOG.exception(_("Failed to update ipsec vpn configuration " + "with edge_id: %s"), edge_id) + raise + + def delete_ipsec_config(self, edge_id): + try: + self.vcns.delete_ipsec_config(edge_id) + except vcns_exc.ResourceNotFound: + LOG.warning(_("IPsec config not found on edge: %s"), edge_id) + except vcns_exc.VcnsApiException: + LOG.exception(_("Failed to delete ipsec vpn configuration " + "with edge_id: %s"), edge_id) + raise + + def get_ipsec_config(self, edge_id): + return self.vcns.get_ipsec_config(edge_id) diff --git a/neutron/plugins/vmware/vshield/vcns.py b/neutron/plugins/vmware/vshield/vcns.py index 3eac7cfe3..157a4405b 100644 --- a/neutron/plugins/vmware/vshield/vcns.py +++ b/neutron/plugins/vmware/vshield/vcns.py @@ -39,6 +39,9 @@ POOL_RESOURCE = "pools" MONITOR_RESOURCE = "monitors" APP_PROFILE_RESOURCE = "applicationprofiles" +# IPsec VPNaaS Constants +IPSEC_VPN_SERVICE = 'ipsec/config' + class Vcns(object): @@ -264,6 +267,18 @@ class Vcns(object): app_profileid) return self.do_request(HTTP_DELETE, uri) + def update_ipsec_config(self, edge_id, ipsec_config): + uri = self._build_uri_path(edge_id, IPSEC_VPN_SERVICE) + return self.do_request(HTTP_PUT, uri, ipsec_config) + + def delete_ipsec_config(self, edge_id): + uri = self._build_uri_path(edge_id, IPSEC_VPN_SERVICE) + return self.do_request(HTTP_DELETE, uri) + + def get_ipsec_config(self, edge_id): + uri = self._build_uri_path(edge_id, IPSEC_VPN_SERVICE) + return self.do_request(HTTP_GET, uri) + def _build_uri_path(self, edge_id, service, resource=None, diff --git a/neutron/plugins/vmware/vshield/vcns_driver.py b/neutron/plugins/vmware/vshield/vcns_driver.py index 8c428e869..e705b3329 100644 --- a/neutron/plugins/vmware/vshield/vcns_driver.py +++ b/neutron/plugins/vmware/vshield/vcns_driver.py @@ -22,6 +22,7 @@ from neutron.openstack.common import log as logging from neutron.plugins.vmware.common import config # noqa from neutron.plugins.vmware.vshield import edge_appliance_driver from neutron.plugins.vmware.vshield import edge_firewall_driver +from neutron.plugins.vmware.vshield import edge_ipsecvpn_driver from neutron.plugins.vmware.vshield import edge_loadbalancer_driver from neutron.plugins.vmware.vshield.tasks import tasks from neutron.plugins.vmware.vshield import vcns @@ -31,7 +32,8 @@ LOG = logging.getLogger(__name__) class VcnsDriver(edge_appliance_driver.EdgeApplianceDriver, edge_firewall_driver.EdgeFirewallDriver, - edge_loadbalancer_driver.EdgeLbDriver): + edge_loadbalancer_driver.EdgeLbDriver, + edge_ipsecvpn_driver.EdgeIPsecVpnDriver): def __init__(self, callbacks): super(VcnsDriver, self).__init__() diff --git a/neutron/tests/unit/db/vpn/test_db_vpnaas.py b/neutron/tests/unit/db/vpn/test_db_vpnaas.py index 96c1f08f5..0c7493445 100644 --- a/neutron/tests/unit/db/vpn/test_db_vpnaas.py +++ b/neutron/tests/unit/db/vpn/test_db_vpnaas.py @@ -32,6 +32,7 @@ from neutron.db.vpn import vpn_db from neutron import extensions from neutron.extensions import vpnaas from neutron import manager +from neutron.openstack.common import uuidutils from neutron.plugins.common import constants from neutron.scheduler import l3_agent_scheduler from neutron.services.vpn import plugin as vpn_plugin @@ -55,34 +56,13 @@ class TestVpnCorePlugin(test_l3_plugin.TestL3NatIntPlugin, self.router_scheduler = l3_agent_scheduler.ChanceScheduler() -class VPNPluginDbTestCase(test_l3_plugin.L3NatTestCaseMixin, - test_db_plugin.NeutronDbPluginV2TestCase): +class VPNTestMixin(object): resource_prefix_map = dict( (k.replace('_', '-'), constants.COMMON_PREFIXES[constants.VPN]) for k in vpnaas.RESOURCE_ATTRIBUTE_MAP ) - def setUp(self, core_plugin=None, vpnaas_plugin=DB_VPN_PLUGIN_KLASS): - service_plugins = {'vpnaas_plugin': vpnaas_plugin} - plugin_str = ('neutron.tests.unit.db.vpn.' - 'test_db_vpnaas.TestVpnCorePlugin') - - super(VPNPluginDbTestCase, self).setUp( - plugin_str, - service_plugins=service_plugins - ) - self._subnet_id = "0c798ed8-33ba-11e2-8b28-000c291c4d14" - self.core_plugin = TestVpnCorePlugin - self.plugin = vpn_plugin.VPNPlugin() - ext_mgr = PluginAwareExtensionManager( - extensions_path, - {constants.CORE: self.core_plugin, - constants.VPN: self.plugin} - ) - app = config.load_paste_app('extensions_test_app') - self.ext_api = ExtensionMiddleware(app, ext_mgr=ext_mgr) - def _create_ikepolicy(self, fmt, name='ikepolicy1', auth_algorithm='sha1', @@ -410,6 +390,53 @@ class VPNPluginDbTestCase(test_l3_plugin.L3NatTestCaseMixin, 'ipsec_site_connection']['id'] ) + def _check_ipsec_site_connection(self, ipsec_site_connection, keys, dpd): + self.assertEqual( + keys, + dict((k, v) for k, v + in ipsec_site_connection.items() + if k in keys)) + self.assertEqual( + dpd, + dict((k, v) for k, v + in ipsec_site_connection['dpd'].items() + if k in dpd)) + + def _set_active(self, model, resource_id): + service_plugin = manager.NeutronManager.get_service_plugins()[ + constants.VPN] + adminContext = context.get_admin_context() + with adminContext.session.begin(subtransactions=True): + resource_db = service_plugin._get_resource( + adminContext, + model, + resource_id) + resource_db.status = constants.ACTIVE + + +class VPNPluginDbTestCase(VPNTestMixin, + test_l3_plugin.L3NatTestCaseMixin, + test_db_plugin.NeutronDbPluginV2TestCase): + def setUp(self, core_plugin=None, vpnaas_plugin=DB_VPN_PLUGIN_KLASS): + service_plugins = {'vpnaas_plugin': vpnaas_plugin} + plugin_str = ('neutron.tests.unit.db.vpn.' + 'test_db_vpnaas.TestVpnCorePlugin') + + super(VPNPluginDbTestCase, self).setUp( + plugin_str, + service_plugins=service_plugins + ) + self._subnet_id = uuidutils.generate_uuid() + self.core_plugin = TestVpnCorePlugin + self.plugin = vpn_plugin.VPNPlugin() + ext_mgr = PluginAwareExtensionManager( + extensions_path, + {constants.CORE: self.core_plugin, + constants.VPN: self.plugin} + ) + app = config.load_paste_app('extensions_test_app') + self.ext_api = ExtensionMiddleware(app, ext_mgr=ext_mgr) + class TestVpnaas(VPNPluginDbTestCase): @@ -890,17 +917,6 @@ class TestVpnaas(VPNPluginDbTestCase): self._delete('routers', router['router']['id'], expected_code=webob.exc.HTTPConflict.code) - def _set_active(self, model, resource_id): - service_plugin = manager.NeutronManager.get_service_plugins()[ - constants.VPN] - adminContext = context.get_admin_context() - with adminContext.session.begin(subtransactions=True): - resource_db = service_plugin._get_resource( - adminContext, - model, - resource_id) - resource_db.status = constants.ACTIVE - def test_update_vpnservice(self): """Test case to update a vpnservice.""" name = 'new_vpnservice1' @@ -1198,18 +1214,6 @@ class TestVpnaas(VPNPluginDbTestCase): setup_overrides=ipv6_setup_params, expected_status_int=400) - def _check_ipsec_site_connection(self, ipsec_site_connection, keys, dpd): - self.assertEqual( - keys, - dict((k, v) for k, v - in ipsec_site_connection.items() - if k in keys)) - self.assertEqual( - dpd, - dict((k, v) for k, v - in ipsec_site_connection['dpd'].items() - if k in dpd)) - def test_delete_ipsec_site_connection(self): """Test case to delete a ipsec_site_connection.""" with self.ipsec_site_connection( diff --git a/neutron/tests/unit/vmware/test_nsx_plugin.py b/neutron/tests/unit/vmware/test_nsx_plugin.py index e53eaac1b..1464b325d 100644 --- a/neutron/tests/unit/vmware/test_nsx_plugin.py +++ b/neutron/tests/unit/vmware/test_nsx_plugin.py @@ -455,11 +455,6 @@ class L3NatTest(test_l3_plugin.L3BaseForIntTests, NsxPluginV2TestCase): plugin_instance.__class__.__name__) self._plugin_class = plugin_instance.__class__ - -class TestL3NatTestCase(L3NatTest, - test_l3_plugin.L3NatDBIntTestCase, - NsxPluginV2TestCase): - def _create_l3_ext_network(self, vlan_id=None): name = 'l3_ext_net' net_type = NetworkTypes.L3_EXT @@ -474,6 +469,11 @@ class TestL3NatTestCase(L3NatTest, pnet.PHYSICAL_NETWORK, pnet.SEGMENTATION_ID)) + +class TestL3NatTestCase(L3NatTest, + test_l3_plugin.L3NatDBIntTestCase, + NsxPluginV2TestCase): + def _test_create_l3_ext_network(self, vlan_id=None): name = 'l3_ext_net' net_type = NetworkTypes.L3_EXT diff --git a/neutron/tests/unit/vmware/vshield/fake_vcns.py b/neutron/tests/unit/vmware/vshield/fake_vcns.py index 37d26c7ac..b346aab9d 100644 --- a/neutron/tests/unit/vmware/vshield/fake_vcns.py +++ b/neutron/tests/unit/vmware/vshield/fake_vcns.py @@ -44,6 +44,11 @@ class FakeVcns(object): "firewallRules": [] } } + self.fake_ipsecvpn_dict = {} + self.temp_ipsecvpn = { + 'featureType': "ipsec_4.0", + 'enabled': True, + 'sites': {'sites': []}} self._fake_virtualservers_dict = {} self._fake_pools_dict = {} self._fake_monitors_dict = {} @@ -364,9 +369,6 @@ class FakeVcns(object): break return self.return_helper(header, response) - # - #Fake Edge LBAAS call - # def create_vip(self, edge_id, vip_new): if not self._fake_virtualservers_dict.get(edge_id): self._fake_virtualservers_dict[edge_id] = {} @@ -525,6 +527,27 @@ class FakeVcns(object): response['config'] = self._fake_loadbalancer_config[edge_id] return self.return_helper(header, response) + def update_ipsec_config(self, edge_id, ipsec_config): + self.fake_ipsecvpn_dict[edge_id] = ipsec_config + header = {'status': 204} + response = "" + return self.return_helper(header, response) + + def delete_ipsec_config(self, edge_id): + header = {'status': 404} + if edge_id in self.fake_ipsecvpn_dict: + header = {'status': 204} + del self.fake_ipsecvpn_dict[edge_id] + response = "" + return self.return_helper(header, response) + + def get_ipsec_config(self, edge_id): + if edge_id not in self.fake_ipsecvpn_dict: + self.fake_ipsecvpn_dict[edge_id] = self.temp_ipsecvpn + header = {'status': 204} + response = self.fake_ipsecvpn_dict[edge_id] + return self.return_helper(header, response) + def enable_service_loadbalancer(self, edge_id, config): header = {'status': 204} response = "" diff --git a/neutron/tests/unit/vmware/vshield/test_vpnaas_plugin.py b/neutron/tests/unit/vmware/vshield/test_vpnaas_plugin.py new file mode 100644 index 000000000..a91ef71b5 --- /dev/null +++ b/neutron/tests/unit/vmware/vshield/test_vpnaas_plugin.py @@ -0,0 +1,372 @@ +# Copyright 2014 VMware, Inc +# +# 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. + +import contextlib +import copy + +import webob.exc + +from neutron.api.v2 import attributes +from neutron.db.vpn import vpn_db +from neutron.extensions import vpnaas +from neutron import manager +from neutron.openstack.common import uuidutils +from neutron.tests.unit.db.vpn import test_db_vpnaas +from neutron.tests.unit.vmware.vshield import test_edge_router + +_uuid = uuidutils.generate_uuid + + +class VPNTestExtensionManager( + test_edge_router.ServiceRouterTestExtensionManager): + + def get_resources(self): + # If l3 resources have been loaded and updated by main API + # router, update the map in the l3 extension so it will load + # the same attributes as the API router + resources = super(VPNTestExtensionManager, self).get_resources() + vpn_attr_map = copy.deepcopy(vpnaas.RESOURCE_ATTRIBUTE_MAP) + for res in vpnaas.RESOURCE_ATTRIBUTE_MAP.keys(): + attr_info = attributes.RESOURCE_ATTRIBUTE_MAP.get(res) + if attr_info: + vpnaas.RESOURCE_ATTRIBUTE_MAP[res] = attr_info + vpn_resources = vpnaas.Vpnaas.get_resources() + # restore the original resources once the controllers are created + vpnaas.RESOURCE_ATTRIBUTE_MAP = vpn_attr_map + resources.extend(vpn_resources) + return resources + + +class TestVpnPlugin(test_db_vpnaas.VPNTestMixin, + test_edge_router.ServiceRouterTest): + + def vcns_vpn_patch(self): + instance = self.vcns_instance + instance.return_value.update_ipsec_config.side_effect = ( + self.fc2.update_ipsec_config) + instance.return_value.get_ipsec_config.side_effect = ( + self.fc2.get_ipsec_config) + instance.return_value.delete_ipsec_config.side_effect = ( + self.fc2.delete_ipsec_config) + + def setUp(self): + # Save the global RESOURCE_ATTRIBUTE_MAP + self.saved_attr_map = {} + for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.items(): + self.saved_attr_map[resource] = attrs.copy() + + super(TestVpnPlugin, self).setUp(ext_mgr=VPNTestExtensionManager()) + self.vcns_vpn_patch() + self.plugin = manager.NeutronManager.get_plugin() + self.router_id = None + + def tearDown(self): + super(TestVpnPlugin, self).tearDown() + # Restore the global RESOURCE_ATTRIBUTE_MAP + attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map + self.ext_api = None + self.plugin = None + + @contextlib.contextmanager + def router(self, vlan_id=None): + with self._create_l3_ext_network(vlan_id) as net: + with self.subnet(cidr='100.0.0.0/24', network=net) as s: + data = {'router': {'tenant_id': self._tenant_id}} + data['router']['service_router'] = True + router_req = self.new_create_request('routers', data, self.fmt) + try: + res = router_req.get_response(self.ext_api) + router = self.deserialize(self.fmt, res) + self._add_external_gateway_to_router( + router['router']['id'], + s['subnet']['network_id']) + router = self._show('routers', router['router']['id']) + yield router + finally: + self._delete('routers', router['router']['id']) + + def test_create_vpnservice(self, **extras): + """Test case to create a vpnservice.""" + description = 'my-vpn-service' + expected = {'name': 'vpnservice1', + 'description': 'my-vpn-service', + 'admin_state_up': True, + 'status': 'ACTIVE', + 'tenant_id': self._tenant_id, } + + expected.update(extras) + with self.subnet(cidr='10.2.0.0/24') as subnet: + with self.router() as router: + expected['router_id'] = router['router']['id'] + expected['subnet_id'] = subnet['subnet']['id'] + name = expected['name'] + with self.vpnservice(name=name, + subnet=subnet, + router=router, + description=description, + **extras) as vpnservice: + self.assertEqual(dict((k, v) for k, v in + vpnservice['vpnservice'].items() + if k in expected), + expected) + + def test_create_vpnservices_with_same_router(self, **extras): + """Test case to create two vpnservices with same router.""" + with self.subnet(cidr='10.2.0.0/24') as subnet: + with self.router() as router: + with self.vpnservice(name='vpnservice1', + subnet=subnet, + router=router): + res = self._create_vpnservice( + 'json', 'vpnservice2', True, + router_id=(router['router']['id']), + subnet_id=(subnet['subnet']['id'])) + self.assertEqual( + res.status_int, webob.exc.HTTPConflict.code) + + def test_update_vpnservice(self): + """Test case to update a vpnservice.""" + name = 'new_vpnservice1' + expected = [('name', name)] + with contextlib.nested( + self.subnet(cidr='10.2.0.0/24'), + self.router()) as (subnet, router): + with self.vpnservice(name=name, + subnet=subnet, + router=router) as vpnservice: + expected.append(('subnet_id', + vpnservice['vpnservice']['subnet_id'])) + expected.append(('router_id', + vpnservice['vpnservice']['router_id'])) + data = {'vpnservice': {'name': name, + 'admin_state_up': False}} + expected.append(('admin_state_up', False)) + self._set_active(vpn_db.VPNService, + vpnservice['vpnservice']['id']) + req = self.new_update_request( + 'vpnservices', + data, + vpnservice['vpnservice']['id']) + res = self.deserialize(self.fmt, + req.get_response(self.ext_api)) + for k, v in expected: + self.assertEqual(res['vpnservice'][k], v) + + def _test_create_ipsec_site_connection(self, key_overrides=None, + ike_key_overrides=None, + ipsec_key_overrides=None, + setup_overrides=None, + expected_status_int=200): + """Create ipsec_site_connection and check results.""" + params = {'ikename': 'ikepolicy1', + 'ipsecname': 'ipsecpolicy1', + 'vpnsname': 'vpnservice1', + 'subnet_cidr': '10.2.0.0/24', + 'subnet_version': 4} + if setup_overrides: + params.update(setup_overrides) + expected = {'name': 'connection1', + 'description': 'my-ipsec-connection', + 'peer_address': '192.168.1.10', + 'peer_id': '192.168.1.10', + 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], + 'initiator': 'bi-directional', + 'mtu': 1500, + 'tenant_id': self._tenant_id, + 'psk': 'abcd', + 'status': 'ACTIVE', + 'admin_state_up': True} + if key_overrides: + expected.update(key_overrides) + + ike_expected = {'name': params['ikename'], + 'auth_algorithm': 'sha1', + 'encryption_algorithm': 'aes-128', + 'ike_version': 'v1', + 'pfs': 'group5'} + if ike_key_overrides: + ike_expected.update(ike_key_overrides) + + ipsec_expected = {'name': params['ipsecname'], + 'auth_algorithm': 'sha1', + 'encryption_algorithm': 'aes-128', + 'pfs': 'group5'} + if ipsec_key_overrides: + ipsec_expected.update(ipsec_key_overrides) + + dpd = {'action': 'hold', + 'interval': 40, + 'timeout': 120} + with contextlib.nested( + self.ikepolicy(self.fmt, ike_expected['name'], + ike_expected['auth_algorithm'], + ike_expected['encryption_algorithm'], + ike_version=ike_expected['ike_version'], + pfs=ike_expected['pfs']), + self.ipsecpolicy(self.fmt, ipsec_expected['name'], + ipsec_expected['auth_algorithm'], + ipsec_expected['encryption_algorithm'], + pfs=ipsec_expected['pfs']), + self.subnet(cidr=params['subnet_cidr'], + ip_version=params['subnet_version']), + self.router()) as ( + ikepolicy, ipsecpolicy, subnet, router): + with self.vpnservice(name=params['vpnsname'], subnet=subnet, + router=router) as vpnservice1: + expected['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] + expected['ipsecpolicy_id'] = ( + ipsecpolicy['ipsecpolicy']['id'] + ) + expected['vpnservice_id'] = ( + vpnservice1['vpnservice']['id'] + ) + try: + with self.ipsec_site_connection( + self.fmt, + expected['name'], + expected['peer_address'], + expected['peer_id'], + expected['peer_cidrs'], + expected['mtu'], + expected['psk'], + expected['initiator'], + dpd['action'], + dpd['interval'], + dpd['timeout'], + vpnservice1, + ikepolicy, + ipsecpolicy, + expected['admin_state_up'], + description=expected['description'] + ) as ipsec_site_connection: + if expected_status_int != 200: + self.fail("Expected failure on create") + self._check_ipsec_site_connection( + ipsec_site_connection['ipsec_site_connection'], + expected, + dpd) + except webob.exc.HTTPClientError as ce: + self.assertEqual(ce.code, expected_status_int) + + def test_create_ipsec_site_connection(self, **extras): + """Test case to create an ipsec_site_connection.""" + self._test_create_ipsec_site_connection(key_overrides=extras) + + def test_create_ipsec_site_connection_invalid_ikepolicy(self): + self._test_create_ipsec_site_connection( + ike_key_overrides={'ike_version': 'v2'}, + expected_status_int=400) + + def test_create_ipsec_site_connection_invalid_ipsecpolicy(self): + self._test_create_ipsec_site_connection( + ipsec_key_overrides={'encryption_algorithm': 'aes-192'}, + expected_status_int=400) + self._test_create_ipsec_site_connection( + ipsec_key_overrides={'pfs': 'group14'}, + expected_status_int=400) + + def _test_update_ipsec_site_connection(self, + update={'name': 'new name'}, + overrides=None, + expected_status_int=200): + """Creates and then updates ipsec_site_connection.""" + expected = {'name': 'new_ipsec_site_connection', + 'ikename': 'ikepolicy1', + 'ipsecname': 'ipsecpolicy1', + 'vpnsname': 'vpnservice1', + 'description': 'my-ipsec-connection', + 'peer_address': '192.168.1.10', + 'peer_id': '192.168.1.10', + 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], + 'initiator': 'bi-directional', + 'mtu': 1500, + 'tenant_id': self._tenant_id, + 'psk': 'abcd', + 'status': 'ACTIVE', + 'admin_state_up': True, + 'action': 'hold', + 'interval': 40, + 'timeout': 120, + 'subnet_cidr': '10.2.0.0/24', + 'subnet_version': 4, + 'make_active': True} + if overrides: + expected.update(overrides) + + with contextlib.nested( + self.ikepolicy(name=expected['ikename']), + self.ipsecpolicy(name=expected['ipsecname']), + self.subnet(cidr=expected['subnet_cidr'], + ip_version=expected['subnet_version']), + self.router() + ) as (ikepolicy, ipsecpolicy, subnet, router): + with self.vpnservice(name=expected['vpnsname'], subnet=subnet, + router=router) as vpnservice1: + expected['vpnservice_id'] = vpnservice1['vpnservice']['id'] + expected['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] + expected['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id'] + with self.ipsec_site_connection( + self.fmt, + expected['name'], + expected['peer_address'], + expected['peer_id'], + expected['peer_cidrs'], + expected['mtu'], + expected['psk'], + expected['initiator'], + expected['action'], + expected['interval'], + expected['timeout'], + vpnservice1, + ikepolicy, + ipsecpolicy, + expected['admin_state_up'], + description=expected['description'] + ) as ipsec_site_connection: + data = {'ipsec_site_connection': update} + if expected.get('make_active'): + self._set_active( + vpn_db.IPsecSiteConnection, + (ipsec_site_connection['ipsec_site_connection'] + ['id'])) + req = self.new_update_request( + 'ipsec-site-connections', + data, + ipsec_site_connection['ipsec_site_connection']['id']) + res = req.get_response(self.ext_api) + self.assertEqual(expected_status_int, res.status_int) + if expected_status_int == 200: + res_dict = self.deserialize(self.fmt, res) + for k, v in update.items(): + self.assertEqual( + res_dict['ipsec_site_connection'][k], v) + + def test_update_ipsec_site_connection(self): + """Test case for valid updates to IPSec site connection.""" + dpd = {'action': 'hold', + 'interval': 40, + 'timeout': 120} + self._test_update_ipsec_site_connection(update={'dpd': dpd}) + self._test_update_ipsec_site_connection(update={'mtu': 2000}) + + def test_delete_ipsec_site_connection(self): + """Test case to delete a ipsec_site_connection.""" + with self.ipsec_site_connection( + no_delete=True) as ipsec_site_connection: + req = self.new_delete_request( + 'ipsec-site-connections', + ipsec_site_connection['ipsec_site_connection']['id'] + ) + res = req.get_response(self.ext_api) + self.assertEqual(res.status_int, 204) -- 2.45.2