]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Support advanced NVP IPsec VPN Service
authorberlin <linb@vmware.com>
Tue, 5 Nov 2013 02:31:00 +0000 (10:31 +0800)
committerberlin <linb@vmware.com>
Mon, 3 Mar 2014 05:18:36 +0000 (13:18 +0800)
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
neutron/plugins/vmware/vshield/edge_ipsecvpn_driver.py [new file with mode: 0644]
neutron/plugins/vmware/vshield/vcns.py
neutron/plugins/vmware/vshield/vcns_driver.py
neutron/tests/unit/db/vpn/test_db_vpnaas.py
neutron/tests/unit/vmware/test_nsx_plugin.py
neutron/tests/unit/vmware/vshield/fake_vcns.py
neutron/tests/unit/vmware/vshield/test_vpnaas_plugin.py [new file with mode: 0644]

index 00b92a707ea3363eada1f9b2b90a3352954b5590..6aa4e2f7849dd4f412c1c8a29b45227f570a1a63 100644 (file)
@@ -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 (file)
index 0000000..ae39187
--- /dev/null
@@ -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)
index 3eac7cfe32ffd93e083fbffb9c2175cc078a6bc0..157a4405b610f4c8217a518f447c9e575cef1cd9 100644 (file)
@@ -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,
index 8c428e8695d0ccf5606655c20bfc9707c25ce9df..e705b33297e8a4c1761c16176dcd019a99837a70 100644 (file)
@@ -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__()
index 96c1f08f521d873e8e2d280b7f781b0972df5392..0c74934453a5b334a7ce26263a5794106f3203e3 100644 (file)
@@ -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(
index e53eaac1b2e9e050af2e89d5787f25a6d02ea758..1464b325d5399ba51131c1483d52f2d74d3a39ef 100644 (file)
@@ -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
index 37d26c7acbc702f4aed0d711b15da93d4a8e2710..b346aab9d33136847871ca3e217c0aa5ee5ffc87 100644 (file)
@@ -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 (file)
index 0000000..a91ef71
--- /dev/null
@@ -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)