diff --git a/neutron/db/vpn/vpn_db.py b/neutron/db/vpn/vpn_db.py index 5f4e6511755..1af84b5f699 100644 --- a/neutron/db/vpn/vpn_db.py +++ b/neutron/db/vpn/vpn_db.py @@ -27,6 +27,7 @@ from neutron.db import l3_agentschedulers_db as l3_agent_db from neutron.db import l3_db from neutron.db import model_base from neutron.db import models_v2 +from neutron.db.vpn import vpn_validator from neutron.extensions import vpnaas from neutron import manager from neutron.openstack.common import excutils @@ -37,8 +38,6 @@ from neutron.plugins.common import utils LOG = logging.getLogger(__name__) -IP_MIN_MTU = {4: 68, 6: 1280} - class IPsecPeerCidr(model_base.BASEV2): """Internal representation of a IPsec Peer Cidrs.""" @@ -167,6 +166,17 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin): """Do the initialization for the vpn service plugin here.""" qdbapi.register_models() + def _get_validator(self): + """Obtain validator to use for attribute validation. + + Subclasses may override this with a different valdiator, as needed. + Note: some UTs will directly create a VPNPluginDb object and then + call its methods, instead of creating a VPNDriverPlugin, which + will have a service driver associated that will provide a + validator object. As a result, we use the reference validator here. + """ + return vpn_validator.VpnReferenceValidator() + def update_status(self, context, model, v_id, status): with context.session.begin(subtransactions=True): v_db = self._get_resource(context, model, v_id) @@ -225,14 +235,17 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin): return self._fields(res, fields) + def _get_subnet_ip_version(self, context, vpnservice_id): + vpn_service_db = self._get_vpnservice(context, vpnservice_id) + subnet = vpn_service_db.subnet['cidr'] + ip_version = netaddr.IPNetwork(subnet).version + return ip_version + def create_ipsec_site_connection(self, context, ipsec_site_connection): ipsec_sitecon = ipsec_site_connection['ipsec_site_connection'] - dpd = ipsec_sitecon['dpd'] - ipsec_sitecon['dpd_action'] = dpd.get('action', 'hold') - ipsec_sitecon['dpd_interval'] = dpd.get('interval', 30) - ipsec_sitecon['dpd_timeout'] = dpd.get('timeout', 120) + validator = self._get_validator() + validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon) tenant_id = self._get_tenant_id_for_create(context, ipsec_sitecon) - self._check_dpd(ipsec_sitecon) with context.session.begin(subtransactions=True): #Check permissions self._get_resource(context, @@ -244,9 +257,11 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin): self._get_resource(context, IPsecPolicy, ipsec_sitecon['ipsecpolicy_id']) - self._check_mtu(context, - ipsec_sitecon['mtu'], - ipsec_sitecon['vpnservice_id']) + vpnservice_id = ipsec_sitecon['vpnservice_id'] + ip_version = self._get_subnet_ip_version(context, vpnservice_id) + validator.validate_ipsec_site_connection(context, + ipsec_sitecon, + ip_version) ipsec_site_conn_db = IPsecSiteConnection( id=uuidutils.generate_uuid(), tenant_id=tenant_id, @@ -264,7 +279,7 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin): dpd_timeout=ipsec_sitecon['dpd_timeout'], admin_state_up=ipsec_sitecon['admin_state_up'], status=constants.PENDING_CREATE, - vpnservice_id=ipsec_sitecon['vpnservice_id'], + vpnservice_id=vpnservice_id, ikepolicy_id=ipsec_sitecon['ikepolicy_id'], ipsecpolicy_id=ipsec_sitecon['ipsecpolicy_id'] ) @@ -277,52 +292,34 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin): context.session.add(peer_cidr_db) return self._make_ipsec_site_connection_dict(ipsec_site_conn_db) - def _check_dpd(self, ipsec_sitecon): - if ipsec_sitecon['dpd_timeout'] <= ipsec_sitecon['dpd_interval']: - raise vpnaas.IPsecSiteConnectionDpdIntervalValueError( - attr='dpd_timeout') - - def _check_mtu(self, context, mtu, vpnservice_id): - vpn_service_db = self._get_vpnservice(context, vpnservice_id) - subnet = vpn_service_db.subnet['cidr'] - version = netaddr.IPNetwork(subnet).version - if mtu < IP_MIN_MTU[version]: - raise vpnaas.IPsecSiteConnectionMtuError(mtu=mtu, version=version) - def update_ipsec_site_connection( self, context, ipsec_site_conn_id, ipsec_site_connection): - conn = ipsec_site_connection['ipsec_site_connection'] + ipsec_sitecon = ipsec_site_connection['ipsec_site_connection'] changed_peer_cidrs = False + validator = self._get_validator() with context.session.begin(subtransactions=True): ipsec_site_conn_db = self._get_resource( context, IPsecSiteConnection, ipsec_site_conn_id) - dpd = conn.get('dpd', {}) - if dpd.get('action'): - conn['dpd_action'] = dpd.get('action') - if dpd.get('interval') or dpd.get('timeout'): - conn['dpd_interval'] = dpd.get( - 'interval', ipsec_site_conn_db.dpd_interval) - conn['dpd_timeout'] = dpd.get( - 'timeout', ipsec_site_conn_db.dpd_timeout) - self._check_dpd(conn) - - if 'mtu' in conn: - self._check_mtu(context, - conn['mtu'], - ipsec_site_conn_db.vpnservice_id) - + vpnservice_id = ipsec_site_conn_db['vpnservice_id'] + ip_version = self._get_subnet_ip_version(context, vpnservice_id) + validator.assign_sensible_ipsec_sitecon_defaults( + ipsec_sitecon, ipsec_site_conn_db) + validator.validate_ipsec_site_connection( + context, + ipsec_sitecon, + ip_version) self.assert_update_allowed(ipsec_site_conn_db) - if "peer_cidrs" in conn: + if "peer_cidrs" in ipsec_sitecon: changed_peer_cidrs = True old_peer_cidr_list = ipsec_site_conn_db['peer_cidrs'] old_peer_cidr_dict = dict( (peer_cidr['cidr'], peer_cidr) for peer_cidr in old_peer_cidr_list) - new_peer_cidr_set = set(conn["peer_cidrs"]) + new_peer_cidr_set = set(ipsec_sitecon["peer_cidrs"]) old_peer_cidr_set = set(old_peer_cidr_dict) new_peer_cidrs = list(new_peer_cidr_set) @@ -333,9 +330,9 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin): cidr=peer_cidr, ipsec_site_connection_id=ipsec_site_conn_id) context.session.add(pcidr) - del conn["peer_cidrs"] - if conn: - ipsec_site_conn_db.update(conn) + del ipsec_sitecon["peer_cidrs"] + if ipsec_sitecon: + ipsec_site_conn_db.update(ipsec_sitecon) result = self._make_ipsec_site_connection_dict(ipsec_site_conn_db) if changed_peer_cidrs: result['peer_cidrs'] = new_peer_cidrs @@ -556,31 +553,12 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin): 'status': vpnservice['status']} return self._fields(res, fields) - def _check_router(self, context, router_id): - l3_plugin = manager.NeutronManager.get_service_plugins().get( - constants.L3_ROUTER_NAT) - router = l3_plugin.get_router(context, router_id) - if not router.get(l3_db.EXTERNAL_GW_INFO): - raise vpnaas.RouterIsNotExternal(router_id=router_id) - - def _check_subnet_id(self, context, router_id, subnet_id): - core_plugin = manager.NeutronManager.get_plugin() - ports = core_plugin.get_ports( - context, - filters={ - 'fixed_ips': {'subnet_id': [subnet_id]}, - 'device_id': [router_id]}) - if not ports: - raise vpnaas.SubnetIsNotConnectedToRouter( - subnet_id=subnet_id, - router_id=router_id) - def create_vpnservice(self, context, vpnservice): vpns = vpnservice['vpnservice'] tenant_id = self._get_tenant_id_for_create(context, vpns) - self._check_router(context, vpns['router_id']) - self._check_subnet_id(context, vpns['router_id'], vpns['subnet_id']) + validator = self._get_validator() with context.session.begin(subtransactions=True): + validator.validate_vpnservice(context, vpns) vpnservice_db = VPNService(id=uuidutils.generate_uuid(), tenant_id=tenant_id, name=vpns['name'], diff --git a/neutron/db/vpn/vpn_validator.py b/neutron/db/vpn/vpn_validator.py new file mode 100644 index 00000000000..5e228615678 --- /dev/null +++ b/neutron/db/vpn/vpn_validator.py @@ -0,0 +1,104 @@ +# Copyright 2014 Cisco Systems, 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. +# +# @author: Paul Michali, Cisco Systems, Inc. + +from neutron.db import l3_db +from neutron.extensions import vpnaas +from neutron import manager +from neutron.plugins.common import constants + + +class VpnReferenceValidator(object): + + """Baseline validation routines for VPN resources.""" + + IP_MIN_MTU = {4: 68, 6: 1280} + + @property + def l3_plugin(self): + try: + return self._l3_plugin + except AttributeError: + self._l3_plugin = manager.NeutronManager.get_service_plugins().get( + constants.L3_ROUTER_NAT) + return self._l3_plugin + + @property + def core_plugin(self): + try: + return self._core_plugin + except AttributeError: + self._core_plugin = manager.NeutronManager.get_plugin() + return self._core_plugin + + def _check_dpd(self, ipsec_sitecon): + """Ensure that DPD timeout is greater than DPD interval.""" + if ipsec_sitecon['dpd_timeout'] <= ipsec_sitecon['dpd_interval']: + raise vpnaas.IPsecSiteConnectionDpdIntervalValueError( + attr='dpd_timeout') + + def _check_mtu(self, context, mtu, ip_version): + if mtu < VpnReferenceValidator.IP_MIN_MTU[ip_version]: + raise vpnaas.IPsecSiteConnectionMtuError(mtu=mtu, + version=ip_version) + + def assign_sensible_ipsec_sitecon_defaults(self, ipsec_sitecon, + prev_conn=None): + """Provide defaults for optional items, if missing. + + Flatten the nested DPD information, and set default values for + any missing information. For connection updates, the previous + values will be used as defaults for any missing items. + """ + if not prev_conn: + prev_conn = {'dpd_action': 'hold', + 'dpd_interval': 30, + 'dpd_timeout': 120} + dpd = ipsec_sitecon.get('dpd', {}) + ipsec_sitecon['dpd_action'] = dpd.get('action', + prev_conn['dpd_action']) + ipsec_sitecon['dpd_interval'] = dpd.get('interval', + prev_conn['dpd_interval']) + ipsec_sitecon['dpd_timeout'] = dpd.get('timeout', + prev_conn['dpd_timeout']) + + def validate_ipsec_site_connection(self, context, ipsec_sitecon, + ip_version): + """Reference implementation of validation for IPSec connection.""" + self._check_dpd(ipsec_sitecon) + mtu = ipsec_sitecon.get('mtu') + if mtu: + self._check_mtu(context, mtu, ip_version) + + def _check_router(self, context, router_id): + router = self.l3_plugin.get_router(context, router_id) + if not router.get(l3_db.EXTERNAL_GW_INFO): + raise vpnaas.RouterIsNotExternal(router_id=router_id) + + def _check_subnet_id(self, context, router_id, subnet_id): + ports = self.core_plugin.get_ports( + context, + filters={ + 'fixed_ips': {'subnet_id': [subnet_id]}, + 'device_id': [router_id]}) + if not ports: + raise vpnaas.SubnetIsNotConnectedToRouter( + subnet_id=subnet_id, + router_id=router_id) + + def validate_vpnservice(self, context, vpnservice): + self._check_router(context, vpnservice['router_id']) + self._check_subnet_id(context, vpnservice['router_id'], + vpnservice['subnet_id']) diff --git a/neutron/services/vpn/plugin.py b/neutron/services/vpn/plugin.py index 74218e2ab6b..e82ef525782 100644 --- a/neutron/services/vpn/plugin.py +++ b/neutron/services/vpn/plugin.py @@ -55,6 +55,9 @@ class VPNDriverPlugin(VPNPlugin, vpn_db.VPNPluginRpcDbMixin): vpnservice = None return self._get_driver_for_vpnservice(vpnservice) + def _get_validator(self): + return self.ipsec_driver.validator + def create_ipsec_site_connection(self, context, ipsec_site_connection): ipsec_site_connection = super( VPNDriverPlugin, self).create_ipsec_site_connection( diff --git a/neutron/services/vpn/service_drivers/__init__.py b/neutron/services/vpn/service_drivers/__init__.py index 17db33384f9..00ee0e7f51f 100644 --- a/neutron/services/vpn/service_drivers/__init__.py +++ b/neutron/services/vpn/service_drivers/__init__.py @@ -18,6 +18,7 @@ import abc import six from neutron.common import rpc as n_rpc +from neutron.db.vpn import vpn_validator from neutron import manager from neutron.openstack.common import log as logging from neutron.plugins.common import constants @@ -28,8 +29,11 @@ LOG = logging.getLogger(__name__) @six.add_metaclass(abc.ABCMeta) class VpnDriver(object): - def __init__(self, service_plugin): + def __init__(self, service_plugin, validator=None): self.service_plugin = service_plugin + if validator is None: + validator = vpn_validator.VpnReferenceValidator() + self.validator = validator @property def service_type(self): diff --git a/neutron/tests/unit/db/vpn/test_db_vpnaas.py b/neutron/tests/unit/db/vpn/test_db_vpnaas.py index 33aea24e95b..10c82675f3f 100644 --- a/neutron/tests/unit/db/vpn/test_db_vpnaas.py +++ b/neutron/tests/unit/db/vpn/test_db_vpnaas.py @@ -869,63 +869,6 @@ class TestVpnaas(VPNPluginDbTestCase): if k in expected), expected) - def test_create_vpnservice_with_invalid_router(self): - """Test case to create a vpnservice with other tenant's router""" - with self.network( - set_context=True, - tenant_id='tenant_a') as network: - with self.subnet(network=network, - cidr='10.2.0.0/24') as subnet: - with self.router( - set_context=True, tenant_id='tenant_a') as router: - router_id = router['router']['id'] - subnet_id = subnet['subnet']['id'] - self._create_vpnservice( - self.fmt, 'fake', - True, router_id, subnet_id, - expected_res_status=webob.exc.HTTPNotFound.code, - set_context=True, tenant_id='tenant_b') - - def test_create_vpnservice_with_router_no_external_gateway(self): - """Test case to create a vpnservice with inner router""" - error_code = 0 - with self.subnet(cidr='10.2.0.0/24') as subnet: - with self.router() as router: - router_id = router['router']['id'] - try: - with self.vpnservice(subnet=subnet, - router=router, - external_router=False): - pass - except webob.exc.HTTPClientError as e: - error_code, error_detail = ( - e.status_code, e.detail['NeutronError']['message']) - self.assertEqual(400, error_code) - msg = str(vpnaas.RouterIsNotExternal(router_id=router_id)) - self.assertEqual(msg, error_detail) - - def test_create_vpnservice_with_nonconnected_subnet(self): - """Test case to create a vpnservice with nonconnected subnet.""" - with self.network() as network: - with self.subnet(network=network, - cidr='10.2.0.0/24') as subnet: - with self.router() as router: - router_id = router['router']['id'] - subnet_id = subnet['subnet']['id'] - self._create_vpnservice( - self.fmt, 'fake', - True, router_id, subnet_id, - expected_res_status=webob.exc.HTTPBadRequest.code) - - def test_delete_router_in_use_by_vpnservice(self): - """Test delete router in use by vpn service.""" - with self.subnet(cidr='10.2.0.0/24') as subnet: - with self.router() as router: - with self.vpnservice(subnet=subnet, - router=router): - self._delete('routers', router['router']['id'], - expected_code=webob.exc.HTTPConflict.code) - def test_update_vpnservice(self): """Test case to update a vpnservice.""" name = 'new_vpnservice1' @@ -1121,16 +1064,6 @@ class TestVpnaas(VPNPluginDbTestCase): self._create_ipsec_site_connection( fmt=self.fmt, name=name, initiator='unsupported', expected_status_int=400) - self._create_ipsec_site_connection( - fmt=self.fmt, - name=name, - dpd_interval=30, - dpd_timeout=20, expected_status_int=400) - self._create_ipsec_site_connection( - fmt=self.fmt, - name=name, - dpd_interval=100, - dpd_timeout=100, expected_status_int=400) def _test_create_ipsec_site_connection(self, key_overrides=None, setup_overrides=None, @@ -1207,22 +1140,6 @@ class TestVpnaas(VPNPluginDbTestCase): """Test case to create an ipsec_site_connection.""" self._test_create_ipsec_site_connection(key_overrides=extras) - def test_create_ipsec_site_connection_invalid_mtu(self): - """Test creating an ipsec_site_connection with invalid MTU.""" - self._test_create_ipsec_site_connection(key_overrides={'mtu': 67}, - expected_status_int=400) - ipv6_overrides = { - 'peer_address': 'fe80::c0a8:10a', - 'peer_id': 'fe80::c0a8:10a', - 'peer_cidrs': ['fe80::c0a8:200/120', 'fe80::c0a8:300/120'], - 'mtu': 1279} - ipv6_setup_params = {'subnet_cidr': 'fe80::a01:0/120', - 'subnet_version': 6} - self._test_create_ipsec_site_connection( - key_overrides=ipv6_overrides, - setup_overrides=ipv6_setup_params, - expected_status_int=400) - def test_delete_ipsec_site_connection(self): """Test case to delete a ipsec_site_connection.""" with self.ipsec_site_connection( @@ -1250,42 +1167,6 @@ class TestVpnaas(VPNPluginDbTestCase): self._test_update_ipsec_site_connection(update={'mtu': 2000}, overrides=ipv6_settings) - def test_update_ipsec_site_connection_with_invalid_dpd(self): - """Test updates to ipsec_site_connection with invalid DPD settings.""" - dpd1 = {'action': 'hold', - 'interval': 100, - 'timeout': 100} - self._test_update_ipsec_site_connection( - update={'dpd': dpd1}, - expected_status_int=400) - dpd2 = {'action': 'hold', - 'interval': 100, - 'timeout': 60} - self._test_update_ipsec_site_connection( - update={'dpd': dpd2}, - expected_status_int=400) - dpd3 = {'action': 'hold', - 'interval': -50, - 'timeout': -100} - self._test_update_ipsec_site_connection( - update={'dpd': dpd3}, - expected_status_int=400) - - def test_update_ipsec_site_connection_with_invalid_mtu(self): - """Test updates to ipsec_site_connection with invalid MTU settings.""" - self._test_update_ipsec_site_connection( - update={'mtu': 67}, expected_status_int=400) - ipv6_settings = { - 'peer_address': 'fe80::c0a8:10a', - 'peer_id': 'fe80::c0a8:10a', - 'peer_cidrs': ['fe80::c0a8:200/120', 'fe80::c0a8:300/120'], - 'subnet_cidr': 'fe80::a02:0/120', - 'subnet_version': 6} - self._test_update_ipsec_site_connection( - update={'mtu': 1279}, - overrides=ipv6_settings, - expected_status_int=400) - def test_update_ipsec_site_connection_with_invalid_state(self): """Test updating an ipsec_site_connection in invalid state.""" self._test_update_ipsec_site_connection( diff --git a/neutron/tests/unit/services/vpn/service_drivers/test_ipsec.py b/neutron/tests/unit/services/vpn/service_drivers/test_ipsec.py index 646827e0e28..e288748ffb9 100644 --- a/neutron/tests/unit/services/vpn/service_drivers/test_ipsec.py +++ b/neutron/tests/unit/services/vpn/service_drivers/test_ipsec.py @@ -14,22 +14,204 @@ # under the License. import mock +# TODO(pcm): Uncomment once oslo.messaging 1.4.0.0a2 or newer is available +# from oslo.config import cfg -from neutron import context +from neutron import context as n_ctx +from neutron.db import l3_db +from neutron.db.vpn import vpn_validator +from neutron.extensions import vpnaas from neutron.openstack.common import uuidutils from neutron.plugins.common import constants +# TODO(pcm): Uncomment once oslo.messaging 1.4.0.0a2 or newer is available +# from neutron.services.vpn import plugin as vpn_plugin from neutron.services.vpn.service_drivers import ipsec as ipsec_driver from neutron.tests import base _uuid = uuidutils.generate_uuid +FAKE_SERVICE_ID = _uuid() FAKE_VPN_CONNECTION = { - 'vpnservice_id': _uuid() + 'vpnservice_id': FAKE_SERVICE_ID } +FAKE_ROUTER_ID = _uuid() FAKE_VPN_SERVICE = { - 'router_id': _uuid() + 'router_id': FAKE_ROUTER_ID } FAKE_HOST = 'fake_host' +FAKE_ROUTER = {l3_db.EXTERNAL_GW_INFO: FAKE_ROUTER_ID} +FAKE_SUBNET_ID = _uuid() +IPV4 = 4 +IPV6 = 6 + +IPSEC_SERVICE_DRIVER = ('neutron.services.vpn.service_drivers.' + 'ipsec.IPsecVPNDriver') + +# TODO(pcm): Uncomment, once oslo.messaging package 1.4.0.0a2 or +# newer is released and available for Neutron. +# +# class TestValidatorSelection(base.BaseTestCase): +# +# def setUp(self): +# super(TestValidatorSelection, self).setUp() +# vpnaas_provider = (constants.VPN + ':vpnaas:' + +# IPSEC_SERVICE_DRIVER + ':default') +# cfg.CONF.set_override('service_provider', +# [vpnaas_provider], +# 'service_providers') +# mock.patch('neutron.common.rpc.create_connection').start() +# self.vpn_plugin = vpn_plugin.VPNDriverPlugin() +# +# def test_reference_driver_used(self): +# self.assertIsInstance(self.vpn_plugin._get_validator(), +# vpn_validator.VpnReferenceValidator) + + +class TestIPsecDriverValidation(base.BaseTestCase): + + def setUp(self): + super(TestIPsecDriverValidation, self).setUp() + self.l3_plugin = mock.Mock() + mock.patch( + 'neutron.manager.NeutronManager.get_service_plugins', + return_value={constants.L3_ROUTER_NAT: self.l3_plugin}).start() + self.core_plugin = mock.Mock() + mock.patch('neutron.manager.NeutronManager.get_plugin', + return_value=self.core_plugin).start() + self.context = n_ctx.Context('some_user', 'some_tenant') + self.validator = vpn_validator.VpnReferenceValidator() + + def test_non_public_router_for_vpn_service(self): + """Failure test of service validate, when router missing ext. I/F.""" + self.l3_plugin.get_router.return_value = {} # No external gateway + vpnservice = {'router_id': 123, 'subnet_id': 456} + self.assertRaises(vpnaas.RouterIsNotExternal, + self.validator.validate_vpnservice, + self.context, vpnservice) + + def test_subnet_not_connected_for_vpn_service(self): + """Failure test of service validate, when subnet not on router.""" + self.l3_plugin.get_router.return_value = FAKE_ROUTER + self.core_plugin.get_ports.return_value = None + vpnservice = {'router_id': FAKE_ROUTER_ID, 'subnet_id': FAKE_SUBNET_ID} + self.assertRaises(vpnaas.SubnetIsNotConnectedToRouter, + self.validator.validate_vpnservice, + self.context, vpnservice) + + def test_defaults_for_ipsec_site_connections_on_create(self): + """Check that defaults are applied correctly. + + MTU has a default and will always be present on create. + However, the DPD settings do not have a default, so + database create method will assign default values for any + missing. In addition, the DPD dict will be flattened + for storage into the database, so we'll do it as part of + assigning defaults. + """ + ipsec_sitecon = {} + self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon) + expected = { + 'dpd_action': 'hold', + 'dpd_timeout': 120, + 'dpd_interval': 30 + } + self.assertEqual(expected, ipsec_sitecon) + + ipsec_sitecon = {'dpd': {'interval': 50}} + self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon) + expected = { + 'dpd': {'interval': 50}, + 'dpd_action': 'hold', + 'dpd_timeout': 120, + 'dpd_interval': 50 + } + self.assertEqual(expected, ipsec_sitecon) + + def test_defaults_for_ipsec_site_connections_on_update(self): + """Check that defaults are used for any values not specified.""" + ipsec_sitecon = {} + prev_connection = {'dpd_action': 'clear', + 'dpd_timeout': 500, + 'dpd_interval': 250} + self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, + prev_connection) + expected = { + 'dpd_action': 'clear', + 'dpd_timeout': 500, + 'dpd_interval': 250 + } + self.assertEqual(expected, ipsec_sitecon) + + ipsec_sitecon = {'dpd': {'timeout': 200}} + prev_connection = {'dpd_action': 'clear', + 'dpd_timeout': 500, + 'dpd_interval': 100} + self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, + prev_connection) + expected = { + 'dpd': {'timeout': 200}, + 'dpd_action': 'clear', + 'dpd_timeout': 200, + 'dpd_interval': 100 + } + self.assertEqual(expected, ipsec_sitecon) + + def test_bad_dpd_settings_on_create(self): + """Failure tests of DPD settings for IPSec conn during create.""" + ipsec_sitecon = {'mtu': 1500, 'dpd_action': 'hold', + 'dpd_interval': 100, 'dpd_timeout': 100} + self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError, + self.validator.validate_ipsec_site_connection, + self.context, ipsec_sitecon, IPV4) + ipsec_sitecon = {'mtu': 1500, 'dpd_action': 'hold', + 'dpd_interval': 100, 'dpd_timeout': 99} + self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError, + self.validator.validate_ipsec_site_connection, + self.context, ipsec_sitecon, IPV4) + + def test_bad_dpd_settings_on_update(self): + """Failure tests of DPD settings for IPSec conn. during update. + + Note: On an update, the user may specify only some of the DPD settings. + Previous values will be assigned for any missing items, so by the + time the validation occurs, all items will be available for checking. + The MTU may not be provided, during validation and will be ignored, + if that is the case. + """ + prev_connection = {'mtu': 2000, + 'dpd_action': 'hold', + 'dpd_interval': 100, + 'dpd_timeout': 120} + ipsec_sitecon = {'dpd': {'interval': 120}} + self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, + prev_connection) + self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError, + self.validator.validate_ipsec_site_connection, + self.context, ipsec_sitecon, IPV4) + + prev_connection = {'mtu': 2000, + 'dpd_action': 'hold', + 'dpd_interval': 100, + 'dpd_timeout': 120} + ipsec_sitecon = {'dpd': {'timeout': 99}} + self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, + prev_connection) + self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError, + self.validator.validate_ipsec_site_connection, + self.context, ipsec_sitecon, IPV4) + + def test_bad_mtu_for_ipsec_connection(self): + """Failure test of invalid MTU values for IPSec conn create/update.""" + ip_version_limits = vpn_validator.VpnReferenceValidator.IP_MIN_MTU + for version, limit in ip_version_limits.items(): + ipsec_sitecon = {'mtu': limit - 1, + 'dpd_action': 'hold', + 'dpd_interval': 100, + 'dpd_timeout': 120} + self.assertRaises( + vpnaas.IPsecSiteConnectionMtuError, + self.validator.validate_ipsec_site_connection, + self.context, ipsec_sitecon, version) class TestIPsecDriver(base.BaseTestCase): @@ -57,7 +239,7 @@ class TestIPsecDriver(base.BaseTestCase): self.driver = ipsec_driver.IPsecVPNDriver(service_plugin) def _test_update(self, func, args): - ctxt = context.Context('', 'somebody') + ctxt = n_ctx.Context('', 'somebody') with mock.patch.object(self.driver.agent_rpc, 'cast') as cast: func(ctxt, *args) cast.assert_called_once_with( diff --git a/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py b/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py index b0cd3fbd1ef..dbdc9996925 100644 --- a/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py +++ b/neutron/tests/unit/services/vpn/test_vpnaas_driver_plugin.py @@ -18,6 +18,7 @@ import mock from neutron.common import constants from neutron import context +from neutron.db.vpn import vpn_validator from neutron import manager from neutron.plugins.common import constants as p_constants from neutron.services.vpn.service_drivers import ipsec as ipsec_driver @@ -32,6 +33,7 @@ VPN_DRIVER_CLASS = 'neutron.services.vpn.plugin.VPNDriverPlugin' class TestVPNDriverPlugin(test_db_vpnaas.TestVpnaas, test_agent_scheduler.AgentSchedulerTestMixIn, test_agent_ext_plugin.AgentDBTestMixIn): + def setUp(self): self.adminContext = context.get_admin_context() driver_cls_p = mock.patch( @@ -40,6 +42,7 @@ class TestVPNDriverPlugin(test_db_vpnaas.TestVpnaas, driver_cls = driver_cls_p.start() self.driver = mock.Mock() self.driver.service_type = ipsec_driver.IPSEC + self.driver.validator = vpn_validator.VpnReferenceValidator() driver_cls.return_value = self.driver super(TestVPNDriverPlugin, self).setUp( vpnaas_plugin=VPN_DRIVER_CLASS)