diff --git a/etc/neutron_vpnaas.conf b/etc/neutron_vpnaas.conf new file mode 100644 index 000000000..161831197 --- /dev/null +++ b/etc/neutron_vpnaas.conf @@ -0,0 +1,11 @@ + +[service_providers] +# Must be in form: +# service_provider=::[:default] +# List of allowed service types includes VPN +# Combination of and must be unique; must also be unique +# This is multiline option +service_provider=VPN:openswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default +# Uncomment the following line (and comment out the OpenSwan VPN line) to enable Cisco's VPN driver. +# service_provider=VPN:cisco:neutron_vpnaas.services.vpn.service_drivers.cisco_ipsec.CiscoCsrIPsecVPNDriver:default + diff --git a/neutron_vpnaas/db/vpn/vpn_db.py b/neutron_vpnaas/db/vpn/vpn_db.py index b0595f47a..fd9669f8b 100644 --- a/neutron_vpnaas/db/vpn/vpn_db.py +++ b/neutron_vpnaas/db/vpn/vpn_db.py @@ -25,14 +25,15 @@ 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.extensions import vpnaas from neutron.i18n import _LW from neutron import manager from neutron.openstack.common import log as logging from neutron.openstack.common import uuidutils from neutron.plugins.common import constants from neutron.plugins.common import utils + from neutron_vpnaas.db.vpn import vpn_validator +from neutron_vpnaas.extensions import vpnaas LOG = logging.getLogger(__name__) diff --git a/neutron_vpnaas/db/vpn/vpn_validator.py b/neutron_vpnaas/db/vpn/vpn_validator.py index 4316bc291..a8db0c394 100644 --- a/neutron_vpnaas/db/vpn/vpn_validator.py +++ b/neutron_vpnaas/db/vpn/vpn_validator.py @@ -13,10 +13,11 @@ # under the License. from neutron.db import l3_db -from neutron.extensions import vpnaas from neutron import manager from neutron.plugins.common import constants +from neutron_vpnaas.extensions import vpnaas + class VpnReferenceValidator(object): diff --git a/neutron_vpnaas/extensions/__init__.py b/neutron_vpnaas/extensions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron_vpnaas/extensions/vpnaas.py b/neutron_vpnaas/extensions/vpnaas.py new file mode 100644 index 000000000..189176d61 --- /dev/null +++ b/neutron_vpnaas/extensions/vpnaas.py @@ -0,0 +1,486 @@ +# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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. + +import abc + +import six + +from neutron.api import extensions +from neutron.api.v2 import attributes as attr +from neutron.api.v2 import resource_helper +from neutron.common import exceptions as nexception +from neutron.plugins.common import constants +from neutron.services import service_base + + +class VPNServiceNotFound(nexception.NotFound): + message = _("VPNService %(vpnservice_id)s could not be found") + + +class IPsecSiteConnectionNotFound(nexception.NotFound): + message = _("ipsec_site_connection %(ipsec_site_conn_id)s not found") + + +class IPsecSiteConnectionDpdIntervalValueError(nexception.InvalidInput): + message = _("ipsec_site_connection %(attr)s is " + "equal to or less than dpd_interval") + + +class IPsecSiteConnectionMtuError(nexception.InvalidInput): + message = _("ipsec_site_connection MTU %(mtu)d is too small " + "for ipv%(version)s") + + +class IKEPolicyNotFound(nexception.NotFound): + message = _("IKEPolicy %(ikepolicy_id)s could not be found") + + +class IPsecPolicyNotFound(nexception.NotFound): + message = _("IPsecPolicy %(ipsecpolicy_id)s could not be found") + + +class IKEPolicyInUse(nexception.InUse): + message = _("IKEPolicy %(ikepolicy_id)s is in use by existing " + "IPsecSiteConnection and can't be updated or deleted") + + +class VPNServiceInUse(nexception.InUse): + message = _("VPNService %(vpnservice_id)s is still in use") + + +# TODO(dougwig) - once this exception is out of neutron, restore this +#class RouterInUseByVPNService(nexception.InUse): +# message = _("Router %(router_id)s is used by VPNService %(vpnservice_id)s") +RouterInUseByVPNService = nexception.RouterInUseByVPNService + + +class SubnetInUseByVPNService(nexception.InUse): + message = _("Subnet %(subnet_id)s is used by VPNService %(vpnservice_id)s") + + +class VPNStateInvalidToUpdate(nexception.BadRequest): + message = _("Invalid state %(state)s of vpnaas resource %(id)s" + " for updating") + + +class IPsecPolicyInUse(nexception.InUse): + message = _("IPsecPolicy %(ipsecpolicy_id)s is in use by existing " + "IPsecSiteConnection and can't be updated or deleted") + + +class DeviceDriverImportError(nexception.NeutronException): + message = _("Can not load driver :%(device_driver)s") + + +class SubnetIsNotConnectedToRouter(nexception.BadRequest): + message = _("Subnet %(subnet_id)s is not " + "connected to Router %(router_id)s") + + +class RouterIsNotExternal(nexception.BadRequest): + message = _("Router %(router_id)s has no external network gateway set") + + +vpn_supported_initiators = ['bi-directional', 'response-only'] +vpn_supported_encryption_algorithms = ['3des', 'aes-128', + 'aes-192', 'aes-256'] +vpn_dpd_supported_actions = [ + 'hold', 'clear', 'restart', 'restart-by-peer', 'disabled' +] +vpn_supported_transform_protocols = ['esp', 'ah', 'ah-esp'] +vpn_supported_encapsulation_mode = ['tunnel', 'transport'] +#TODO(nati) add kilobytes when we support it +vpn_supported_lifetime_units = ['seconds'] +vpn_supported_pfs = ['group2', 'group5', 'group14'] +vpn_supported_ike_versions = ['v1', 'v2'] +vpn_supported_auth_mode = ['psk'] +vpn_supported_auth_algorithms = ['sha1'] +vpn_supported_phase1_negotiation_mode = ['main'] + +vpn_lifetime_limits = (60, attr.UNLIMITED) +positive_int = (0, attr.UNLIMITED) + +RESOURCE_ATTRIBUTE_MAP = { + + 'vpnservices': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'subnet_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + 'router_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + 'admin_state_up': {'allow_post': True, 'allow_put': True, + 'default': True, + 'convert_to': attr.convert_to_boolean, + 'is_visible': True}, + 'status': {'allow_post': False, 'allow_put': False, + 'is_visible': True} + }, + + 'ipsec_site_connections': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'peer_address': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True}, + 'peer_id': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True}, + 'peer_cidrs': {'allow_post': True, 'allow_put': True, + 'convert_to': attr.convert_to_list, + 'validate': {'type:subnet_list': None}, + 'is_visible': True}, + 'route_mode': {'allow_post': False, 'allow_put': False, + 'default': 'static', + 'is_visible': True}, + 'mtu': {'allow_post': True, 'allow_put': True, + 'default': '1500', + 'validate': {'type:range': positive_int}, + 'convert_to': attr.convert_to_int, + 'is_visible': True}, + 'initiator': {'allow_post': True, 'allow_put': True, + 'default': 'bi-directional', + 'validate': {'type:values': vpn_supported_initiators}, + 'is_visible': True}, + 'auth_mode': {'allow_post': False, 'allow_put': False, + 'default': 'psk', + 'validate': {'type:values': vpn_supported_auth_mode}, + 'is_visible': True}, + 'psk': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True}, + 'dpd': {'allow_post': True, 'allow_put': True, + 'convert_to': attr.convert_none_to_empty_dict, + 'is_visible': True, + 'default': {}, + 'validate': { + 'type:dict_or_empty': { + 'actions': { + 'type:values': vpn_dpd_supported_actions, + }, + 'interval': { + 'type:range': positive_int + }, + 'timeout': { + 'type:range': positive_int + }}}}, + 'admin_state_up': {'allow_post': True, 'allow_put': True, + 'default': True, + 'convert_to': attr.convert_to_boolean, + 'is_visible': True}, + 'status': {'allow_post': False, 'allow_put': False, + 'is_visible': True}, + 'vpnservice_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + 'ikepolicy_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + 'ipsecpolicy_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True} + }, + + 'ipsecpolicies': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'transform_protocol': { + 'allow_post': True, + 'allow_put': True, + 'default': 'esp', + 'validate': { + 'type:values': vpn_supported_transform_protocols}, + 'is_visible': True}, + 'auth_algorithm': { + 'allow_post': True, + 'allow_put': True, + 'default': 'sha1', + 'validate': { + 'type:values': vpn_supported_auth_algorithms + }, + 'is_visible': True}, + 'encryption_algorithm': { + 'allow_post': True, + 'allow_put': True, + 'default': 'aes-128', + 'validate': { + 'type:values': vpn_supported_encryption_algorithms + }, + 'is_visible': True}, + 'encapsulation_mode': { + 'allow_post': True, + 'allow_put': True, + 'default': 'tunnel', + 'validate': { + 'type:values': vpn_supported_encapsulation_mode + }, + 'is_visible': True}, + 'lifetime': {'allow_post': True, 'allow_put': True, + 'convert_to': attr.convert_none_to_empty_dict, + 'default': {}, + 'validate': { + 'type:dict_or_empty': { + 'units': { + 'type:values': vpn_supported_lifetime_units, + }, + 'value': { + 'type:range': vpn_lifetime_limits + }}}, + 'is_visible': True}, + 'pfs': {'allow_post': True, 'allow_put': True, + 'default': 'group5', + 'validate': {'type:values': vpn_supported_pfs}, + 'is_visible': True} + }, + + 'ikepolicies': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'auth_algorithm': {'allow_post': True, 'allow_put': True, + 'default': 'sha1', + 'validate': { + 'type:values': vpn_supported_auth_algorithms}, + 'is_visible': True}, + 'encryption_algorithm': { + 'allow_post': True, 'allow_put': True, + 'default': 'aes-128', + 'validate': {'type:values': vpn_supported_encryption_algorithms}, + 'is_visible': True}, + 'phase1_negotiation_mode': { + 'allow_post': True, 'allow_put': True, + 'default': 'main', + 'validate': { + 'type:values': vpn_supported_phase1_negotiation_mode + }, + 'is_visible': True}, + 'lifetime': {'allow_post': True, 'allow_put': True, + 'convert_to': attr.convert_none_to_empty_dict, + 'default': {}, + 'validate': { + 'type:dict_or_empty': { + 'units': { + 'type:values': vpn_supported_lifetime_units, + }, + 'value': { + 'type:range': vpn_lifetime_limits, + }}}, + 'is_visible': True}, + 'ike_version': {'allow_post': True, 'allow_put': True, + 'default': 'v1', + 'validate': { + 'type:values': vpn_supported_ike_versions}, + 'is_visible': True}, + 'pfs': {'allow_post': True, 'allow_put': True, + 'default': 'group5', + 'validate': {'type:values': vpn_supported_pfs}, + 'is_visible': True} + } +} + + +class Vpnaas(extensions.ExtensionDescriptor): + + @classmethod + def get_name(cls): + return "VPN service" + + @classmethod + def get_alias(cls): + return "vpnaas" + + @classmethod + def get_description(cls): + return "Extension for VPN service" + + @classmethod + def get_namespace(cls): + return "https://wiki.openstack.org/Neutron/VPNaaS" + + @classmethod + def get_updated(cls): + return "2013-05-29T10:00:00-00:00" + + @classmethod + def get_resources(cls): + special_mappings = {'ikepolicies': 'ikepolicy', + 'ipsecpolicies': 'ipsecpolicy'} + plural_mappings = resource_helper.build_plural_mappings( + special_mappings, RESOURCE_ATTRIBUTE_MAP) + plural_mappings['peer_cidrs'] = 'peer_cidr' + attr.PLURALS.update(plural_mappings) + return resource_helper.build_resource_info(plural_mappings, + RESOURCE_ATTRIBUTE_MAP, + constants.VPN, + register_quota=True, + translate_name=True) + + @classmethod + def get_plugin_interface(cls): + return VPNPluginBase + + def update_attributes_map(self, attributes): + super(Vpnaas, self).update_attributes_map( + attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} + + +@six.add_metaclass(abc.ABCMeta) +class VPNPluginBase(service_base.ServicePluginBase): + + def get_plugin_name(self): + return constants.VPN + + def get_plugin_type(self): + return constants.VPN + + def get_plugin_description(self): + return 'VPN service plugin' + + @abc.abstractmethod + def get_vpnservices(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_vpnservice(self, context, vpnservice_id, fields=None): + pass + + @abc.abstractmethod + def create_vpnservice(self, context, vpnservice): + pass + + @abc.abstractmethod + def update_vpnservice(self, context, vpnservice_id, vpnservice): + pass + + @abc.abstractmethod + def delete_vpnservice(self, context, vpnservice_id): + pass + + @abc.abstractmethod + def get_ipsec_site_connections(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_ipsec_site_connection(self, context, + ipsecsite_conn_id, fields=None): + pass + + @abc.abstractmethod + def create_ipsec_site_connection(self, context, ipsec_site_connection): + pass + + @abc.abstractmethod + def update_ipsec_site_connection(self, context, + ipsecsite_conn_id, ipsec_site_connection): + pass + + @abc.abstractmethod + def delete_ipsec_site_connection(self, context, ipsecsite_conn_id): + pass + + @abc.abstractmethod + def get_ikepolicy(self, context, ikepolicy_id, fields=None): + pass + + @abc.abstractmethod + def get_ikepolicies(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def create_ikepolicy(self, context, ikepolicy): + pass + + @abc.abstractmethod + def update_ikepolicy(self, context, ikepolicy_id, ikepolicy): + pass + + @abc.abstractmethod + def delete_ikepolicy(self, context, ikepolicy_id): + pass + + @abc.abstractmethod + def get_ipsecpolicies(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None): + pass + + @abc.abstractmethod + def create_ipsecpolicy(self, context, ipsecpolicy): + pass + + @abc.abstractmethod + def update_ipsecpolicy(self, context, ipsecpolicy_id, ipsecpolicy): + pass + + @abc.abstractmethod + def delete_ipsecpolicy(self, context, ipsecpolicy_id): + pass diff --git a/neutron_vpnaas/services/vpn/vpn_service.py b/neutron_vpnaas/services/vpn/vpn_service.py index 8d5c2c710..ec0d714df 100644 --- a/neutron_vpnaas/services/vpn/vpn_service.py +++ b/neutron_vpnaas/services/vpn/vpn_service.py @@ -17,11 +17,12 @@ from oslo.config import cfg from oslo.utils import importutils from neutron import context as n_context -from neutron.extensions import vpnaas from neutron.openstack.common import log as logging from neutron.services import advanced_service from neutron.services import provider_configuration as provconfig +from neutron_vpnaas.extensions import vpnaas + LOG = logging.getLogger(__name__) DEVICE_DRIVERS = 'device_drivers' diff --git a/neutron_vpnaas/tests/unit/db/vpn/test_db_vpnaas.py b/neutron_vpnaas/tests/unit/db/vpn/test_db_vpnaas.py index 3b48610a4..a54dd9c26 100644 --- a/neutron_vpnaas/tests/unit/db/vpn/test_db_vpnaas.py +++ b/neutron_vpnaas/tests/unit/db/vpn/test_db_vpnaas.py @@ -26,7 +26,6 @@ from neutron.db import agentschedulers_db from neutron.db import l3_agentschedulers_db from neutron.db import servicetype_db as sdb 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 @@ -37,6 +36,8 @@ from neutron_vpnaas.db.vpn import vpn_db from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.tests import base +from neutron_vpnaas.extensions import vpnaas + DB_CORE_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' DB_VPN_PLUGIN_KLASS = "neutron_vpnaas.services.vpn.plugin.VPNPlugin" ROOTDIR = os.path.normpath(os.path.join( diff --git a/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py b/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py index 4c2bf5cf0..591d70952 100644 --- a/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py +++ b/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py @@ -16,15 +16,15 @@ import contextlib import mock -from oslo.config import cfg - from neutron import context as n_ctx from neutron.db import l3_db from neutron.db import servicetype_db as st_db -from neutron.extensions import vpnaas from neutron.openstack.common import uuidutils from neutron.plugins.common import constants +from oslo.config import cfg + from neutron_vpnaas.db.vpn import vpn_validator +from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.services.vpn.service_drivers import ipsec as ipsec_driver from neutron_vpnaas.tests import base diff --git a/neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py b/neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py index 5fd5794d3..7afad9543 100644 --- a/neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py +++ b/neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py @@ -14,13 +14,13 @@ # under the License. import mock -from oslo.config import cfg - from neutron.agent.common import config as agent_config from neutron.agent.l3 import router_info from neutron.agent.linux import iptables_manager -from neutron.extensions import vpnaas from neutron.openstack.common import uuidutils +from oslo.config import cfg + +from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn import agent as vpn_agent from neutron_vpnaas.services.vpn import device_drivers from neutron_vpnaas.services.vpn import vpn_service diff --git a/neutron_vpnaas/tests/unit/services/vpn/test_vpnaas_extension.py b/neutron_vpnaas/tests/unit/services/vpn/test_vpnaas_extension.py index 7fd96d6a1..2db3560a7 100644 --- a/neutron_vpnaas/tests/unit/services/vpn/test_vpnaas_extension.py +++ b/neutron_vpnaas/tests/unit/services/vpn/test_vpnaas_extension.py @@ -16,13 +16,12 @@ import copy import mock -from webob import exc - -from neutron.extensions import vpnaas from neutron.openstack.common import uuidutils from neutron.plugins.common import constants from neutron.tests.unit import test_api_v2 +from webob import exc +from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.tests import base @@ -39,7 +38,7 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase): 'ikepolicy': 'ikepolicies', 'ipsec_site_connection': 'ipsec-site-connections'} self._setUpExtension( - 'neutron.extensions.vpnaas.VPNPluginBase', constants.VPN, + 'neutron_vpnaas.extensions.vpnaas.VPNPluginBase', constants.VPN, vpnaas.RESOURCE_ATTRIBUTE_MAP, vpnaas.Vpnaas, 'vpn', plural_mappings=plural_mappings, use_quota=True) diff --git a/setup.cfg b/setup.cfg index 08ca66c5b..ddf36291c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ packages = neutron_vpnaas data_files = etc/neutron = + etc/neutron_vpnaas.conf etc/vpn_agent.ini etc/neutron/rootwrap.d = etc/neutron/rootwrap.d/vpnaas.filters