neutron-vpnaas-12.0.1/0000775000175000017500000000000013370231105014570 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/0000775000175000017500000000000013370231105017632 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/version.py0000666000175000017500000000126513370230606021704 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # # 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 pbr.version version_info = pbr.version.VersionInfo('neutron-vpnaas') neutron-vpnaas-12.0.1/neutron_vpnaas/__init__.py0000666000175000017500000000136413370230615021756 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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 gettext import six if six.PY2: gettext.install('neutron', unicode=1) else: gettext.install('neutron') neutron-vpnaas-12.0.1/neutron_vpnaas/cmd/0000775000175000017500000000000013370231105020375 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/cmd/__init__.py0000666000175000017500000000206513370230615022520 0ustar zuulzuul00000000000000# 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 logging as sys_logging from oslo_reports import guru_meditation_report as gmr from neutron_vpnaas import version # During the call to gmr.TextGuruMeditation.setup_autorun(), Guru Meditation # Report tries to start logging. Set a handler here to accommodate this. logger = sys_logging.getLogger(None) if not logger.handlers: logger.addHandler(sys_logging.StreamHandler()) _version_string = version.version_info.release_string() gmr.TextGuruMeditation.setup_autorun(version=_version_string) neutron-vpnaas-12.0.1/neutron_vpnaas/cmd/eventlet/0000775000175000017500000000000013370231105022223 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/cmd/eventlet/__init__.py0000666000175000017500000000114513370230615024344 0ustar zuulzuul00000000000000# 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 eventlet eventlet.monkey_patch() neutron-vpnaas-12.0.1/neutron_vpnaas/cmd/eventlet/vyatta_agent.py0000666000175000017500000000135013370230615025271 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from neutron_vpnaas.services.vpn import vyatta_agent def main(): vyatta_agent.main() neutron-vpnaas-12.0.1/neutron_vpnaas/opts.py0000666000175000017500000000246013370230606021202 0ustar zuulzuul00000000000000# 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 neutron.services.provider_configuration import neutron_vpnaas.services.vpn.agent import neutron_vpnaas.services.vpn.device_drivers.ipsec import neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec def list_agent_opts(): return [ ('vpnagent', neutron_vpnaas.services.vpn.agent.vpn_agent_opts), ('ipsec', neutron_vpnaas.services.vpn.device_drivers.ipsec.ipsec_opts), ('strongswan', neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec. strongswan_opts), ('pluto', neutron_vpnaas.services.vpn.device_drivers.ipsec.pluto_opts) ] def list_opts(): return [ ('service_providers', neutron.services.provider_configuration.serviceprovider_opts) ] neutron-vpnaas-12.0.1/neutron_vpnaas/extensions/0000775000175000017500000000000013370231105022031 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/extensions/vpn_endpoint_groups.py0000666000175000017500000001017213370230615026515 0ustar zuulzuul00000000000000# (c) Copyright 2015 NEC Corporation, 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.v2 import resource_helper from neutron_lib.api import converters from neutron_lib.api import extensions from neutron_lib.db import constants as db_const from neutron_lib.plugins import constants as nconstants from neutron_vpnaas.services.vpn.common import constants RESOURCE_ATTRIBUTE_MAP = { 'endpoint_groups': { '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': db_const.PROJECT_ID_FIELD_SIZE}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': db_const.NAME_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': { 'type:string': db_const.DESCRIPTION_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'type': {'allow_post': True, 'allow_put': False, 'validate': { 'type:values': constants.VPN_SUPPORTED_ENDPOINT_TYPES, }, 'is_visible': True}, 'endpoints': {'allow_post': True, 'allow_put': False, 'convert_to': converters.convert_to_list, 'is_visible': True}, }, } class Vpn_endpoint_groups(extensions.ExtensionDescriptor): @classmethod def get_name(cls): return "VPN Endpoint Groups" @classmethod def get_alias(cls): return "vpn-endpoint-groups" @classmethod def get_description(cls): return "VPN endpoint groups support" @classmethod def get_updated(cls): return "2015-08-04T10:00:00-00:00" @classmethod def get_resources(cls): plural_mappings = resource_helper.build_plural_mappings( {}, RESOURCE_ATTRIBUTE_MAP) return resource_helper.build_resource_info(plural_mappings, RESOURCE_ATTRIBUTE_MAP, nconstants.VPN, register_quota=True, translate_name=True) def get_required_extensions(self): return ["vpnaas"] def update_attributes_map(self, attributes): super(Vpn_endpoint_groups, 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 VPNEndpointGroupsPluginBase(object): @abc.abstractmethod def create_endpoint_group(self, context, endpoint_group): pass @abc.abstractmethod def update_endpoint_group(self, context, endpoint_group_id, endpoint_group): pass @abc.abstractmethod def delete_endpoint_group(self, context, endpoint_group_id): pass @abc.abstractmethod def get_endpoint_group(self, context, endpoint_group_id, fields=None): pass @abc.abstractmethod def get_endpoint_groups(self, context, filters=None, fields=None): pass neutron-vpnaas-12.0.1/neutron_vpnaas/extensions/__init__.py0000666000175000017500000000000013370230606024137 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/extensions/vpn_flavors.py0000666000175000017500000000362513370230615024757 0ustar zuulzuul00000000000000# Copyright 2017 Eayun, 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_lib.api import extensions from neutron_lib import exceptions as nexception from neutron_vpnaas._i18n import _ class FlavorsPluginNotLoaded(nexception.NotFound): message = _("Flavors plugin not found") class NoProviderFoundForFlavor(nexception.NotFound): message = _("No service provider found for flavor %(flavor_id)s") EXTENDED_ATTRIBUTES_2_0 = { 'vpnservices': { 'flavor_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid_or_none': None}, 'is_visible': True, 'default': None} } } class Vpn_flavors(extensions.ExtensionDescriptor): """Extension class supporting flavors for vpnservices.""" @classmethod def get_name(cls): return "VPN Service Flavor Extension" @classmethod def get_alias(cls): return 'vpn-flavors' @classmethod def get_description(cls): return "Flavor support for vpnservices." @classmethod def get_updated(cls): return "2017-04-19T00:00:00-00:00" def get_extended_resources(self, version): if version == "2.0": return EXTENDED_ATTRIBUTES_2_0 else: return {} def get_required_extensions(self): return ["vpnaas"] def get_optional_extensions(self): return ["flavors"] neutron-vpnaas-12.0.1/neutron_vpnaas/extensions/vpnaas.py0000666000175000017500000005422313370230615023710 0ustar zuulzuul00000000000000# (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 from neutron_lib.api import converters from neutron_lib.api import extensions from neutron_lib.api import validators from neutron_lib.db import constants as db_const from neutron_lib import exceptions as nexception from neutron_lib.plugins import constants as nconstants from neutron_lib.services import base as service_base import six from neutron.api.v2 import resource_helper from neutron_vpnaas._i18n import _ 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 IPsecSiteConnectionPeerCidrError(nexception.InvalidInput): message = _("ipsec_site_connection peer cidr %(peer_cidr)s is " "invalid CIDR") 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") class SubnetInUseByVPNService(nexception.InUse): message = _("Subnet %(subnet_id)s is used by VPNService %(vpnservice_id)s") class SubnetInUseByEndpointGroup(nexception.InUse): message = _("Subnet %(subnet_id)s is used by endpoint group %(group_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") class VPNPeerAddressNotResolved(nexception.InvalidInput): message = _("Peer address %(peer_address)s cannot be resolved") class ExternalNetworkHasNoSubnet(nexception.BadRequest): message = _("Router's %(router_id)s external network has " "no %(ip_version)s subnet") class VPNEndpointGroupNotFound(nexception.NotFound): message = _("Endpoint group %(endpoint_group_id)s could not be found") class InvalidEndpointInEndpointGroup(nexception.InvalidInput): message = _("Endpoint '%(endpoint)s' is invalid for group " "type '%(group_type)s': %(why)s") class MissingEndpointForEndpointGroup(nexception.BadRequest): message = _("No endpoints specified for endpoint group '%(group)s'") class NonExistingSubnetInEndpointGroup(nexception.InvalidInput): message = _("Subnet %(subnet)s in endpoint group does not exist") class MixedIPVersionsForIPSecEndpoints(nexception.BadRequest): message = _("Endpoints in group %(group)s do not have the same IP " "version, as required for IPSec site-to-site connection") class MixedIPVersionsForPeerCidrs(nexception.BadRequest): message = _("Peer CIDRs do not have the same IP version, as required " "for IPSec site-to-site connection") class MixedIPVersionsForIPSecConnection(nexception.BadRequest): message = _("IP versions are not compatible between peer and local " "endpoints") class InvalidEndpointGroup(nexception.BadRequest): message = _("Endpoint group%(suffix)s %(which)s cannot be specified, " "when VPN Service has subnet specified") class WrongEndpointGroupType(nexception.BadRequest): message = _("Endpoint group %(which)s type is '%(group_type)s' and " "should be '%(expected)s'") class PeerCidrsInvalid(nexception.BadRequest): message = _("Peer CIDRs cannot be specified, when using endpoint " "groups") class MissingPeerCidrs(nexception.BadRequest): message = _("Missing peer CIDRs for IPsec site-to-site connection") class MissingRequiredEndpointGroup(nexception.BadRequest): message = _("Missing endpoint group%(suffix)s %(which)s for IPSec " "site-to-site connection") class EndpointGroupInUse(nexception.BadRequest): message = _("Endpoint group %(group_id)s is in use and cannot be deleted") def _validate_subnet_list_or_none(data, key_specs=None): if data is not None: return validators.validate_subnet_list(data, key_specs) validators.add_validator('type:subnet_list_or_none', _validate_subnet_list_or_none) 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', 'sha256', 'sha384', 'sha512'] vpn_supported_phase1_negotiation_mode = ['main'] vpn_lifetime_limits = (60, validators.UNLIMITED) positive_int = (0, validators.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': db_const.PROJECT_ID_FIELD_SIZE}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': db_const.NAME_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': { 'type:string': db_const.DESCRIPTION_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'subnet_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid_or_none': None}, 'is_visible': True, 'default': None}, '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': converters.convert_to_boolean, 'is_visible': True}, 'external_v4_ip': {'allow_post': False, 'allow_put': False, 'is_visible': True}, 'external_v6_ip': {'allow_post': False, 'allow_put': False, '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': db_const.PROJECT_ID_FIELD_SIZE}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': db_const.NAME_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': { 'type:string': db_const.DESCRIPTION_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'local_id': {'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': converters.convert_to_list, 'validate': {'type:subnet_list_or_none': None}, 'is_visible': True, 'default': None}, 'local_ep_group_id': {'allow_post': True, 'allow_put': True, 'validate': {'type:uuid_or_none': None}, 'is_visible': True, 'default': None}, 'peer_ep_group_id': {'allow_post': True, 'allow_put': True, 'validate': {'type:uuid_or_none': None}, 'is_visible': True, 'default': None}, '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': converters.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': converters.convert_none_to_empty_dict, 'is_visible': True, 'default': {}, 'validate': { 'type:dict_or_empty': { 'action': { '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': converters.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': db_const.PROJECT_ID_FIELD_SIZE}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': db_const.NAME_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': { 'type:string': db_const.DESCRIPTION_FIELD_SIZE}, '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': converters.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': db_const.PROJECT_ID_FIELD_SIZE}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': db_const.NAME_FIELD_SIZE}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': { 'type:string': db_const.DESCRIPTION_FIELD_SIZE}, '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': converters.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' return resource_helper.build_resource_info(plural_mappings, RESOURCE_ATTRIBUTE_MAP, nconstants.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 nconstants.VPN def get_plugin_type(self): return nconstants.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 neutron-vpnaas-12.0.1/neutron_vpnaas/services/0000775000175000017500000000000013370231105021455 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/__init__.py0000666000175000017500000000000013370230606023563 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/0000775000175000017500000000000013370231105022260 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/__init__.py0000666000175000017500000000000013370230606024366 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/vpn_service.py0000666000175000017500000000335313370230606025170 0ustar zuulzuul00000000000000# Copyright 2014 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.services import provider_configuration as provconfig from oslo_log import log as logging from oslo_utils import importutils from neutron_vpnaas.extensions import vpnaas LOG = logging.getLogger(__name__) DEVICE_DRIVERS = 'device_drivers' class VPNService(object): """VPN Service observer.""" def __init__(self, l3_agent): self.conf = l3_agent.conf def load_device_drivers(self, host): """Loads one or more device drivers for VPNaaS.""" drivers = [] for device_driver in self.conf.vpnagent.vpn_device_driver: device_driver = provconfig.get_provider_driver_class( device_driver, DEVICE_DRIVERS) try: drivers.append(importutils.import_object(device_driver, self, host)) LOG.debug('Loaded VPNaaS device driver: %s', device_driver) except ImportError: raise vpnaas.DeviceDriverImportError( device_driver=device_driver) return drivers neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/agent.py0000666000175000017500000001016713370230615023744 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, Inc. # Copyright 2017, Fujitsu Limited # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib.agent import l3_extension from oslo_config import cfg from oslo_log import log as logging from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn import vpn_service LOG = logging.getLogger(__name__) vpn_agent_opts = [ cfg.MultiStrOpt( 'vpn_device_driver', default=['neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanDriver'], sample_default=['neutron_vpnaas.services.vpn.device_drivers.ipsec.' 'OpenSwanDriver, ' 'neutron_vpnaas.services.vpn.device_drivers.' 'cisco_ipsec.CiscoCsrIPsecDriver, ' 'neutron_vpnaas.services.vpn.device_drivers.' 'vyatta_ipsec.VyattaIPSecDriver, ' 'neutron_vpnaas.services.vpn.device_drivers.' 'strongswan_ipsec.StrongSwanDriver, ' 'neutron_vpnaas.services.vpn.device_drivers.' 'fedora_strongswan_ipsec.FedoraStrongSwanDriver, ' 'neutron_vpnaas.services.vpn.device_drivers.' 'libreswan_ipsec.LibreSwanDriver'], help=_("The vpn device drivers Neutron will use")), ] cfg.CONF.register_opts(vpn_agent_opts, 'vpnagent') class VPNAgent(l3_extension.L3AgentExtension): """VPNaaS Agent support to be used by Neutron L3 agent.""" def initialize(self, connection, driver_type): LOG.debug("Loading VPNaaS") def consume_api(self, agent_api): LOG.debug("Loading consume_api for VPNaaS") self.agent_api = agent_api def __init__(self, host, conf): LOG.debug("Initializing VPNaaS agent") self.agent_api = None self.conf = conf self.host = host self.service = vpn_service.VPNService(self) self.device_drivers = self.service.load_device_drivers(self.host) def add_router(self, context, data): """Handles router add event""" ri = self.agent_api.get_router_info(data['id']) if ri is not None: for device_driver in self.device_drivers: device_driver.create_router(ri) device_driver.sync(context, [ri.router]) else: LOG.debug("Router %s was concurrently deleted while " "creating VPN for it", data['id']) def update_router(self, context, data): """Handles router update event""" for device_driver in self.device_drivers: device_driver.sync(context, [data]) def delete_router(self, context, data): """Handles router delete event""" for device_driver in self.device_drivers: device_driver.destroy_router(data) def ha_state_change(self, context, data): """Enable the vpn process when router transitioned to master. And disable vpn process for backup router. """ router_id = data['router_id'] state = data['state'] for device_driver in self.device_drivers: if router_id in device_driver.processes: process = device_driver.processes[router_id] if state == 'master': process.enable() else: process.disable() class L3WithVPNaaS(VPNAgent): def __init__(self, conf=None): if conf: self.conf = conf else: self.conf = cfg.CONF super(L3WithVPNaaS, self).__init__( host=self.conf.host, conf=self.conf) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/0000775000175000017500000000000013370231105025456 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/__init__.py0000666000175000017500000000724113370230615027602 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, 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. import abc from neutron.common import rpc as n_rpc from neutron_lib import constants from neutron_lib.plugins import directory from oslo_log import log as logging import oslo_messaging import six from neutron_vpnaas.services.vpn.service_drivers import driver_validator LOG = logging.getLogger(__name__) @six.add_metaclass(abc.ABCMeta) class VpnDriver(object): def __init__(self, service_plugin, validator=None): self.service_plugin = service_plugin if validator is None: validator = driver_validator.VpnDriverValidator(self) self.validator = validator self.name = '' @property def l3_plugin(self): return directory.get_plugin(constants.L3) @property def service_type(self): pass @abc.abstractmethod def create_vpnservice(self, context, vpnservice): pass @abc.abstractmethod def update_vpnservice( self, context, old_vpnservice, vpnservice): pass @abc.abstractmethod def delete_vpnservice(self, context, vpnservice): pass @abc.abstractmethod def create_ipsec_site_connection(self, context, ipsec_site_connection): pass @abc.abstractmethod def update_ipsec_site_connection(self, context, old_ipsec_site_connection, ipsec_site_connection): pass @abc.abstractmethod def delete_ipsec_site_connection(self, context, ipsec_site_connection): pass class BaseIPsecVpnAgentApi(object): """Base class for IPSec API to agent.""" def __init__(self, topic, default_version, driver): self.topic = topic self.driver = driver target = oslo_messaging.Target(topic=topic, version=default_version) self.client = n_rpc.get_client(target) def _agent_notification(self, context, method, router_id, version=None, **kwargs): """Notify update for the agent. This method will find where is the router, and dispatch notification for the agent. """ admin_context = context if context.is_admin else context.elevated() if not version: version = self.target.version l3_agents = self.driver.l3_plugin.get_l3_agents_hosting_routers( admin_context, [router_id], admin_state_up=True, active=True) for l3_agent in l3_agents: LOG.debug('Notify agent at %(topic)s.%(host)s the message ' '%(method)s %(args)s', {'topic': self.topic, 'host': l3_agent.host, 'method': method, 'args': kwargs}) cctxt = self.client.prepare(server=l3_agent.host, version=version) cctxt.cast(context, method, **kwargs) def vpnservice_updated(self, context, router_id, **kwargs): """Send update event of vpnservices.""" kwargs['router'] = {'id': router_id} self._agent_notification(context, 'vpnservice_updated', router_id, **kwargs) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/vyatta_ipsec.py0000666000175000017500000000264413370230615030540 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from neutron.common import rpc as n_rpc from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn.service_drivers import base_ipsec IPSEC = 'ipsec' BASE_IPSEC_VERSION = '1.0' class VyattaIPsecDriver(base_ipsec.BaseIPsecVPNDriver): def __init__(self, service_plugin): super(VyattaIPsecDriver, self).__init__(service_plugin) def create_rpc_conn(self): self.endpoints = [base_ipsec.IPsecVpnDriverCallBack(self)] self.conn = n_rpc.create_connection() self.conn.create_consumer( topics.BROCADE_IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = base_ipsec.IPsecVpnAgentApi( topics.BROCADE_IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/ipsec.py0000666000175000017500000000307313370230615027145 0ustar zuulzuul00000000000000# Copyright 2015, Nachi Ueno, NTT I3, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.common import rpc as n_rpc from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn.service_drivers import base_ipsec from neutron_vpnaas.services.vpn.service_drivers import ipsec_validator IPSEC = 'ipsec' BASE_IPSEC_VERSION = '1.0' class IPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver): """VPN Service Driver class for IPsec.""" def __init__(self, service_plugin): super(IPsecVPNDriver, self).__init__( service_plugin, ipsec_validator.IpsecVpnValidator(self)) def create_rpc_conn(self): self.endpoints = [base_ipsec.IPsecVpnDriverCallBack(self)] self.conn = n_rpc.create_connection() self.conn.create_consumer( topics.IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = base_ipsec.IPsecVpnAgentApi( topics.IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/cisco_ipsec.py0000666000175000017500000002302313370230615030322 0ustar zuulzuul00000000000000# 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. from neutron.common import rpc as n_rpc from oslo_log import log as logging import oslo_messaging from neutron.db.models import servicetype from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn import service_drivers from neutron_vpnaas.services.vpn.service_drivers import base_ipsec from neutron_vpnaas.services.vpn.service_drivers \ import cisco_csr_db as csr_id_map from neutron_vpnaas.services.vpn.service_drivers import cisco_validator LOG = logging.getLogger(__name__) IPSEC = 'ipsec' BASE_IPSEC_VERSION = '1.0' LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400}, 'IPSec Policy': {'min': 120, 'max': 2592000}} MIN_CSR_MTU = 1500 MAX_CSR_MTU = 9192 VRF_SUFFIX_LEN = 6 T2_PORT_NAME = 't2_p:' class CiscoCsrIPsecVpnDriverCallBack(object): """Handler for agent to plugin RPC messaging.""" # history # 1.0 Initial version target = oslo_messaging.Target(version=BASE_IPSEC_VERSION) def __init__(self, driver): super(CiscoCsrIPsecVpnDriverCallBack, self).__init__() self.driver = driver def create_rpc_dispatcher(self): return n_rpc.PluginRpcDispatcher([self]) def get_vpn_services_using(self, context, router_id): query = context.session.query(vpn_models.VPNService) query = query.join( servicetype.ProviderResourceAssociation, servicetype.ProviderResourceAssociation.resource_id == vpn_models.VPNService.id) query = query.join(vpn_models.IPsecSiteConnection) query = query.join(vpn_models.IKEPolicy) query = query.join(vpn_models.IPsecPolicy) query = query.join(vpn_models.IPsecPeerCidr) query = query.filter(vpn_models.VPNService.router_id == router_id) query = query.filter( servicetype.ProviderResourceAssociation.provider_name == self.driver.name) return query.all() def get_vpn_services_on_host(self, context, host=None): """Returns info on the VPN services on the host.""" routers = self.driver.l3_plugin.get_active_routers_for_host(context, host) host_vpn_services = [] for router in routers: vpn_services = self.get_vpn_services_using(context, router['id']) for vpn_service in vpn_services: host_vpn_services.append( self.driver.make_vpnservice_dict(context, vpn_service, router)) return host_vpn_services def update_status(self, context, status): """Update status of all vpnservices.""" plugin = self.driver.service_plugin plugin.update_status_by_agent(context, status) class CiscoCsrIPsecVpnAgentApi(service_drivers.BaseIPsecVpnAgentApi): """API and handler for Cisco IPSec plugin to agent RPC messaging.""" target = oslo_messaging.Target(version=BASE_IPSEC_VERSION) def __init__(self, topic, default_version, driver): super(CiscoCsrIPsecVpnAgentApi, self).__init__( topic, default_version, driver) def _agent_notification(self, context, method, router_id, version=None, **kwargs): """Notify update for the agent. Find the host for the router being notified and then dispatches a notification for the VPN device driver. """ admin_context = context if context.is_admin else context.elevated() if not version: version = self.target.version host = self.driver.l3_plugin.get_host_for_router(admin_context, router_id) LOG.debug('Notify agent at %(topic)s.%(host)s the message ' '%(method)s %(args)s for router %(router)s', {'topic': self.topic, 'host': host, 'method': method, 'args': kwargs, 'router': router_id}) cctxt = self.client.prepare(server=host, version=version) cctxt.cast(context, method, **kwargs) class CiscoCsrIPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver): """Cisco CSR VPN Service Driver class for IPsec.""" def __init__(self, service_plugin): super(CiscoCsrIPsecVPNDriver, self).__init__( service_plugin, cisco_validator.CiscoCsrVpnValidator(self)) def create_rpc_conn(self): self.endpoints = [CiscoCsrIPsecVpnDriverCallBack(self)] self.conn = n_rpc.create_connection() self.conn.create_consumer( topics.CISCO_IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = CiscoCsrIPsecVpnAgentApi( topics.CISCO_IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self) def create_ipsec_site_connection(self, context, ipsec_site_connection): vpnservice = self.service_plugin._get_vpnservice( context, ipsec_site_connection['vpnservice_id']) csr_id_map.create_tunnel_mapping(context, ipsec_site_connection) self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'], reason='ipsec-conn-create') def update_ipsec_site_connection( self, context, old_ipsec_site_connection, ipsec_site_connection): vpnservice = self.service_plugin._get_vpnservice( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated( context, vpnservice['router_id'], reason='ipsec-conn-update') def delete_ipsec_site_connection(self, context, ipsec_site_connection): vpnservice = self.service_plugin._get_vpnservice( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'], reason='ipsec-conn-delete') def update_vpnservice(self, context, old_vpnservice, vpnservice): self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'], reason='vpn-service-update') def delete_vpnservice(self, context, vpnservice): self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'], reason='vpn-service-delete') def get_cisco_connection_mappings(self, conn_id, context): """Obtain persisted mappings for IDs related to connection.""" tunnel_id, ike_id, ipsec_id = csr_id_map.get_tunnel_mapping_for( conn_id, context.session) return {'site_conn_id': u'Tunnel%d' % tunnel_id, 'ike_policy_id': u'%d' % ike_id, 'ipsec_policy_id': u'%s' % ipsec_id} def _create_interface(self, interface_info): hosting_info = interface_info['hosting_info'] vlan = hosting_info['segmentation_id'] # Port name "currently" is t{1,2}_p:1, as only one router per CSR, # but will keep a semi-generic algorithm port_name = hosting_info['hosting_port_name'] name, sep, num = port_name.partition(':') offset = 1 if name in T2_PORT_NAME else 0 if_num = int(num) * 2 + offset return 'GigabitEthernet%d.%d' % (if_num, vlan) def _get_router_info(self, router_info): hosting_device = router_info['hosting_device'] return {'rest_mgmt_ip': hosting_device['management_ip_address'], 'username': hosting_device['credentials']['username'], 'password': hosting_device['credentials']['password'], 'inner_if_name': self._create_interface( router_info['_interfaces'][0]), 'outer_if_name': self._create_interface( router_info['gw_port']), 'vrf': 'nrouter-' + router_info['id'][:VRF_SUFFIX_LEN], 'timeout': 30} # Hard-coded for now def make_vpnservice_dict(self, context, vpnservice, router_info): """Collect all service info, including Cisco info for IPSec conn.""" vpnservice_dict = dict(vpnservice) # Populate tenant_id for RPC compat vpnservice_dict['tenant_id'] = vpnservice_dict['project_id'] vpnservice_dict['ipsec_conns'] = [] vpnservice_dict['subnet'] = dict(vpnservice.subnet) vpnservice_dict['router_info'] = self._get_router_info(router_info) for ipsec_conn in vpnservice.ipsec_site_connections: ipsec_conn_dict = dict(ipsec_conn) ipsec_conn_dict['ike_policy'] = dict(ipsec_conn.ikepolicy) ipsec_conn_dict['ipsec_policy'] = dict(ipsec_conn.ipsecpolicy) ipsec_conn_dict['peer_cidrs'] = [ peer_cidr.cidr for peer_cidr in ipsec_conn.peer_cidrs] ipsec_conn_dict['cisco'] = self.get_cisco_connection_mappings( ipsec_conn['id'], context) vpnservice_dict['ipsec_conns'].append(ipsec_conn_dict) return vpnservice_dict neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/ipsec_validator.py0000666000175000017500000000721113370230615031210 0ustar zuulzuul00000000000000# Copyright 2015 Awcloud Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import exceptions as nexception from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn.service_drivers import driver_validator class IpsecValidationFailure(nexception.BadRequest): message = _("IPSec does not support %(resource)s attribute %(key)s " "with value '%(value)s'") class IkeValidationFailure(nexception.BadRequest): message = _("IKE does not support %(resource)s attribute %(key)s " "with value '%(value)s'") class IpsecVpnValidator(driver_validator.VpnDriverValidator): """Driver-specific validator methods for the Openswan, Strongswan and Libreswan. """ def _check_transform_protocol(self, context, transform_protocol): """Restrict selecting ah-esp as IPSec Policy transform protocol. For those *Swan implementations, the 'ah-esp' transform protocol is not supported and therefore the request should be rejected. """ if transform_protocol == "ah-esp": raise IpsecValidationFailure( resource='IPsec Policy', key='transform_protocol', value=transform_protocol) def _check_auth_algorithm(self, context, auth_algorithm): """Restrict selecting sha384 and sha512 as IPSec Policy auth algorithm. For those *Swan implementations, the 'sha384' and 'sha512' auth algorithm is not supported and therefore request should be rejected. """ if auth_algorithm in ["sha384", "sha512"]: raise IpsecValidationFailure( resource='IPsec Policy', key='auth_algorithm', value=auth_algorithm) def validate_ipsec_policy(self, context, ipsec_policy): transform_protocol = ipsec_policy.get('transform_protocol') self._check_transform_protocol(context, transform_protocol) auth_algorithm = ipsec_policy.get('auth_algorithm') self._check_auth_algorithm(context, auth_algorithm) def validate_ike_policy(self, context, ike_policy): """Restrict selecting sha384 and sha512 as IKE Policy auth algorithm. For those *Swan implementations, the 'sha384' and 'sha512' auth algorithm is not supported and therefore request should be rejected. """ auth_algorithm = ike_policy.get('auth_algorithm') if auth_algorithm in ["sha384", "sha512"]: raise IkeValidationFailure( resource='IKE Policy', key='auth_algorithm', value=auth_algorithm) def validate_ipsec_site_connection(self, context, ipsec_sitecon): if 'ikepolicy_id' in ipsec_sitecon: ike_policy = self.driver.service_plugin.get_ikepolicy( context, ipsec_sitecon['ikepolicy_id']) self.validate_ike_policy(context, ike_policy) if 'ipsecpolicy_id' in ipsec_sitecon: ipsec_policy = self.driver.service_plugin.get_ipsecpolicy( context, ipsec_sitecon['ipsecpolicy_id']) self.validate_ipsec_policy(context, ipsec_policy) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/cisco_validator.py0000666000175000017500000001430513370230615031207 0ustar zuulzuul00000000000000# 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. import netaddr from netaddr import core as net_exc from neutron_lib import exceptions as nexception from oslo_log import log as logging from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn.service_drivers import driver_validator LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400}, 'IPSec Policy': {'min': 120, 'max': 2592000}} MIN_CSR_MTU = 1500 MAX_CSR_MTU = 9192 LOG = logging.getLogger(__name__) class CsrValidationFailure(nexception.BadRequest): message = _("Cisco CSR does not support %(resource)s attribute %(key)s " "with value '%(value)s'") class CiscoCsrVpnValidator(driver_validator.VpnDriverValidator): """Driver-specific validator methods for the Cisco CSR.""" def validate_lifetime(self, for_policy, policy_info): """Ensure lifetime in secs and value is supported, based on policy.""" units = policy_info['lifetime']['units'] if units != 'seconds': raise CsrValidationFailure(resource=for_policy, key='lifetime:units', value=units) value = policy_info['lifetime']['value'] if (value < LIFETIME_LIMITS[for_policy]['min'] or value > LIFETIME_LIMITS[for_policy]['max']): raise CsrValidationFailure(resource=for_policy, key='lifetime:value', value=value) def validate_ike_version(self, policy_info): """Ensure IKE policy is v1 for current REST API.""" version = policy_info['ike_version'] if version != 'v1': raise CsrValidationFailure(resource='IKE Policy', key='ike_version', value=version) def validate_mtu(self, conn_info): """Ensure the MTU value is supported.""" mtu = conn_info['mtu'] if mtu < MIN_CSR_MTU or mtu > MAX_CSR_MTU: raise CsrValidationFailure(resource='IPSec Connection', key='mtu', value=mtu) def validate_public_ip_present(self, router): """Ensure there is one gateway IP specified for the router used.""" gw_port = router.gw_port if not gw_port or len(gw_port.fixed_ips) != 1: raise CsrValidationFailure(resource='IPSec Connection', key='router:gw_port:ip_address', value='missing') def validate_peer_id(self, ipsec_conn): """Ensure that an IP address is specified for peer ID.""" # TODO(pcm) Should we check peer_address too? peer_id = ipsec_conn['peer_id'] try: netaddr.IPAddress(peer_id) except net_exc.AddrFormatError: raise CsrValidationFailure(resource='IPSec Connection', key='peer_id', value=peer_id) def validate_ipsec_encap_mode(self, ipsec_policy): """Ensure IPSec policy encap mode is tunnel for current REST API.""" mode = ipsec_policy['encapsulation_mode'] if mode != 'tunnel': raise CsrValidationFailure(resource='IPsec Policy', key='encapsulation_mode', value=mode) def validate_ike_auth_algorithm(self, ike_policy): """Ensure IKE Policy auth algorithm is supported.""" auth_algorithm = ike_policy.get('auth_algorithm') if auth_algorithm in ["sha384", "sha512"]: raise CsrValidationFailure(resource='IKE Policy', key='auth_algorithm', value=auth_algorithm) def validate_ipsec_auth_algorithm(self, ipsec_policy): """Ensure IPSec Policy auth algorithm is supported.""" auth_algorithm = ipsec_policy.get('auth_algorithm') if auth_algorithm in ["sha384", "sha512"]: raise CsrValidationFailure(resource='IPsec Policy', key='auth_algorithm', value=auth_algorithm) def validate_ipsec_site_connection(self, context, ipsec_sitecon): """Validate IPSec site connection for Cisco CSR. Do additional checks that relate to the Cisco CSR. """ service_plugin = self.driver.service_plugin if 'ikepolicy_id' in ipsec_sitecon: ike_policy = service_plugin.get_ikepolicy( context, ipsec_sitecon['ikepolicy_id']) self.validate_lifetime('IKE Policy', ike_policy) self.validate_ike_version(ike_policy) self.validate_ike_auth_algorithm(ike_policy) if 'ipsecpolicy_id' in ipsec_sitecon: ipsec_policy = service_plugin.get_ipsecpolicy( context, ipsec_sitecon['ipsecpolicy_id']) self.validate_lifetime('IPSec Policy', ipsec_policy) self.validate_ipsec_auth_algorithm(ipsec_policy) self.validate_ipsec_encap_mode(ipsec_policy) if 'vpnservice_id' in ipsec_sitecon: vpn_service = service_plugin.get_vpnservice( context, ipsec_sitecon['vpnservice_id']) router = self.l3_plugin._get_router( context, vpn_service['router_id']) self.validate_public_ip_present(router) if 'mtu' in ipsec_sitecon: self.validate_mtu(ipsec_sitecon) if 'peer_id' in ipsec_sitecon: self.validate_peer_id(ipsec_sitecon) LOG.debug("IPSec connection validated for Cisco CSR") neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/driver_validator.py0000666000175000017500000000175013370230606031402 0ustar zuulzuul00000000000000# Copyright 2017 Eayun, 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. # class VpnDriverValidator(object): """Driver-specific validation routines for VPN resources.""" def __init__(self, driver): self.driver = driver @property def l3_plugin(self): return self.driver.l3_plugin def validate_ipsec_site_connection(self, context, ipsec_sitecon): """Driver can override this for its additional validations.""" pass neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/cisco_csr_db.py0000666000175000017500000002362413370230615030462 0ustar zuulzuul00000000000000# 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. from neutron_lib.db import model_base from neutron_lib import exceptions as nexception from oslo_db import exception as db_exc from oslo_log import log as logging import sqlalchemy as sa from sqlalchemy.orm import exc as sql_exc from neutron_vpnaas._i18n import _ from neutron_vpnaas.db.vpn import vpn_models LOG = logging.getLogger(__name__) # Note: Artificially limit these to reduce mapping table size and performance # Tunnel can be 0..7FFFFFFF, IKE policy can be 1..10000, IPSec policy can be # 1..31 characters long. MAX_CSR_TUNNELS = 10000 MAX_CSR_IKE_POLICIES = 2000 MAX_CSR_IPSEC_POLICIES = 2000 TUNNEL = 'Tunnel' IKE_POLICY = 'IKE Policy' IPSEC_POLICY = 'IPSec Policy' MAPPING_LIMITS = {TUNNEL: (0, MAX_CSR_TUNNELS), IKE_POLICY: (1, MAX_CSR_IKE_POLICIES), IPSEC_POLICY: (1, MAX_CSR_IPSEC_POLICIES)} class CsrInternalError(nexception.NeutronException): message = _("Fatal - %(reason)s") class IdentifierMap(model_base.BASEV2): """Maps OpenStack IDs to compatible numbers for Cisco CSR.""" __tablename__ = 'cisco_csr_identifier_map' ipsec_site_conn_id = sa.Column(sa.String(36), sa.ForeignKey('ipsec_site_connections.id', ondelete="CASCADE"), primary_key=True) csr_tunnel_id = sa.Column(sa.Integer, nullable=False) csr_ike_policy_id = sa.Column(sa.Integer, nullable=False) csr_ipsec_policy_id = sa.Column(sa.Integer, nullable=False) def get_next_available_id(session, table_field, id_type): """Find first unused id for the specified field in IdentifierMap table. As entries are removed, find the first "hole" and return that as the next available ID. To improve performance, artificially limit the number of entries to a smaller range. Currently, these IDs are globally unique. Could enhance in the future to be unique per router (CSR). """ min_value = MAPPING_LIMITS[id_type][0] max_value = MAPPING_LIMITS[id_type][1] rows = session.query(table_field).order_by(table_field) used_ids = set([row[0] for row in rows]) all_ids = set(range(min_value, max_value + min_value)) available_ids = all_ids - used_ids if not available_ids: msg = _("No available Cisco CSR %(type)s IDs from " "%(min)d..%(max)d") % {'type': id_type, 'min': min_value, 'max': max_value} LOG.error(msg) raise IndexError(msg) return available_ids.pop() def get_next_available_tunnel_id(session): """Find first available tunnel ID from 0..MAX_CSR_TUNNELS-1.""" return get_next_available_id(session, IdentifierMap.csr_tunnel_id, TUNNEL) def get_next_available_ike_policy_id(session): """Find first available IKE Policy ID from 1..MAX_CSR_IKE_POLICIES.""" return get_next_available_id(session, IdentifierMap.csr_ike_policy_id, IKE_POLICY) def get_next_available_ipsec_policy_id(session): """Find first available IPSec Policy ID from 1..MAX_CSR_IKE_POLICIES.""" return get_next_available_id(session, IdentifierMap.csr_ipsec_policy_id, IPSEC_POLICY) def find_conn_with_policy(policy_field, policy_id, conn_id, session): """Return ID of another connection (if any) that uses same policy ID.""" qry = session.query(vpn_models.IPsecSiteConnection.id) match = qry.filter_request( policy_field == policy_id, vpn_models.IPsecSiteConnection.id != conn_id).first() if match: return match[0] def find_connection_using_ike_policy(ike_policy_id, conn_id, session): """Return ID of another connection that uses same IKE policy ID.""" return find_conn_with_policy(vpn_models.IPsecSiteConnection.ikepolicy_id, ike_policy_id, conn_id, session) def find_connection_using_ipsec_policy(ipsec_policy_id, conn_id, session): """Return ID of another connection that uses same IPSec policy ID.""" return find_conn_with_policy(vpn_models.IPsecSiteConnection.ipsecpolicy_id, ipsec_policy_id, conn_id, session) def lookup_policy(policy_type, policy_field, conn_id, session): """Obtain specified policy's mapping from other connection.""" try: return session.query(policy_field).filter_by( ipsec_site_conn_id=conn_id).one()[0] except sql_exc.NoResultFound: msg = _("Database inconsistency between IPSec connection and " "Cisco CSR mapping table (%s)") % policy_type raise CsrInternalError(reason=msg) def lookup_ike_policy_id_for(conn_id, session): """Obtain existing Cisco CSR IKE policy ID from another connection.""" return lookup_policy(IKE_POLICY, IdentifierMap.csr_ike_policy_id, conn_id, session) def lookup_ipsec_policy_id_for(conn_id, session): """Obtain existing Cisco CSR IPSec policy ID from another connection.""" return lookup_policy(IPSEC_POLICY, IdentifierMap.csr_ipsec_policy_id, conn_id, session) def determine_csr_policy_id(policy_type, conn_policy_field, map_policy_field, policy_id, conn_id, session): """Use existing or reserve a new policy ID for Cisco CSR use. TODO(pcm) FUTURE: Once device driver adds support for IKE/IPSec policy ID sharing, add call to find_conn_with_policy() to find used ID and then call lookup_policy() to find the current mapping for that ID. """ csr_id = get_next_available_id(session, map_policy_field, policy_type) LOG.debug("Reserved new CSR ID %(csr_id)d for %(policy)s " "ID %(policy_id)s", {'csr_id': csr_id, 'policy': policy_type, 'policy_id': policy_id}) return csr_id def determine_csr_ike_policy_id(ike_policy_id, conn_id, session): """Use existing, or reserve a new IKE policy ID for Cisco CSR.""" return determine_csr_policy_id(IKE_POLICY, vpn_models.IPsecSiteConnection.ikepolicy_id, IdentifierMap.csr_ike_policy_id, ike_policy_id, conn_id, session) def determine_csr_ipsec_policy_id(ipsec_policy_id, conn_id, session): """Use existing, or reserve a new IPSec policy ID for Cisco CSR.""" return determine_csr_policy_id( IPSEC_POLICY, vpn_models.IPsecSiteConnection.ipsecpolicy_id, IdentifierMap.csr_ipsec_policy_id, ipsec_policy_id, conn_id, session) def get_tunnel_mapping_for(conn_id, session): try: entry = session.query(IdentifierMap).filter_by( ipsec_site_conn_id=conn_id).one() LOG.debug("Mappings for IPSec connection %(conn)s - " "tunnel=%(tunnel)s ike_policy=%(csr_ike)d " "ipsec_policy=%(csr_ipsec)d", {'conn': conn_id, 'tunnel': entry.csr_tunnel_id, 'csr_ike': entry.csr_ike_policy_id, 'csr_ipsec': entry.csr_ipsec_policy_id}) return (entry.csr_tunnel_id, entry.csr_ike_policy_id, entry.csr_ipsec_policy_id) except sql_exc.NoResultFound: msg = _("Existing entry for IPSec connection %s not found in Cisco " "CSR mapping table") % conn_id raise CsrInternalError(reason=msg) def create_tunnel_mapping(context, conn_info): """Create Cisco CSR IDs, using mapping table and OpenStack UUIDs.""" conn_id = conn_info['id'] ike_policy_id = conn_info['ikepolicy_id'] ipsec_policy_id = conn_info['ipsecpolicy_id'] tenant_id = conn_info['tenant_id'] with context.session.begin(): csr_tunnel_id = get_next_available_tunnel_id(context.session) csr_ike_id = determine_csr_ike_policy_id(ike_policy_id, conn_id, context.session) csr_ipsec_id = determine_csr_ipsec_policy_id(ipsec_policy_id, conn_id, context.session) map_entry = IdentifierMap(tenant_id=tenant_id, ipsec_site_conn_id=conn_id, csr_tunnel_id=csr_tunnel_id, csr_ike_policy_id=csr_ike_id, csr_ipsec_policy_id=csr_ipsec_id) try: context.session.add(map_entry) # Force committing to database context.session.flush() except db_exc.DBDuplicateEntry: msg = _("Attempt to create duplicate entry in Cisco CSR " "mapping table for connection %s") % conn_id raise CsrInternalError(reason=msg) LOG.info("Mapped connection %(conn_id)s to Tunnel%(tunnel_id)d " "using IKE policy ID %(ike_id)d and IPSec policy " "ID %(ipsec_id)d", {'conn_id': conn_id, 'tunnel_id': csr_tunnel_id, 'ike_id': csr_ike_id, 'ipsec_id': csr_ipsec_id}) def delete_tunnel_mapping(context, conn_info): conn_id = conn_info['id'] with context.session.begin(): sess_qry = context.session.query(IdentifierMap) sess_qry.filter_by(ipsec_site_conn_id=conn_id).delete() LOG.info("Removed mapping for connection %s", conn_id) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/service_drivers/base_ipsec.py0000666000175000017500000002561213370230615030142 0ustar zuulzuul00000000000000# Copyright 2015, Nachi Ueno, NTT I3, 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. import abc import netaddr import oslo_messaging import six from neutron.db.models import l3agent from neutron.db.models import servicetype from neutron_lib import constants as lib_constants from neutron_lib.plugins import directory from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.services.vpn import service_drivers IPSEC = 'ipsec' BASE_IPSEC_VERSION = '1.0' class IPsecVpnDriverCallBack(object): """Callback for IPSecVpnDriver rpc.""" # history # 1.0 Initial version target = oslo_messaging.Target(version=BASE_IPSEC_VERSION) def __init__(self, driver): super(IPsecVpnDriverCallBack, self).__init__() self.driver = driver def _get_agent_hosting_vpn_services(self, context, host): plugin = directory.get_plugin() agent = plugin._get_agent_by_type_and_host( context, lib_constants.AGENT_TYPE_L3, host) agent_conf = plugin.get_configuration_dict(agent) # Retrieve the agent_mode to check if this is the # right agent to deploy the vpn service. In the # case of distributed the vpn service should reside # only on a dvr_snat node. agent_mode = agent_conf.get('agent_mode', 'legacy') if not agent.admin_state_up or agent_mode == 'dvr': return [] query = context.session.query(vpn_models.VPNService) query = query.join(vpn_models.IPsecSiteConnection) query = query.join(l3agent.RouterL3AgentBinding, l3agent.RouterL3AgentBinding.router_id == vpn_models.VPNService.router_id) query = query.join( servicetype.ProviderResourceAssociation, servicetype.ProviderResourceAssociation.resource_id == vpn_models.VPNService.id) query = query.filter( l3agent.RouterL3AgentBinding.l3_agent_id == agent.id) query = query.filter( servicetype.ProviderResourceAssociation.provider_name == self.driver.name) return query def get_vpn_services_on_host(self, context, host=None): """Returns the vpnservices on the host.""" vpnservices = self._get_agent_hosting_vpn_services( context, host) plugin = self.driver.service_plugin local_cidr_map = plugin._build_local_subnet_cidr_map(context) return [self.driver.make_vpnservice_dict(vpnservice, local_cidr_map) for vpnservice in vpnservices] def update_status(self, context, status): """Update status of vpnservices.""" plugin = self.driver.service_plugin plugin.update_status_by_agent(context, status) class IPsecVpnAgentApi(service_drivers.BaseIPsecVpnAgentApi): """Agent RPC API for IPsecVPNAgent.""" target = oslo_messaging.Target(version=BASE_IPSEC_VERSION) def __init__(self, topic, default_version, driver): super(IPsecVpnAgentApi, self).__init__( topic, default_version, driver) @six.add_metaclass(abc.ABCMeta) class BaseIPsecVPNDriver(service_drivers.VpnDriver): """Base VPN Service Driver class.""" def __init__(self, service_plugin, validator=None): super(BaseIPsecVPNDriver, self).__init__(service_plugin, validator) self.create_rpc_conn() @property def service_type(self): return IPSEC @abc.abstractmethod def create_rpc_conn(self): pass def create_ipsec_site_connection(self, context, ipsec_site_connection): vpnservice = self.service_plugin._get_vpnservice( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated(context, vpnservice['router_id']) def update_ipsec_site_connection( self, context, old_ipsec_site_connection, ipsec_site_connection): vpnservice = self.service_plugin._get_vpnservice( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated(context, vpnservice['router_id']) def delete_ipsec_site_connection(self, context, ipsec_site_connection): vpnservice = self.service_plugin._get_vpnservice( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated(context, vpnservice['router_id']) def create_ikepolicy(self, context, ikepolicy): pass def delete_ikepolicy(self, context, ikepolicy): pass def update_ikepolicy(self, context, old_ikepolicy, ikepolicy): pass def create_ipsecpolicy(self, context, ipsecpolicy): pass def delete_ipsecpolicy(self, context, ipsecpolicy): pass def update_ipsecpolicy(self, context, old_ipsec_policy, ipsecpolicy): pass def _get_gateway_ips(self, router): """Obtain the IPv4 and/or IPv6 GW IP for the router. If there are multiples, (arbitrarily) use the first one. """ v4_ip = v6_ip = None for fixed_ip in router.gw_port['fixed_ips']: addr = fixed_ip['ip_address'] vers = netaddr.IPAddress(addr).version if vers == 4: if v4_ip is None: v4_ip = addr elif v6_ip is None: v6_ip = addr return v4_ip, v6_ip def create_vpnservice(self, context, vpnservice_dict): """Get the gateway IP(s) and save for later use. For the reference implementation, this side's tunnel IP (external_ip) will be the router's GW IP. IPSec connections will use a GW IP of the same version, as is used for the peer, so we will collect the first IP for each version (if they exist) and save them. """ vpnservice = self.service_plugin._get_vpnservice(context, vpnservice_dict['id']) v4_ip, v6_ip = self._get_gateway_ips(vpnservice.router) vpnservice_dict['external_v4_ip'] = v4_ip vpnservice_dict['external_v6_ip'] = v6_ip self.service_plugin.set_external_tunnel_ips(context, vpnservice_dict['id'], v4_ip=v4_ip, v6_ip=v6_ip) def update_vpnservice(self, context, old_vpnservice, vpnservice): self.agent_rpc.vpnservice_updated(context, vpnservice['router_id']) def delete_vpnservice(self, context, vpnservice): self.agent_rpc.vpnservice_updated(context, vpnservice['router_id']) def get_external_ip_based_on_peer(self, vpnservice, ipsec_site_con): """Use service's external IP, based on peer IP version.""" vers = netaddr.IPAddress(ipsec_site_con['peer_address']).version if vers == 4: ip_to_use = vpnservice.external_v4_ip else: ip_to_use = vpnservice.external_v6_ip # TODO(pcm): Add validator to check that connection's peer address has # a version that is available in service table, so can fail early and # don't need a check here. return ip_to_use def make_vpnservice_dict(self, vpnservice, local_cidr_map): """Convert vpnservice information for vpn agent. also converting parameter name for vpn agent driver """ vpnservice_dict = dict(vpnservice) # Populate tenant_id for RPC compat vpnservice_dict['tenant_id'] = vpnservice_dict['project_id'] vpnservice_dict['ipsec_site_connections'] = [] if vpnservice.subnet: vpnservice_dict['subnet'] = dict(vpnservice.subnet) else: vpnservice_dict['subnet'] = None # NOTE: Following is used for rolling upgrades, where agent may be # at version N, and server at N+1. We need to populate the subnet # with (only) the CIDR from the first connection's local endpoint # group. subnet_cidr = None # Not removing external_ip from vpnservice_dict, as some providers # may be still using it from vpnservice_dict. Will use whichever IP # is specified. vpnservice_dict['external_ip'] = ( vpnservice.external_v4_ip or vpnservice.external_v6_ip) for ipsec_site_connection in vpnservice.ipsec_site_connections: ipsec_site_connection_dict = dict(ipsec_site_connection) try: netaddr.IPAddress(ipsec_site_connection_dict['peer_id']) if ipsec_site_connection_dict['local_id']: netaddr.IPAddress(ipsec_site_connection_dict['local_id']) except netaddr.core.AddrFormatError: ipsec_site_connection_dict['peer_id'] = ( '@' + ipsec_site_connection_dict['peer_id']) if ipsec_site_connection_dict['local_id']: ipsec_site_connection_dict['local_id'] = ( '@' + ipsec_site_connection_dict['local_id']) ipsec_site_connection_dict['ikepolicy'] = dict( ipsec_site_connection.ikepolicy) ipsec_site_connection_dict['ipsecpolicy'] = dict( ipsec_site_connection.ipsecpolicy) vpnservice_dict['ipsec_site_connections'].append( ipsec_site_connection_dict) if vpnservice.subnet: local_cidrs = [vpnservice.subnet.cidr] peer_cidrs = [ peer_cidr.cidr for peer_cidr in ipsec_site_connection.peer_cidrs] else: local_cidrs = [local_cidr_map[ep.endpoint] for ep in ipsec_site_connection.local_ep_group.endpoints] peer_cidrs = [ ep.endpoint for ep in ipsec_site_connection.peer_ep_group.endpoints] if not subnet_cidr: epg = ipsec_site_connection.local_ep_group subnet_cidr = local_cidr_map[epg.endpoints[0].endpoint] ipsec_site_connection_dict['peer_cidrs'] = peer_cidrs ipsec_site_connection_dict['local_cidrs'] = local_cidrs ipsec_site_connection_dict['local_ip_vers'] = netaddr.IPNetwork( local_cidrs[0]).version ipsec_site_connection_dict['external_ip'] = ( self.get_external_ip_based_on_peer(vpnservice, ipsec_site_connection_dict)) if not vpnservice.subnet: vpnservice_dict['subnet'] = {'cidr': subnet_cidr} return vpnservice_dict neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/common/0000775000175000017500000000000013370231105023550 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/common/__init__.py0000666000175000017500000000000013370230606025656 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/common/netns_wrapper.py0000666000175000017500000001326713370230606027031 0ustar zuulzuul00000000000000# Copyright (c) 2015 OpenStack Foundation. # 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 errno import os import sys from eventlet.green import subprocess from neutron.common import config from neutron.common import utils from oslo_config import cfg from oslo_log import log as logging from oslo_rootwrap import wrapper import six from neutron_vpnaas._i18n import _ if six.PY3: import configparser as ConfigParser else: import ConfigParser LOG = logging.getLogger(__name__) def setup_conf(): cli_opts = [ cfg.DictOpt('mount_paths', required=True, help=_('Dict of paths to bind-mount (source:target) ' 'prior to launch subprocess.')), cfg.ListOpt( 'cmd', required=True, help=_('Command line to execute as a subprocess ' 'provided as comma-separated list of arguments.')), cfg.StrOpt('rootwrap_config', default='/etc/neutron/rootwrap.conf', help=_('Rootwrap configuration file.')), ] conf = cfg.CONF conf.register_cli_opts(cli_opts) return conf def execute(cmd): if not cmd: return cmd = map(str, cmd) LOG.debug("Running command: %s", cmd) env = os.environ.copy() obj = utils.subprocess_popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) _stdout, _stderr = obj.communicate() msg = ('Command: %(cmd)s Exit code: %(returncode)s ' 'Stdout: %(stdout)s Stderr: %(stderr)s' % {'cmd': cmd, 'returncode': obj.returncode, 'stdout': _stdout, 'stderr': _stderr}) LOG.debug(msg) obj.stdin.close() # Pass the output to calling process sys.stdout.write(msg) sys.stdout.flush() return obj.returncode def filter_command(command, rootwrap_config): # Load rootwrap configuration try: rawconfig = ConfigParser.RawConfigParser() rawconfig.read(rootwrap_config) rw_config = wrapper.RootwrapConfig(rawconfig) except ValueError as exc: LOG.error('Incorrect value in %(config)s: %(exc)s', {'config': rootwrap_config, 'exc': exc}) sys.exit(errno.EINVAL) except ConfigParser.Error: LOG.error('Incorrect configuration file: %(config)s', {'config': rootwrap_config}) sys.exit(errno.EINVAL) # Check if command matches any of the loaded filters filters = wrapper.load_filters(rw_config.filters_path) try: wrapper.match_filter(filters, command, exec_dirs=rw_config.exec_dirs) except wrapper.FilterMatchNotExecutable as exc: LOG.error('Command %(command)s is not executable: ' '%(path)s (filter match = %(name)s)', {'command': command, 'path': exc.match.exec_path, 'name': exc.match.name}) sys.exit(errno.EINVAL) except wrapper.NoFilterMatched: LOG.error('Unauthorized command: %(cmd)s (no filter matched)', {'cmd': command}) sys.exit(errno.EPERM) def execute_with_mount(): conf = setup_conf() conf() config.setup_logging() if not conf.cmd: LOG.error('No command provided, exiting') return errno.EINVAL if not conf.mount_paths: LOG.error('No mount path provided, exiting') return errno.EINVAL # Both sudoers and rootwrap.conf will not exist in the directory /etc # after bind-mount, so we can't use utils.execute(conf.cmd, # run_as_root=True). That's why we have to check here if cmd matches # CommandFilter filter_command(conf.cmd, conf.rootwrap_config) # Make sure the process is running in net namespace invoked by ip # netns exec(/proc/[pid]/ns/net) which is since Linux 3.0, # as we can't check mount namespace(/proc/[pid]/ns/mnt) # which is since Linux 3.8. For more detail please refer the link # http://man7.org/linux/man-pages/man7/namespaces.7.html if os.path.samefile(os.path.join('/proc/1/ns/net'), os.path.join('/proc', str(os.getpid()), 'ns/net')): LOG.error('Cannot run without netns, exiting') return errno.EINVAL for path, new_path in conf.mount_paths.items(): if not os.path.isdir(new_path): # Sometimes all directories are not ready LOG.debug('%s is not directory', new_path) continue if os.path.isdir(path) and os.path.isabs(path): return_code = execute(['mount', '--bind', new_path, path]) if return_code == 0: LOG.info('%(new_path)s has been ' 'bind-mounted in %(path)s', {'new_path': new_path, 'path': path}) else: LOG.error('Failed to bind-mount ' '%(new_path)s in %(path)s', {'new_path': new_path, 'path': path}) return execute(conf.cmd) def main(): sys.exit(execute_with_mount()) if __name__ == "__main__": main() neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/common/topics.py0000666000175000017500000000164213370230615025435 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, 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. IPSEC_DRIVER_TOPIC = 'ipsec_driver' IPSEC_AGENT_TOPIC = 'ipsec_agent' CISCO_IPSEC_DRIVER_TOPIC = 'cisco_csr_ipsec_driver' CISCO_IPSEC_AGENT_TOPIC = 'cisco_csr_ipsec_agent' BROCADE_IPSEC_DRIVER_TOPIC = 'brocade_vyatta_ipsec_driver' BROCADE_IPSEC_AGENT_TOPIC = 'brocade_vyatta_ipsec_agent' neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/common/constants.py0000666000175000017500000000227513370230606026153 0ustar zuulzuul00000000000000# Copyright 2015 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. # Endpoint group types SUBNET_ENDPOINT = 'subnet' CIDR_ENDPOINT = 'cidr' VLAN_ENDPOINT = 'vlan' NETWORK_ENDPOINT = 'network' ROUTER_ENDPOINT = 'router' # NOTE: Type usage... # IPSec local endpoints - subnet, IPSec peer endpoints - cidr # BGP VPN local endpoints - network # Direct connect style endpoints - vlan # IMPORTANT: The ordering of these is important, as it is used in an enum # for the database (and migration script). Only add to this list. VPN_SUPPORTED_ENDPOINT_TYPES = [ SUBNET_ENDPOINT, CIDR_ENDPOINT, NETWORK_ENDPOINT, VLAN_ENDPOINT, ROUTER_ENDPOINT, ] neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/0000775000175000017500000000000013370231105025255 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/__init__.py0000666000175000017500000000176513370230606027406 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, 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. import abc import six @six.add_metaclass(abc.ABCMeta) class DeviceDriver(object): def __init__(self, agent, host): pass @abc.abstractmethod def sync(self, context, processes): pass @abc.abstractmethod def create_router(self, process_id): pass @abc.abstractmethod def destroy_router(self, process_id): pass neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/vyatta_ipsec.py0000666000175000017500000002513113370230615030333 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, 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. import collections import pprint from networking_brocade.vyatta.common import exceptions as v_exc from networking_brocade.vyatta.common import vrouter_config from networking_brocade.vyatta.vpn import config as vyatta_vpn_config from neutron.common import rpc as n_rpc from neutron_lib import context as n_ctx from oslo_config import cfg from oslo_log import log as logging import oslo_messaging as messaging from oslo_service import loopingcall from oslo_service import periodic_task from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn import device_drivers LOG = logging.getLogger(__name__) _KEY_CONNECTIONS = 'ipsec_site_connections' _KEY_IKEPOLICY = 'ikepolicy' _KEY_ESPPOLICY = 'ipsecpolicy' class _DriverRPCEndpoint(object): """ VPN device driver RPC endpoint (server > agent) history 1.0 Initial version """ target = messaging.Target(version='1.0') def __init__(self, driver): self.driver = driver def vpnservice_updated(self, context, **kwargs): self.driver.sync(context, []) class NeutronServerAPI(object): """ VPN service driver RPC endpoint (agent > server) """ def __init__(self, topic): target = messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) def get_vpn_services_on_host(self, context, host): # make RPC call to neutron server cctxt = self.client.prepare() data = cctxt.call(context, 'get_vpn_services_on_host', host=host) vpn_services = list() for svc in data: try: for conn in svc[_KEY_CONNECTIONS]: vyatta_vpn_config.validate_svc_connection(conn) except v_exc.InvalidVPNServiceError: LOG.error('Invalid or incomplete VPN service data: ' 'id={id}'.format(id=svc.get('id'))) continue vpn_services.append(svc) # return transformed data to caller return vpn_services def update_status(self, context, status): cctxt = self.client.prepare() cctxt.cast(context, 'update_status', status=status) class VyattaIPSecDriver(device_drivers.DeviceDriver): """ Vyatta VPN device driver """ rpc_endpoint_factory = _DriverRPCEndpoint def __init__(self, vpn_service, host): super(VyattaIPSecDriver, self).__init__(vpn_service, host) self.vpn_service = vpn_service self.host = host # register RPC endpoint conn = n_rpc.create_connection() node_topic = '%s.%s' % (topics.BROCADE_IPSEC_AGENT_TOPIC, self.host) endpoints = [self.rpc_endpoint_factory(self)] conn.create_consumer(node_topic, endpoints, fanout=False) conn.consume_in_threads() # initialize agent to server RPC link self.server_api = NeutronServerAPI( topics.BROCADE_IPSEC_DRIVER_TOPIC) # initialize VPN service cache (to keep service state) self._svc_cache = list() self._router_resources_cache = dict() # setup periodic task. All periodic task require fully configured # device driver. It will be called asynchronously, and soon, so it # should be last, when all configuration is done. self._periodic_tasks = periodic = _VyattaPeriodicTasks(self) loop = loopingcall.DynamicLoopingCall(periodic) loop.start(initial_delay=5) def sync(self, context, processes): """ Called by _DriverRPCEndpoint instance. """ svc_update = self.server_api.get_vpn_services_on_host( context, self.host) to_del, to_change, to_add = self._svc_diff( self._svc_cache, svc_update) for svc in to_del: resources = self.get_router_resources(svc['router_id']) self._svc_delete(svc, resources) for old, new in to_change: resources = self.get_router_resources(old['router_id']) self._svc_delete(old, resources) self._svc_add(new, resources) for svc in to_add: resources = self.get_router_resources(svc['router_id']) self._svc_add(svc, resources) self._svc_cache = svc_update def create_router(self, router): router_id = router.router_id vrouter = self.vpn_service.get_router_client(router_id) config_raw = vrouter.get_vrouter_configuration() resources = self.get_router_resources(router_id) with resources.make_patch() as patch: vrouter_svc = vyatta_vpn_config.parse_vrouter_config( vrouter_config.parse_config(config_raw), patch) for svc in vrouter_svc: svc['router_id'] = router_id self._svc_cache.extend(vrouter_svc) def destroy_router(self, router_id): to_del = list() for idx, svc in enumerate(self._svc_cache): if svc['router_id'] != router_id: continue resources = self.get_router_resources(svc['router_id']) self._svc_delete(svc, resources) to_del.insert(0, idx) for idx in to_del: del self._svc_cache[idx] def _svc_add(self, svc, resources): vrouter = self.vpn_service.get_router_client(svc['router_id']) for conn in svc[_KEY_CONNECTIONS]: with resources.make_patch() as patch: iface = self._get_router_gw_iface(vrouter, svc['router_id']) batch = vyatta_vpn_config.connect_setup_commands( vrouter, iface, svc, conn, patch) vrouter.exec_cmd_batch(batch) def _svc_delete(self, svc, resources): vrouter = self.vpn_service.get_router_client(svc['router_id']) for conn in svc[_KEY_CONNECTIONS]: with resources.make_patch() as patch: iface = self._get_router_gw_iface(vrouter, svc['router_id']) batch = vyatta_vpn_config.connect_remove_commands( vrouter, iface, svc, conn, patch) vrouter.exec_cmd_batch(batch) def _svc_diff(self, svc_old, svc_new): state_key = 'admin_state_up' old_idnr = set(x['id'] for x in svc_old) new_idnr = set(x['id'] for x in svc_new if x[state_key]) to_del = old_idnr - new_idnr to_add = new_idnr - old_idnr possible_change = old_idnr & new_idnr svc_old = dict((x['id'], x) for x in svc_old) svc_new = dict((x['id'], x) for x in svc_new) to_del = [svc_old[x] for x in to_del] to_add = [svc_new[x] for x in to_add] to_change = list() for idnr in possible_change: old = svc_old[idnr] new = svc_new[idnr] assert old['router_id'] == new['router_id'] vrouter = self.vpn_service.get_router_client(old['router_id']) gw_iface = self._get_router_gw_iface(vrouter, old['router_id']) if vyatta_vpn_config.compare_vpn_services( vrouter, gw_iface, old, new): continue to_change.append((old, new)) return to_del, to_change, to_add def get_active_services(self): return tuple(self._svc_cache) def get_router_resources(self, router_id): try: res = self._router_resources_cache[router_id] except KeyError: res = vyatta_vpn_config.RouterResources(router_id) self._router_resources_cache[router_id] = res return res def update_status(self, ctx, stat): LOG.debug('STAT: %s', pprint.pformat(stat)) self.server_api.update_status(ctx, stat) def _get_router_gw_iface(self, vrouter, router_id): router = self.vpn_service.get_router(router_id) try: gw_interface = vrouter.get_ethernet_if_id( router['gw_port']['mac_address']) except KeyError: raise v_exc.InvalidL3AgentStateError(description=_( 'Router id={0} have no external gateway.').format( router['id'])) return gw_interface class _VyattaPeriodicTasks(periodic_task.PeriodicTasks): def __init__(self, driver): super(_VyattaPeriodicTasks, self).__init__(cfg.CONF) self.driver = driver def __call__(self): ctx_admin = n_ctx.get_admin_context() return self.run_periodic_tasks(ctx_admin) @periodic_task.periodic_task(spacing=5) def grab_vpn_status(self, ctx): LOG.debug('VPN device driver periodic task: grab_vpn_status.') svc_by_vrouter = collections.defaultdict(list) for svc in self.driver.get_active_services(): svc_by_vrouter[svc['router_id']].append(svc) status = list() for router_id, svc_set in svc_by_vrouter.items(): vrouter = self.driver.vpn_service.get_router_client(router_id) resources = self.driver.get_router_resources(router_id) try: ipsec_sa = vrouter.get_vpn_ipsec_sa() except v_exc.VRouterOperationError as e: LOG.warning('Failed to fetch tunnel stats from router ' '{0}: {1}'.format(router_id, unicode(e))) continue conn_ok = vyatta_vpn_config.parse_vpn_connections( ipsec_sa, resources) for svc in svc_set: svc_ok = True conn_stat = dict() for conn in svc[_KEY_CONNECTIONS]: ok = conn['id'] in conn_ok svc_ok = svc_ok and ok conn_stat[conn['id']] = { 'status': 'ACTIVE' if ok else 'DOWN', 'updated_pending_status': True } status.append({ 'id': svc['id'], 'status': 'ACTIVE' if svc_ok else 'DOWN', 'updated_pending_status': True, 'ipsec_site_connections': conn_stat }) self.driver.update_status(ctx, status) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/0000775000175000017500000000000013370231105027070 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/0000775000175000017500000000000013370231105030722 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.templateneutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.templa0000666000175000017500000000727713370230615034201 0ustar zuulzuul00000000000000# Configuration for {{vpnservice.name}} config setup nat_traversal=yes virtual_private={{virtual_privates}} conn %default ikelifetime=480m keylife=60m keyingtries=%forever {% for ipsec_site_connection in vpnservice.ipsec_site_connections if ipsec_site_connection.admin_state_up -%} conn {{ipsec_site_connection.id}} {% if ipsec_site_connection['local_ip_vers'] == 6 -%} # To recognize the given IP addresses in this config # as IPv6 addresses by pluto whack. Default is ipv4 connaddrfamily=ipv6 # openswan can't process defaultroute for ipv6. # Assign gateway address as leftnexthop leftnexthop={{ipsec_site_connection.external_ip}} # rightnexthop is not mandatory for ipsec, so no need in ipv6. {% else -%} # NOTE: a default route is required for %defaultroute to work... leftnexthop=%defaultroute rightnexthop=%defaultroute {% endif -%} left={{ipsec_site_connection.external_ip}} leftid={{ipsec_site_connection.local_id}} auto={{ipsec_site_connection.initiator}} # NOTE:REQUIRED # [subnet] {% if ipsec_site_connection['local_cidrs']|length == 1 -%} leftsubnet={{ipsec_site_connection['local_cidrs'][0]}} {% else -%} leftsubnets={ {{ipsec_site_connection['local_cidrs']|join(' ')}} } {% endif -%} # [updown] # What "updown" script to run to adjust routing and/or firewalling when # the status of the connection changes (default "ipsec _updown"). # "--route yes" allows to specify such routing options as mtu and metric. leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ###################### # [peer_address] right={{ipsec_site_connection.peer_address}} # [peer_id] rightid={{ipsec_site_connection.peer_id}} # [peer_cidrs] rightsubnets={ {{ipsec_site_connection['peer_cidrs']|join(' ')}} } # rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) # [mtu] mtu={{ipsec_site_connection.mtu}} # [dpd_action] dpdaction={{ipsec_site_connection.dpd_action}} # [dpd_interval] dpddelay={{ipsec_site_connection.dpd_interval}} # [dpd_timeout] dpdtimeout={{ipsec_site_connection.dpd_timeout}} # [auth_mode] authby=secret ###################### # IKEPolicy params ###################### #ike version ikev2={{ipsec_site_connection.ikepolicy.ike_version}} # [encryption_algorithm]-[auth_algorithm]-[pfs] ike={{ipsec_site_connection.ikepolicy.encryption_algorithm}}-{{ipsec_site_connection.ikepolicy.auth_algorithm}};{{ipsec_site_connection.ikepolicy.pfs}} # [lifetime_value] ikelifetime={{ipsec_site_connection.ikepolicy.lifetime_value}}s # NOTE: it looks lifetime_units=kilobytes can't be enforced (could be seconds, hours, days...) ########################## # IPsecPolicys params ########################## # [transform_protocol] phase2={{ipsec_site_connection.ipsecpolicy.transform_protocol}} {% if ipsec_site_connection.ipsecpolicy.transform_protocol == "ah" -%} # AH protocol does not support encryption # [auth_algorithm]-[pfs] phase2alg={{ipsec_site_connection.ipsecpolicy.auth_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}} {% else -%} # [encryption_algorithm]-[auth_algorithm]-[pfs] phase2alg={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}} {% endif -%} # [encapsulation_mode] type={{ipsec_site_connection.ipsecpolicy.encapsulation_mode}} # [lifetime_value] lifetime={{ipsec_site_connection.ipsecpolicy.lifetime_value}}s # lifebytes=100000 if lifetime_units=kilobytes (IKEv2 only) {% endfor -%} ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.secret.templateneutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.secret.temp0000666000175000017500000000034613370230615034212 0ustar zuulzuul00000000000000# Configuration for {{vpnservice.name}} {% for ipsec_site_connection in vpnservice.ipsec_site_connections -%} {{ipsec_site_connection.local_id}} {{ipsec_site_connection.peer_id}} : PSK "{{ipsec_site_connection.psk}}" {% endfor %} neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/0000775000175000017500000000000013370231105031275 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.templateneutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.temp0000666000175000017500000000312013370230615034216 0ustar zuulzuul00000000000000# Configuration for {{vpnservice.name}} config setup conn %default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 authby=psk mobike=no {% for ipsec_site_connection in vpnservice.ipsec_site_connections%} conn {{ipsec_site_connection.id}} keyexchange={{ipsec_site_connection.ikepolicy.ike_version}} left={{ipsec_site_connection.external_ip}} leftsubnet={{ipsec_site_connection['local_cidrs']|join(',')}} leftid={{ipsec_site_connection.local_id}} leftfirewall=yes right={{ipsec_site_connection.peer_address}} rightsubnet={{ipsec_site_connection['peer_cidrs']|join(',')}} rightid={{ipsec_site_connection.peer_id}} auto=route dpdaction={{ipsec_site_connection.dpd_action}} dpddelay={{ipsec_site_connection.dpd_interval}}s dpdtimeout={{ipsec_site_connection.dpd_timeout}}s ike={{ipsec_site_connection.ikepolicy.encryption_algorithm}}-{{ipsec_site_connection.ikepolicy.auth_algorithm}}-{{ipsec_site_connection.ikepolicy.pfs}} ikelifetime={{ipsec_site_connection.ikepolicy.lifetime_value}}s {%- if ipsec_site_connection.ipsecpolicy.transform_protocol == "ah" %} ah={{ipsec_site_connection.ipsecpolicy.auth_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}} {%- else %} esp={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}} {%- endif %} lifetime={{ipsec_site_connection.ipsecpolicy.lifetime_value}}s type={{ipsec_site_connection.ipsecpolicy.encapsulation_mode}} {% endfor %} ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.secret.templateneutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.secret.te0000666000175000017500000000034413370230615034226 0ustar zuulzuul00000000000000# Configuration for {{vpnservice.name}}{% for ipsec_site_connection in vpnservice.ipsec_site_connections %} {{ipsec_site_connection.local_id}} {{ipsec_site_connection.peer_id}} : PSK "{{ipsec_site_connection.psk}}" {% endfor %} ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/strongswan.conf.templateneutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/strongswan.conf0000666000175000017500000000022213370230606034354 0ustar zuulzuul00000000000000charon { load_modular = yes plugins { include strongswan.d/charon/*.conf } } include strongswan.d/*.conf neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/ipsec.py0000666000175000017500000012263313370230615026750 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, 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. import abc import copy import filecmp import os import re import shutil import socket import eventlet import jinja2 import netaddr from neutron.agent.linux import ip_lib from neutron.agent.linux import utils as agent_utils from neutron.common import rpc as n_rpc from neutron.plugins.common import utils as plugin_utils from neutron_lib.api import validators from neutron_lib import constants from neutron_lib import context from neutron_lib.utils import file as file_utils from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging import oslo_messaging from oslo_service import loopingcall from oslo_utils import fileutils import six from neutron_vpnaas._i18n import _ from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn import device_drivers LOG = logging.getLogger(__name__) TEMPLATE_PATH = os.path.dirname(os.path.abspath(__file__)) ipsec_opts = [ cfg.StrOpt( 'config_base_dir', default='$state_path/ipsec', help=_('Location to store ipsec server config files')), cfg.IntOpt('ipsec_status_check_interval', default=60, help=_("Interval for checking ipsec status")), cfg.BoolOpt('enable_detailed_logging', default=False, help=_("Enable detail logging for ipsec pluto process. " "If the flag set to True, the detailed logging will " "be written into config_base_dir//log. " "Note: This setting applies to OpenSwan and LibreSwan " "only. StrongSwan logs to syslog.")), ] cfg.CONF.register_opts(ipsec_opts, 'ipsec') openswan_opts = [ cfg.StrOpt( 'ipsec_config_template', default=os.path.join( TEMPLATE_PATH, 'template/openswan/ipsec.conf.template'), help=_('Template file for ipsec configuration')), cfg.StrOpt( 'ipsec_secret_template', default=os.path.join( TEMPLATE_PATH, 'template/openswan/ipsec.secret.template'), help=_('Template file for ipsec secret configuration')) ] cfg.CONF.register_opts(openswan_opts, 'openswan') pluto_opts = [ cfg.IntOpt('shutdown_check_timeout', default=1, help=_('Initial interval in seconds for checking if pluto ' 'daemon is shutdown'), deprecated_group='libreswan'), cfg.IntOpt('shutdown_check_retries', default=5, help=_('The maximum number of retries for checking for ' 'pluto daemon shutdown'), deprecated_group='libreswan'), cfg.FloatOpt('shutdown_check_back_off', default=1.5, help=_('A factor to increase the retry interval for ' 'each retry'), deprecated_group='libreswan'), cfg.BoolOpt('restart_check_config', default=False, help=_('Enable this flag to avoid from unnecessary restart'), deprecated_group='libreswan') ] cfg.CONF.register_opts(pluto_opts, 'pluto') JINJA_ENV = None IPSEC_CONNS = 'ipsec_site_connections' def _get_template(template_file): global JINJA_ENV if not JINJA_ENV: templateLoader = jinja2.FileSystemLoader(searchpath="/") JINJA_ENV = jinja2.Environment(loader=templateLoader, autoescape=True) return JINJA_ENV.get_template(template_file) @six.add_metaclass(abc.ABCMeta) class BaseSwanProcess(object): """Swan Family Process Manager This class manages start/restart/stop ipsec process. This class create/delete config template """ binary = "ipsec" CONFIG_DIRS = [ 'var/run', 'log', 'etc', 'etc/ipsec.d/aacerts', 'etc/ipsec.d/acerts', 'etc/ipsec.d/cacerts', 'etc/ipsec.d/certs', 'etc/ipsec.d/crls', 'etc/ipsec.d/ocspcerts', 'etc/ipsec.d/policies', 'etc/ipsec.d/private', 'etc/ipsec.d/reqs', 'etc/pki/nssdb/' ] DIALECT_MAP = { "3des": "3des", "aes-128": "aes128", "aes-256": "aes256", "aes-192": "aes192", "sha256": "sha2_256", "group2": "modp1024", "group5": "modp1536", "group14": "modp2048", "group15": "modp3072", "bi-directional": "start", "response-only": "add", "v2": "insist", "v1": "never" } STATUS_DICT = { 'erouted': constants.ACTIVE, 'unrouted': constants.DOWN } STATUS_RE = '\d\d\d "([a-f0-9\-]+).* (unrouted|erouted);' STATUS_NOT_RUNNING_RE = 'Command:.*ipsec.*status.*Exit code: [1|3]$' STATUS_IPSEC_SA_ESTABLISHED_RE = ( '\d{3} #\d+: "([a-f0-9\-]+).*established.*newest IPSEC') STATUS_IPSEC_SA_ESTABLISHED_RE2 = ( '\d{3} #\d+: "([a-f0-9\-\/x]+).*established.*newest IPSEC') def __init__(self, conf, process_id, vpnservice, namespace): self.conf = conf self.id = process_id self.updated_pending_status = False self.namespace = namespace self.connection_status = {} self.config_dir = os.path.join( self.conf.ipsec.config_base_dir, self.id) self.etc_dir = os.path.join(self.config_dir, 'etc') self.log_dir = os.path.join(self.config_dir, 'log') self.update_vpnservice(vpnservice) self.STATUS_PATTERN = re.compile(self.STATUS_RE) self.STATUS_NOT_RUNNING_PATTERN = re.compile( self.STATUS_NOT_RUNNING_RE) self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN = re.compile( self.STATUS_IPSEC_SA_ESTABLISHED_RE) self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN2 = re.compile( self.STATUS_IPSEC_SA_ESTABLISHED_RE2) self.STATUS_MAP = self.STATUS_DICT def translate_dialect(self): if not self.vpnservice: return for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: self._dialect(ipsec_site_conn, 'initiator') self._dialect(ipsec_site_conn['ikepolicy'], 'ike_version') for key in ['encryption_algorithm', 'auth_algorithm', 'pfs']: self._dialect(ipsec_site_conn['ikepolicy'], key) self._dialect(ipsec_site_conn['ipsecpolicy'], key) if (('local_id' not in ipsec_site_conn.keys()) or (not ipsec_site_conn['local_id'])): ipsec_site_conn['local_id'] = ipsec_site_conn['external_ip'] def update_vpnservice(self, vpnservice): self.vpnservice = vpnservice self.translate_dialect() def _dialect(self, obj, key): obj[key] = self.DIALECT_MAP.get(obj[key], obj[key]) @abc.abstractmethod def ensure_configs(self): pass def ensure_config_file(self, kind, template, vpnservice, file_mode=None): """Update config file, based on current settings for service.""" config_str = self._gen_config_content(template, vpnservice) config_file_name = self._get_config_filename(kind) if file_mode is None: file_utils.replace_file(config_file_name, config_str) else: file_utils.replace_file(config_file_name, config_str, file_mode) def remove_config(self): """Remove whole config file.""" agent_utils.execute( cmd=["rm", "-rf", self.config_dir], run_as_root=True) def _get_config_filename(self, kind): config_dir = self.etc_dir return os.path.join(config_dir, kind) def ensure_config_dir(self, vpnservice): """Create config directory if it does not exist.""" fileutils.ensure_tree(self.config_dir, 0o755) for subdir in self.CONFIG_DIRS: dir_path = os.path.join(self.config_dir, subdir) fileutils.ensure_tree(dir_path, 0o755) def _gen_config_content(self, template_file, vpnservice): template = _get_template(template_file) return template.render( {'vpnservice': vpnservice, 'state_path': self.conf.state_path}) @abc.abstractmethod def get_status(self): pass @property def status(self): if self.active: return constants.ACTIVE return constants.DOWN @property def active(self): """Check if the process is active or not.""" if not self.namespace: return False try: status = self.get_status() self._extract_and_record_connection_status(status) if not self.connection_status: return False except RuntimeError: return False return True def update(self): """Update Status based on vpnservice configuration.""" # Disable the process if a vpnservice is disabled or it has no # enabled IPSec site connections. vpnservice_has_active_ipsec_site_conns = any( [ipsec_site_conn['admin_state_up'] for ipsec_site_conn in self.vpnservice['ipsec_site_connections']]) if (not self.vpnservice['admin_state_up'] or not vpnservice_has_active_ipsec_site_conns): self.disable() else: self.enable() if plugin_utils.in_pending_status(self.vpnservice['status']): self.updated_pending_status = True self.vpnservice['status'] = self.status for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: if plugin_utils.in_pending_status(ipsec_site_conn['status']): conn_id = ipsec_site_conn['id'] conn_status = self.connection_status.get(conn_id) if not conn_status: continue conn_status['updated_pending_status'] = True ipsec_site_conn['status'] = conn_status['status'] def enable(self): """Enabling the process.""" try: self.ensure_configs() if self.active: self.restart() else: self.start() except RuntimeError: LOG.exception( "Failed to enable vpn process on router %s", self.id) def disable(self): """Disabling the process.""" try: if self.active: self.stop() self.remove_config() except RuntimeError: LOG.exception( "Failed to disable vpn process on router %s", self.id) @abc.abstractmethod def restart(self): """Restart process.""" @abc.abstractmethod def start(self): """Start process.""" @abc.abstractmethod def stop(self): """Stop process.""" def _check_status_line(self, line): """Parse a line and search for status information. If a connection has an established Security Association, it will be considered ACTIVE. Otherwise, even if a status line shows that a connection is active, it will be marked as DOWN-ed. """ # pluto is not running so just exit if self.STATUS_NOT_RUNNING_PATTERN.search(line): self.connection_status = {} raise StopIteration() m = self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN.search(line) if m: connection_id = m.group(1) return connection_id, constants.ACTIVE else: m = self.STATUS_PATTERN.search(line) if m: connection_id = m.group(1) return connection_id, constants.DOWN return None, None def _extract_and_record_connection_status(self, status_output): if not status_output: self.connection_status = {} return for line in status_output.split('\n'): try: conn_id, conn_status = self._check_status_line(line) except StopIteration: break if conn_id: self._record_connection_status(conn_id, conn_status) def _record_connection_status(self, connection_id, status, force_status_update=False): conn_info = self.connection_status.get(connection_id) if not conn_info: self.connection_status[connection_id] = { 'status': status, 'updated_pending_status': force_status_update } else: conn_info['status'] = status if force_status_update: conn_info['updated_pending_status'] = True class OpenSwanProcess(BaseSwanProcess): """OpenSwan Process manager class. This process class uses three commands (1) ipsec pluto: IPsec IKE keying daemon (2) ipsec addconn: Adds new ipsec addconn (3) ipsec whack: control interface for IPSEC keying daemon """ def __init__(self, conf, process_id, vpnservice, namespace): super(OpenSwanProcess, self).__init__(conf, process_id, vpnservice, namespace) self.secrets_file = os.path.join( self.etc_dir, 'ipsec.secrets') self.config_file = os.path.join( self.etc_dir, 'ipsec.conf') self.pid_path = os.path.join( self.config_dir, 'var', 'run', 'pluto') self.pid_file = '%s.pid' % self.pid_path def _execute(self, cmd, check_exit_code=True, extra_ok_codes=None): """Execute command on namespace.""" ip_wrapper = ip_lib.IPWrapper(namespace=self.namespace) return ip_wrapper.netns.execute(cmd, check_exit_code=check_exit_code, extra_ok_codes=extra_ok_codes) def ensure_configs(self): """Generate config files which are needed for OpenSwan. If there is no directory, this function will create dirs. """ self.ensure_config_dir(self.vpnservice) self.ensure_config_file( 'ipsec.conf', self.conf.openswan.ipsec_config_template, self.vpnservice) self.ensure_config_file( 'ipsec.secrets', self.conf.openswan.ipsec_secret_template, self.vpnservice, 0o600) def _copy_configs(self): if not cfg.CONF.pluto.restart_check_config: return config_file_name = self._get_config_filename('ipsec.conf') if os.path.isfile(config_file_name): shutil.copyfile(config_file_name, config_file_name + '.old') config_file_name = self._get_config_filename('ipsec.secrets') if os.path.isfile(config_file_name): shutil.copyfile(config_file_name, config_file_name + '.old') os.chmod(config_file_name + '.old', 0o600) def _process_running(self): """Checks if process is still running.""" # If no PID file, we assume the process is not running. if not os.path.exists(self.pid_file): return False try: # We take an ask-forgiveness-not-permission approach and rely # on throwing to tell us something. If the pid file exists, # delve into the process information and check if it matches # our expected command line. with open(self.pid_file, 'r') as f: pid = f.readline().strip() with open('/proc/%s/cmdline' % pid) as cmd_line_file: cmd_line = cmd_line_file.readline() if self.pid_path in cmd_line and 'pluto' in cmd_line: # Okay the process is probably a pluto process # and it contains the pid_path in the command # line... could be a race. Log to error and return # that it is *NOT* okay to clean up files. We are # logging to error instead of debug because it # indicates something bad has happened and this is # valuable information for figuring it out. LOG.error('Process %(pid)s exists with command ' 'line %(cmd_line)s.', {'pid': pid, 'cmd_line': cmd_line}) return True except IOError as e: # This is logged as "info" instead of error because it simply # means that we couldn't find the files to check on them. LOG.info('Unable to find control files on startup for ' 'router %(router)s: %(msg)s', {'router': self.id, 'msg': e}) return False def _cleanup_control_files(self): try: ctl_file = '%s.ctl' % self.pid_path LOG.debug('Removing %(pidfile)s and %(ctlfile)s', {'pidfile': self.pid_file, 'ctlfile': ctl_file}) if os.path.exists(self.pid_file): os.remove(self.pid_file) if os.path.exists(ctl_file): os.remove(ctl_file) except OSError as e: LOG.error('Unable to remove pluto control ' 'files for router %(router)s. %(msg)s', {'router': self.id, 'msg': e}) def get_status(self): return self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--status'], extra_ok_codes=[1, 3]) def _config_changed(self): secrets_file = os.path.join( self.etc_dir, 'ipsec.secrets') config_file = os.path.join( self.etc_dir, 'ipsec.conf') if not os.path.isfile(secrets_file + '.old'): return True if not os.path.isfile(config_file + '.old'): return True if not filecmp.cmp(secrets_file, secrets_file + '.old'): return True if not filecmp.cmp(config_file, config_file + '.old'): return True return False def restart(self): """Restart the process.""" should_be_restart = False if self._config_changed() or not cfg.CONF.pluto.restart_check_config: should_be_restart = True if not should_be_restart: return # stop() followed immediately by a start() runs the risk that the # current pluto daemon has not had a chance to shutdown. We check # the current process information to see if the daemon is still # running and if so, wait a short interval and retry. self.stop() wait_interval = cfg.CONF.pluto.shutdown_check_timeout for i in range(cfg.CONF.pluto.shutdown_check_retries): if not self._process_running(): self._cleanup_control_files() break eventlet.sleep(wait_interval) wait_interval *= cfg.CONF.pluto.shutdown_check_back_off else: LOG.warning('Server appears to still be running, restart ' 'of router %s may fail', self.id) self.start() return def _resolve_fqdn(self, fqdn): # The first addrinfo member from the list returned by # socket.getaddrinfo is used for the address resolution. # The code doesn't filter for ipv4 or ipv6 address. try: addrinfo = socket.getaddrinfo(fqdn, None)[0] return addrinfo[-1][0] except socket.gaierror: LOG.exception("Peer address %s cannot be resolved", fqdn) def _get_nexthop(self, address, connection_id): # check if address is an ip address or fqdn invalid_ip_address = validators.validate_ip_address(address) if invalid_ip_address: ip_addr = self._resolve_fqdn(address) if not ip_addr: self._record_connection_status(connection_id, constants.ERROR, force_status_update=True) raise vpnaas.VPNPeerAddressNotResolved(peer_address=address) else: ip_addr = address routes = self._execute(['ip', 'route', 'get', ip_addr]) if routes.find('via') >= 0: return routes.split(' ')[2] return address def _virtual_privates(self, vpnservice): """Returns line of virtual_privates. virtual_private contains the networks that are allowed as subnet for the remote client. """ virtual_privates = [] nets = [] for ipsec_site_conn in vpnservice['ipsec_site_connections']: nets += ipsec_site_conn['local_cidrs'] nets += ipsec_site_conn['peer_cidrs'] for net in nets: version = netaddr.IPNetwork(net).version virtual_privates.append('%%v%s:%s' % (version, net)) virtual_privates.sort() return ','.join(virtual_privates) def _gen_config_content(self, template_file, vpnservice): template = _get_template(template_file) virtual_privates = self._virtual_privates(vpnservice) return template.render( {'vpnservice': vpnservice, 'virtual_privates': virtual_privates}) def start_pluto(self): cmd = [self.binary, 'pluto', '--ctlbase', self.pid_path, '--ipsecdir', self.etc_dir, '--use-netkey', '--uniqueids', '--nat_traversal', '--secretsfile', self.secrets_file] if self.conf.ipsec.enable_detailed_logging: cmd += ['--perpeerlog', '--perpeerlogbase', self.log_dir] self._execute(cmd) def add_ipsec_connection(self, nexthop, conn_id): self._execute([self.binary, 'addconn', '--ctlbase', '%s.ctl' % self.pid_path, '--defaultroutenexthop', nexthop, '--config', self.config_file, conn_id ]) def start_whack_listening(self): #TODO(nati) fix this when openswan is fixed #Due to openswan bug, this command always exit with 3 self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--listen' ], check_exit_code=False) def shutdown_whack(self): self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--shutdown' ]) def initiate_connection(self, conn_name): self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--name', conn_name, '--asynchronous', '--initiate' ]) def terminate_connection(self, conn_name): self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--name', conn_name, '--terminate' ]) def start(self): """Start the process. Note: if there is not namespace yet, just do nothing, and wait next event. """ if not self.namespace: return # NOTE: The restart operation calls the parent's start() instead of # this one to avoid having to special case the startup file check. # If anything is added to this method that needs to run whenever # a restart occurs, it should be either added to the restart() # override or things refactored to special-case start() when # called from restart(). # If, by any reason, ctl and pid files weren't cleaned up, pluto # won't be able to rewrite them and will fail to start. So we check # to see if the process is running and if not, attempt a cleanup. # In either case we fall through to allow the pluto process to # start or fail in the usual way. if not self._process_running(): self._cleanup_control_files() #start pluto IKE keying daemon self.start_pluto() #add connections for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: # Don't add a connection if its admin state is down if not ipsec_site_conn['admin_state_up']: continue nexthop = self._get_nexthop(ipsec_site_conn['peer_address'], ipsec_site_conn['id']) self.add_ipsec_connection(nexthop, ipsec_site_conn['id']) #start whack ipsec keying daemon self.start_whack_listening() for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: if (not ipsec_site_conn['initiator'] == 'start' or not ipsec_site_conn['admin_state_up']): continue #initiate ipsec connection self.initiate_connection(ipsec_site_conn['id']) self._copy_configs() def get_established_connections(self): connections = [] status_output = self.get_status() if not status_output: return connections for line in status_output.split('\n'): if self.STATUS_NOT_RUNNING_PATTERN.search(line): return connections m = self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN2.search(line) if m: connection = m.group(1) if connection in connections: continue connections.append(connection) return connections def disconnect(self): if not self.namespace: return if not self.vpnservice: return connections = self.get_established_connections() for conn_name in connections: self.terminate_connection(conn_name) def stop(self): #Stop process using whack #Note this will also stop pluto self.disconnect() self.shutdown_whack() self.connection_status = {} class IPsecVpnDriverApi(object): """IPSecVpnDriver RPC api.""" @log_helpers.log_method_call def __init__(self, topic): target = oslo_messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) @log_helpers.log_method_call def get_vpn_services_on_host(self, context, host): """Get list of vpnservices. The vpnservices including related ipsec_site_connection, ikepolicy and ipsecpolicy on this host """ cctxt = self.client.prepare() return cctxt.call(context, 'get_vpn_services_on_host', host=host) @log_helpers.log_method_call def update_status(self, context, status): """Update local status. This method call updates status attribute of VPNServices. """ cctxt = self.client.prepare() return cctxt.call(context, 'update_status', status=status) @six.add_metaclass(abc.ABCMeta) class IPsecDriver(device_drivers.DeviceDriver): """VPN Device Driver for IPSec. This class is designed for use with L3-agent now. However this driver will be used with another agent in future so the use of "Router" is kept minimal now. Instead of router_id, we are using process_id in this code. """ # history # 1.0 Initial version target = oslo_messaging.Target(version='1.0') def __init__(self, vpn_service, host): # TODO(pc_m) Replace vpn_service with config arg, once all driver # implementations no longer need vpn_service. self.conf = vpn_service.conf self.host = host self.conn = n_rpc.create_connection() self.context = context.get_admin_context_without_session() self.topic = topics.IPSEC_AGENT_TOPIC node_topic = '%s.%s' % (self.topic, self.host) self.processes = {} self.routers = {} self.process_status_cache = {} self.endpoints = [self] self.conn.create_consumer(node_topic, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = IPsecVpnDriverApi(topics.IPSEC_DRIVER_TOPIC) self.process_status_cache_check = loopingcall.FixedIntervalLoopingCall( self.report_status, self.context) self.process_status_cache_check.start( interval=self.conf.ipsec.ipsec_status_check_interval) def get_namespace(self, router_id): """Get namespace of router. :router_id: router_id :returns: namespace string. Note: If the router is a DVR, then the SNAT namespace will be provided. If the router does not exist, return None. """ router = self.routers.get(router_id) if not router: return # For DVR, use SNAT namespace # TODO(pcm): Use router object method to tell if DVR, when available if router.router['distributed']: return router.snat_namespace.name else: return router.ns_name def get_router_based_iptables_manager(self, router): """Returns router based iptables manager In DVR routers the IPsec VPN service should run inside the snat namespace. So the iptables manager used for snat namespace is different from the iptables manager used for the qr namespace in a non dvr based router. This function will check the router type and then will return the right iptables manager. If DVR enabled router it will return the snat_iptables_manager otherwise it will return the legacy iptables_manager. """ # TODO(pcm): Use router object method to tell if DVR, when available if router.router['distributed']: return router.snat_iptables_manager else: return router.iptables_manager def add_nat_rule(self, router_id, chain, rule, top=False): """Add nat rule in namespace. :param router_id: router_id :param chain: a string of chain name :param rule: a string of rule :param top: if top is true, the rule will be placed on the top of chain Note if there is no router, this method does nothing """ router = self.routers.get(router_id) if not router: return iptables_manager = self.get_router_based_iptables_manager(router) iptables_manager.ipv4['nat'].add_rule(chain, rule, top=top) def remove_nat_rule(self, router_id, chain, rule, top=False): """Remove nat rule in namespace. :param router_id: router_id :param chain: a string of chain name :param rule: a string of rule :param top: unused needed to have same argument with add_nat_rule """ router = self.routers.get(router_id) if not router: return iptables_manager = self.get_router_based_iptables_manager(router) iptables_manager.ipv4['nat'].remove_rule(chain, rule, top=top) def iptables_apply(self, router_id): """Apply IPtables. :param router_id: router_id This method do nothing if there is no router """ router = self.routers.get(router_id) if not router: return iptables_manager = self.get_router_based_iptables_manager(router) iptables_manager.apply() def _update_nat(self, vpnservice, func): """Setting up nat rule in iptables. We need to setup nat rule for ipsec packet. :param vpnservice: vpnservices :param func: self.add_nat_rule or self.remove_nat_rule """ router_id = vpnservice['router_id'] for ipsec_site_connection in vpnservice['ipsec_site_connections']: for local_cidr in ipsec_site_connection['local_cidrs']: # This ipsec rule is not needed for ipv6. if netaddr.IPNetwork(local_cidr).version == 6: continue for peer_cidr in ipsec_site_connection['peer_cidrs']: func(router_id, 'POSTROUTING', '-s %s -d %s -m policy ' '--dir out --pol ipsec ' '-j ACCEPT ' % (local_cidr, peer_cidr), top=True) self.iptables_apply(router_id) @log_helpers.log_method_call def vpnservice_updated(self, context, **kwargs): """Vpnservice updated rpc handler VPN Service Driver will call this method when vpnservices updated. Then this method start sync with server. """ router = kwargs.get('router', None) self.sync(context, [router] if router else []) @abc.abstractmethod def create_process(self, process_id, vpnservice, namespace): pass def ensure_process(self, process_id, vpnservice=None): """Ensuring process. If the process doesn't exist, it will create process and store it in self.process """ process = self.processes.get(process_id) if not process or not process.namespace: namespace = self.get_namespace(process_id) process = self.create_process( process_id, vpnservice, namespace) self.processes[process_id] = process elif vpnservice: process.update_vpnservice(vpnservice) return process def create_router(self, router): """Handling create router event. Agent calls this method, when the process namespace is ready. Note: process_id == router_id == vpnservice_id """ process_id = router.router_id self.routers[process_id] = router if process_id in self.processes: # In case of vpnservice is created # before router's namespace process = self.processes[process_id] self._update_nat(process.vpnservice, self.add_nat_rule) # Don't run ipsec process for backup HA router if router.router['ha'] and router.ha_state == 'backup': return process.enable() def destroy_process(self, process_id): """Destroy process. Disable the process, remove the nat rule, and remove the process manager for the processes that no longer are running vpn service. """ if process_id in self.processes: process = self.processes[process_id] process.disable() vpnservice = process.vpnservice if vpnservice: self._update_nat(vpnservice, self.remove_nat_rule) del self.processes[process_id] def destroy_router(self, process_id): """Handling destroy_router event. Agent calls this method, when the process namespace is deleted. """ self.destroy_process(process_id) if process_id in self.routers: del self.routers[process_id] def get_process_status_cache(self, process): if not self.process_status_cache.get(process.id): self.process_status_cache[process.id] = { 'status': None, 'id': process.vpnservice['id'], 'updated_pending_status': False, 'ipsec_site_connections': {}} return self.process_status_cache[process.id] def is_status_updated(self, process, previous_status): if process.updated_pending_status: return True if process.status != previous_status['status']: return True if (process.connection_status != previous_status['ipsec_site_connections']): return True def unset_updated_pending_status(self, process): process.updated_pending_status = False for connection_status in process.connection_status.values(): connection_status['updated_pending_status'] = False def copy_process_status(self, process): return { 'id': process.vpnservice['id'], 'status': process.status, 'updated_pending_status': process.updated_pending_status, 'ipsec_site_connections': copy.deepcopy(process.connection_status) } def update_downed_connections(self, process_id, new_status): """Update info to be reported, if connections just went down. If there is no longer any information for a connection, because it has been removed (e.g. due to an admin down of VPN service or IPSec connection), but there was previous status information for the connection, mark the connection as down for reporting purposes. """ if process_id in self.process_status_cache: for conn in self.process_status_cache[process_id][IPSEC_CONNS]: if conn not in new_status[IPSEC_CONNS]: new_status[IPSEC_CONNS][conn] = { 'status': constants.DOWN, 'updated_pending_status': True } def should_be_reported(self, context, process): if (context.is_admin or process.vpnservice["tenant_id"] == context.tenant_id): return True @log_helpers.log_method_call def report_status(self, context): status_changed_vpn_services = [] for process in self.processes.values(): if not self.should_be_reported(context, process): continue previous_status = self.get_process_status_cache(process) if self.is_status_updated(process, previous_status): new_status = self.copy_process_status(process) self.update_downed_connections(process.id, new_status) status_changed_vpn_services.append(new_status) self.process_status_cache[process.id] = ( self.copy_process_status(process)) # We need unset updated_pending status after it # is reported to the server side self.unset_updated_pending_status(process) if status_changed_vpn_services: self.agent_rpc.update_status( context, status_changed_vpn_services) @log_helpers.log_method_call @lockutils.synchronized('vpn-agent', 'neutron-') def sync(self, context, routers): """Sync status with server side. :param context: context object for RPC call :param routers: Router objects which is created in this sync event There could be many failure cases should be considered including the followings. 1) Agent class restarted 2) Failure on process creation 3) VpnService is deleted during agent down 4) RPC failure In order to handle, these failure cases, This driver takes simple sync strategies. """ vpnservices = self.agent_rpc.get_vpn_services_on_host( context, self.host) router_ids = [vpnservice['router_id'] for vpnservice in vpnservices] sync_router_ids = [router['id'] for router in routers] self._sync_vpn_processes(vpnservices, sync_router_ids) self._delete_vpn_processes(sync_router_ids, router_ids) self._cleanup_stale_vpn_processes(router_ids) self.report_status(context) def _sync_vpn_processes(self, vpnservices, sync_router_ids): # Ensure the ipsec process is enabled only for # - the vpn services which are not yet in self.processes # - vpn services whose router id is in 'sync_router_ids' for vpnservice in vpnservices: if vpnservice['router_id'] not in self.processes or ( vpnservice['router_id'] in sync_router_ids): process = self.ensure_process(vpnservice['router_id'], vpnservice=vpnservice) self._update_nat(vpnservice, self.add_nat_rule) router = self.routers.get(vpnservice['router_id']) if not router: continue # For HA router, spawn vpn process on master router # and terminate vpn process on backup router if router.router['ha'] and router.ha_state == 'backup': process.disable() else: process.update() def _delete_vpn_processes(self, sync_router_ids, vpn_router_ids): # Delete any IPSec processes that are # associated with routers, but are not running the VPN service. for process_id in sync_router_ids: if process_id not in vpn_router_ids: self.destroy_process(process_id) def _cleanup_stale_vpn_processes(self, vpn_router_ids): # Delete any IPSec processes running # VPN that do not have an associated router. process_ids = [pid for pid in self.processes if pid not in vpn_router_ids] for process_id in process_ids: self.destroy_process(process_id) class OpenSwanDriver(IPsecDriver): def create_process(self, process_id, vpnservice, namespace): return OpenSwanProcess( self.conf, process_id, vpnservice, namespace) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/cisco_ipsec.py0000666000175000017500000010032213370230615030117 0ustar zuulzuul00000000000000# 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. import collections from neutron.common import rpc as n_rpc from neutron.plugins.common import utils as plugin_utils from neutron_lib import constants from neutron_lib import context as ctx from neutron_lib import exceptions as nexception from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import log as logging import oslo_messaging from oslo_service import loopingcall import requests from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn import device_drivers from neutron_vpnaas.services.vpn.device_drivers import ( cisco_csr_rest_client as csr_client) ipsec_opts = [ cfg.IntOpt('status_check_interval', default=60, help=_("Status check interval for Cisco CSR IPSec connections")) ] cfg.CONF.register_opts(ipsec_opts, 'cisco_csr_ipsec') LOG = logging.getLogger(__name__) RollbackStep = collections.namedtuple('RollbackStep', ['action', 'resource_id', 'title']) class CsrResourceCreateFailure(nexception.NeutronException): message = _("Cisco CSR failed to create %(resource)s (%(which)s)") class CsrAdminStateChangeFailure(nexception.NeutronException): message = _("Cisco CSR failed to change %(tunnel)s admin state to " "%(state)s") class CsrDriverMismatchError(nexception.NeutronException): message = _("Required %(resource)s attribute %(attr)s mapping for Cisco " "CSR is missing in device driver") class CsrUnknownMappingError(nexception.NeutronException): message = _("Device driver does not have a mapping of '%(value)s for " "attribute %(attr)s of %(resource)s") class CiscoCsrIPsecVpnDriverApi(object): """RPC API for agent to plugin messaging.""" def __init__(self, topic): target = oslo_messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) def get_vpn_services_on_host(self, context, host): """Get list of vpnservices on this host. The vpnservices including related ipsec_site_connection, ikepolicy, ipsecpolicy, and Cisco info on this host. """ cctxt = self.client.prepare() return cctxt.call(context, 'get_vpn_services_on_host', host=host) def update_status(self, context, status): """Update status for all VPN services and connections.""" cctxt = self.client.prepare() return cctxt.call(context, 'update_status', status=status) class CiscoCsrIPsecDriver(device_drivers.DeviceDriver): """Cisco CSR VPN Device Driver for IPSec. This class is designed for use with L3-agent now. However this driver will be used with another agent in future. so the use of "Router" is kept minimal now. Instead of router_id, we are using process_id in this code. """ # history # 1.0 Initial version target = oslo_messaging.Target(version='1.0') def __init__(self, vpn_service, host): # TODO(pc_m): Once all driver implementations no longer need # vpn_service argument, replace with just config argument. self.host = host self.conn = n_rpc.create_connection() context = ctx.get_admin_context_without_session() node_topic = '%s.%s' % (topics.CISCO_IPSEC_AGENT_TOPIC, self.host) self.service_state = {} self.endpoints = [self] self.conn.create_consumer(node_topic, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = ( CiscoCsrIPsecVpnDriverApi(topics.CISCO_IPSEC_DRIVER_TOPIC)) self.periodic_report = loopingcall.FixedIntervalLoopingCall( self.report_status, context) self.periodic_report.start( interval=vpn_service.conf.cisco_csr_ipsec.status_check_interval) LOG.debug("Device driver initialized for %s", node_topic) def vpnservice_updated(self, context, **kwargs): """Handle VPNaaS service driver change notifications.""" LOG.debug("Handling VPN service update notification '%s'", kwargs.get('reason', '')) self.sync(context, []) def create_vpn_service(self, service_data): """Create new entry to track VPN service and its connections.""" csr = csr_client.CsrRestClient(service_data['router_info']) vpn_service_id = service_data['id'] self.service_state[vpn_service_id] = CiscoCsrVpnService( service_data, csr) return self.service_state[vpn_service_id] def update_connection(self, context, vpn_service_id, conn_data): """Handle notification for a single IPSec connection.""" vpn_service = self.service_state[vpn_service_id] conn_id = conn_data['id'] conn_is_admin_up = conn_data[u'admin_state_up'] if conn_id in vpn_service.conn_state: # Existing connection... ipsec_conn = vpn_service.conn_state[conn_id] config_changed = ipsec_conn.check_for_changes(conn_data) if config_changed: LOG.debug("Update: Existing connection %s changed", conn_id) ipsec_conn.delete_ipsec_site_connection(context, conn_id) ipsec_conn.create_ipsec_site_connection(context, conn_data) ipsec_conn.conn_info = conn_data if ipsec_conn.forced_down: if vpn_service.is_admin_up and conn_is_admin_up: LOG.debug("Update: Connection %s no longer admin down", conn_id) ipsec_conn.set_admin_state(is_up=True) ipsec_conn.forced_down = False else: if not vpn_service.is_admin_up or not conn_is_admin_up: LOG.debug("Update: Connection %s forced to admin down", conn_id) ipsec_conn.set_admin_state(is_up=False) ipsec_conn.forced_down = True else: # New connection... ipsec_conn = vpn_service.create_connection(conn_data) ipsec_conn.create_ipsec_site_connection(context, conn_data) if not vpn_service.is_admin_up or not conn_is_admin_up: LOG.debug("Update: Created new connection %s in admin down " "state", conn_id) ipsec_conn.set_admin_state(is_up=False) ipsec_conn.forced_down = True else: LOG.debug("Update: Created new connection %s", conn_id) ipsec_conn.is_dirty = False ipsec_conn.last_status = conn_data['status'] ipsec_conn.is_admin_up = conn_is_admin_up return ipsec_conn def update_service(self, context, service_data): """Handle notification for a single VPN Service and its connections.""" vpn_service_id = service_data['id'] if vpn_service_id in self.service_state: LOG.debug("Update: Existing VPN service %s detected", vpn_service_id) vpn_service = self.service_state[vpn_service_id] else: LOG.debug("Update: New VPN service %s detected", vpn_service_id) vpn_service = self.create_vpn_service(service_data) if not vpn_service: return vpn_service.is_dirty = False vpn_service.connections_removed = False vpn_service.last_status = service_data['status'] vpn_service.is_admin_up = service_data[u'admin_state_up'] for conn_data in service_data['ipsec_conns']: self.update_connection(context, vpn_service_id, conn_data) LOG.debug("Update: Completed update processing") return vpn_service def update_all_services_and_connections(self, context): """Update services and connections based on plugin info. Perform any create and update operations and then update status. Mark every visited connection as no longer "dirty" so they will not be deleted at end of sync processing. """ services_data = self.agent_rpc.get_vpn_services_on_host(context, self.host) LOG.debug("Sync updating for %d VPN services", len(services_data)) vpn_services = [] for service_data in services_data: vpn_service = self.update_service(context, service_data) if vpn_service: vpn_services.append(vpn_service) return vpn_services def mark_existing_connections_as_dirty(self): """Mark all existing connections as "dirty" for sync.""" service_count = 0 connection_count = 0 for service_state in self.service_state.values(): service_state.is_dirty = True service_count += 1 for conn_id in service_state.conn_state: service_state.conn_state[conn_id].is_dirty = True connection_count += 1 LOG.debug("Mark: %(service)d VPN services and %(conn)d IPSec " "connections marked dirty", {'service': service_count, 'conn': connection_count}) def remove_unknown_connections(self, context): """Remove connections that are not known by service driver.""" service_count = 0 connection_count = 0 for vpn_service_id, vpn_service in list(self.service_state.items()): dirty = [c_id for c_id, c in vpn_service.conn_state.items() if c.is_dirty] vpn_service.connections_removed = len(dirty) > 0 for conn_id in dirty: conn_state = vpn_service.conn_state[conn_id] conn_state.delete_ipsec_site_connection(context, conn_id) connection_count += 1 del vpn_service.conn_state[conn_id] if vpn_service.is_dirty: service_count += 1 del self.service_state[vpn_service_id] elif dirty: self.connections_removed = True LOG.debug("Sweep: Removed %(service)d dirty VPN service%(splural)s " "and %(conn)d dirty IPSec connection%(cplural)s", {'service': service_count, 'conn': connection_count, 'splural': 's'[service_count == 1:], 'cplural': 's'[connection_count == 1:]}) def build_report_for_connections_on(self, vpn_service): """Create the report fragment for IPSec connections on a service. Collect the current status from the Cisco CSR and use that to update the status and generate report fragment for each connection on the service. If there is no status information, or no change, then no report info will be created for the connection. The combined report data is returned. """ LOG.debug("Report: Collecting status for IPSec connections on VPN " "service %s", vpn_service.service_id) tunnels = vpn_service.get_ipsec_connections_status() report = {} for connection in vpn_service.conn_state.values(): if connection.forced_down: LOG.debug("Connection %s forced down", connection.conn_id) current_status = constants.DOWN else: current_status = connection.find_current_status_in(tunnels) LOG.debug("Connection %(conn)s reported %(status)s", {'conn': connection.conn_id, 'status': current_status}) frag = connection.update_status_and_build_report(current_status) if frag: LOG.debug("Report: Adding info for IPSec connection %s", connection.conn_id) report.update(frag) return report def build_report_for_service(self, vpn_service): """Create the report info for a VPN service and its IPSec connections. Get the report info for the connections on the service, and include it into the report info for the VPN service. If there is no report info for the connection, then no change has occurred and no report will be generated. If there is only one connection for the service, we'll set the service state to match the connection (with ERROR seen as DOWN). """ conn_report = self.build_report_for_connections_on(vpn_service) if conn_report or vpn_service.connections_removed: pending_handled = plugin_utils.in_pending_status( vpn_service.last_status) vpn_service.update_last_status() LOG.debug("Report: Adding info for VPN service %s", vpn_service.service_id) return {u'id': vpn_service.service_id, u'status': vpn_service.last_status, u'updated_pending_status': pending_handled, u'ipsec_site_connections': conn_report} else: return {} @lockutils.synchronized('vpn-agent', 'neutron-') def report_status(self, context): """Report status of all VPN services and IPSec connections to plugin. This is called periodically by the agent, to push up changes in status. Use a lock to serialize access to (and changing of) running state. """ return self.report_status_internal(context) def report_status_internal(self, context): """Generate report and send to plugin, if anything changed.""" service_report = [] LOG.debug("Report: Starting status report processing") for vpn_service_id, vpn_service in self.service_state.items(): LOG.debug("Report: Collecting status for VPN service %s", vpn_service_id) report = self.build_report_for_service(vpn_service) if report: service_report.append(report) if service_report: LOG.info("Sending status report update to plugin") self.agent_rpc.update_status(context, service_report) LOG.debug("Report: Completed status report processing") return service_report @lockutils.synchronized('vpn-agent', 'neutron-') def sync(self, context, routers): """Synchronize with plugin and report current status. Mark all "known" services/connections as dirty, update them based on information from the plugin, remove (sweep) any connections that are not updated (dirty), and report updates, if any, back to plugin. Called when update/delete a service or create/update/delete a connection (vpnservice_updated message), or router change (_process_routers). Use lock to serialize access (and changes) to running state for VPN service and IPsec connections. """ self.mark_existing_connections_as_dirty() self.update_all_services_and_connections(context) self.remove_unknown_connections(context) self.report_status_internal(context) def create_router(self, router): """Actions taken when router created.""" # Note: Since Cisco CSR is running out-of-band, nothing to do here pass def destroy_router(self, process_id): """Actions taken when router deleted.""" # Note: Since Cisco CSR is running out-of-band, nothing to do here pass class CiscoCsrVpnService(object): """Maintains state/status information for a service and its connections.""" def __init__(self, service_data, csr): self.service_id = service_data['id'] self.conn_state = {} self.csr = csr self.is_admin_up = True # TODO(pcm) FUTURE - handle sharing of policies def create_connection(self, conn_data): conn_id = conn_data['id'] self.conn_state[conn_id] = CiscoCsrIPSecConnection(conn_data, self.csr) return self.conn_state[conn_id] def get_connection(self, conn_id): return self.conn_state.get(conn_id) def conn_status(self, conn_id): conn_state = self.get_connection(conn_id) if conn_state: return conn_state.last_status def snapshot_conn_state(self, ipsec_conn): """Create/obtain connection state and save current status.""" conn_state = self.conn_state.setdefault( ipsec_conn['id'], CiscoCsrIPSecConnection(ipsec_conn, self.csr)) conn_state.last_status = ipsec_conn['status'] conn_state.is_dirty = False return conn_state STATUS_MAP = {'ERROR': constants.ERROR, 'UP-ACTIVE': constants.ACTIVE, 'UP-IDLE': constants.ACTIVE, 'UP-NO-IKE': constants.ACTIVE, 'DOWN': constants.DOWN, 'DOWN-NEGOTIATING': constants.DOWN} def get_ipsec_connections_status(self): """Obtain current status of all tunnels on a Cisco CSR. Convert them to OpenStack status values. """ tunnels = self.csr.read_tunnel_statuses() for tunnel in tunnels: LOG.debug("CSR Reports %(tunnel)s status '%(status)s'", {'tunnel': tunnel[0], 'status': tunnel[1]}) return dict(map(lambda x: (x[0], self.STATUS_MAP[x[1]]), tunnels)) def find_matching_connection(self, tunnel_id): """Find IPSec connection using Cisco CSR tunnel specified, if any.""" for connection in self.conn_state.values(): if connection.tunnel == tunnel_id: return connection.conn_id def no_connections_up(self): return not any(c.last_status == 'ACTIVE' for c in self.conn_state.values()) def update_last_status(self): if not self.is_admin_up or self.no_connections_up(): self.last_status = constants.DOWN else: self.last_status = constants.ACTIVE class CiscoCsrIPSecConnection(object): """State and actions for IPSec site-to-site connections.""" def __init__(self, conn_info, csr): self.conn_info = conn_info self.csr = csr self.steps = [] self.forced_down = False self.changed = False @property def conn_id(self): return self.conn_info['id'] @property def is_admin_up(self): return self.conn_info['admin_state_up'] @is_admin_up.setter def is_admin_up(self, is_up): self.conn_info['admin_state_up'] = is_up @property def tunnel(self): return self.conn_info['cisco']['site_conn_id'] def check_for_changes(self, curr_conn): return not all([self.conn_info[attr] == curr_conn[attr] for attr in ('mtu', 'psk', 'peer_address', 'peer_cidrs', 'ike_policy', 'ipsec_policy', 'cisco')]) def find_current_status_in(self, statuses): if self.tunnel in statuses: return statuses[self.tunnel] else: return constants.ERROR def update_status_and_build_report(self, current_status): if current_status != self.last_status: pending_handled = plugin_utils.in_pending_status(self.last_status) self.last_status = current_status return {self.conn_id: {'status': current_status, 'updated_pending_status': pending_handled}} else: return {} DIALECT_MAP = {'ike_policy': {'name': 'IKE Policy', 'v1': u'v1', # auth_algorithm -> hash 'sha1': u'sha', # encryption_algorithm -> encryption '3des': u'3des', 'aes-128': u'aes', 'aes-192': u'aes192', 'aes-256': u'aes256', # pfs -> dhGroup 'group2': 2, 'group5': 5, 'group14': 14}, 'ipsec_policy': {'name': 'IPSec Policy', # auth_algorithm -> esp-authentication 'sha1': u'esp-sha-hmac', # transform_protocol -> ah 'esp': None, 'ah': u'ah-sha-hmac', 'ah-esp': u'ah-sha-hmac', # encryption_algorithm -> esp-encryption '3des': u'esp-3des', 'aes-128': u'esp-aes', 'aes-192': u'esp-192-aes', 'aes-256': u'esp-256-aes', # pfs -> pfs 'group2': u'group2', 'group5': u'group5', 'group14': u'group14'}} def translate_dialect(self, resource, attribute, info): """Map VPNaaS attributes values to CSR values for a resource.""" name = self.DIALECT_MAP[resource]['name'] if attribute not in info: raise CsrDriverMismatchError(resource=name, attr=attribute) value = info[attribute].lower() if value in self.DIALECT_MAP[resource]: return self.DIALECT_MAP[resource][value] raise CsrUnknownMappingError(resource=name, attr=attribute, value=value) def create_psk_info(self, psk_id, conn_info): """Collect/create attributes needed for pre-shared key.""" return {u'keyring-name': psk_id, u'pre-shared-key-list': [ {u'key': conn_info['psk'], u'encrypted': False, u'peer-address': conn_info['peer_address']}]} def create_ike_policy_info(self, ike_policy_id, conn_info): """Collect/create/map attributes needed for IKE policy.""" for_ike = 'ike_policy' policy_info = conn_info[for_ike] version = self.translate_dialect(for_ike, 'ike_version', policy_info) encrypt_algorithm = self.translate_dialect(for_ike, 'encryption_algorithm', policy_info) auth_algorithm = self.translate_dialect(for_ike, 'auth_algorithm', policy_info) group = self.translate_dialect(for_ike, 'pfs', policy_info) lifetime = policy_info['lifetime_value'] return {u'version': version, u'priority-id': ike_policy_id, u'encryption': encrypt_algorithm, u'hash': auth_algorithm, u'dhGroup': group, u'lifetime': lifetime} def create_ipsec_policy_info(self, ipsec_policy_id, info): """Collect/create attributes needed for IPSec policy. Note: OpenStack will provide a default encryption algorithm, if one is not provided, so a authentication only configuration of (ah, sha1), which maps to ah-sha-hmac transform protocol, cannot be selected. As a result, we'll always configure the encryption algorithm, and will select ah-sha-hmac for transform protocol. """ for_ipsec = 'ipsec_policy' policy_info = info[for_ipsec] transform_protocol = self.translate_dialect(for_ipsec, 'transform_protocol', policy_info) auth_algorithm = self.translate_dialect(for_ipsec, 'auth_algorithm', policy_info) encrypt_algorithm = self.translate_dialect(for_ipsec, 'encryption_algorithm', policy_info) group = self.translate_dialect(for_ipsec, 'pfs', policy_info) lifetime = policy_info['lifetime_value'] settings = {u'policy-id': ipsec_policy_id, u'protection-suite': { u'esp-encryption': encrypt_algorithm, u'esp-authentication': auth_algorithm}, u'lifetime-sec': lifetime, u'pfs': group, u'anti-replay-window-size': u'disable'} if transform_protocol: settings[u'protection-suite'][u'ah'] = transform_protocol return settings def create_site_connection_info(self, site_conn_id, ipsec_policy_id, conn_info): """Collect/create attributes needed for the IPSec connection.""" mtu = conn_info['mtu'] return { u'vpn-interface-name': site_conn_id, u'ipsec-policy-id': ipsec_policy_id, u'remote-device': { u'tunnel-ip-address': conn_info['peer_address'] }, u'mtu': mtu } def create_routes_info(self, site_conn_id, conn_info): """Collect/create attributes for static routes.""" routes_info = [] for peer_cidr in conn_info.get('peer_cidrs', []): route = {u'destination-network': peer_cidr, u'outgoing-interface': site_conn_id} route_id = csr_client.make_route_id(peer_cidr, site_conn_id) routes_info.append((route_id, route)) return routes_info def _check_create(self, resource, which): """Determine if REST create request was successful.""" if self.csr.status == requests.codes.CREATED: LOG.debug("%(resource)s %(which)s is configured", {'resource': resource, 'which': which}) return LOG.error("Unable to create %(resource)s %(which)s: " "%(status)d", {'resource': resource, 'which': which, 'status': self.csr.status}) # ToDO(pcm): Set state to error raise CsrResourceCreateFailure(resource=resource, which=which) def do_create_action(self, action_suffix, info, resource_id, title): """Perform a single REST step for IPSec site connection create.""" create_action = 'create_%s' % action_suffix try: getattr(self.csr, create_action)(info) except AttributeError: LOG.exception("Internal error - '%s' is not defined", create_action) raise CsrResourceCreateFailure(resource=title, which=resource_id) self._check_create(title, resource_id) self.steps.append(RollbackStep(action_suffix, resource_id, title)) def _verify_deleted(self, status, resource, which): """Determine if REST delete request was successful.""" if status in (requests.codes.NO_CONTENT, requests.codes.NOT_FOUND): LOG.debug("%(resource)s configuration %(which)s was removed", {'resource': resource, 'which': which}) else: LOG.warning("Unable to delete %(resource)s %(which)s: " "%(status)d", {'resource': resource, 'which': which, 'status': status}) def do_rollback(self): """Undo create steps that were completed successfully.""" for step in reversed(self.steps): delete_action = 'delete_%s' % step.action LOG.debug("Performing rollback action %(action)s for " "resource %(resource)s", {'action': delete_action, 'resource': step.title}) try: getattr(self.csr, delete_action)(step.resource_id) except AttributeError: LOG.exception("Internal error - '%s' is not defined", delete_action) raise CsrResourceCreateFailure(resource=step.title, which=step.resource_id) self._verify_deleted(self.csr.status, step.title, step.resource_id) self.steps = [] def create_ipsec_site_connection(self, context, conn_info): """Creates an IPSec site-to-site connection on CSR. Create the PSK, IKE policy, IPSec policy, connection, static route, and (future) DPD. """ # Get all the IDs conn_id = conn_info['id'] psk_id = conn_id site_conn_id = conn_info['cisco']['site_conn_id'] ike_policy_id = conn_info['cisco']['ike_policy_id'] ipsec_policy_id = conn_info['cisco']['ipsec_policy_id'] LOG.debug('Creating IPSec connection %s', conn_id) # Get all the attributes needed to create try: psk_info = self.create_psk_info(psk_id, conn_info) ike_policy_info = self.create_ike_policy_info(ike_policy_id, conn_info) ipsec_policy_info = self.create_ipsec_policy_info(ipsec_policy_id, conn_info) connection_info = self.create_site_connection_info(site_conn_id, ipsec_policy_id, conn_info) routes_info = self.create_routes_info(site_conn_id, conn_info) except (CsrUnknownMappingError, CsrDriverMismatchError) as e: LOG.exception(e) return try: self.do_create_action('pre_shared_key', psk_info, conn_id, 'Pre-Shared Key') self.do_create_action('ike_policy', ike_policy_info, ike_policy_id, 'IKE Policy') self.do_create_action('ipsec_policy', ipsec_policy_info, ipsec_policy_id, 'IPSec Policy') self.do_create_action('ipsec_connection', connection_info, site_conn_id, 'IPSec Connection') # TODO(pcm): FUTURE - Do DPD for v1 and handle if >1 connection # and different DPD settings for route_id, route_info in routes_info: self.do_create_action('static_route', route_info, route_id, 'Static Route') except CsrResourceCreateFailure: self.do_rollback() LOG.info("FAILED: Create of IPSec site-to-site connection %s", conn_id) else: LOG.info("SUCCESS: Created IPSec site-to-site connection %s", conn_id) def delete_ipsec_site_connection(self, context, conn_id): """Delete the site-to-site IPSec connection. This will be best effort and will continue, if there are any failures. """ LOG.debug('Deleting IPSec connection %s', conn_id) if not self.steps: LOG.warning('Unable to find connection %s', conn_id) else: self.do_rollback() LOG.info("SUCCESS: Deleted IPSec site-to-site connection %s", conn_id) def set_admin_state(self, is_up): """Change the admin state for the IPSec connection.""" self.csr.set_ipsec_connection_state(self.tunnel, admin_up=is_up) if self.csr.status != requests.codes.NO_CONTENT: state = "UP" if is_up else "DOWN" LOG.error("Unable to change %(tunnel)s admin state to " "%(state)s", {'tunnel': self.tunnel, 'state': state}) raise CsrAdminStateChangeFailure(tunnel=self.tunnel, state=state) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/fedora_strongswan_ipsec.py0000666000175000017500000000761013370230615032552 0ustar zuulzuul00000000000000# Copyright (c) 2015 IBM, 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. import os from oslo_config import cfg from neutron_vpnaas.services.vpn.device_drivers import ipsec from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec TEMPLATE_PATH = os.path.dirname(os.path.abspath(__file__)) cfg.CONF.set_default(name='default_config_area', default=os.path.join( TEMPLATE_PATH, '/usr/share/strongswan/templates/' 'config/strongswan.d'), group='strongswan') class FedoraStrongSwanProcess(strongswan_ipsec.StrongSwanProcess): binary = 'strongswan' CONFIG_DIRS = [ 'var/run', 'log', 'etc', 'etc/strongswan/ipsec.d/aacerts', 'etc/strongswan/ipsec.d/acerts', 'etc/strongswan/ipsec.d/cacerts', 'etc/strongswan/ipsec.d/certs', 'etc/strongswan/ipsec.d/crls', 'etc/strongswan/ipsec.d/ocspcerts', 'etc/strongswan/ipsec.d/policies', 'etc/strongswan/ipsec.d/private', 'etc/strongswan/ipsec.d/reqs', 'etc/pki/nssdb/' ] STATUS_NOT_RUNNING_RE = ('Command:.*[ipsec|strongswan].*status.*' 'Exit code: [1|3] ') def __init__(self, conf, process_id, vpnservice, namespace): super(FedoraStrongSwanProcess, self).__init__(conf, process_id, vpnservice, namespace) def ensure_configs(self): """Generate config files which are needed for StrongSwan. If there is no directory, this function will create dirs. """ self.ensure_config_dir(self.vpnservice) self.ensure_config_file( 'ipsec.conf', cfg.CONF.strongswan.ipsec_config_template, self.vpnservice) self.ensure_config_file( 'strongswan.conf', cfg.CONF.strongswan.strongswan_config_template, self.vpnservice) self.ensure_config_file( 'ipsec.secrets', cfg.CONF.strongswan.ipsec_secret_template, self.vpnservice, 0o600) self.copy_and_overwrite(cfg.CONF.strongswan.default_config_area, self._get_config_filename('strongswan.d')) # Fedora uses /usr/share/strongswan/templates/config/ as strongswan # template directory. But /usr/share/strongswan/templates/config/ # strongswan.d does not include charon. Those configuration files # are in /usr/share/strongswan/templates/config/plugins directory. charon_dir = os.path.join( cfg.CONF.strongswan.default_config_area, 'charon') if not os.path.exists(charon_dir): plugins_dir = os.path.join( cfg.CONF.strongswan.default_config_area, '../plugins') self.copy_and_overwrite( plugins_dir, self._get_config_filename('strongswan.d/charon')) def _get_config_filename(self, kind): config_dir = '%s/strongswan' % self.etc_dir return os.path.join(config_dir, kind) class FedoraStrongSwanDriver(ipsec.IPsecDriver): def create_process(self, process_id, vpnservice, namespace): return FedoraStrongSwanProcess( self.conf, process_id, vpnservice, namespace) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/cisco_csr_rest_client.py0000666000175000017500000003054013370230615032202 0ustar zuulzuul00000000000000# 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. import time import netaddr from oslo_log import log as logging from oslo_serialization import jsonutils import requests from requests import exceptions as r_exc TIMEOUT = 20.0 LOG = logging.getLogger(__name__) HEADER_CONTENT_TYPE_JSON = {'content-type': 'application/json'} URL_BASE = 'https://%(host)s/api/v1/%(resource)s' # CSR RESTapi URIs URI_VPN_IPSEC_POLICIES = 'vpn-svc/ipsec/policies' URI_VPN_IPSEC_POLICIES_ID = URI_VPN_IPSEC_POLICIES + '/%s' URI_VPN_IKE_POLICIES = 'vpn-svc/ike/policies' URI_VPN_IKE_POLICIES_ID = URI_VPN_IKE_POLICIES + '/%s' URI_VPN_IKE_KEYRINGS = 'vpn-svc/ike/keyrings' URI_VPN_IKE_KEYRINGS_ID = URI_VPN_IKE_KEYRINGS + '/%s' URI_VPN_IKE_KEEPALIVE = 'vpn-svc/ike/keepalive' URI_VPN_SITE_TO_SITE = 'vpn-svc/site-to-site' URI_VPN_SITE_TO_SITE_ID = URI_VPN_SITE_TO_SITE + '/%s' URI_VPN_SITE_TO_SITE_STATE = URI_VPN_SITE_TO_SITE + '/%s/state' URI_VPN_SITE_ACTIVE_SESSIONS = URI_VPN_SITE_TO_SITE + '/active/sessions' URI_ROUTING_STATIC_ROUTES = 'routing-svc/static-routes' URI_ROUTING_STATIC_ROUTES_ID = URI_ROUTING_STATIC_ROUTES + '/%s' def make_route_id(cidr, interface): """Build ID that will be used to identify route for later deletion.""" net = netaddr.IPNetwork(cidr) return '%(network)s_%(prefix)s_%(interface)s' % { 'network': net.network, 'prefix': net.prefixlen, 'interface': interface} class CsrRestClient(object): """REST CsrRestClient for accessing the Cisco Cloud Services Router.""" def __init__(self, settings): self.port = str(settings.get('protocol_port', 55443)) self.host = ':'.join([settings.get('rest_mgmt_ip', ''), self.port]) self.auth = (settings['username'], settings['password']) self.inner_if_name = settings.get('inner_if_name', '') self.outer_if_name = settings.get('outer_if_name', '') self.token = None self.vrf = settings.get('vrf', '') self.vrf_prefix = 'vrf/%s/' % self.vrf if self.vrf else "" self.status = requests.codes.OK self.timeout = settings.get('timeout') self.max_tries = 5 self.session = requests.Session() def _response_info_for(self, response, method): """Return contents or location from response. For a POST or GET with a 200 response, the response content is returned. For a POST with a 201 response, return the header's location, which contains the identifier for the created resource. If there is an error, return the response content, so that it can be used in error processing ('error-code', 'error-message', and 'detail' fields). """ if method in ('POST', 'GET') and self.status == requests.codes.OK: LOG.debug('RESPONSE: %s', response.json()) return response.json() if method == 'POST' and self.status == requests.codes.CREATED: return response.headers.get('location', '') if self.status >= requests.codes.BAD_REQUEST and response.content: if b'error-code' in response.content: content = jsonutils.loads(response.content) LOG.debug("Error response content %s", content) return content def _request(self, method, url, **kwargs): """Perform REST request and save response info.""" try: LOG.debug("%(method)s: Request for %(resource)s payload: " "%(payload)s", {'method': method.upper(), 'resource': url, 'payload': kwargs.get('data')}) start_time = time.time() response = self.session.request(method, url, verify=False, timeout=self.timeout, **kwargs) LOG.debug("%(method)s Took %(time).2f seconds to process", {'method': method.upper(), 'time': time.time() - start_time}) except (r_exc.Timeout, r_exc.SSLError) as te: # Should never see SSLError, unless requests package is old (<2.0) timeout_val = 0.0 if self.timeout is None else self.timeout LOG.warning("%(method)s: Request timeout%(ssl)s " "(%(timeout).3f sec) for CSR(%(host)s)", {'method': method, 'timeout': timeout_val, 'ssl': '(SSLError)' if isinstance(te, r_exc.SSLError) else '', 'host': self.host}) self.status = requests.codes.REQUEST_TIMEOUT except r_exc.ConnectionError: LOG.exception("%(method)s: Unable to connect to " "CSR(%(host)s)", {'method': method, 'host': self.host}) self.status = requests.codes.NOT_FOUND except Exception as e: LOG.error("%(method)s: Unexpected error for CSR (%(host)s): " "%(error)s", {'method': method, 'host': self.host, 'error': e}) self.status = requests.codes.INTERNAL_SERVER_ERROR else: self.status = response.status_code LOG.debug("%(method)s: Completed [%(status)s]", {'method': method, 'status': self.status}) return self._response_info_for(response, method) def authenticate(self): """Obtain a token to use for subsequent CSR REST requests. This is called when there is no token yet, or if the token has expired and attempts to use it resulted in an UNAUTHORIZED REST response. """ url = URL_BASE % {'host': self.host, 'resource': 'auth/token-services'} headers = {'Content-Length': '0', 'Accept': 'application/json'} headers.update(HEADER_CONTENT_TYPE_JSON) LOG.debug("%(auth)s with CSR %(host)s", {'auth': 'Authenticating' if self.token is None else 'Reauthenticating', 'host': self.host}) self.token = None response = self._request("POST", url, headers=headers, auth=self.auth) if response: self.token = response['token-id'] LOG.debug("Successfully authenticated with CSR %s", self.host) return True LOG.error("Failed authentication with CSR %(host)s [%(status)s]", {'host': self.host, 'status': self.status}) def _do_request(self, method, resource, payload=None, more_headers=None, full_url=False): """Perform a REST request to a CSR resource. If this is the first time interacting with the CSR, a token will be obtained. If the request fails, due to an expired token, the token will be obtained and the request will be retried once more. """ if self.token is None: if not self.authenticate(): return if full_url: url = resource else: url = ('https://%(host)s/api/v1/%(resource)s' % {'host': self.host, 'resource': resource}) headers = {'Accept': 'application/json', 'X-auth-token': self.token} if more_headers: headers.update(more_headers) if payload: payload = jsonutils.dumps(payload) response = self._request(method, url, data=payload, headers=headers) if self.status == requests.codes.UNAUTHORIZED: if not self.authenticate(): return headers['X-auth-token'] = self.token response = self._request(method, url, data=payload, headers=headers) if self.status != requests.codes.REQUEST_TIMEOUT: return response LOG.error("%(method)s: Request timeout for CSR(%(host)s)", {'method': method, 'host': self.host}) def get_request(self, resource, full_url=False): """Perform a REST GET requests for a CSR resource.""" return self._do_request('GET', resource, full_url=full_url) def post_request(self, resource, payload=None): """Perform a POST request to a CSR resource.""" return self._do_request('POST', resource, payload=payload, more_headers=HEADER_CONTENT_TYPE_JSON) def put_request(self, resource, payload=None): """Perform a PUT request to a CSR resource.""" return self._do_request('PUT', resource, payload=payload, more_headers=HEADER_CONTENT_TYPE_JSON) def delete_request(self, resource): """Perform a DELETE request on a CSR resource.""" return self._do_request('DELETE', resource, more_headers=HEADER_CONTENT_TYPE_JSON) # VPN Specific APIs def create_ike_policy(self, policy_info): base_ike_policy_info = {u'version': u'v1', u'local-auth-method': u'pre-share'} base_ike_policy_info.update(policy_info) return self.post_request(URI_VPN_IKE_POLICIES, payload=base_ike_policy_info) def create_ipsec_policy(self, policy_info): base_ipsec_policy_info = {u'mode': u'tunnel'} base_ipsec_policy_info.update(policy_info) return self.post_request(URI_VPN_IPSEC_POLICIES, payload=base_ipsec_policy_info) def create_pre_shared_key(self, psk_info): return self.post_request(self.vrf_prefix + URI_VPN_IKE_KEYRINGS, payload=psk_info) def create_ipsec_connection(self, connection_info): base_conn_info = { u'vpn-type': u'site-to-site', u'ip-version': u'ipv4', u'local-device': { u'tunnel-ip-address': self.outer_if_name, u'ip-address': self.inner_if_name } } connection_info.update(base_conn_info) if self.vrf: connection_info[u'tunnel-vrf'] = self.vrf return self.post_request(self.vrf_prefix + URI_VPN_SITE_TO_SITE, payload=connection_info) def configure_ike_keepalive(self, keepalive_info): base_keepalive_info = {u'periodic': True} keepalive_info.update(base_keepalive_info) return self.put_request(URI_VPN_IKE_KEEPALIVE, keepalive_info) def create_static_route(self, route_info): return self.post_request(self.vrf_prefix + URI_ROUTING_STATIC_ROUTES, payload=route_info) def delete_static_route(self, route_id): return self.delete_request( self.vrf_prefix + URI_ROUTING_STATIC_ROUTES_ID % route_id) def set_ipsec_connection_state(self, tunnel, admin_up=True): """Set the IPSec site-to-site connection (tunnel) admin state. Note: When a tunnel is created, it will be admin up. """ info = {u'vpn-interface-name': tunnel, u'enabled': admin_up} return self.put_request( self.vrf_prefix + URI_VPN_SITE_TO_SITE_STATE % tunnel, info) def delete_ipsec_connection(self, conn_id): return self.delete_request( self.vrf_prefix + URI_VPN_SITE_TO_SITE_ID % conn_id) def delete_ipsec_policy(self, policy_id): return self.delete_request(URI_VPN_IPSEC_POLICIES_ID % policy_id) def delete_ike_policy(self, policy_id): return self.delete_request(URI_VPN_IKE_POLICIES_ID % policy_id) def delete_pre_shared_key(self, key_id): return self.delete_request( self.vrf_prefix + URI_VPN_IKE_KEYRINGS_ID % key_id) def read_tunnel_statuses(self): results = self.get_request(self.vrf_prefix + URI_VPN_SITE_ACTIVE_SESSIONS) if self.status != requests.codes.OK or not results: return [] tunnels = [(t[u'vpn-interface-name'], t[u'status']) for t in results['items']] return tunnels neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/libreswan_ipsec.py0000666000175000017500000001242213370230615031010 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, 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. import os import os.path from neutron.agent.linux import ip_lib from neutron_vpnaas.services.vpn.device_drivers import ipsec NS_WRAPPER = 'neutron-vpn-netns-wrapper' class LibreSwanProcess(ipsec.OpenSwanProcess): """Libreswan Process manager class. Libreswan needs nssdb initialised before running pluto daemon. """ def __init__(self, conf, process_id, vpnservice, namespace): super(LibreSwanProcess, self).__init__(conf, process_id, vpnservice, namespace) def _ipsec_execute(self, cmd, check_exit_code=True, extra_ok_codes=None): """Execute ipsec command on namespace. This execute is wrapped by namespace wrapper. The namespace wrapper will bind /etc and /var/run """ ip_wrapper = ip_lib.IPWrapper(namespace=self.namespace) mount_paths = {'/etc': '%s/etc' % self.config_dir, '/var/run': '%s/var/run' % self.config_dir} mount_paths_str = ','.join( "%s:%s" % (source, target) for source, target in mount_paths.items()) return ip_wrapper.netns.execute( [NS_WRAPPER, '--mount_paths=%s' % mount_paths_str, '--cmd=%s,%s' % (self.binary, ','.join(cmd))], check_exit_code=check_exit_code, extra_ok_codes=extra_ok_codes) def _ensure_needed_files(self): # addconn reads from /etc/hosts and /etc/resolv.conf. As /etc would be # bind-mounted, create these two empty files in the target directory. with open('%s/etc/hosts' % self.config_dir, 'a'): pass with open('%s/etc/resolv.conf' % self.config_dir, 'a'): pass def ensure_configs(self): """Generate config files which are needed for Libreswan. Initialise the nssdb, otherwise pluto daemon will fail to run. """ # Since we set ipsec.secrets to be owned by root, the standard # mechanisms for setting up the config files will get a permission # problem when attempting to overwrite the file, so we need to # remove it first. secrets_file = self._get_config_filename('ipsec.secrets') if os.path.exists(secrets_file): os.remove(secrets_file) super(LibreSwanProcess, self).ensure_configs() # LibreSwan uses the capabilities library to restrict access to # ipsec.secrets to users that have explicit access. Since pluto is # running as root and the file has 0600 perms, we must set the # owner of the file to root. self._execute(['chown', '--from=%s' % os.getuid(), 'root:root', secrets_file]) # Libreswan needs to write logs to this directory. self._execute(['chown', '--from=%s' % os.getuid(), 'root:root', self.log_dir]) self._ensure_needed_files() # Load the ipsec kernel module if not loaded self._ipsec_execute(['_stackmanager', 'start']) # checknss creates nssdb only if it is missing # It is added in Libreswan version v3.10 # For prior versions use initnss try: self._ipsec_execute(['checknss']) except RuntimeError: self._ipsec_execute(['initnss']) def get_status(self): return self._ipsec_execute(['whack', '--status'], extra_ok_codes=[1, 3]) def start_pluto(self): cmd = ['pluto', '--use-netkey', '--uniqueids'] if self.conf.ipsec.enable_detailed_logging: cmd += ['--perpeerlog', '--perpeerlogbase', self.log_dir] self._ipsec_execute(cmd) def add_ipsec_connection(self, nexthop, conn_id): # Connections will be automatically added as auto=start/add for # initiator=bi-directional/response-only specified in the config. pass def start_whack_listening(self): # NOTE(huntxu): This is a workaround for with a weak (len<8) secret, # "ipsec whack --listen" will exit with 3. self._ipsec_execute(['whack', '--listen'], extra_ok_codes=[3]) def shutdown_whack(self): self._ipsec_execute(['whack', '--shutdown']) def initiate_connection(self, conn_name): self._ipsec_execute( ['whack', '--name', conn_name, '--asynchronous', '--initiate']) def terminate_connection(self, conn_name): self._ipsec_execute(['whack', '--name', conn_name, '--terminate']) class LibreSwanDriver(ipsec.IPsecDriver): def create_process(self, process_id, vpnservice, namespace): return LibreSwanProcess( self.conf, process_id, vpnservice, namespace) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py0000666000175000017500000001561013370230606031231 0ustar zuulzuul00000000000000# Copyright (c) 2015 Canonical, 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. import os from oslo_config import cfg from oslo_log import log as logging from neutron.agent.linux import ip_lib from neutron.agent.linux import utils from neutron_lib import constants from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn.device_drivers import ipsec LOG = logging.getLogger(__name__) TEMPLATE_PATH = os.path.dirname(os.path.abspath(__file__)) strongswan_opts = [ cfg.StrOpt( 'ipsec_config_template', default=os.path.join( TEMPLATE_PATH, 'template/strongswan/ipsec.conf.template'), help=_('Template file for ipsec configuration.')), cfg.StrOpt( 'strongswan_config_template', default=os.path.join( TEMPLATE_PATH, 'template/strongswan/strongswan.conf.template'), help=_('Template file for strongswan configuration.')), cfg.StrOpt( 'ipsec_secret_template', default=os.path.join( TEMPLATE_PATH, 'template/strongswan/ipsec.secret.template'), help=_('Template file for ipsec secret configuration.')), cfg.StrOpt( 'default_config_area', default=os.path.join( TEMPLATE_PATH, '/etc/strongswan.d'), help=_('The area where default StrongSwan configuration ' 'files are located.')) ] cfg.CONF.register_opts(strongswan_opts, 'strongswan') NS_WRAPPER = 'neutron-vpn-netns-wrapper' class StrongSwanProcess(ipsec.BaseSwanProcess): # ROUTED means route created. (only for auto=route mode) # CONNECTING means route created, connection tunnel is negotiating. # INSTALLED means route created, # also connection tunnel installed. (traffic can pass) DIALECT_MAP = dict(ipsec.BaseSwanProcess.DIALECT_MAP) STATUS_DICT = { 'ROUTED': constants.DOWN, 'CONNECTING': constants.DOWN, 'INSTALLED': constants.ACTIVE } STATUS_RE = '([a-f0-9\-]+).* (ROUTED|CONNECTING|INSTALLED)' STATUS_NOT_RUNNING_RE = 'Command:.*ipsec.*status.*Exit code: [1|3] ' def __init__(self, conf, process_id, vpnservice, namespace): self.DIALECT_MAP['v1'] = 'ikev1' self.DIALECT_MAP['v2'] = 'ikev2' self.DIALECT_MAP['sha256'] = 'sha256' self._strongswan_piddir = self._get_strongswan_piddir() LOG.debug("strongswan piddir is '%s'", (self._strongswan_piddir)) super(StrongSwanProcess, self).__init__(conf, process_id, vpnservice, namespace) def _get_strongswan_piddir(self): return utils.execute( cmd=[self.binary, "--piddir"], run_as_root=True).strip() def _check_status_line(self, line): """Parse a line and search for status information. If a given line contains status information for a connection, extract the status and mark the connection as ACTIVE or DOWN according to the STATUS_MAP. """ m = self.STATUS_PATTERN.search(line) if m: connection_id = m.group(1) status = self.STATUS_MAP[m.group(2)] return connection_id, status return None, None def _execute(self, cmd, check_exit_code=True, extra_ok_codes=None): """Execute command on namespace. This execute is wrapped by namespace wrapper. The namespace wrapper will bind /etc/ and /var/run """ ip_wrapper = ip_lib.IPWrapper(namespace=self.namespace) return ip_wrapper.netns.execute( [NS_WRAPPER, '--mount_paths=/etc:%s/etc,%s:%s/var/run' % ( self.config_dir, self._strongswan_piddir, self.config_dir), '--cmd=%s' % ','.join(cmd)], check_exit_code=check_exit_code, extra_ok_codes=extra_ok_codes) def copy_and_overwrite(self, from_path, to_path): # NOTE(toabctl): the agent may run as non-root user, so rm/copy as root if os.path.exists(to_path): utils.execute( cmd=["rm", "-rf", to_path], run_as_root=True) utils.execute( cmd=["cp", "-a", from_path, to_path], run_as_root=True) def ensure_configs(self): """Generate config files which are needed for StrongSwan. If there is no directory, this function will create dirs. """ self.ensure_config_dir(self.vpnservice) self.ensure_config_file( 'ipsec.conf', cfg.CONF.strongswan.ipsec_config_template, self.vpnservice) self.ensure_config_file( 'strongswan.conf', cfg.CONF.strongswan.strongswan_config_template, self.vpnservice) self.ensure_config_file( 'ipsec.secrets', cfg.CONF.strongswan.ipsec_secret_template, self.vpnservice, 0o600) self.copy_and_overwrite(cfg.CONF.strongswan.default_config_area, self._get_config_filename('strongswan.d')) def get_status(self): return self._execute([self.binary, 'status'], extra_ok_codes=[1, 3]) def restart(self): """Restart the process.""" self.reload() def reload(self): """Reload the process. Sends a USR1 signal to ipsec starter which in turn reloads the whole configuration on the running IKE daemon charon based on the actual ipsec.conf. Currently established connections are not affected by configuration changes. """ self._execute([self.binary, 'reload']) def start(self): """Start the process for only auto=route mode now. Note: if there is no namespace yet, just do nothing, and wait next event. """ if not self.namespace: return self._execute([self.binary, 'start']) # initiate ipsec connection for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: self._execute([self.binary, 'stroke', 'up-nb', ipsec_site_conn['id']]) def stop(self): self._execute([self.binary, 'stop']) self.connection_status = {} class StrongSwanDriver(ipsec.IPsecDriver): def create_process(self, process_id, vpnservice, namespace): return StrongSwanProcess( self.conf, process_id, vpnservice, namespace) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/plugin.py0000666000175000017500000002340613370230615024144 0ustar zuulzuul00000000000000 # (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. from neutron.db import servicetype_db as st_db from neutron.services.flavors import flavors_plugin from neutron.services import provider_configuration as pconf from neutron.services import service_base from neutron_lib import context as ncontext from neutron_lib import exceptions as lib_exc from neutron_lib.exceptions import flavors as flav_exc from neutron_lib.plugins import constants from neutron_lib.plugins import directory from oslo_log import log as logging from neutron_vpnaas.db.vpn import vpn_db from neutron_vpnaas.extensions import vpn_flavors LOG = logging.getLogger(__name__) def add_provider_configuration(type_manager, service_type): type_manager.add_provider_configuration( service_type, pconf.ProviderConfiguration('neutron_vpnaas')) class VPNPlugin(vpn_db.VPNPluginDb): """Implementation of the VPN Service Plugin. This class manages the workflow of VPNaaS request/response. Most DB related works are implemented in class vpn_db.VPNPluginDb. """ supported_extension_aliases = ["vpnaas", "vpn-endpoint-groups", "service-type", "vpn-flavors"] path_prefix = "/vpn" class VPNDriverPlugin(VPNPlugin, vpn_db.VPNPluginRpcDbMixin): """VpnPlugin which supports VPN Service Drivers.""" #TODO(nati) handle ikepolicy and ipsecpolicy update usecase def __init__(self): super(VPNDriverPlugin, self).__init__() self.service_type_manager = st_db.ServiceTypeManager.get_instance() add_provider_configuration(self.service_type_manager, constants.VPN) # Load the service driver from neutron.conf. self.drivers, self.default_provider = service_base.load_drivers( constants.VPN, self) self._check_orphan_vpnservice_associations() # Associate driver names to driver objects for driver_name, driver in self.drivers.items(): driver.name = driver_name LOG.info(("VPN plugin using service drivers: %(service_drivers)s, " "default: %(default_driver)s"), {'service_drivers': self.drivers.keys(), 'default_driver': self.default_provider}) # Try to find the flavor plugin only once self._flavors_plugin = directory.get_plugin(constants.FLAVORS) vpn_db.subscribe() def _check_orphan_vpnservice_associations(self): context = ncontext.get_admin_context() vpnservices = self.get_vpnservices(context) vpnservice_ids = [vpnservice['id'] for vpnservice in vpnservices] stm = self.service_type_manager provider_names = stm.get_provider_names_by_resource_ids( context, vpnservice_ids) lost_providers = set() lost_vpnservices = [] for vpnservice_id, provider in provider_names.items(): if provider not in self.drivers: lost_providers.add(provider) lost_vpnservices.append(vpnservice_id) if lost_providers or lost_vpnservices: # Provider are kept internally, we need to inform users about # the related VPN services. msg = ( "Delete associated vpnservices %(vpnservices)s before " "removing providers %(providers)s." ) % {'vpnservices': lost_vpnservices, 'providers': list(lost_providers)} LOG.exception(msg) raise SystemExit(msg) # Deal with upgrade. Associate existing VPN services to default # provider. unasso_vpnservices = [ vpnservice_id for vpnservice_id in vpnservice_ids if vpnservice_id not in provider_names] if unasso_vpnservices: LOG.info( ("Associating VPN services %(unasso_vpnservices)s to " "default provider %(default_provider)s."), {'unasso_vpnservices': unasso_vpnservices, 'default_provider': self.default_provider}) for vpnservice_id in unasso_vpnservices: stm.add_resource_association( context, constants.VPN, self.default_provider, vpnservice_id) def _get_provider_for_flavor(self, context, flavor_id): if flavor_id: if not self._flavors_plugin: raise vpn_flavors.FlavorsPluginNotLoaded() fl_db = flavors_plugin.FlavorsPlugin.get_flavor( self._flavors_plugin, context, flavor_id) if fl_db['service_type'] != constants.VPN: raise lib_exc.InvalidServiceType( service_type=fl_db['service_type']) if not fl_db['enabled']: raise flav_exc.FlavorDisabled() providers = flavors_plugin.FlavorsPlugin.get_flavor_next_provider( self._flavors_plugin, context, fl_db['id']) provider = providers[0].get('provider') if provider not in self.drivers: raise vpn_flavors.NoProviderFoundForFlavor(flavor_id=flavor_id) else: # Use default provider provider = self.default_provider LOG.debug("Selected provider %s", provider) return provider def _get_driver_for_vpnservice(self, context, vpnservice): stm = self.service_type_manager provider_names = stm.get_provider_names_by_resource_ids( context, [vpnservice['id']]) provider = provider_names.get(vpnservice['id']) return self.drivers[provider] def _get_driver_for_ipsec_site_connection(self, context, ipsec_site_connection): # Only vpnservice_id is required as the vpnservice should be already # associated with a provider after its creation. vpnservice = {'id': ipsec_site_connection['vpnservice_id']} return self._get_driver_for_vpnservice(context, vpnservice) def create_ipsec_site_connection(self, context, ipsec_site_connection): driver = self._get_driver_for_ipsec_site_connection( context, ipsec_site_connection['ipsec_site_connection']) driver.validator.validate_ipsec_site_connection( context, ipsec_site_connection['ipsec_site_connection']) ipsec_site_connection = super( VPNDriverPlugin, self).create_ipsec_site_connection( context, ipsec_site_connection) driver.create_ipsec_site_connection(context, ipsec_site_connection) return ipsec_site_connection def delete_ipsec_site_connection(self, context, ipsec_conn_id): ipsec_site_connection = self.get_ipsec_site_connection( context, ipsec_conn_id) super(VPNDriverPlugin, self).delete_ipsec_site_connection( context, ipsec_conn_id) driver = self._get_driver_for_ipsec_site_connection( context, ipsec_site_connection) driver.delete_ipsec_site_connection(context, ipsec_site_connection) def update_ipsec_site_connection( self, context, ipsec_conn_id, ipsec_site_connection): old_ipsec_site_connection = self.get_ipsec_site_connection( context, ipsec_conn_id) driver = self._get_driver_for_ipsec_site_connection( context, old_ipsec_site_connection) driver.validator.validate_ipsec_site_connection( context, ipsec_site_connection['ipsec_site_connection']) ipsec_site_connection = super( VPNDriverPlugin, self).update_ipsec_site_connection( context, ipsec_conn_id, ipsec_site_connection) driver.update_ipsec_site_connection( context, old_ipsec_site_connection, ipsec_site_connection) return ipsec_site_connection def create_vpnservice(self, context, vpnservice): provider = self._get_provider_for_flavor( context, vpnservice['vpnservice'].get('flavor_id')) vpnservice = super( VPNDriverPlugin, self).create_vpnservice(context, vpnservice) self.service_type_manager.add_resource_association( context, constants.VPN, provider, vpnservice['id']) driver = self.drivers[provider] driver.create_vpnservice(context, vpnservice) return vpnservice def update_vpnservice(self, context, vpnservice_id, vpnservice): old_vpn_service = self.get_vpnservice(context, vpnservice_id) new_vpn_service = super( VPNDriverPlugin, self).update_vpnservice(context, vpnservice_id, vpnservice) driver = self._get_driver_for_vpnservice(context, old_vpn_service) driver.update_vpnservice(context, old_vpn_service, new_vpn_service) return new_vpn_service def delete_vpnservice(self, context, vpnservice_id): vpnservice = self._get_vpnservice(context, vpnservice_id) super(VPNDriverPlugin, self).delete_vpnservice(context, vpnservice_id) driver = self._get_driver_for_vpnservice(context, vpnservice) self.service_type_manager.del_resource_associations( context, [vpnservice_id]) driver.delete_vpnservice(context, vpnservice) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/vyatta_vpn_service.py0000666000175000017500000000267213370230615026563 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_vpnaas.services.vpn import vpn_service class VyattaVPNService(vpn_service.VPNService): """Vyatta VPN Service handler.""" def __init__(self, l3_agent): """Creates a Vyatta VPN Service instance. NOTE: Directly accessing l3_agent here is an interim solution until we move to have a router object given down to device drivers to access router related methods """ super(VyattaVPNService, self).__init__(l3_agent) self.l3_agent = l3_agent def get_router_client(self, router_id): """ Get Router RESTapi client """ return self.l3_agent.get_router_client(router_id) def get_router(self, router_id): """ Get Router Object """ return self.l3_agent.get_router(router_id) neutron-vpnaas-12.0.1/neutron_vpnaas/services/vpn/vyatta_agent.py0000666000175000017500000000305013370230615025325 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from networking_brocade.vyatta.common import l3_agent as vyatta_l3 from neutron.agent import l3_agent as entry from oslo_config import cfg from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn import vyatta_vpn_service vpn_agent_opts = [ cfg.MultiStrOpt( 'vpn_device_driver', default=['neutron_vpnaas.services.vpn.device_drivers.' 'vyatta_ipsec.VyattaIPSecDriver'], help=_("The vpn device drivers Neutron will use")), ] cfg.CONF.register_opts(vpn_agent_opts, 'vpnagent') class VyattaVPNAgent(vyatta_l3.L3AgentMiddleware): def __init__(self, host, conf=None): super(VyattaVPNAgent, self).__init__(host, conf) self.service = vyatta_vpn_service.VyattaVPNService(self) self.device_drivers = self.service.load_device_drivers(host) def main(): entry.main( manager='neutron_vpnaas.services.vpn.vyatta_agent.VyattaVPNAgent') neutron-vpnaas-12.0.1/neutron_vpnaas/_i18n.py0000666000175000017500000000204013370230606021125 0ustar zuulzuul00000000000000# 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 oslo_i18n DOMAIN = "neutron_vpnaas" _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary # The contextual translation function using the name "_C" _C = _translators.contextual_form # The plural translation function using the name "_P" _P = _translators.plural_form def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) neutron-vpnaas-12.0.1/neutron_vpnaas/db/0000775000175000017500000000000013370231105020217 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/__init__.py0000666000175000017500000000000013370230606022325 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/0000775000175000017500000000000013370231105022210 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/__init__.py0000666000175000017500000000000013370230606024316 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/0000775000175000017500000000000013370231105026040 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/README0000666000175000017500000000004613370230606026727 0ustar zuulzuul00000000000000Generic single-database configuration.neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/__init__.py0000666000175000017500000000121613370230606030160 0ustar zuulzuul00000000000000# Copyright 2015 Mirantis 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. VPNAAS_VERSION_TABLE = 'alembic_version_vpnaas' neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/script.py.mako0000666000175000017500000000203513370230606030653 0ustar zuulzuul00000000000000# Copyright ${create_date.year} # # 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. # """${message} Revision ID: ${up_revision} Revises: ${down_revision} Create Date: ${create_date} """ # revision identifiers, used by Alembic. revision = ${repr(up_revision)} down_revision = ${repr(down_revision)} % if branch_labels: branch_labels = ${repr(branch_labels)} %endif from alembic import op import sqlalchemy as sa ${imports if imports else ""} def upgrade(): ${upgrades if upgrades else "pass"} neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/0000775000175000017500000000000013370231105027710 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/0000775000175000017500000000000013370231105031156 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/0000775000175000017500000000000013370231105032435 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000020300000000000011210 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/41b509d10b5e_vpnaas_endpoint_groups.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/41b509d10000666000175000017500000000405213370230606033441 0ustar zuulzuul00000000000000# (c) Copyright 2015 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. """VPNaaS endpoint groups Revision ID: 41b509d10b5e Revises: 24f28869838b Create Date: 2015-08-06 18:21:03.241664 """ from alembic import op import sqlalchemy as sa from neutron_vpnaas.services.vpn.common import constants # revision identifiers, used by Alembic. revision = '41b509d10b5e' down_revision = '24f28869838b' def upgrade(): op.create_table( 'vpn_endpoint_groups', sa.Column('id', sa.String(length=36), nullable=False, primary_key=True), sa.Column('tenant_id', sa.String(length=255), index=True), sa.Column('name', sa.String(length=255)), sa.Column('description', sa.String(length=255)), sa.Column('endpoint_type', sa.Enum(constants.SUBNET_ENDPOINT, constants.CIDR_ENDPOINT, constants.VLAN_ENDPOINT, constants.NETWORK_ENDPOINT, constants.ROUTER_ENDPOINT, name='endpoint_type'), nullable=False), ) op.create_table( 'vpn_endpoints', sa.Column('endpoint', sa.String(length=255), nullable=False), sa.Column('endpoint_group_id', sa.String(36), nullable=False), sa.ForeignKeyConstraint(['endpoint_group_id'], ['vpn_endpoint_groups.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('endpoint', 'endpoint_group_id'), ) ././@LongLink0000000000000000000000000000020300000000000011210 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/28ee739a7e4b_multiple_local_subnets.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/28ee739a0000666000175000017500000000404613370230606033542 0ustar zuulzuul00000000000000# (c) Copyright 2015 Cisco Systems 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. # """Multiple local subnets Revision ID: 28ee739a7e4b Revises: 41b509d10b5e Create Date: 2015-09-09 20:32:54.231765 """ from alembic import op import sqlalchemy as sa from neutron.db import migration # revision identifiers, used by Alembic. revision = '28ee739a7e4b' down_revision = '41b509d10b5e' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.MITAKA] def upgrade(): op.add_column('ipsec_site_connections', sa.Column('local_ep_group_id', sa.String(length=36), nullable=True)) op.add_column('ipsec_site_connections', sa.Column('peer_ep_group_id', sa.String(length=36), nullable=True)) op.create_foreign_key(constraint_name=None, source_table='ipsec_site_connections', referent_table='vpn_endpoint_groups', local_cols=['local_ep_group_id'], remote_cols=['id']) op.create_foreign_key(constraint_name=None, source_table='ipsec_site_connections', referent_table='vpn_endpoint_groups', local_cols=['peer_ep_group_id'], remote_cols=['id']) op.alter_column('vpnservices', 'subnet_id', existing_type=sa.String(length=36), nullable=True) neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/0000775000175000017500000000000013370231105032773 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000020500000000000011212 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/2cb4ee992b41_multiple_local_subnets.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/2cb4ee0000666000175000017500000001543713370230606034003 0ustar zuulzuul00000000000000# (c) Copyright 2015 Cisco Systems 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. # """Multiple local subnets Revision ID: 2cb4ee992b41 Revises: 2c82e782d734 Create Date: 2015-09-09 20:32:54.254267 """ from alembic import op from oslo_utils import uuidutils import sqlalchemy as sa from sqlalchemy.sql import expression as sa_expr from neutron.db import migration from neutron_vpnaas.services.vpn.common import constants as v_constants # revision identifiers, used by Alembic. revision = '2cb4ee992b41' down_revision = '2c82e782d734' depends_on = ('28ee739a7e4b',) # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.MITAKA] vpnservices = sa.Table( 'vpnservices', sa.MetaData(), sa.Column('id', sa.String(length=36), nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=False), sa.Column('name', sa.String(255)), sa.Column('description', sa.String(255)), sa.Column('status', sa.String(16), nullable=False), sa.Column('admin_state_up', sa.Boolean(), nullable=False), sa.Column('external_v4_ip', sa.String(16)), sa.Column('external_v6_ip', sa.String(64)), sa.Column('subnet_id', sa.String(36)), sa.Column('router_id', sa.String(36), nullable=False)) ipsec_site_conns = sa.Table( 'ipsec_site_connections', sa.MetaData(), sa.Column('id', sa.String(length=36), nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=False), sa.Column('name', sa.String(255)), sa.Column('description', sa.String(255)), sa.Column('peer_address', sa.String(255), nullable=False), sa.Column('peer_id', sa.String(255), nullable=False), sa.Column('route_mode', sa.String(8), nullable=False), sa.Column('mtu', sa.Integer, nullable=False), sa.Column('initiator', sa.Enum("bi-directional", "response-only", name="vpn_initiators"), nullable=False), sa.Column('auth_mode', sa.String(16), nullable=False), sa.Column('psk', sa.String(255), nullable=False), sa.Column('dpd_action', sa.Enum("hold", "clear", "restart", "disabled", "restart-by-peer", name="vpn_dpd_actions"), nullable=False), sa.Column('dpd_interval', sa.Integer, nullable=False), sa.Column('dpd_timeout', sa.Integer, nullable=False), sa.Column('status', sa.String(16), nullable=False), sa.Column('admin_state_up', sa.Boolean(), nullable=False), sa.Column('vpnservice_id', sa.String(36), nullable=False), sa.Column('ipsecpolicy_id', sa.String(36), nullable=False), sa.Column('ikepolicy_id', sa.String(36), nullable=False), sa.Column('local_ep_group_id', sa.String(36)), sa.Column('peer_ep_group_id', sa.String(36))) ipsecpeercidrs = sa.Table( 'ipsecpeercidrs', sa.MetaData(), sa.Column('cidr', sa.String(32), nullable=False, primary_key=True), sa.Column('ipsec_site_connection_id', sa.String(36), primary_key=True)) def _make_endpoint_groups(new_groups, new_endpoints): """Create endpoint groups and their corresponding endpoints.""" md = sa.MetaData() engine = op.get_bind() sa.Table('vpn_endpoint_groups', md, autoload=True, autoload_with=engine) op.bulk_insert(md.tables['vpn_endpoint_groups'], new_groups) sa.Table('vpn_endpoints', md, autoload=True, autoload_with=engine) op.bulk_insert(md.tables['vpn_endpoints'], new_endpoints) def _update_connections(connection_map): """Store the endpoint group IDs in the connections.""" for conn_id, mapping in connection_map.items(): stmt = ipsec_site_conns.update().where( ipsec_site_conns.c.id == conn_id).values( local_ep_group_id=mapping['local'], peer_ep_group_id=mapping['peer']) op.execute(stmt) def upgrade(): new_groups = [] new_endpoints = [] service_map = {} session = sa.orm.Session(bind=op.get_bind()) vpn_services = session.query(vpnservices).filter( vpnservices.c.subnet_id is not None).all() for vpn_service in vpn_services: subnet_id = vpn_service.subnet_id if subnet_id is None: continue # Skip new service entries # Define the subnet group group_id = uuidutils.generate_uuid() group = {'id': group_id, 'name': '', 'description': '', 'tenant_id': vpn_service.tenant_id, 'endpoint_type': v_constants.SUBNET_ENDPOINT} new_groups.append(group) # Define the (sole) endpoint endpoint = {'endpoint_group_id': group_id, 'endpoint': subnet_id} new_endpoints.append(endpoint) # Save info to use for connections service_map[vpn_service.id] = group_id connection_map = {} ipsec_conns = session.query(ipsec_site_conns).all() for connection in ipsec_conns: peer_cidrs = session.query(ipsecpeercidrs.c.cidr).filter( ipsecpeercidrs.c.ipsec_site_connection_id == connection.id).all() if not peer_cidrs: continue # Skip new style connections # Define the CIDR group group_id = uuidutils.generate_uuid() group = {'id': group_id, 'name': '', 'description': '', 'tenant_id': connection.tenant_id, 'endpoint_type': v_constants.CIDR_ENDPOINT} new_groups.append(group) # Define the endpoint(s) for peer_cidr in peer_cidrs: endpoint = {'endpoint_group_id': group_id, 'endpoint': peer_cidr[0]} new_endpoints.append(endpoint) # Save the endpoint group ID info for the connection vpn_service = connection.vpnservice_id connection_map[connection.id] = {'local': service_map[vpn_service], 'peer': group_id} # Create all the defined endpoint groups and their endpoints _make_endpoint_groups(new_groups, new_endpoints) # Refer to new groups, in the IPSec connections _update_connections(connection_map) # Remove the peer_cidrs from IPSec connections op.execute(sa_expr.table('ipsecpeercidrs').delete()) # Remove the subnets from VPN services stmt = vpnservices.update().where( vpnservices.c.subnet_id is not None).values( subnet_id=None) op.execute(stmt) session.commit() neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/CONTRACT_HEAD0000666000175000017500000000001513370230615031634 0ustar zuulzuul00000000000000b6a2519ab7dc neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD0000666000175000017500000000001513370230606031376 0ustar zuulzuul0000000000000095601446dbcc neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/0000775000175000017500000000000013370231105030777 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/0000775000175000017500000000000013370231105032256 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000022000000000000011207 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/38893903cbde_add_auth_algorithm_sha384_and_sha512.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/38893903c0000666000175000017500000000253113370230606033306 0ustar zuulzuul00000000000000# Copyright 2016 # # 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. # """add_auth_algorithm_sha384_and_sha512 Revision ID: 38893903cbde Revises: 52783a36bd67 Create Date: 2016-11-04 18:00:49.219140 """ from neutron.db import migration import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '38893903cbde' down_revision = '52783a36bd67' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.OCATA] new_auth = sa.Enum('sha1', 'sha256', 'sha384', 'sha512', name='vpn_auth_algorithms') def upgrade(): migration.alter_enum('ikepolicies', 'auth_algorithm', new_auth, nullable=False, do_drop=False) migration.alter_enum('ipsecpolicies', 'auth_algorithm', new_auth, nullable=False, do_rename=False, do_create=False) ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/3ea02b2a773e_add_index_tenant_id.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/3ea02b2a773e_add_index0000666000175000017500000000214413370230606033447 0ustar zuulzuul00000000000000# Copyright 2015 OpenStack Foundation # # 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. # """add_index_tenant_id Revision ID: 3ea02b2a773e Revises: start_neutron_vpnaas Create Date: 2015-02-10 17:51:10.752504 """ from alembic import op # revision identifiers, used by Alembic. revision = '3ea02b2a773e' down_revision = 'start_neutron_vpnaas' TABLES = ['ipsecpolicies', 'ikepolicies', 'ipsec_site_connections', 'vpnservices'] def upgrade(): for table in TABLES: op.create_index(op.f('ix_%s_tenant_id' % table), table, ['tenant_id'], unique=False) neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/0000775000175000017500000000000013370231105031222 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/0000775000175000017500000000000013370231105032501 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000017500000000000011220 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/52783a36bd67_support_local_id.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/52783a360000666000175000017500000000225213370230606033436 0ustar zuulzuul00000000000000# Copyright 2016 # # 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. # """support local id Revision ID: 52783a36bd67 Revises: fe637dc3f042 Create Date: 2016-04-26 21:40:40.244196 """ from alembic import op import sqlalchemy as sa from neutron.db import migration # revision identifiers, used by Alembic. revision = '52783a36bd67' down_revision = 'fe637dc3f042' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.NEWTON] def upgrade(): op.add_column('ipsec_site_connections', sa.Column('local_id', sa.String(length=255), nullable=True)) ././@LongLink0000000000000000000000000000017300000000000011216 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/fe637dc3f042_support_sha256.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/fe637dc30000666000175000017500000000230013370230606033652 0ustar zuulzuul00000000000000# Copyright 2016 # # 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. # """support sha256 Revision ID: fe637dc3f042 Revises: 28ee739a7e4b Create Date: 2016-04-08 22:33:53.286083 """ from neutron.db import migration import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'fe637dc3f042' down_revision = '28ee739a7e4b' new_auth = sa.Enum('sha1', 'sha256', name='vpn_auth_algorithms') def upgrade(): migration.alter_enum('ikepolicies', 'auth_algorithm', new_auth, nullable=False, do_drop=False) migration.alter_enum('ipsecpolicies', 'auth_algorithm', new_auth, nullable=False, do_rename=False, do_create=False) neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/0000775000175000017500000000000013370231105033037 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000020700000000000011214 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/b6a2519ab7dc_rename_tenant_to_project.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/b6a2510000666000175000017500000000656313370230606033703 0ustar zuulzuul00000000000000# 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. # """rename tenant to project Revision ID: b6a2519ab7dc Revises: 2cb4ee992b41 Create Date: 2016-07-13 02:40:51.683659 """ from alembic import op import sqlalchemy as sa from sqlalchemy.engine import reflection from neutron.db import migration # revision identifiers, used by Alembic. revision = 'b6a2519ab7dc' down_revision = '2cb4ee992b41' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.NEWTON, migration.OCATA, migration.PIKE] _INSPECTOR = None def get_inspector(): """Reuse inspector""" global _INSPECTOR if _INSPECTOR: return _INSPECTOR bind = op.get_bind() _INSPECTOR = reflection.Inspector.from_engine(bind) return _INSPECTOR def get_tables(): """ Returns hardcoded list of tables which have ``tenant_id`` column. The list is hard-coded to match the state of the schema when this upgrade script is run. """ tables = [ 'ipsecpolicies', 'ikepolicies', 'ipsec_site_connections', 'vpnservices', 'vpn_endpoint_groups', ] return tables def get_columns(table): """Returns list of columns for given table.""" inspector = get_inspector() return inspector.get_columns(table) def get_data(): """Returns combined list of tuples: [(table, column)]. The list is built from tables with a tenant_id column. """ output = [] tables = get_tables() for table in tables: columns = get_columns(table) for column in columns: if column['name'] == 'tenant_id': output.append((table, column)) return output def alter_column(table, column): old_name = 'tenant_id' new_name = 'project_id' op.alter_column( table_name=table, column_name=old_name, new_column_name=new_name, existing_type=column['type'], existing_nullable=column['nullable'] ) def recreate_index(index, table_name): old_name = index['name'] new_name = old_name.replace('tenant', 'project') op.drop_index(op.f(old_name), table_name) op.create_index(new_name, table_name, ['project_id']) def upgrade(): """Code reused from Change-Id: I87a8ef342ccea004731ba0192b23a8e79bc382dc """ inspector = get_inspector() data = get_data() for table, column in data: alter_column(table, column) indexes = inspector.get_indexes(table) for index in indexes: if 'tenant_id' in index['name']: recreate_index(index, table) def contract_creation_exceptions(): """Special migration for the blueprint to support Keystone V3. We drop all tenant_id columns and create project_id columns instead. """ return { sa.Column: ['.'.join([table, 'project_id']) for table in get_tables()], sa.Index: get_tables() } neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/0000775000175000017500000000000013370231105030640 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/0000775000175000017500000000000013370231105032117 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000020700000000000011214 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/95601446dbcc_add_flavor_id_to_vpnservices.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/95601446db0000666000175000017500000000240713370230615033304 0ustar zuulzuul00000000000000# Copyright 2017 Eayun, 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. # """add flavor id to vpnservices Revision ID: 95601446dbcc Revises: 38893903cbde Create Date: 2017-04-10 10:14:41.724811 """ from alembic import op import sqlalchemy as sa from neutron.db import migration # revision identifiers, used by Alembic. revision = '95601446dbcc' down_revision = '38893903cbde' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.PIKE] def upgrade(): op.add_column('vpnservices', sa.Column('flavor_id', sa.String(length=36), nullable=True)) op.create_foreign_key('fk_vpnservices_flavors_id', 'vpnservices', 'flavors', ['flavor_id'], ['id']) neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/kilo_release.py0000666000175000017500000000151413370230606032730 0ustar zuulzuul00000000000000# 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. # """kilo Revision ID: kilo Revises: 3ea02b2a773e Create Date: 2015-04-16 00:00:00.000000 """ # revision identifiers, used by Alembic. revision = 'kilo' down_revision = '3ea02b2a773e' def upgrade(): """A no-op migration for marking the Kilo release.""" pass ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/start_neutron_vpnaas.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/start_neutron_vpnaas.p0000666000175000017500000000153613370230606034364 0ustar zuulzuul00000000000000# Copyright 2014 OpenStack Foundation # # 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. # """start neutron-vpnaas chain Revision ID: start_neutron_vpnaas Revises: None Create Date: 2014-12-09 18:50:01.946832 """ # revision identifiers, used by Alembic. revision = 'start_neutron_vpnaas' down_revision = None def upgrade(): pass neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/0000775000175000017500000000000013370231105031362 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/0000775000175000017500000000000013370231105032641 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000021500000000000011213 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/24f28869838b_add_fields_to_vpn_service_table.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/24f28860000666000175000017500000000234613370230606033523 0ustar zuulzuul00000000000000# Copyright 2015 OpenStack Foundation # # 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. # """Add fields to VPN service table Revision ID: 24f28869838b Revises: 30018084ed99 Create Date: 2015-07-06 14:52:24.339246 """ from alembic import op import sqlalchemy as sa from neutron.db import migration # revision identifiers, used by Alembic. revision = '24f28869838b' down_revision = '30018084ed99' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.LIBERTY] def upgrade(): op.add_column('vpnservices', sa.Column('external_v4_ip', sa.String(16), nullable=True)) op.add_column('vpnservices', sa.Column('external_v6_ip', sa.String(64), nullable=True)) ././@LongLink0000000000000000000000000000016500000000000011217 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/30018084ed99_initial.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/30018080000666000175000017500000000157413370230606033425 0ustar zuulzuul00000000000000# 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. # """Initial no-op Liberty expand rule. Revision ID: 30018084ed99 Revises: kilo Create Date: 2015-07-16 00:00:00.000000 """ from neutron.db.migration import cli # revision identifiers, used by Alembic. revision = '30018084ed99' down_revision = 'kilo' branch_labels = (cli.EXPAND_BRANCH,) def upgrade(): pass neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/0000775000175000017500000000000013370231105033177 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000020500000000000011212 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/56893333aa52_fix_identifier_map_fk.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/568930000666000175000017500000000377213370230606033640 0ustar zuulzuul00000000000000# Copyright(c) 2015, Oracle and/or its affiliates. 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. """fix identifier map fk Revision ID: 56893333aa52 Revises: kilo Create Date: 2015-06-11 12:09:01.263253 """ from alembic import op import sqlalchemy as sa from sqlalchemy.sql import column from sqlalchemy.sql import expression as expr from sqlalchemy.sql import func from sqlalchemy.sql import table from neutron.db import migration from neutron.db.migration import cli # revision identifiers, used by Alembic. revision = '56893333aa52' down_revision = 'kilo' branch_labels = (cli.CONTRACT_BRANCH,) def upgrade(): # re-size existing data if necessary identifier_map = table('cisco_csr_identifier_map', column('ipsec_site_conn_id', sa.String(36))) ipsec_site_conn_id = identifier_map.columns['ipsec_site_conn_id'] op.execute(identifier_map.update(values={ ipsec_site_conn_id: expr.case([(func.length(ipsec_site_conn_id) > 36, func.substr(ipsec_site_conn_id, 1, 36))], else_=ipsec_site_conn_id)})) # Need to drop foreign key constraint before mysql will allow changes with migration.remove_fks_from_table('cisco_csr_identifier_map'): op.alter_column(table_name='cisco_csr_identifier_map', column_name='ipsec_site_conn_id', type_=sa.String(36), existing_nullable=False) ././@LongLink0000000000000000000000000000022100000000000011210 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/333dfd6afaa2_populate_vpn_service_table_fields.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/333df0000666000175000017500000000624113370230606033756 0ustar zuulzuul00000000000000# Copyright 2015 OpenStack Foundation # # 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. # """Populate VPN service table fields Revision ID: 333dfd6afaa2 Revises: 56893333aa52 Create Date: 2015-07-27 16:43:59.123456 """ from alembic import op import netaddr import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '333dfd6afaa2' down_revision = '56893333aa52' depends_on = '24f28869838b' VPNService = sa.Table('vpnservices', sa.MetaData(), sa.Column('router_id', sa.String(36), nullable=False), sa.Column('external_v4_ip', sa.String(16)), sa.Column('external_v6_ip', sa.String(64)), sa.Column('id', sa.String(36), nullable=False, primary_key=True)) Router = sa.Table('routers', sa.MetaData(), sa.Column('gw_port_id', sa.String(36)), sa.Column('id', sa.String(36), nullable=False, primary_key=True)) Port = sa.Table('ports', sa.MetaData(), sa.Column('id', sa.String(36), nullable=False, primary_key=True)) IPAllocation = sa.Table('ipallocations', sa.MetaData(), sa.Column('ip_address', sa.String(64), nullable=False, primary_key=True), sa.Column('port_id', sa.String(36))) def _migrate_external_ips(engine): """Use router external IPs to populate external_v*_ip entries. For each service, look through the associated router's gw_port['fixed_ips'] list and store any IPv4 and/or IPv6 addresses into the new fields. If there are multiple addresses for an IP version, then only the first one will be stored (the same as the reference driver does). """ session = sa.orm.Session(bind=engine.connect()) services = session.query(VPNService).all() for service in services: addresses = session.query(IPAllocation.c.ip_address).filter( service.router_id == Router.c.id, Router.c.gw_port_id == Port.c.id, Port.c.id == IPAllocation.c.port_id).all() have_version = [] for address in addresses: version = netaddr.IPAddress(address[0]).version if version in have_version: continue have_version.append(version) update = {'external_v%s_ip' % version: address[0]} op.execute(VPNService.update().where( VPNService.c.id == service.id).values(update)) session.commit() def upgrade(): # Use the router to populate the fields for_engine = op.get_bind() _migrate_external_ips(for_engine) ././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/2c82e782d734_drop_tenant_id_in_cisco_csr_identifier_.pyneutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/2c82e0000666000175000017500000000206213370230606033754 0ustar zuulzuul00000000000000# Copyright 2015 Mirantis 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. # """drop_tenant_id_in_cisco_csr_identifier_map Revision ID: 2c82e782d734 Revises: 333dfd6afaa2 Create Date: 2015-08-20 15:17:09.897944 """ from alembic import op from neutron.db import migration # revision identifiers, used by Alembic. revision = '2c82e782d734' down_revision = '333dfd6afaa2' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.LIBERTY] def upgrade(): op.drop_column('cisco_csr_identifier_map', 'tenant_id') neutron-vpnaas-12.0.1/neutron_vpnaas/db/migration/alembic_migrations/env.py0000666000175000017500000000475013370230606027217 0ustar zuulzuul00000000000000# Copyright 2014 OpenStack Foundation # # 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 logging import config as logging_config from alembic import context from neutron_lib.db import model_base from oslo_config import cfg from oslo_db.sqlalchemy import session import sqlalchemy as sa from sqlalchemy import event from neutron_vpnaas.db.migration import alembic_migrations MYSQL_ENGINE = None config = context.config neutron_config = config.neutron_config logging_config.fileConfig(config.config_file_name) target_metadata = model_base.BASEV2.metadata def set_mysql_engine(): try: mysql_engine = neutron_config.command.mysql_engine except cfg.NoSuchOptError: mysql_engine = None global MYSQL_ENGINE MYSQL_ENGINE = (mysql_engine or model_base.BASEV2.__table_args__['mysql_engine']) def run_migrations_offline(): set_mysql_engine() kwargs = dict() if neutron_config.database.connection: kwargs['url'] = neutron_config.database.connection else: kwargs['dialect_name'] = neutron_config.database.engine kwargs['version_table'] = alembic_migrations.VPNAAS_VERSION_TABLE context.configure(**kwargs) with context.begin_transaction(): context.run_migrations() @event.listens_for(sa.Table, 'after_parent_attach') def set_storage_engine(target, parent): if MYSQL_ENGINE: target.kwargs['mysql_engine'] = MYSQL_ENGINE def run_migrations_online(): set_mysql_engine() engine = session.create_engine(neutron_config.database.connection) connection = engine.connect() context.configure( connection=connection, target_metadata=target_metadata, version_table=alembic_migrations.VPNAAS_VERSION_TABLE ) try: with context.begin_transaction(): context.run_migrations() finally: connection.close() engine.dispose() if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online() neutron-vpnaas-12.0.1/neutron_vpnaas/db/vpn/0000775000175000017500000000000013370231105021022 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/vpn/__init__.py0000666000175000017500000000000013370230606023130 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/vpn/vpn_db.py0000666000175000017500000010534313370230615022661 0ustar zuulzuul00000000000000# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. # (c) Copyright 2015 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. from neutron.db import common_db_mixin as base_db from neutron.db import models_v2 from neutron.plugins.common import utils from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as lib_constants from neutron_lib.exceptions import l3 as l3_exception from neutron_lib.plugins import constants as p_constants from neutron_lib.plugins import directory from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import uuidutils import sqlalchemy as sa from sqlalchemy.orm import exc from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.db.vpn import vpn_validator from neutron_vpnaas.extensions import vpn_endpoint_groups from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.common import constants as v_constants LOG = logging.getLogger(__name__) class VPNPluginDb(vpnaas.VPNPluginBase, vpn_endpoint_groups.VPNEndpointGroupsPluginBase, base_db.CommonDbMixin): """VPN plugin database class using SQLAlchemy models.""" def _get_validator(self): """Obtain validator to use for attribute validation. Subclasses may override this with a different validator, 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) v_db.update({'status': status}) def _get_resource(self, context, model, v_id): try: r = self._get_by_id(context, model, v_id) except exc.NoResultFound: with excutils.save_and_reraise_exception(reraise=False) as ctx: if issubclass(model, vpn_models.IPsecSiteConnection): raise vpnaas.IPsecSiteConnectionNotFound( ipsec_site_conn_id=v_id ) elif issubclass(model, vpn_models.IKEPolicy): raise vpnaas.IKEPolicyNotFound(ikepolicy_id=v_id) elif issubclass(model, vpn_models.IPsecPolicy): raise vpnaas.IPsecPolicyNotFound(ipsecpolicy_id=v_id) elif issubclass(model, vpn_models.VPNService): raise vpnaas.VPNServiceNotFound(vpnservice_id=v_id) elif issubclass(model, vpn_models.VPNEndpointGroup): raise vpnaas.VPNEndpointGroupNotFound( endpoint_group_id=v_id) ctx.reraise = True return r def assert_update_allowed(self, obj): status = getattr(obj, 'status', None) _id = getattr(obj, 'id', None) if utils.in_pending_status(status): raise vpnaas.VPNStateInvalidToUpdate(id=_id, state=status) def _make_ipsec_site_connection_dict(self, ipsec_site_conn, fields=None): res = {'id': ipsec_site_conn['id'], 'tenant_id': ipsec_site_conn['tenant_id'], 'name': ipsec_site_conn['name'], 'description': ipsec_site_conn['description'], 'peer_address': ipsec_site_conn['peer_address'], 'peer_id': ipsec_site_conn['peer_id'], 'local_id': ipsec_site_conn['local_id'], 'route_mode': ipsec_site_conn['route_mode'], 'mtu': ipsec_site_conn['mtu'], 'auth_mode': ipsec_site_conn['auth_mode'], 'psk': ipsec_site_conn['psk'], 'initiator': ipsec_site_conn['initiator'], 'dpd': { 'action': ipsec_site_conn['dpd_action'], 'interval': ipsec_site_conn['dpd_interval'], 'timeout': ipsec_site_conn['dpd_timeout'] }, 'admin_state_up': ipsec_site_conn['admin_state_up'], 'status': ipsec_site_conn['status'], 'vpnservice_id': ipsec_site_conn['vpnservice_id'], 'ikepolicy_id': ipsec_site_conn['ikepolicy_id'], 'ipsecpolicy_id': ipsec_site_conn['ipsecpolicy_id'], 'peer_cidrs': [pcidr['cidr'] for pcidr in ipsec_site_conn['peer_cidrs']], 'local_ep_group_id': ipsec_site_conn['local_ep_group_id'], 'peer_ep_group_id': ipsec_site_conn['peer_ep_group_id'], } return self._fields(res, fields) def get_endpoint_info(self, context, ipsec_sitecon): """Obtain all endpoint info, and store in connection for validation.""" ipsec_sitecon['local_epg_subnets'] = self.get_endpoint_group( context, ipsec_sitecon['local_ep_group_id']) ipsec_sitecon['peer_epg_cidrs'] = self.get_endpoint_group( context, ipsec_sitecon['peer_ep_group_id']) def validate_connection_info(self, context, validator, ipsec_sitecon, vpnservice): """Collect info and validate connection. If endpoint groups used (default), collect the group info and do not specify the IP version (as it will come from endpoints). Otherwise, get the IP version from the (legacy) subnet for validation purposes. NOTE: Once the deprecated subnet is removed, the caller can just call get_endpoint_info() and validate_ipsec_site_connection(). """ if ipsec_sitecon['local_ep_group_id']: self.get_endpoint_info(context, ipsec_sitecon) ip_version = None else: ip_version = vpnservice.subnet.ip_version validator.validate_ipsec_site_connection(context, ipsec_sitecon, ip_version, vpnservice) def create_ipsec_site_connection(self, context, ipsec_site_connection): ipsec_sitecon = ipsec_site_connection['ipsec_site_connection'] validator = self._get_validator() validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon) with context.session.begin(subtransactions=True): #Check permissions vpnservice_id = ipsec_sitecon['vpnservice_id'] self._get_resource(context, vpn_models.VPNService, vpnservice_id) self._get_resource(context, vpn_models.IKEPolicy, ipsec_sitecon['ikepolicy_id']) self._get_resource(context, vpn_models.IPsecPolicy, ipsec_sitecon['ipsecpolicy_id']) vpnservice = self._get_vpnservice(context, vpnservice_id) validator.validate_ipsec_conn_optional_args(ipsec_sitecon, vpnservice.subnet) self.validate_connection_info(context, validator, ipsec_sitecon, vpnservice) validator.resolve_peer_address(ipsec_sitecon, vpnservice.router) ipsec_site_conn_db = vpn_models.IPsecSiteConnection( id=uuidutils.generate_uuid(), tenant_id=ipsec_sitecon['tenant_id'], name=ipsec_sitecon['name'], description=ipsec_sitecon['description'], peer_address=ipsec_sitecon['peer_address'], peer_id=ipsec_sitecon['peer_id'], local_id=ipsec_sitecon['local_id'], route_mode='static', mtu=ipsec_sitecon['mtu'], auth_mode='psk', psk=ipsec_sitecon['psk'], initiator=ipsec_sitecon['initiator'], dpd_action=ipsec_sitecon['dpd_action'], dpd_interval=ipsec_sitecon['dpd_interval'], dpd_timeout=ipsec_sitecon['dpd_timeout'], admin_state_up=ipsec_sitecon['admin_state_up'], status=lib_constants.PENDING_CREATE, vpnservice_id=vpnservice_id, ikepolicy_id=ipsec_sitecon['ikepolicy_id'], ipsecpolicy_id=ipsec_sitecon['ipsecpolicy_id'], local_ep_group_id=ipsec_sitecon['local_ep_group_id'], peer_ep_group_id=ipsec_sitecon['peer_ep_group_id'] ) context.session.add(ipsec_site_conn_db) for cidr in ipsec_sitecon['peer_cidrs']: peer_cidr_db = vpn_models.IPsecPeerCidr( cidr=cidr, ipsec_site_connection_id=ipsec_site_conn_db['id'] ) context.session.add(peer_cidr_db) return self._make_ipsec_site_connection_dict(ipsec_site_conn_db) def update_ipsec_site_connection( self, context, ipsec_site_conn_id, 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, vpn_models.IPsecSiteConnection, ipsec_site_conn_id) vpnservice_id = ipsec_site_conn_db['vpnservice_id'] vpnservice = self._get_vpnservice(context, vpnservice_id) validator.assign_sensible_ipsec_sitecon_defaults( ipsec_sitecon, ipsec_site_conn_db) validator.validate_ipsec_conn_optional_args(ipsec_sitecon, vpnservice.subnet) self.validate_connection_info(context, validator, ipsec_sitecon, vpnservice) if 'peer_address' in ipsec_sitecon: validator.resolve_peer_address(ipsec_sitecon, vpnservice.router) self.assert_update_allowed(ipsec_site_conn_db) 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(ipsec_sitecon["peer_cidrs"]) old_peer_cidr_set = set(old_peer_cidr_dict) new_peer_cidrs = list(new_peer_cidr_set) for peer_cidr in old_peer_cidr_set - new_peer_cidr_set: context.session.delete(old_peer_cidr_dict[peer_cidr]) for peer_cidr in new_peer_cidr_set - old_peer_cidr_set: pcidr = vpn_models.IPsecPeerCidr( cidr=peer_cidr, ipsec_site_connection_id=ipsec_site_conn_id) context.session.add(pcidr) # Note: Unconditionally remove peer_cidrs, as they will be set to # previous, if unchanged (to be able to validate above). 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 return result def delete_ipsec_site_connection(self, context, ipsec_site_conn_id): with context.session.begin(subtransactions=True): ipsec_site_conn_db = self._get_resource( context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id) context.session.delete(ipsec_site_conn_db) def _get_ipsec_site_connection( self, context, ipsec_site_conn_id): return self._get_resource( context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id) def get_ipsec_site_connection(self, context, ipsec_site_conn_id, fields=None): ipsec_site_conn_db = self._get_ipsec_site_connection( context, ipsec_site_conn_id) return self._make_ipsec_site_connection_dict( ipsec_site_conn_db, fields) def get_ipsec_site_connections(self, context, filters=None, fields=None): return self._get_collection(context, vpn_models.IPsecSiteConnection, self._make_ipsec_site_connection_dict, filters=filters, fields=fields) def update_ipsec_site_conn_status(self, context, conn_id, new_status): with context.session.begin(): self._update_connection_status(context, conn_id, new_status, True) def _update_connection_status(self, context, conn_id, new_status, updated_pending): """Update the connection status, if changed. If the connection is not in a pending state, unconditionally update the status. Likewise, if in a pending state, and have an indication that the status has changed, then update the database. """ try: conn_db = self._get_ipsec_site_connection(context, conn_id) except vpnaas.IPsecSiteConnectionNotFound: return if not utils.in_pending_status(conn_db.status) or updated_pending: conn_db.status = new_status def _make_ikepolicy_dict(self, ikepolicy, fields=None): res = {'id': ikepolicy['id'], 'tenant_id': ikepolicy['tenant_id'], 'name': ikepolicy['name'], 'description': ikepolicy['description'], 'auth_algorithm': ikepolicy['auth_algorithm'], 'encryption_algorithm': ikepolicy['encryption_algorithm'], 'phase1_negotiation_mode': ikepolicy['phase1_negotiation_mode'], 'lifetime': { 'units': ikepolicy['lifetime_units'], 'value': ikepolicy['lifetime_value'], }, 'ike_version': ikepolicy['ike_version'], 'pfs': ikepolicy['pfs'] } return self._fields(res, fields) def create_ikepolicy(self, context, ikepolicy): ike = ikepolicy['ikepolicy'] validator = self._get_validator() lifetime_info = ike['lifetime'] lifetime_units = lifetime_info.get('units', 'seconds') lifetime_value = lifetime_info.get('value', 3600) with context.session.begin(subtransactions=True): validator.validate_ike_policy(context, ike) ike_db = vpn_models.IKEPolicy( id=uuidutils.generate_uuid(), tenant_id=ike['tenant_id'], name=ike['name'], description=ike['description'], auth_algorithm=ike['auth_algorithm'], encryption_algorithm=ike['encryption_algorithm'], phase1_negotiation_mode=ike['phase1_negotiation_mode'], lifetime_units=lifetime_units, lifetime_value=lifetime_value, ike_version=ike['ike_version'], pfs=ike['pfs'] ) context.session.add(ike_db) return self._make_ikepolicy_dict(ike_db) def update_ikepolicy(self, context, ikepolicy_id, ikepolicy): ike = ikepolicy['ikepolicy'] validator = self._get_validator() with context.session.begin(subtransactions=True): validator.validate_ike_policy(context, ike) if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ikepolicy_id=ikepolicy_id).first(): raise vpnaas.IKEPolicyInUse(ikepolicy_id=ikepolicy_id) ike_db = self._get_resource( context, vpn_models.IKEPolicy, ikepolicy_id) if ike: lifetime_info = ike.get('lifetime') if lifetime_info: if lifetime_info.get('units'): ike['lifetime_units'] = lifetime_info['units'] if lifetime_info.get('value'): ike['lifetime_value'] = lifetime_info['value'] ike_db.update(ike) return self._make_ikepolicy_dict(ike_db) def delete_ikepolicy(self, context, ikepolicy_id): with context.session.begin(subtransactions=True): if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ikepolicy_id=ikepolicy_id).first(): raise vpnaas.IKEPolicyInUse(ikepolicy_id=ikepolicy_id) ike_db = self._get_resource( context, vpn_models.IKEPolicy, ikepolicy_id) context.session.delete(ike_db) def get_ikepolicy(self, context, ikepolicy_id, fields=None): ike_db = self._get_resource( context, vpn_models.IKEPolicy, ikepolicy_id) return self._make_ikepolicy_dict(ike_db, fields) def get_ikepolicies(self, context, filters=None, fields=None): return self._get_collection(context, vpn_models.IKEPolicy, self._make_ikepolicy_dict, filters=filters, fields=fields) def _make_ipsecpolicy_dict(self, ipsecpolicy, fields=None): res = {'id': ipsecpolicy['id'], 'tenant_id': ipsecpolicy['tenant_id'], 'name': ipsecpolicy['name'], 'description': ipsecpolicy['description'], 'transform_protocol': ipsecpolicy['transform_protocol'], 'auth_algorithm': ipsecpolicy['auth_algorithm'], 'encryption_algorithm': ipsecpolicy['encryption_algorithm'], 'encapsulation_mode': ipsecpolicy['encapsulation_mode'], 'lifetime': { 'units': ipsecpolicy['lifetime_units'], 'value': ipsecpolicy['lifetime_value'], }, 'pfs': ipsecpolicy['pfs'] } return self._fields(res, fields) def create_ipsecpolicy(self, context, ipsecpolicy): ipsecp = ipsecpolicy['ipsecpolicy'] validator = self._get_validator() lifetime_info = ipsecp['lifetime'] lifetime_units = lifetime_info.get('units', 'seconds') lifetime_value = lifetime_info.get('value', 3600) with context.session.begin(subtransactions=True): validator.validate_ipsec_policy(context, ipsecp) ipsecp_db = vpn_models.IPsecPolicy( id=uuidutils.generate_uuid(), tenant_id=ipsecp['tenant_id'], name=ipsecp['name'], description=ipsecp['description'], transform_protocol=ipsecp['transform_protocol'], auth_algorithm=ipsecp['auth_algorithm'], encryption_algorithm=ipsecp['encryption_algorithm'], encapsulation_mode=ipsecp['encapsulation_mode'], lifetime_units=lifetime_units, lifetime_value=lifetime_value, pfs=ipsecp['pfs']) context.session.add(ipsecp_db) return self._make_ipsecpolicy_dict(ipsecp_db) def update_ipsecpolicy(self, context, ipsecpolicy_id, ipsecpolicy): ipsecp = ipsecpolicy['ipsecpolicy'] validator = self._get_validator() with context.session.begin(subtransactions=True): validator.validate_ipsec_policy(context, ipsecp) if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ipsecpolicy_id=ipsecpolicy_id).first(): raise vpnaas.IPsecPolicyInUse(ipsecpolicy_id=ipsecpolicy_id) ipsecp_db = self._get_resource( context, vpn_models.IPsecPolicy, ipsecpolicy_id) if ipsecp: lifetime_info = ipsecp.get('lifetime') if lifetime_info: if lifetime_info.get('units'): ipsecp['lifetime_units'] = lifetime_info['units'] if lifetime_info.get('value'): ipsecp['lifetime_value'] = lifetime_info['value'] ipsecp_db.update(ipsecp) return self._make_ipsecpolicy_dict(ipsecp_db) def delete_ipsecpolicy(self, context, ipsecpolicy_id): with context.session.begin(subtransactions=True): if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ipsecpolicy_id=ipsecpolicy_id).first(): raise vpnaas.IPsecPolicyInUse(ipsecpolicy_id=ipsecpolicy_id) ipsec_db = self._get_resource( context, vpn_models.IPsecPolicy, ipsecpolicy_id) context.session.delete(ipsec_db) def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None): ipsec_db = self._get_resource( context, vpn_models.IPsecPolicy, ipsecpolicy_id) return self._make_ipsecpolicy_dict(ipsec_db, fields) def get_ipsecpolicies(self, context, filters=None, fields=None): return self._get_collection(context, vpn_models.IPsecPolicy, self._make_ipsecpolicy_dict, filters=filters, fields=fields) def _make_vpnservice_dict(self, vpnservice, fields=None): res = {'id': vpnservice['id'], 'name': vpnservice['name'], 'description': vpnservice['description'], 'tenant_id': vpnservice['tenant_id'], 'subnet_id': vpnservice['subnet_id'], 'router_id': vpnservice['router_id'], 'flavor_id': vpnservice['flavor_id'], 'admin_state_up': vpnservice['admin_state_up'], 'external_v4_ip': vpnservice['external_v4_ip'], 'external_v6_ip': vpnservice['external_v6_ip'], 'status': vpnservice['status']} return self._fields(res, fields) def create_vpnservice(self, context, vpnservice): vpns = vpnservice['vpnservice'] flavor_id = vpns.get('flavor_id', None) validator = self._get_validator() with context.session.begin(subtransactions=True): validator.validate_vpnservice(context, vpns) vpnservice_db = vpn_models.VPNService( id=uuidutils.generate_uuid(), tenant_id=vpns['tenant_id'], name=vpns['name'], description=vpns['description'], subnet_id=vpns['subnet_id'], router_id=vpns['router_id'], flavor_id=flavor_id, admin_state_up=vpns['admin_state_up'], status=lib_constants.PENDING_CREATE) context.session.add(vpnservice_db) return self._make_vpnservice_dict(vpnservice_db) def set_external_tunnel_ips(self, context, vpnservice_id, v4_ip=None, v6_ip=None): """Update the external tunnel IP(s) for service.""" vpns = {'external_v4_ip': v4_ip, 'external_v6_ip': v6_ip} with context.session.begin(subtransactions=True): vpns_db = self._get_resource(context, vpn_models.VPNService, vpnservice_id) vpns_db.update(vpns) return self._make_vpnservice_dict(vpns_db) def update_vpnservice(self, context, vpnservice_id, vpnservice): vpns = vpnservice['vpnservice'] with context.session.begin(subtransactions=True): vpns_db = self._get_resource(context, vpn_models.VPNService, vpnservice_id) self.assert_update_allowed(vpns_db) if vpns: vpns_db.update(vpns) return self._make_vpnservice_dict(vpns_db) def delete_vpnservice(self, context, vpnservice_id): with context.session.begin(subtransactions=True): if context.session.query(vpn_models.IPsecSiteConnection).filter_by( vpnservice_id=vpnservice_id ).first(): raise vpnaas.VPNServiceInUse(vpnservice_id=vpnservice_id) vpns_db = self._get_resource(context, vpn_models.VPNService, vpnservice_id) context.session.delete(vpns_db) def _get_vpnservice(self, context, vpnservice_id): return self._get_resource(context, vpn_models.VPNService, vpnservice_id) def get_vpnservice(self, context, vpnservice_id, fields=None): vpns_db = self._get_resource(context, vpn_models.VPNService, vpnservice_id) return self._make_vpnservice_dict(vpns_db, fields) def get_vpnservices(self, context, filters=None, fields=None): return self._get_collection(context, vpn_models.VPNService, self._make_vpnservice_dict, filters=filters, fields=fields) def check_router_in_use(self, context, router_id): vpnservices = self.get_vpnservices( context, filters={'router_id': [router_id]}) if vpnservices: plural = "s" if len(vpnservices) > 1 else "" services = ",".join([v['id'] for v in vpnservices]) raise l3_exception.RouterInUse( router_id=router_id, reason="is currently used by VPN service%(plural)s " "(%(services)s)" % {'plural': plural, 'services': services}) def check_subnet_in_use(self, context, subnet_id, router_id): with context.session.begin(subtransactions=True): vpnservices = context.session.query( vpn_models.VPNService).filter_by( subnet_id=subnet_id, router_id=router_id).first() if vpnservices: raise vpnaas.SubnetInUseByVPNService( subnet_id=subnet_id, vpnservice_id=vpnservices['id']) def check_subnet_in_use_by_endpoint_group(self, context, subnet_id): with context.session.begin(subtransactions=True): query = context.session.query(vpn_models.VPNEndpointGroup) query = query.filter(vpn_models.VPNEndpointGroup.endpoint_type == v_constants.SUBNET_ENDPOINT) query = query.join( vpn_models.VPNEndpoint, sa.and_(vpn_models.VPNEndpoint.endpoint_group_id == vpn_models.VPNEndpointGroup.id, vpn_models.VPNEndpoint.endpoint == subnet_id)) group = query.first() if group: raise vpnaas.SubnetInUseByEndpointGroup( subnet_id=subnet_id, group_id=group['id']) def _make_endpoint_group_dict(self, endpoint_group, fields=None): res = {'id': endpoint_group['id'], 'tenant_id': endpoint_group['tenant_id'], 'name': endpoint_group['name'], 'description': endpoint_group['description'], 'type': endpoint_group['endpoint_type'], 'endpoints': [ep['endpoint'] for ep in endpoint_group['endpoints']]} return self._fields(res, fields) def create_endpoint_group(self, context, endpoint_group): group = endpoint_group['endpoint_group'] validator = self._get_validator() with context.session.begin(subtransactions=True): validator.validate_endpoint_group(context, group) endpoint_group_db = vpn_models.VPNEndpointGroup( id=uuidutils.generate_uuid(), tenant_id=group['tenant_id'], name=group['name'], description=group['description'], endpoint_type=group['type']) context.session.add(endpoint_group_db) for endpoint in group['endpoints']: endpoint_db = vpn_models.VPNEndpoint( endpoint=endpoint, endpoint_group_id=endpoint_group_db['id'] ) context.session.add(endpoint_db) return self._make_endpoint_group_dict(endpoint_group_db) def update_endpoint_group(self, context, endpoint_group_id, endpoint_group): group_changes = endpoint_group['endpoint_group'] # Note: Endpoints cannot be changed, so will not do validation with context.session.begin(subtransactions=True): endpoint_group_db = self._get_resource(context, vpn_models.VPNEndpointGroup, endpoint_group_id) endpoint_group_db.update(group_changes) return self._make_endpoint_group_dict(endpoint_group_db) def delete_endpoint_group(self, context, endpoint_group_id): with context.session.begin(subtransactions=True): self.check_endpoint_group_not_in_use(context, endpoint_group_id) endpoint_group_db = self._get_resource( context, vpn_models.VPNEndpointGroup, endpoint_group_id) context.session.delete(endpoint_group_db) def get_endpoint_group(self, context, endpoint_group_id, fields=None): endpoint_group_db = self._get_resource( context, vpn_models.VPNEndpointGroup, endpoint_group_id) return self._make_endpoint_group_dict(endpoint_group_db, fields) def get_endpoint_groups(self, context, filters=None, fields=None): return self._get_collection(context, vpn_models.VPNEndpointGroup, self._make_endpoint_group_dict, filters=filters, fields=fields) def check_endpoint_group_not_in_use(self, context, group_id): query = context.session.query(vpn_models.IPsecSiteConnection) query = query.filter( sa.or_( vpn_models.IPsecSiteConnection.local_ep_group_id == group_id, vpn_models.IPsecSiteConnection.peer_ep_group_id == group_id) ) if query.first(): raise vpnaas.EndpointGroupInUse(group_id=group_id) class VPNPluginRpcDbMixin(object): def _build_local_subnet_cidr_map(self, context): """Build a dict of all local endpoint subnets, with list of CIDRs.""" query = context.session.query(models_v2.Subnet.id, models_v2.Subnet.cidr) query = query.join(vpn_models.VPNEndpoint, vpn_models.VPNEndpoint.endpoint == models_v2.Subnet.id) query = query.join(vpn_models.VPNEndpointGroup, vpn_models.VPNEndpointGroup.id == vpn_models.VPNEndpoint.endpoint_group_id) query = query.join(vpn_models.IPsecSiteConnection, vpn_models.IPsecSiteConnection.local_ep_group_id == vpn_models.VPNEndpointGroup.id) return {sn.id: sn.cidr for sn in query.all()} def update_status_by_agent(self, context, service_status_info_list): """Updating vpnservice and vpnconnection status. :param context: context variable :param service_status_info_list: list of status The structure is [{id: vpnservice_id, status: ACTIVE|DOWN|ERROR, updated_pending_status: True|False ipsec_site_connections: { ipsec_site_connection_id: { status: ACTIVE|DOWN|ERROR, updated_pending_status: True|False } }] The agent will set updated_pending_status as True, when agent updates any pending status. """ with context.session.begin(subtransactions=True): for vpnservice in service_status_info_list: try: vpnservice_db = self._get_vpnservice( context, vpnservice['id']) except vpnaas.VPNServiceNotFound: LOG.warning('vpnservice %s in db is already deleted', vpnservice['id']) continue if (not utils.in_pending_status(vpnservice_db.status) or vpnservice['updated_pending_status']): vpnservice_db.status = vpnservice['status'] for conn_id, conn in vpnservice[ 'ipsec_site_connections'].items(): self._update_connection_status( context, conn_id, conn['status'], conn['updated_pending_status']) def vpn_callback(resource, event, trigger, **kwargs): vpn_plugin = directory.get_plugin(p_constants.VPN) if vpn_plugin: context = kwargs.get('context') router_id = kwargs.get('router_id') if resource == resources.ROUTER_GATEWAY: vpn_plugin.check_router_in_use(context, router_id) elif resource == resources.ROUTER_INTERFACE: subnet_id = kwargs.get('subnet_id') vpn_plugin.check_subnet_in_use(context, subnet_id, router_id) def migration_callback(resource, event, trigger, **kwargs): context = kwargs['context'] router = kwargs['router'] vpn_plugin = directory.get_plugin(p_constants.VPN) if vpn_plugin: vpn_plugin.check_router_in_use(context, router['id']) return True def subnet_callback(resource, event, trigger, **kwargs): """Respond to subnet based notifications - see if subnet in use.""" context = kwargs['context'] subnet_id = kwargs['subnet_id'] vpn_plugin = directory.get_plugin(p_constants.VPN) if vpn_plugin: vpn_plugin.check_subnet_in_use_by_endpoint_group(context, subnet_id) def subscribe(): registry.subscribe( vpn_callback, resources.ROUTER_GATEWAY, events.BEFORE_DELETE) registry.subscribe( vpn_callback, resources.ROUTER_INTERFACE, events.BEFORE_DELETE) registry.subscribe( migration_callback, resources.ROUTER, events.BEFORE_UPDATE) registry.subscribe( subnet_callback, resources.SUBNET, events.BEFORE_DELETE) # NOTE(armax): multiple VPN service plugins (potentially out of tree) may # inherit from vpn_db and may need the callbacks to be processed. Having an # implicit subscription (through the module import) preserves the existing # behavior, and at the same time it avoids fixing it manually in each and # every vpn plugin outta there. That said, The subscription is also made # explicitly in the reference vpn plugin. The subscription operation is # idempotent so there is no harm in registering the same callback multiple # times. subscribe() neutron-vpnaas-12.0.1/neutron_vpnaas/db/vpn/vpn_models.py0000666000175000017500000002217313370230606023556 0ustar zuulzuul00000000000000# (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. from neutron_lib.db import constants as db_const from neutron_lib.db import model_base import sqlalchemy as sa from sqlalchemy import orm from neutron.db.models import l3 from neutron.db import models_v2 from neutron_vpnaas.services.vpn.common import constants class IPsecPeerCidr(model_base.BASEV2): """Internal representation of a IPsec Peer Cidrs.""" cidr = sa.Column(sa.String(32), nullable=False, primary_key=True) ipsec_site_connection_id = sa.Column( sa.String(36), sa.ForeignKey('ipsec_site_connections.id', ondelete="CASCADE"), primary_key=True) class IPsecPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 IPsecPolicy Object.""" __tablename__ = 'ipsecpolicies' name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE)) transform_protocol = sa.Column(sa.Enum("esp", "ah", "ah-esp", name="ipsec_transform_protocols"), nullable=False) auth_algorithm = sa.Column(sa.Enum("sha1", "sha256", "sha384", "sha512", name="vpn_auth_algorithms"), nullable=False) encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128", "aes-256", "aes-192", name="vpn_encrypt_algorithms"), nullable=False) encapsulation_mode = sa.Column(sa.Enum("tunnel", "transport", name="ipsec_encapsulations"), nullable=False) lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes", name="vpn_lifetime_units"), nullable=False) lifetime_value = sa.Column(sa.Integer, nullable=False) pfs = sa.Column(sa.Enum("group2", "group5", "group14", name="vpn_pfs"), nullable=False) class IKEPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 IKEPolicy Object.""" __tablename__ = 'ikepolicies' name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE)) auth_algorithm = sa.Column(sa.Enum("sha1", "sha256", "sha384", "sha512", name="vpn_auth_algorithms"), nullable=False) encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128", "aes-256", "aes-192", name="vpn_encrypt_algorithms"), nullable=False) phase1_negotiation_mode = sa.Column(sa.Enum("main", name="ike_phase1_mode"), nullable=False) lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes", name="vpn_lifetime_units"), nullable=False) lifetime_value = sa.Column(sa.Integer, nullable=False) ike_version = sa.Column(sa.Enum("v1", "v2", name="ike_versions"), nullable=False) pfs = sa.Column(sa.Enum("group2", "group5", "group14", name="vpn_pfs"), nullable=False) class IPsecSiteConnection(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a IPsecSiteConnection Object.""" __tablename__ = 'ipsec_site_connections' name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE)) peer_address = sa.Column(sa.String(255), nullable=False) peer_id = sa.Column(sa.String(255), nullable=False) local_id = sa.Column(sa.String(255), nullable=True) route_mode = sa.Column(sa.String(8), nullable=False) mtu = sa.Column(sa.Integer, nullable=False) initiator = sa.Column(sa.Enum("bi-directional", "response-only", name="vpn_initiators"), nullable=False) auth_mode = sa.Column(sa.String(16), nullable=False) psk = sa.Column(sa.String(255), nullable=False) dpd_action = sa.Column(sa.Enum("hold", "clear", "restart", "disabled", "restart-by-peer", name="vpn_dpd_actions"), nullable=False) dpd_interval = sa.Column(sa.Integer, nullable=False) dpd_timeout = sa.Column(sa.Integer, nullable=False) status = sa.Column(sa.String(16), nullable=False) admin_state_up = sa.Column(sa.Boolean(), nullable=False) vpnservice_id = sa.Column(sa.String(36), sa.ForeignKey('vpnservices.id'), nullable=False) ipsecpolicy_id = sa.Column(sa.String(36), sa.ForeignKey('ipsecpolicies.id'), nullable=False) ikepolicy_id = sa.Column(sa.String(36), sa.ForeignKey('ikepolicies.id'), nullable=False) ipsecpolicy = orm.relationship( IPsecPolicy, backref='ipsec_site_connection') ikepolicy = orm.relationship(IKEPolicy, backref='ipsec_site_connection') peer_cidrs = orm.relationship(IPsecPeerCidr, backref='ipsec_site_connection', lazy='joined', cascade='all, delete, delete-orphan') local_ep_group_id = sa.Column(sa.String(36), sa.ForeignKey('vpn_endpoint_groups.id')) peer_ep_group_id = sa.Column(sa.String(36), sa.ForeignKey('vpn_endpoint_groups.id')) local_ep_group = orm.relationship("VPNEndpointGroup", foreign_keys=local_ep_group_id) peer_ep_group = orm.relationship("VPNEndpointGroup", foreign_keys=peer_ep_group_id) class VPNService(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 VPNService Object.""" name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE)) status = sa.Column(sa.String(16), nullable=False) admin_state_up = sa.Column(sa.Boolean(), nullable=False) external_v4_ip = sa.Column(sa.String(16)) external_v6_ip = sa.Column(sa.String(64)) subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id')) router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'), nullable=False) subnet = orm.relationship(models_v2.Subnet) router = orm.relationship(l3.Router) ipsec_site_connections = orm.relationship( IPsecSiteConnection, backref='vpnservice', cascade="all, delete-orphan") flavor_id = sa.Column(sa.String(36), sa.ForeignKey( 'flavors.id', name='fk_vpnservices_flavors_id')) class VPNEndpoint(model_base.BASEV2): """Endpoints used in VPN connections. All endpoints in a group must be of the same type. Note: the endpoint is an 'opaque' field used to hold different endpoint types, and be flexible enough to use for future types. """ __tablename__ = 'vpn_endpoints' endpoint = sa.Column(sa.String(255), nullable=False, primary_key=True) endpoint_group_id = sa.Column(sa.String(36), sa.ForeignKey('vpn_endpoint_groups.id', ondelete="CASCADE"), primary_key=True) class VPNEndpointGroup(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Collection of endpoints of a specific type, for VPN connections.""" __tablename__ = 'vpn_endpoint_groups' name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE)) endpoint_type = sa.Column(sa.Enum(*constants.VPN_SUPPORTED_ENDPOINT_TYPES, name="vpn_endpoint_type"), nullable=False) endpoints = orm.relationship(VPNEndpoint, backref='endpoint_group', lazy='joined', cascade='all, delete, delete-orphan') neutron-vpnaas-12.0.1/neutron_vpnaas/db/vpn/vpn_validator.py0000666000175000017500000003503413370230615024260 0ustar zuulzuul00000000000000# 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. import socket import netaddr from neutron.db import l3_db from neutron.db import models_v2 from neutron_lib.api import validators from neutron_lib import constants as const from neutron_lib import exceptions as nexception from neutron_lib.plugins import directory from neutron_vpnaas._i18n import _ from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.common import constants class VpnReferenceValidator(object): """ Baseline validation routines for VPN resources. The validations here should be common to all VPN service providers and only raise exceptions from neutron_vpnaas.extensions.vpnaas. """ IP_MIN_MTU = {4: 68, 6: 1280} @property def l3_plugin(self): try: return self._l3_plugin except AttributeError: self._l3_plugin = directory.get_plugin(const.L3) return self._l3_plugin @property def core_plugin(self): try: return self._core_plugin except AttributeError: self._core_plugin = directory.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 _validate_peer_address(self, ip_version, router): # NOTE: peer_address ip version should match with # at least one external gateway address ip version. # ipsec won't work with IPv6 LLA and neutron unaware GUA. # So to support vpnaas with ipv6, external network must # have ipv6 subnet for fixed_ip in router.gw_port['fixed_ips']: addr = fixed_ip['ip_address'] if ip_version == netaddr.IPAddress(addr).version: return raise vpnaas.ExternalNetworkHasNoSubnet( router_id=router.id, ip_version="IPv6" if ip_version == 6 else "IPv4") def resolve_peer_address(self, ipsec_sitecon, router): address = ipsec_sitecon['peer_address'] # check if address is an ip address or fqdn invalid_ip_address = validators.validate_ip_address(address) if invalid_ip_address: # resolve fqdn try: addrinfo = socket.getaddrinfo(address, None)[0] ipsec_sitecon['peer_address'] = addrinfo[-1][0] except socket.gaierror: raise vpnaas.VPNPeerAddressNotResolved(peer_address=address) ip_version = netaddr.IPAddress(ipsec_sitecon['peer_address']).version self._validate_peer_address(ip_version, router) def _get_local_subnets(self, context, endpoint_group): if endpoint_group['type'] != constants.SUBNET_ENDPOINT: raise vpnaas.WrongEndpointGroupType( group_type=endpoint_group['type'], which=endpoint_group['id'], expected=constants.SUBNET_ENDPOINT) subnet_ids = endpoint_group['endpoints'] return context.session.query(models_v2.Subnet).filter( models_v2.Subnet.id.in_(subnet_ids)).all() def _get_peer_cidrs(self, endpoint_group): if endpoint_group['type'] != constants.CIDR_ENDPOINT: raise vpnaas.WrongEndpointGroupType( group_type=endpoint_group['type'], which=endpoint_group['id'], expected=constants.CIDR_ENDPOINT) return endpoint_group['endpoints'] def _check_local_endpoint_ip_versions(self, group_id, local_subnets): """Ensure all subnets in endpoint group have the same IP version. Will return the IP version, so it can be used for inter-group testing. """ if len(local_subnets) == 1: return local_subnets[0]['ip_version'] ip_versions = set([subnet['ip_version'] for subnet in local_subnets]) if len(ip_versions) > 1: raise vpnaas.MixedIPVersionsForIPSecEndpoints(group=group_id) return ip_versions.pop() def _check_peer_endpoint_ip_versions(self, group_id, peer_cidrs): """Ensure all CIDRs in endpoint group have the same IP version. Will return the IP version, so it can be used for inter-group testing. """ if len(peer_cidrs) == 1: return netaddr.IPNetwork(peer_cidrs[0]).version ip_versions = set([netaddr.IPNetwork(pc).version for pc in peer_cidrs]) if len(ip_versions) > 1: raise vpnaas.MixedIPVersionsForIPSecEndpoints(group=group_id) return ip_versions.pop() def _check_peer_cidrs(self, peer_cidrs): """Ensure all CIDRs have the valid format.""" for peer_cidr in peer_cidrs: msg = validators.validate_subnet(peer_cidr) if msg: raise vpnaas.IPsecSiteConnectionPeerCidrError( peer_cidr=peer_cidr) def _check_peer_cidrs_ip_versions(self, peer_cidrs): """Ensure all CIDRs have the same IP version.""" if len(peer_cidrs) == 1: return netaddr.IPNetwork(peer_cidrs[0]).version ip_versions = set([netaddr.IPNetwork(pc).version for pc in peer_cidrs]) if len(ip_versions) > 1: raise vpnaas.MixedIPVersionsForPeerCidrs() return ip_versions.pop() def _check_local_subnets_on_router(self, context, router, local_subnets): for subnet in local_subnets: self._check_subnet_id(context, router, subnet['id']) def _validate_compatible_ip_versions(self, local_ip_version, peer_ip_version): if local_ip_version != peer_ip_version: raise vpnaas.MixedIPVersionsForIPSecConnection() def validate_ipsec_conn_optional_args(self, ipsec_sitecon, subnet): """Ensure that proper combinations of optional args are used. When VPN service has a subnet, then we must have peer_cidrs, and cannot have any endpoint groups. If no subnet for the service, then we must have both endpoint groups, and no peer_cidrs. Method will form a string indicating which endpoints are incorrect, for any exception raised. """ local_epg_id = ipsec_sitecon.get('local_ep_group_id') peer_epg_id = ipsec_sitecon.get('peer_ep_group_id') peer_cidrs = ipsec_sitecon.get('peer_cidrs') if subnet: if not peer_cidrs: raise vpnaas.MissingPeerCidrs() epgs = [] if local_epg_id: epgs.append('local') if peer_epg_id: epgs.append('peer') if epgs: which = ' and '.join(epgs) suffix = 's' if len(epgs) > 1 else '' raise vpnaas.InvalidEndpointGroup(which=which, suffix=suffix) else: if peer_cidrs: raise vpnaas.PeerCidrsInvalid() epgs = [] if not local_epg_id: epgs.append('local') if not peer_epg_id: epgs.append('peer') if epgs: which = ' and '.join(epgs) suffix = 's' if len(epgs) > 1 else '' raise vpnaas.MissingRequiredEndpointGroup(which=which, suffix=suffix) def assign_sensible_ipsec_sitecon_defaults(self, ipsec_sitecon, prev_conn=None): """Provide defaults for optional items, if missing. With endpoint groups capabilities, the peer_cidr (legacy mode) and endpoint group IDs (new mode), are optional. For updating, we need to provide the previous values for any missing values, so that we can detect if the update request is attempting to mix modes. 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 prev_conn: ipsec_sitecon.setdefault( 'peer_cidrs', [pc['cidr'] for pc in prev_conn['peer_cidrs']]) ipsec_sitecon.setdefault('local_ep_group_id', prev_conn['local_ep_group_id']) ipsec_sitecon.setdefault('peer_ep_group_id', prev_conn['peer_ep_group_id']) else: 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, local_ip_version, vpnservice=None): """Reference implementation of validation for IPSec connection. This makes sure that IP versions are the same. For endpoint groups, we use the local subnet(s) IP versions, and peer CIDR(s) IP versions. For legacy mode, we use the (sole) subnet IP version, and the peer CIDR(s). All IP versions must be the same. This method also checks peer_cidrs format(legacy mode), MTU (based on the local IP version), and DPD settings. """ if not local_ip_version: # Using endpoint groups local_subnets = self._get_local_subnets( context, ipsec_sitecon['local_epg_subnets']) self._check_local_subnets_on_router( context, vpnservice['router_id'], local_subnets) local_ip_version = self._check_local_endpoint_ip_versions( ipsec_sitecon['local_ep_group_id'], local_subnets) peer_cidrs = self._get_peer_cidrs(ipsec_sitecon['peer_epg_cidrs']) peer_ip_version = self._check_peer_endpoint_ip_versions( ipsec_sitecon['peer_ep_group_id'], peer_cidrs) else: self._check_peer_cidrs(ipsec_sitecon['peer_cidrs']) peer_ip_version = self._check_peer_cidrs_ip_versions( ipsec_sitecon['peer_cidrs']) self._validate_compatible_ip_versions(local_ip_version, peer_ip_version) self._check_dpd(ipsec_sitecon) mtu = ipsec_sitecon.get('mtu') if mtu: self._check_mtu(context, mtu, local_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']) if vpnservice['subnet_id'] is not None: self._check_subnet_id(context, vpnservice['router_id'], vpnservice['subnet_id']) def validate_ipsec_policy(self, context, ipsec_policy): """Reference implementation of validation for IPSec Policy. Service driver can override and implement specific logic for IPSec Policy validation. """ pass def _validate_cidrs(self, cidrs): """Ensure valid IPv4/6 CIDRs.""" for cidr in cidrs: msg = validators.validate_subnet(cidr) if msg: raise vpnaas.InvalidEndpointInEndpointGroup( group_type=constants.CIDR_ENDPOINT, endpoint=cidr, why=_("Invalid CIDR")) def _validate_subnets(self, context, subnet_ids): """Ensure UUIDs OK and subnets exist.""" for subnet_id in subnet_ids: msg = validators.validate_uuid(subnet_id) if msg: raise vpnaas.InvalidEndpointInEndpointGroup( group_type=constants.SUBNET_ENDPOINT, endpoint=subnet_id, why=_('Invalid UUID')) try: self.core_plugin.get_subnet(context, subnet_id) except nexception.SubnetNotFound: raise vpnaas.NonExistingSubnetInEndpointGroup( subnet=subnet_id) def validate_endpoint_group(self, context, endpoint_group): """Reference validator for endpoint group. Ensures that there is at least one endpoint, all the endpoints in the group are of the same type, and that the endpoints are "valid". Note: Only called for create, as endpoints cannot be changed. """ endpoints = endpoint_group['endpoints'] if not endpoints: raise vpnaas.MissingEndpointForEndpointGroup(group=endpoint_group) group_type = endpoint_group['type'] if group_type == constants.CIDR_ENDPOINT: self._validate_cidrs(endpoints) elif group_type == constants.SUBNET_ENDPOINT: self._validate_subnets(context, endpoints) def validate_ike_policy(self, context, ike_policy): """Reference implementation of validation for IKE Policy. Service driver can override and implement specific logic for IKE Policy validation. """ pass neutron-vpnaas-12.0.1/neutron_vpnaas/db/models/0000775000175000017500000000000013370231105021502 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/models/__init__.py0000666000175000017500000000000013370230606023610 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/db/models/head.py0000666000175000017500000000210513370230615022762 0ustar zuulzuul00000000000000# Copyright (c) 2014 OpenStack Foundation. # 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. """ The module provides all database models at current HEAD. Its purpose is to create comparable metadata with current database schema. Based on this comparison database can be healed with healing migration. """ from neutron.db.migration.models import head from neutron_vpnaas.db.vpn import vpn_db # noqa from neutron_vpnaas.services.vpn.service_drivers import cisco_csr_db # noqa def get_metadata(): return head.model_base.BASEV2.metadata neutron-vpnaas-12.0.1/neutron_vpnaas/tests/0000775000175000017500000000000013370231105020774 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/__init__.py0000666000175000017500000000000013370230606023102 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/base.py0000666000175000017500000000202213370230606022263 0ustar zuulzuul00000000000000# Copyright 2014 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from neutron.tests import base as n_base from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_plugin from neutron.tests.unit.extensions import base as test_api_v2_extension class BaseTestCase(n_base.BaseTestCase): pass class ExtensionTestCase(test_api_v2_extension.ExtensionTestCase): pass class NeutronDbPluginV2TestCase(test_db_plugin.NeutronDbPluginV2TestCase): pass neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/0000775000175000017500000000000013370231105021753 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/__init__.py0000666000175000017500000000126713370230606024101 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg cfg.CONF.use_stderr = False neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/extensions/0000775000175000017500000000000013370231105024152 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/extensions/test_vpn_endpoint_groups.py0000666000175000017500000002044313370230615031677 0ustar zuulzuul00000000000000# (c) Copyright 2015 NEC Corporation, 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 copy import mock from oslo_utils import uuidutils from webob import exc from neutron.tests.unit.api.v2 import test_base as test_api_v2 from neutron_lib.plugins import constants as nconstants from neutron_vpnaas.extensions import vpn_endpoint_groups from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.common import constants from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid _get_path = test_api_v2._get_path class VpnEndpointGroupsTestPlugin( vpnaas.VPNPluginBase, vpn_endpoint_groups.VPNEndpointGroupsPluginBase): pass class VpnEndpointGroupsTestCase(base.ExtensionTestCase): fmt = 'json' def setUp(self): super(VpnEndpointGroupsTestCase, self).setUp() plural_mappings = {'endpoint_group': 'endpoint-groups'} self._setUpExtension( 'neutron_vpnaas.tests.unit.extensions.test_vpn_endpoint_groups.' 'VpnEndpointGroupsTestPlugin', nconstants.VPN, vpn_endpoint_groups.RESOURCE_ATTRIBUTE_MAP, vpn_endpoint_groups.Vpn_endpoint_groups, 'vpn', plural_mappings=plural_mappings, use_quota=True) def helper_test_endpoint_group_create(self, data): """Check that the endpoint_group_create works. Uses passed in endpoint group information, which specifies an endpoint type and values. """ data['endpoint_group'].update({'tenant_id': _uuid(), 'name': 'my endpoint group', 'description': 'my description'}) return_value = copy.copy(data['endpoint_group']) return_value.update({'id': _uuid()}) instance = self.plugin.return_value instance.create_endpoint_group.return_value = return_value res = self.api.post(_get_path('vpn/endpoint-groups', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_endpoint_group.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('endpoint_group', res) self.assertDictSupersetOf(return_value, res['endpoint_group']) def test_create_cidr_endpoint_group_create(self): """Test creation of CIDR type endpoint group.""" data = {'endpoint_group': {'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24', '20.20.20.0/24']}} self.helper_test_endpoint_group_create(data) def test_create_subnet_endpoint_group_create(self): """Test creation of subnet type endpoint group.""" data = {'endpoint_group': {'type': constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), _uuid()]}} self.helper_test_endpoint_group_create(data) def test_create_vlan_endpoint_group_create(self): """Test creation of VLAN type endpoint group.""" data = {'endpoint_group': {'type': constants.VLAN_ENDPOINT, 'endpoints': ['100', '200', '300', '400']}} self.helper_test_endpoint_group_create(data) def test_get_endpoint_group(self): """Test show for endpoint group.""" endpoint_group_id = _uuid() return_value = {'id': endpoint_group_id, 'tenant_id': _uuid(), 'name': 'my-endpoint-group', 'description': 'my endpoint group', 'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24']} instance = self.plugin.return_value instance.get_endpoint_group.return_value = return_value res = self.api.get(_get_path('vpn/endpoint-groups', id=endpoint_group_id, fmt=self.fmt)) instance.get_endpoint_group.assert_called_with(mock.ANY, endpoint_group_id, fields=mock.ANY) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('endpoint_group', res) self.assertEqual(res['endpoint_group'], return_value) def test_endpoint_group_list(self): """Test listing all endpoint groups.""" return_value = [{'id': _uuid(), 'tenant_id': _uuid(), 'name': 'my-endpoint-group', 'description': 'my endpoint group', 'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24']}, {'id': _uuid(), 'tenant_id': _uuid(), 'name': 'another-endpoint-group', 'description': 'second endpoint group', 'type': constants.VLAN_ENDPOINT, 'endpoints': ['100', '200', '300']}] instance = self.plugin.return_value instance.get_endpoint_groups.return_value = return_value res = self.api.get(_get_path('vpn/endpoint-groups', fmt=self.fmt)) instance.get_endpoint_groups.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(res.status_int, exc.HTTPOk.code) def test_endpoint_group_delete(self): """Test deleting an endpoint group.""" self._test_entity_delete('endpoint_group') def test_endpoint_group_update(self): """Test updating endpoint_group.""" endpoint_group_id = _uuid() update_data = {'endpoint_group': {'description': 'new description'}} return_value = {'id': endpoint_group_id, 'tenant_id': _uuid(), 'name': 'my-endpoint-group', 'description': 'new_description', 'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24']} instance = self.plugin.return_value instance.update_endpoint_group.return_value = return_value res = self.api.put(_get_path('vpn/endpoint-groups', id=endpoint_group_id, fmt=self.fmt), self.serialize(update_data)) instance.update_endpoint_group.assert_called_with( mock.ANY, endpoint_group_id, endpoint_group=update_data) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('endpoint_group', res) self.assertEqual(res['endpoint_group'], return_value) def test_fail_updating_endpoints_in_endpoint_group(self): """Test fails to update the endpoints in an endpoint group. This documents that we are not allowing endpoints to be updated (currently), as doing so, implies that the connection using the enclosing endpoint group would also need to be updated. For now, a new endpoint group can be created, and the connection can be updated to point to the new endpoint group. """ endpoint_group_id = _uuid() update_data = {'endpoint_group': {'endpoints': ['10.10.10.0/24']}} res = self.api.put(_get_path('vpn/endpoint-groups', id=endpoint_group_id, fmt=self.fmt), params=self.serialize(update_data), expect_errors=True) self.assertEqual(exc.HTTPBadRequest.code, res.status_int) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/extensions/__init__.py0000666000175000017500000000000013370230606026260 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/extensions/test_vpnaas.py0000666000175000017500000006457413370230615027102 0ustar zuulzuul00000000000000# (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 copy import mock from neutron.tests.unit.api.v2 import test_base as test_api_v2 from neutron_lib.plugins import constants as nconstants from oslo_utils import uuidutils from webob import exc from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid _get_path = test_api_v2._get_path class VpnaasExtensionTestCase(base.ExtensionTestCase): fmt = 'json' def setUp(self): super(VpnaasExtensionTestCase, self).setUp() plural_mappings = {'ipsecpolicy': 'ipsecpolicies', 'ikepolicy': 'ikepolicies', 'ipsec_site_connection': 'ipsec-site-connections', 'endpoint_group': 'endpoint-groups'} self._setUpExtension( 'neutron_vpnaas.extensions.vpnaas.VPNPluginBase', nconstants.VPN, vpnaas.RESOURCE_ATTRIBUTE_MAP, vpnaas.Vpnaas, 'vpn', plural_mappings=plural_mappings, use_quota=True) def test_ikepolicy_create(self): """Test case to create an ikepolicy.""" ikepolicy_id = _uuid() data = {'ikepolicy': {'name': 'ikepolicy1', 'description': 'myikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'main', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid()}} return_value = copy.copy(data['ikepolicy']) return_value.update({'id': ikepolicy_id}) instance = self.plugin.return_value instance.create_ikepolicy.return_value = return_value res = self.api.post(_get_path('vpn/ikepolicies', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_ikepolicy.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertDictSupersetOf(return_value, res['ikepolicy']) def test_ikepolicy_list(self): """Test case to list all ikepolicies.""" ikepolicy_id = _uuid() return_value = [{'name': 'ikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'ike_version': 'v1', 'id': ikepolicy_id}] instance = self.plugin.return_value instance.get_ikepolicies.return_value = return_value res = self.api.get(_get_path('vpn/ikepolicies', fmt=self.fmt)) instance.get_ikepolicies.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_ikepolicy_update(self): """Test case to update an ikepolicy.""" ikepolicy_id = _uuid() update_data = {'ikepolicy': {'name': 'ikepolicy1', 'encryption_algorithm': 'aes-256'}} return_value = {'name': 'ikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-256', 'phase1_negotiation_mode': 'main', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ikepolicy_id} instance = self.plugin.return_value instance.update_ikepolicy.return_value = return_value res = self.api.put(_get_path('vpn/ikepolicies', id=ikepolicy_id, fmt=self.fmt), self.serialize(update_data)) instance.update_ikepolicy.assert_called_with(mock.ANY, ikepolicy_id, ikepolicy=update_data) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertEqual(return_value, res['ikepolicy']) def test_ikepolicy_get(self): """Test case to get or show an ikepolicy.""" ikepolicy_id = _uuid() return_value = {'name': 'ikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'main', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ikepolicy_id} instance = self.plugin.return_value instance.get_ikepolicy.return_value = return_value res = self.api.get(_get_path('vpn/ikepolicies', id=ikepolicy_id, fmt=self.fmt)) instance.get_ikepolicy.assert_called_with(mock.ANY, ikepolicy_id, fields=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertEqual(return_value, res['ikepolicy']) def test_ikepolicy_delete(self): """Test case to delete an ikepolicy.""" self._test_entity_delete('ikepolicy') def test_ipsecpolicy_create(self): """Test case to create an ipsecpolicy.""" ipsecpolicy_id = _uuid() data = {'ipsecpolicy': {'name': 'ipsecpolicy1', 'description': 'myipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'transform_protocol': 'esp', 'pfs': 'group5', 'tenant_id': _uuid()}} return_value = copy.copy(data['ipsecpolicy']) return_value.update({'id': ipsecpolicy_id}) instance = self.plugin.return_value instance.create_ipsecpolicy.return_value = return_value res = self.api.post(_get_path('vpn/ipsecpolicies', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_ipsecpolicy.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsecpolicy', res) self.assertDictSupersetOf(return_value, res['ipsecpolicy']) def test_ipsecpolicy_list(self): """Test case to list an ipsecpolicy.""" ipsecpolicy_id = _uuid() return_value = [{'name': 'ipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'id': ipsecpolicy_id}] instance = self.plugin.return_value instance.get_ipsecpolicies.return_value = return_value res = self.api.get(_get_path('vpn/ipsecpolicies', fmt=self.fmt)) instance.get_ipsecpolicies.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_ipsecpolicy_update(self): """Test case to update an ipsecpolicy.""" ipsecpolicy_id = _uuid() update_data = {'ipsecpolicy': {'name': 'ipsecpolicy1', 'encryption_algorithm': 'aes-256'}} return_value = {'name': 'ipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'transform_protocol': 'esp', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ipsecpolicy_id} instance = self.plugin.return_value instance.update_ipsecpolicy.return_value = return_value res = self.api.put(_get_path('vpn/ipsecpolicies', id=ipsecpolicy_id, fmt=self.fmt), self.serialize(update_data)) instance.update_ipsecpolicy.assert_called_with(mock.ANY, ipsecpolicy_id, ipsecpolicy=update_data) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsecpolicy', res) self.assertEqual(return_value, res['ipsecpolicy']) def test_ipsecpolicy_get(self): """Test case to get or show an ipsecpolicy.""" ipsecpolicy_id = _uuid() return_value = {'name': 'ipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'transform_protocol': 'esp', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ipsecpolicy_id} instance = self.plugin.return_value instance.get_ipsecpolicy.return_value = return_value res = self.api.get(_get_path('vpn/ipsecpolicies', id=ipsecpolicy_id, fmt=self.fmt)) instance.get_ipsecpolicy.assert_called_with(mock.ANY, ipsecpolicy_id, fields=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsecpolicy', res) self.assertEqual(return_value, res['ipsecpolicy']) def test_ipsecpolicy_delete(self): """Test case to delete an ipsecpolicy.""" self._test_entity_delete('ipsecpolicy') def _test_vpnservice_create(self, more_args, defaulted_args): """Helper to test VPN service creation. Allows additional args to be specified for different test cases. Includes expected args, for case where an optional args are not specified and API applies defaults. """ data = {'vpnservice': {'name': 'vpnservice1', 'description': 'descr_vpn1', 'router_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid()}} data['vpnservice'].update(more_args) # Add in any default values for args that were not provided actual_args = copy.copy(data) actual_args['vpnservice'].update(defaulted_args) return_value = copy.copy(data['vpnservice']) return_value.update({'status': "ACTIVE", 'id': _uuid()}) return_value.update(defaulted_args) instance = self.plugin.return_value instance.create_vpnservice.return_value = return_value res = self.api.post(_get_path('vpn/vpnservices', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_vpnservice.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('vpnservice', res) self.assertDictSupersetOf(return_value, res['vpnservice']) def test_vpnservice_create(self): """Create VPN service using subnet (older API).""" subnet = {'subnet_id': _uuid()} self._test_vpnservice_create(more_args=subnet, defaulted_args={}) def test_vpnservice_create_no_subnet(self): """Test case to create a vpnservice w/o subnet (newer API).""" no_subnet = {'subnet_id': None} self._test_vpnservice_create(more_args={}, defaulted_args=no_subnet) def test_vpnservice_list(self): """Test case to list all vpnservices.""" vpnservice_id = _uuid() return_value = [{'name': 'vpnservice1', 'tenant_id': _uuid(), 'status': 'ACTIVE', 'id': vpnservice_id}] instance = self.plugin.return_value instance.get_vpnservice.return_value = return_value res = self.api.get(_get_path('vpn/vpnservices', fmt=self.fmt)) instance.get_vpnservices.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_vpnservice_update(self): """Test case to update a vpnservice.""" vpnservice_id = _uuid() update_data = {'vpnservice': {'admin_state_up': False}} return_value = {'name': 'vpnservice1', 'admin_state_up': False, 'subnet_id': _uuid(), 'router_id': _uuid(), 'tenant_id': _uuid(), 'status': "ACTIVE", 'id': vpnservice_id} instance = self.plugin.return_value instance.update_vpnservice.return_value = return_value res = self.api.put(_get_path('vpn/vpnservices', id=vpnservice_id, fmt=self.fmt), self.serialize(update_data)) instance.update_vpnservice.assert_called_with(mock.ANY, vpnservice_id, vpnservice=update_data) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('vpnservice', res) self.assertEqual(return_value, res['vpnservice']) def test_vpnservice_get(self): """Test case to get or show a vpnservice.""" vpnservice_id = _uuid() return_value = {'name': 'vpnservice1', 'admin_state_up': True, 'subnet_id': _uuid(), 'router_id': _uuid(), 'tenant_id': _uuid(), 'status': "ACTIVE", 'id': vpnservice_id} instance = self.plugin.return_value instance.get_vpnservice.return_value = return_value res = self.api.get(_get_path('vpn/vpnservices', id=vpnservice_id, fmt=self.fmt)) instance.get_vpnservice.assert_called_with(mock.ANY, vpnservice_id, fields=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('vpnservice', res) self.assertEqual(return_value, res['vpnservice']) def test_vpnservice_delete(self): """Test case to delete a vpnservice.""" self._test_entity_delete('vpnservice') def _test_ipsec_site_connection_create(self, more_args, defaulted_args): """Helper to test creating IPSec connection.""" ipsecsite_con_id = _uuid() ikepolicy_id = _uuid() ipsecpolicy_id = _uuid() data = { 'ipsec_site_connection': {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': ikepolicy_id, 'ipsecpolicy_id': ipsecpolicy_id, 'vpnservice_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid()} } data['ipsec_site_connection'].update(more_args) # Add in any default values for args that were not provided actual_args = copy.copy(data) actual_args['ipsec_site_connection'].update(defaulted_args) return_value = copy.copy(data['ipsec_site_connection']) return_value.update({'status': "ACTIVE", 'id': ipsecsite_con_id}) return_value.update(defaulted_args) instance = self.plugin.return_value instance.create_ipsec_site_connection.return_value = return_value res = self.api.post(_get_path('vpn/ipsec-site-connections', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_ipsec_site_connection.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsec_site_connection', res) self.assertDictSupersetOf(return_value, res['ipsec_site_connection']) def test_ipsec_site_connection_create(self): """Create an IPSec connection with peer CIDRs (old API).""" more_args = {'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'local_id': ''} no_endpoint_groups = {'local_ep_group_id': None, 'peer_ep_group_id': None} self._test_ipsec_site_connection_create( more_args=more_args, defaulted_args=no_endpoint_groups) def test_ipsec_site_connection_create_with_endpoints(self): """Create an IPSec connection with endpoint groups (new API).""" more_args = {'local_ep_group_id': _uuid(), 'peer_ep_group_id': _uuid(), 'local_id': ''} no_peer_cidrs = {'peer_cidrs': []} self._test_ipsec_site_connection_create(more_args=more_args, defaulted_args=no_peer_cidrs) def test_ipsec_site_connection_create_with_invalid_cidr_format(self): peer_cidrs = ['192.168.2.0/24', '10/8'] data = { 'ipsec_site_connection': {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': peer_cidrs, 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': _uuid(), 'ipsecpolicy_id': _uuid(), 'vpnservice_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid()} } res = self.api.post(_get_path('vpn/ipsec-site-connections', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertEqual(exc.HTTPBadRequest.code, res.status_int) def test_ipsec_site_connection_list(self): """Test case to list all ipsec_site_connections.""" ipsecsite_con_id = _uuid() return_value = [{'name': 'connection1', 'peer_address': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'route_mode': 'static', 'auth_mode': 'psk', 'local_ep_group_id': None, 'peer_ep_group_id': None, 'tenant_id': _uuid(), 'status': 'ACTIVE', 'id': ipsecsite_con_id}] instance = self.plugin.return_value instance.get_ipsec_site_connections.return_value = return_value res = self.api.get( _get_path('vpn/ipsec-site-connections', fmt=self.fmt)) instance.get_ipsec_site_connections.assert_called_with( mock.ANY, fields=mock.ANY, filters=mock.ANY ) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_ipsec_site_connection_update(self): """Test case to update a ipsec_site_connection.""" ipsecsite_con_id = _uuid() update_data = {'ipsec_site_connection': {'admin_state_up': False}} return_value = {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': _uuid(), 'ipsecpolicy_id': _uuid(), 'vpnservice_id': _uuid(), 'admin_state_up': False, 'local_ep_group_id': None, 'peer_ep_group_id': None, 'tenant_id': _uuid(), 'status': 'ACTIVE', 'id': ipsecsite_con_id} instance = self.plugin.return_value instance.update_ipsec_site_connection.return_value = return_value res = self.api.put(_get_path('vpn/ipsec-site-connections', id=ipsecsite_con_id, fmt=self.fmt), self.serialize(update_data)) instance.update_ipsec_site_connection.assert_called_with( mock.ANY, ipsecsite_con_id, ipsec_site_connection=update_data ) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsec_site_connection', res) self.assertEqual(return_value, res['ipsec_site_connection']) def test_ipsec_site_connection_get(self): """Test case to get or show a ipsec_site_connection.""" ipsecsite_con_id = _uuid() return_value = {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': _uuid(), 'ipsecpolicy_id': _uuid(), 'vpnservice_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid(), 'local_ep_group_id': None, 'peer_ep_group_id': None, 'status': 'ACTIVE', 'id': ipsecsite_con_id} instance = self.plugin.return_value instance.get_ipsec_site_connection.return_value = return_value res = self.api.get(_get_path('vpn/ipsec-site-connections', id=ipsecsite_con_id, fmt=self.fmt)) instance.get_ipsec_site_connection.assert_called_with( mock.ANY, ipsecsite_con_id, fields=mock.ANY ) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsec_site_connection', res) self.assertEqual(return_value, res['ipsec_site_connection']) def test_ipsec_site_connection_delete(self): """Test case to delete a ipsec_site_connection.""" self._test_entity_delete('ipsec_site_connection') neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/0000775000175000017500000000000013370231105023576 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/__init__.py0000666000175000017500000000000013370230606025704 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/0000775000175000017500000000000013370231105024401 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/__init__.py0000666000175000017500000000000013370230606026507 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/test_plugin.py0000666000175000017500000003731413370230615027327 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, 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. import contextlib import mock from neutron.db import servicetype_db as st_db from neutron.services.flavors.flavors_plugin import FlavorsPlugin from neutron.tests.unit.db import test_agentschedulers_db from neutron.tests.unit.extensions import test_agent as test_agent_ext_plugin from neutron_lib import constants as lib_constants from neutron_lib import context from neutron_lib import exceptions as lib_exc from neutron_lib.exceptions import flavors as flav_exc from neutron_lib.plugins import constants as p_constants from neutron_lib.plugins import directory from oslo_utils import uuidutils from neutron_vpnaas.extensions import vpn_flavors from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.services.vpn.service_drivers import driver_validator from neutron_vpnaas.services.vpn.service_drivers import ipsec as ipsec_driver from neutron_vpnaas.tests import base from neutron_vpnaas.tests.unit.db.vpn import test_vpn_db as test_db_vpnaas FAKE_HOST = test_agent_ext_plugin.L3_HOSTA VPN_DRIVER_CLASS = 'neutron_vpnaas.services.vpn.plugin.VPNDriverPlugin' IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.' 'ipsec.IPsecVPNDriver') CISCO_IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.' 'cisco_ipsec.CiscoCsrIPsecVPNDriver') _uuid = uuidutils.generate_uuid class TestVPNDriverPlugin(test_db_vpnaas.TestVpnaas, test_agentschedulers_db.AgentSchedulerTestMixIn, test_agent_ext_plugin.AgentDBTestMixIn): def setUp(self): driver_cls_p = mock.patch( 'neutron_vpnaas.services.vpn.' 'service_drivers.ipsec.IPsecVPNDriver') driver_cls = driver_cls_p.start() self.driver = mock.Mock() self.driver.service_type = ipsec_driver.IPSEC self.driver.validator = driver_validator.VpnDriverValidator( self.driver) driver_cls.return_value = self.driver super(TestVPNDriverPlugin, self).setUp( vpnaas_plugin=VPN_DRIVER_CLASS) # Note: Context must be created after BaseTestCase.setUp() so that # config for policy is set. self.adminContext = context.get_admin_context() def test_create_ipsec_site_connection(self, **extras): super(TestVPNDriverPlugin, self).test_create_ipsec_site_connection() self.driver.create_ipsec_site_connection.assert_called_once_with( mock.ANY, mock.ANY) self.driver.delete_ipsec_site_connection.assert_called_once_with( mock.ANY, mock.ANY) def test_create_vpnservice(self): mock.patch('neutron_vpnaas.services.vpn.plugin.' 'VPNDriverPlugin._get_driver_for_vpnservice', return_value=self.driver).start() stm = directory.get_plugin(p_constants.VPN).service_type_manager stm.add_resource_association = mock.Mock() super(TestVPNDriverPlugin, self).test_create_vpnservice() self.driver.create_vpnservice.assert_called_once_with( mock.ANY, mock.ANY) stm.add_resource_association.assert_called_once_with( mock.ANY, p_constants.VPN, 'vpnaas', mock.ANY) def test_delete_vpnservice(self, **extras): stm = directory.get_plugin(p_constants.VPN).service_type_manager stm.del_resource_associations = mock.Mock() super(TestVPNDriverPlugin, self).test_delete_vpnservice() self.driver.delete_vpnservice.assert_called_once_with( mock.ANY, mock.ANY) stm.del_resource_associations.assert_called_once_with( mock.ANY, [mock.ANY]) def test_update_vpnservice(self, **extras): super(TestVPNDriverPlugin, self).test_update_vpnservice() self.driver.update_vpnservice.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY) @contextlib.contextmanager def vpnservice_set(self): """Test case to create a ipsec_site_connection.""" vpnservice_name = "vpn1" ipsec_site_connection_name = "ipsec_site_connection" ikename = "ikepolicy1" ipsecname = "ipsecpolicy1" description = "my-vpn-connection" keys = {'name': vpnservice_name, 'description': "my-vpn-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, 'dpd_action': 'hold', 'dpd_interval': 40, 'dpd_timeout': 120, 'tenant_id': self._tenant_id, 'psk': 'abcd', 'status': 'PENDING_CREATE', 'admin_state_up': True} with self.ikepolicy(name=ikename) as ikepolicy: with self.ipsecpolicy(name=ipsecname) as ipsecpolicy: with self.subnet() as subnet: with self.router() as router: plugin = directory.get_plugin() agent = {'host': FAKE_HOST, 'agent_type': lib_constants.AGENT_TYPE_L3, 'binary': 'fake-binary', 'topic': 'fake-topic'} plugin.create_or_update_agent(self.adminContext, agent) plugin.schedule_router( self.adminContext, router['router']['id']) with self.vpnservice(name=vpnservice_name, subnet=subnet, router=router) as vpnservice1: keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ( ipsecpolicy['ipsecpolicy']['id'] ) keys['vpnservice_id'] = ( vpnservice1['vpnservice']['id'] ) with self.ipsec_site_connection( self.fmt, ipsec_site_connection_name, keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], keys['dpd_action'], keys['dpd_interval'], keys['dpd_timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=description, ): yield vpnservice1['vpnservice'] def test_update_status(self): with self.vpnservice_set() as vpnservice: self._register_agent_states() service_plugin = directory.get_plugin(p_constants.VPN) service_plugin.update_status_by_agent( self.adminContext, [{'status': 'ACTIVE', 'ipsec_site_connections': {}, 'updated_pending_status': True, 'id': vpnservice['id']}]) vpnservice = service_plugin.get_vpnservice( self.adminContext, vpnservice['id']) self.assertEqual(lib_constants.ACTIVE, vpnservice['status']) class TestVPNDriverPluginMultipleDrivers(base.BaseTestCase): def setUp(self): super(TestVPNDriverPluginMultipleDrivers, self).setUp() vpnaas_providers = [ {'service_type': p_constants.VPN, 'name': 'ipsec', 'driver': IPSEC_SERVICE_DRIVER, 'default': True}, {'service_type': p_constants.VPN, 'name': 'cisco', 'driver': CISCO_IPSEC_SERVICE_DRIVER, 'default': False}] self.service_providers = ( mock.patch.object(st_db.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = vpnaas_providers self.adminContext = context.get_admin_context() @contextlib.contextmanager def vpnservices_providers_set(self, vpnservices=None, provider_names=None): if not vpnservices: vpnservices = [] if not provider_names: provider_names = {} stm = st_db.ServiceTypeManager() stm.get_provider_names_by_resource_ids = mock.Mock( return_value=provider_names) mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=stm).start() mock.patch('neutron_vpnaas.db.vpn.vpn_db.VPNPluginDb.get_vpnservices', return_value=vpnservices).start() yield stm def test_multiple_drivers_loaded(self): with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual(2, len(driver_plugin.drivers)) self.assertEqual('ipsec', driver_plugin.default_provider) self.assertIn('ipsec', driver_plugin.drivers) self.assertEqual('ipsec', driver_plugin.drivers['ipsec'].name) self.assertIn('cisco', driver_plugin.drivers) self.assertEqual('cisco', driver_plugin.drivers['cisco'].name) def test_provider_lost(self): LOST_SERVICE_ID = _uuid() LOST_PROVIDER_SERVICE = {'id': LOST_SERVICE_ID} with self.vpnservices_providers_set( vpnservices=[LOST_PROVIDER_SERVICE], provider_names={LOST_SERVICE_ID: 'LOST_PROVIDER'} ): self.assertRaises(SystemExit, vpn_plugin.VPNDriverPlugin) def test_unasso_vpnservices(self): UNASSO_SERVICE_ID = _uuid() with self.vpnservices_providers_set( vpnservices=[{'id': UNASSO_SERVICE_ID}] ) as stm: stm.add_resource_association = mock.Mock() vpn_plugin.VPNDriverPlugin() stm.add_resource_association.assert_called_once_with( mock.ANY, p_constants.VPN, 'ipsec', UNASSO_SERVICE_ID) def test_get_driver_for_vpnservice(self): CISCO_VPNSERVICE_ID = _uuid() CISCO_VPNSERVICE = {'id': CISCO_VPNSERVICE_ID} provider_names = {CISCO_VPNSERVICE_ID: 'cisco'} with self.vpnservices_providers_set(provider_names=provider_names): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual( driver_plugin.drivers['cisco'], driver_plugin._get_driver_for_vpnservice( self.adminContext, CISCO_VPNSERVICE)) def test_get_driver_for_ipsec_site_connection(self): IPSEC_VPNSERVICE_ID = _uuid() IPSEC_SITE_CONNECTION = {'vpnservice_id': IPSEC_VPNSERVICE_ID} provider_names = {IPSEC_VPNSERVICE_ID: 'ipsec'} with self.vpnservices_providers_set(provider_names=provider_names): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual( driver_plugin.drivers['ipsec'], driver_plugin._get_driver_for_ipsec_site_connection( self.adminContext, IPSEC_SITE_CONNECTION)) def test_get_provider_for_none_flavor_id(self): with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() provider = driver_plugin._get_provider_for_flavor( self.adminContext, None) self.assertEqual( driver_plugin.default_provider, provider) def test_get_provider_for_flavor_id_plugin_not_loaded(self): with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( vpn_flavors.FlavorsPluginNotLoaded, driver_plugin._get_provider_for_flavor, self.adminContext, _uuid()) def test_get_provider_for_flavor_id_invalid_type(self): FAKE_FLAVOR = {'service_type': 'NOT_VPN'} directory.add_plugin(p_constants.FLAVORS, FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( lib_exc.InvalidServiceType, driver_plugin._get_provider_for_flavor, self.adminContext, _uuid()) def test_get_provider_for_flavor_id_flavor_disabled(self): FAKE_FLAVOR = {'service_type': p_constants.VPN, 'enabled': False} directory.add_plugin(p_constants.FLAVORS, FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( flav_exc.FlavorDisabled, driver_plugin._get_provider_for_flavor, self.adminContext, _uuid()) def test_get_provider_for_flavor_id_provider_not_found(self): FLAVOR_ID = _uuid() FAKE_FLAVOR = {'id': FLAVOR_ID, 'service_type': p_constants.VPN, 'enabled': True} PROVIDERS = [{'provider': 'SOME_PROVIDER'}] directory.add_plugin(p_constants.FLAVORS, FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() mock.patch( 'neutron.services.flavors.flavors_plugin.' 'FlavorsPlugin.get_flavor_next_provider', return_value=PROVIDERS).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( vpn_flavors.NoProviderFoundForFlavor, driver_plugin._get_provider_for_flavor, self.adminContext, FLAVOR_ID) def test_get_provider_for_flavor_id(self): FLAVOR_ID = _uuid() FAKE_FLAVOR = {'id': FLAVOR_ID, 'service_type': p_constants.VPN, 'enabled': True} PROVIDERS = [{'provider': 'cisco'}] directory.add_plugin(p_constants.FLAVORS, FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() mock.patch( 'neutron.services.flavors.flavors_plugin.' 'FlavorsPlugin.get_flavor_next_provider', return_value=PROVIDERS).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual( 'cisco', driver_plugin._get_provider_for_flavor( self.adminContext, FLAVOR_ID)) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py0000666000175000017500000000665313370230606030356 0ustar zuulzuul00000000000000# Copyright 2014 OpenStack Foundation. # 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 mock from neutron_lib.callbacks import registry from oslo_config import cfg from oslo_utils import uuidutils 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 from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid VPNAAS_NOP_DEVICE = ('neutron_vpnaas.tests.unit.services.' 'vpn.test_vpn_service.NoopDeviceDriver') VPNAAS_DEFAULT_DEVICE = ('neutron_vpnaas.services.vpn.' 'device_drivers.ipsec.OpenSwanDriver') FAKE_ROUTER_ID = _uuid() class NoopDeviceDriver(device_drivers.DeviceDriver): def sync(self, context, processes): pass def create_router(self, router_info): pass def destroy_router(self, process_id): pass class VPNBaseTestCase(base.BaseTestCase): def setUp(self): super(VPNBaseTestCase, self).setUp() self.conf = cfg.CONF self.ri_kwargs = {'router': {'id': FAKE_ROUTER_ID, 'ha': False}, 'agent_conf': self.conf, 'interface_driver': mock.sentinel.interface_driver} class TestVirtualPrivateNetworkDeviceDriverLoading(VPNBaseTestCase): def setUp(self): super(TestVirtualPrivateNetworkDeviceDriverLoading, self).setUp() cfg.CONF.register_opts(vpn_agent.vpn_agent_opts, 'vpnagent') self.agent = mock.Mock() self.agent.conf = cfg.CONF mock.patch.object(registry, 'subscribe').start() self.service = vpn_service.VPNService(self.agent) def test_loading_vpn_device_drivers(self): """Get two device drivers (in a list) for VPNaaS.""" cfg.CONF.set_override('vpn_device_driver', [VPNAAS_NOP_DEVICE, VPNAAS_NOP_DEVICE], 'vpnagent') drivers = self.service.load_device_drivers('host') self.assertEqual(2, len(drivers)) self.assertIn(drivers[0].__class__.__name__, VPNAAS_NOP_DEVICE) self.assertIn(drivers[1].__class__.__name__, VPNAAS_NOP_DEVICE) def test_use_default_for_vpn_device_driver(self): """When no VPNaaS device drivers specified, we get the default.""" drivers = self.service.load_device_drivers('host') self.assertEqual(1, len(drivers)) self.assertIn(drivers[0].__class__.__name__, VPNAAS_DEFAULT_DEVICE) def test_fail_no_such_vpn_device_driver(self): """Failure test of import error for VPNaaS device driver.""" cfg.CONF.set_override('vpn_device_driver', ['no.such.class'], 'vpnagent') self.assertRaises(vpnaas.DeviceDriverImportError, self.service.load_device_drivers, 'host') neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/0000775000175000017500000000000013370231105027577 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/__init__.py0000666000175000017500000000000013370230606031705 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_cisco_ipsec.py0000666000175000017500000005475013370230615033515 0ustar zuulzuul00000000000000# 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. import mock from six import moves from neutron.db import servicetype_db as st_db from neutron.tests.unit import testlib_api from neutron_lib import constants as lib_const from neutron_lib import context as n_ctx from neutron_lib.plugins import constants from neutron_lib.plugins import directory from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.db.vpn import vpn_validator from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.services.vpn.service_drivers import cisco_csr_db as csr_db from neutron_vpnaas.services.vpn.service_drivers \ import cisco_ipsec as ipsec_driver from neutron_vpnaas.services.vpn.service_drivers \ import cisco_validator as validator from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_VPN_CONN_ID = _uuid() FAKE_SERVICE_ID = _uuid() FAKE_VPN_CONNECTION = { 'vpnservice_id': FAKE_SERVICE_ID, 'id': FAKE_VPN_CONN_ID, 'ikepolicy_id': _uuid(), 'ipsecpolicy_id': _uuid(), 'tenant_id': _uuid() } FAKE_ROUTER_ID = _uuid() FAKE_VPN_SERVICE = { 'router_id': FAKE_ROUTER_ID } FAKE_HOST = 'fake_host' IPV4 = 4 CISCO_IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.' 'cisco_ipsec.CiscoCsrIPsecVPNDriver') class TestCiscoValidatorSelection(base.BaseTestCase): def setUp(self): super(TestCiscoValidatorSelection, self).setUp() # TODO(armax): remove this if branch as soon as the ServiceTypeManager # API for adding provider configurations becomes available if not hasattr(st_db.ServiceTypeManager, 'add_provider_configuration'): vpnaas_provider = (constants.VPN + ':vpnaas:' + CISCO_IPSEC_SERVICE_DRIVER + ':default') cfg.CONF.set_override( 'service_provider', [vpnaas_provider], 'service_providers') else: vpnaas_provider = [{ 'service_type': constants.VPN, 'name': 'vpnaas', 'driver': CISCO_IPSEC_SERVICE_DRIVER, 'default': True }] # override the default service provider self.service_providers = ( mock.patch.object(st_db.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = vpnaas_provider mock.patch('neutron.common.rpc.create_connection').start() stm = st_db.ServiceTypeManager() stm.get_provider_names_by_resource_ids = mock.Mock( return_value={}) mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=stm).start() mock.patch('neutron_vpnaas.db.vpn.vpn_db.VPNPluginDb.get_vpnservices', return_value=[]).start() self.vpn_plugin = vpn_plugin.VPNDriverPlugin() def test_reference_driver_used(self): default_provider = self.vpn_plugin.default_provider default_driver = self.vpn_plugin.drivers[default_provider] self.assertIsInstance(default_driver.validator, validator.CiscoCsrVpnValidator) class TestCiscoIPsecDriverValidation(base.BaseTestCase): def setUp(self): super(TestCiscoIPsecDriverValidation, self).setUp() self.context = n_ctx.Context('some_user', 'some_tenant') self.vpn_service = {'router_id': '123'} self.router = mock.Mock() driver = mock.Mock() self.service_plugin = mock.Mock() driver.service_plugin = self.service_plugin self.validator = validator.CiscoCsrVpnValidator(driver) def test_ike_version_unsupported(self): """Failure test that Cisco CSR REST API does not support IKE v2.""" policy_info = {'ike_version': 'v2', 'lifetime': {'units': 'seconds', 'value': 60}} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_ike_version, policy_info) def test_ike_lifetime_not_in_seconds(self): """Failure test of unsupported lifetime units for IKE policy.""" policy_info = {'lifetime': {'units': 'kilobytes', 'value': 1000}} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_lifetime, "IKE Policy", policy_info) def test_ipsec_lifetime_not_in_seconds(self): """Failure test of unsupported lifetime units for IPSec policy.""" policy_info = {'lifetime': {'units': 'kilobytes', 'value': 1000}} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_lifetime, "IPSec Policy", policy_info) def test_ike_lifetime_seconds_values_at_limits(self): """Test valid lifetime values for IKE policy.""" policy_info = {'lifetime': {'units': 'seconds', 'value': 60}} self.validator.validate_lifetime('IKE Policy', policy_info) policy_info = {'lifetime': {'units': 'seconds', 'value': 86400}} self.validator.validate_lifetime('IKE Policy', policy_info) def test_ipsec_lifetime_seconds_values_at_limits(self): """Test valid lifetime values for IPSec policy.""" policy_info = {'lifetime': {'units': 'seconds', 'value': 120}} self.validator.validate_lifetime('IPSec Policy', policy_info) policy_info = {'lifetime': {'units': 'seconds', 'value': 2592000}} self.validator.validate_lifetime('IPSec Policy', policy_info) def test_ike_lifetime_values_invalid(self): """Failure test of unsupported lifetime values for IKE policy.""" which = "IKE Policy" policy_info = {'lifetime': {'units': 'seconds', 'value': 59}} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_lifetime, which, policy_info) policy_info = {'lifetime': {'units': 'seconds', 'value': 86401}} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_lifetime, which, policy_info) def test_ipsec_lifetime_values_invalid(self): """Failure test of unsupported lifetime values for IPSec policy.""" which = "IPSec Policy" policy_info = {'lifetime': {'units': 'seconds', 'value': 119}} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_lifetime, which, policy_info) policy_info = {'lifetime': {'units': 'seconds', 'value': 2592001}} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_lifetime, which, policy_info) def test_ipsec_connection_with_mtu_at_limits(self): """Test IPSec site-to-site connection with MTU at limits.""" conn_info = {'mtu': 1500} self.validator.validate_mtu(conn_info) conn_info = {'mtu': 9192} self.validator.validate_mtu(conn_info) def test_ipsec_connection_with_invalid_mtu(self): """Failure test of IPSec site connection with unsupported MTUs.""" conn_info = {'mtu': 1499} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_mtu, conn_info) conn_info = {'mtu': 9193} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_mtu, conn_info) def simulate_gw_ip_available(self): """Helper function indicating that tunnel has a gateway IP.""" def have_one(): return 1 self.router.gw_port.fixed_ips.__len__ = have_one ip_addr_mock = mock.Mock() self.router.gw_port.fixed_ips = [ip_addr_mock] def test_have_public_ip_for_router(self): """Ensure that router for IPSec connection has gateway IP.""" self.simulate_gw_ip_available() try: self.validator.validate_public_ip_present(self.router) except Exception: self.fail("Unexpected exception on validation") def test_router_with_missing_gateway_ip(self): """Failure test of IPSec connection with missing gateway IP.""" self.simulate_gw_ip_available() self.router.gw_port = None self.assertRaises(validator.CsrValidationFailure, self.validator.validate_public_ip_present, self.router) def test_peer_id_is_an_ip_address(self): """Ensure peer ID is an IP address for IPsec connection create.""" ipsec_sitecon = {'peer_id': '10.10.10.10'} self.validator.validate_peer_id(ipsec_sitecon) def test_peer_id_is_not_ip_address(self): """Failure test of peer_id that is not an IP address.""" ipsec_sitecon = {'peer_id': 'some-site.com'} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_peer_id, ipsec_sitecon) def test_validation_for_create_ipsec_connection(self): """Ensure all validation passes for IPSec site connection create.""" self.simulate_gw_ip_available() self.service_plugin.get_ikepolicy = mock.Mock( return_value={'ike_version': 'v1', 'lifetime': {'units': 'seconds', 'value': 60}}) self.service_plugin.get_ipsecpolicy = mock.Mock( return_value={'lifetime': {'units': 'seconds', 'value': 120}, 'encapsulation_mode': 'tunnel'}) self.service_plugin.get_vpnservice = mock.Mock( return_value=self.vpn_service) self.validator.driver.l3_plugin._get_router = mock.Mock( return_value=self.router) # Provide the minimum needed items to validate ipsec_sitecon = {'id': '1', 'vpnservice_id': FAKE_SERVICE_ID, 'ikepolicy_id': '123', 'ipsecpolicy_id': '2', 'mtu': 1500, 'peer_id': '10.10.10.10'} # Using defaults for DPD info expected = {'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120} expected.update(ipsec_sitecon) plugin_validator = vpn_validator.VpnReferenceValidator() plugin_validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon) self.validator.validate_ipsec_site_connection(self.context, ipsec_sitecon) self.assertEqual(expected, ipsec_sitecon) def test_ipsec_encap_mode_unsupported(self): """Failure test for unsupported encap mode for IPsec policy.""" policy_info = {'encapsulation_mode': 'transport'} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_ipsec_encap_mode, policy_info) def test_ipsec_auth_algorithm_unsupported(self): """Failure test for unsupported auth algorithm for IPSec Policy.""" auth_algorithm = {'auth_algorithm': 'sha384'} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_ipsec_auth_algorithm, auth_algorithm) auth_algorithm = {'auth_algorithm': 'sha512'} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_ipsec_auth_algorithm, auth_algorithm) def test_ike_auth_algorithm_unsupported(self): """Failure test for unsupported auth algorithm for IKE Policy.""" auth_algorithm = {'auth_algorithm': 'sha384'} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_ike_auth_algorithm, auth_algorithm) auth_algorithm = {'auth_algorithm': 'sha512'} self.assertRaises(validator.CsrValidationFailure, self.validator.validate_ike_auth_algorithm, auth_algorithm) class TestCiscoIPsecDriverMapping(base.BaseTestCase): def setUp(self): super(TestCiscoIPsecDriverMapping, self).setUp() self.context = mock.patch.object(n_ctx, 'Context').start() self.session = self.context.session self.query_mock = self.session.query.return_value.order_by def test_identifying_first_mapping_id(self): """Make sure first available ID is obtained for each ID type.""" # Simulate mapping table is empty - get first one self.query_mock.return_value = [] next_id = csr_db.get_next_available_tunnel_id(self.session) self.assertEqual(0, next_id) next_id = csr_db.get_next_available_ike_policy_id(self.session) self.assertEqual(1, next_id) next_id = csr_db.get_next_available_ipsec_policy_id(self.session) self.assertEqual(1, next_id) def test_last_mapping_id_available(self): """Make sure can get the last ID for each of the table types.""" # Simulate query indicates table is full self.query_mock.return_value = [ (x, ) for x in moves.range(csr_db.MAX_CSR_TUNNELS - 1)] next_id = csr_db.get_next_available_tunnel_id(self.session) self.assertEqual(csr_db.MAX_CSR_TUNNELS - 1, next_id) self.query_mock.return_value = [ (x, ) for x in moves.range(1, csr_db.MAX_CSR_IKE_POLICIES)] next_id = csr_db.get_next_available_ike_policy_id(self.session) self.assertEqual(csr_db.MAX_CSR_IKE_POLICIES, next_id) self.query_mock.return_value = [ (x, ) for x in moves.range(1, csr_db.MAX_CSR_IPSEC_POLICIES)] next_id = csr_db.get_next_available_ipsec_policy_id(self.session) self.assertEqual(csr_db.MAX_CSR_IPSEC_POLICIES, next_id) def test_reusing_first_available_mapping_id(self): """Ensure that we reuse the first available ID. Make sure that the next lowest ID is obtained from the mapping table when there are "holes" from deletions. Database query sorts the entries, so will return them in order. Using tunnel ID, as the logic is the same for each ID type. """ self.query_mock.return_value = [(0, ), (1, ), (2, ), (5, ), (6, )] next_id = csr_db.get_next_available_tunnel_id(self.session) self.assertEqual(3, next_id) def test_no_more_mapping_ids_available(self): """Failure test of trying to reserve ID, when none available.""" self.query_mock.return_value = [ (x, ) for x in moves.range(csr_db.MAX_CSR_TUNNELS)] self.assertRaises(IndexError, csr_db.get_next_available_tunnel_id, self.session) self.query_mock.return_value = [ (x, ) for x in moves.range(1, csr_db.MAX_CSR_IKE_POLICIES + 1)] self.assertRaises(IndexError, csr_db.get_next_available_ike_policy_id, self.session) self.query_mock.return_value = [ (x, ) for x in moves.range(1, csr_db.MAX_CSR_IPSEC_POLICIES + 1)] self.assertRaises(IndexError, csr_db.get_next_available_ipsec_policy_id, self.session) def test_create_tunnel_mappings(self): """Ensure successfully create new tunnel mappings.""" # Simulate that first IDs are obtained self.query_mock.return_value = [] map_db_mock = mock.patch.object(csr_db, 'IdentifierMap').start() conn_info = {'ikepolicy_id': '10', 'ipsecpolicy_id': '50', 'id': '100', 'tenant_id': '1000'} csr_db.create_tunnel_mapping(self.context, conn_info) map_db_mock.assert_called_once_with(csr_tunnel_id=0, csr_ike_policy_id=1, csr_ipsec_policy_id=1, ipsec_site_conn_id='100', tenant_id='1000') # Create another, with next ID of 2 for all IDs (not mocking each # ID separately, so will not have different IDs). self.query_mock.return_value = [(0, ), (1, )] map_db_mock.reset_mock() conn_info = {'ikepolicy_id': '20', 'ipsecpolicy_id': '60', 'id': '101', 'tenant_id': '1000'} csr_db.create_tunnel_mapping(self.context, conn_info) map_db_mock.assert_called_once_with(csr_tunnel_id=2, csr_ike_policy_id=2, csr_ipsec_policy_id=2, ipsec_site_conn_id='101', tenant_id='1000') class TestCiscoIPsecDriver(testlib_api.SqlTestCase): """Test that various incoming requests are sent to device driver.""" def setUp(self): super(TestCiscoIPsecDriver, self).setUp() mock.patch('neutron.common.rpc.create_connection').start() self._fake_vpn_router_id = _uuid() service_plugin = mock.Mock() service_plugin._get_vpnservice.return_value = { 'router_id': self._fake_vpn_router_id } l3_plugin = mock.Mock() directory.add_plugin(lib_const.L3, l3_plugin) l3_plugin.get_host_for_router.return_value = FAKE_HOST l3_agent = mock.Mock() l3_agent.host = 'some-host' l3_plugin.get_l3_agents_hosting_routers.return_value = [l3_agent] self.driver = ipsec_driver.CiscoCsrIPsecVPNDriver(service_plugin) mock.patch.object(csr_db, 'create_tunnel_mapping').start() self.context = n_ctx.Context('some_user', 'some_tenant') def _test_update(self, func, args, additional_info=None): with mock.patch.object(self.driver.agent_rpc.client, 'cast') as rpc_mock, \ mock.patch.object(self.driver.agent_rpc.client, 'prepare') as prepare_mock: prepare_mock.return_value = self.driver.agent_rpc.client func(self.context, *args) prepare_args = {'server': 'fake_host', 'version': '1.0'} prepare_mock.assert_called_once_with(**prepare_args) rpc_mock.assert_called_once_with(self.context, 'vpnservice_updated', **additional_info) def test_create_ipsec_site_connection(self): self._test_update(self.driver.create_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'reason': 'ipsec-conn-create', 'router': {'id': self._fake_vpn_router_id}}) def test_update_ipsec_site_connection(self): self._test_update(self.driver.update_ipsec_site_connection, [FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION], {'reason': 'ipsec-conn-update', 'router': {'id': self._fake_vpn_router_id}}) def test_delete_ipsec_site_connection(self): self._test_update(self.driver.delete_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'reason': 'ipsec-conn-delete', 'router': {'id': self._fake_vpn_router_id}}) def test_update_vpnservice(self): self._test_update(self.driver.update_vpnservice, [FAKE_VPN_SERVICE, FAKE_VPN_SERVICE], {'reason': 'vpn-service-update', 'router': {'id': FAKE_VPN_SERVICE['router_id']}}) def test_delete_vpnservice(self): self._test_update(self.driver.delete_vpnservice, [FAKE_VPN_SERVICE], {'reason': 'vpn-service-delete', 'router': {'id': FAKE_VPN_SERVICE['router_id']}}) class TestCiscoIPsecDriverRequests(base.BaseTestCase): """Test handling device driver requests for service info.""" def setUp(self): super(TestCiscoIPsecDriverRequests, self).setUp() mock.patch('neutron.common.rpc.create_connection').start() service_plugin = mock.Mock() self.driver = ipsec_driver.CiscoCsrIPsecVPNDriver(service_plugin) def test_build_router_tunnel_interface_name(self): """Check formation of inner/outer interface name for CSR router.""" router_info = { '_interfaces': [ {'hosting_info': {'segmentation_id': 100, 'hosting_port_name': 't1_p:1'}} ], 'gw_port': {'hosting_info': {'segmentation_id': 200, 'hosting_port_name': 't2_p:1'}} } self.assertEqual( 'GigabitEthernet2.100', self.driver._create_interface(router_info['_interfaces'][0])) self.assertEqual( 'GigabitEthernet3.200', self.driver._create_interface(router_info['gw_port'])) def test_build_router_info(self): """Check creation of CSR info to send to device driver.""" router_info = { 'hosting_device': { 'management_ip_address': '1.1.1.1', 'credentials': {'username': 'me', 'password': 'secret'} }, 'gw_port': {'hosting_info': {'segmentation_id': 101, 'hosting_port_name': 't2_p:1'}}, 'id': u'c607b58e-f150-4289-b83f-45623578d122', '_interfaces': [ {'hosting_info': {'segmentation_id': 100, 'hosting_port_name': 't1_p:1'}} ] } expected = {'rest_mgmt_ip': '1.1.1.1', 'username': 'me', 'password': 'secret', 'inner_if_name': 'GigabitEthernet2.100', 'outer_if_name': 'GigabitEthernet3.101', 'vrf': 'nrouter-c607b5', 'timeout': 30} self.assertEqual(expected, self.driver._get_router_info(router_info)) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py0000666000175000017500000005436113370230615032333 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, 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. import mock from neutron.db import servicetype_db as st_db from neutron_lib import context as n_ctx from neutron_lib.plugins import constants from neutron_lib.plugins import directory from oslo_utils import uuidutils 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.services.vpn.service_drivers import ipsec_validator from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_SERVICE_ID = _uuid() FAKE_VPN_CONNECTION = { 'vpnservice_id': FAKE_SERVICE_ID } FAKE_ROUTER_ID = _uuid() FAKE_VPN_SERVICE = { 'router_id': FAKE_ROUTER_ID } FAKE_HOST = 'fake_host' FAKE_CONN_ID = _uuid() IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.' 'ipsec.IPsecVPNDriver') class FakeSqlQueryObject(dict): """To fake SqlAlchemy query object and access keys as attributes.""" def __init__(self, **entries): self.__dict__.update(entries) super(FakeSqlQueryObject, self).__init__(**entries) class TestValidatorSelection(base.BaseTestCase): def setUp(self): super(TestValidatorSelection, self).setUp() vpnaas_provider = [{ 'service_type': constants.VPN, 'name': 'vpnaas', 'driver': IPSEC_SERVICE_DRIVER, 'default': True }] # override the default service provider self.service_providers = ( mock.patch.object(st_db.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = vpnaas_provider mock.patch('neutron.common.rpc.create_connection').start() stm = st_db.ServiceTypeManager() stm.get_provider_names_by_resource_ids = mock.Mock( return_value={}) mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=stm).start() mock.patch('neutron_vpnaas.db.vpn.vpn_db.VPNPluginDb.get_vpnservices', return_value=[]).start() self.vpn_plugin = vpn_plugin.VPNDriverPlugin() def test_reference_driver_used(self): default_provider = self.vpn_plugin.default_provider default_driver = self.vpn_plugin.drivers[default_provider] self.assertIsInstance(default_driver.validator, ipsec_validator.IpsecVpnValidator) class TestIPsecDriver(base.BaseTestCase): def setUp(self): super(TestIPsecDriver, self).setUp() mock.patch('neutron.common.rpc.create_connection').start() l3_agent = mock.Mock() l3_agent.host = FAKE_HOST plugin = mock.Mock() plugin.get_l3_agents_hosting_routers.return_value = [l3_agent] directory.add_plugin(constants.CORE, plugin) directory.add_plugin(constants.L3, plugin) self.svc_plugin = mock.Mock() self.svc_plugin.get_l3_agents_hosting_routers.return_value = [l3_agent] self._fake_vpn_router_id = _uuid() self.svc_plugin._get_vpnservice.return_value = { 'router_id': self._fake_vpn_router_id } self.driver = ipsec_driver.IPsecVPNDriver(self.svc_plugin) self.validator = ipsec_validator.IpsecVpnValidator(self.driver) self.context = n_ctx.get_admin_context() def _test_update(self, func, args, additional_info=None): ctxt = n_ctx.Context('', 'somebody') with mock.patch.object(self.driver.agent_rpc.client, 'cast' ) as rpc_mock, \ mock.patch.object(self.driver.agent_rpc.client, 'prepare' ) as prepare_mock: prepare_mock.return_value = self.driver.agent_rpc.client func(ctxt, *args) prepare_args = {'server': 'fake_host', 'version': '1.0'} prepare_mock.assert_called_once_with(**prepare_args) rpc_mock.assert_called_once_with(ctxt, 'vpnservice_updated', **additional_info) def test_create_ipsec_site_connection(self): self._test_update(self.driver.create_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_update_ipsec_site_connection(self): self._test_update(self.driver.update_ipsec_site_connection, [FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_delete_ipsec_site_connection(self): self._test_update(self.driver.delete_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_update_vpnservice(self): self._test_update(self.driver.update_vpnservice, [FAKE_VPN_SERVICE, FAKE_VPN_SERVICE], {'router': {'id': FAKE_VPN_SERVICE['router_id']}}) def test_delete_vpnservice(self): self._test_update(self.driver.delete_vpnservice, [FAKE_VPN_SERVICE], {'router': {'id': FAKE_VPN_SERVICE['router_id']}}) def prepare_dummy_query_objects(self, info): """Create fake query objects to test dict creation for sync oper.""" external_ip = '10.0.0.99' peer_address = '10.0.0.2' peer_endpoints = info.get('peer_endpoints', []) local_endpoints = info.get('local_endpoints', []) peer_cidrs = info.get('peer_cidrs', ['40.4.0.0/24', '50.5.0.0/24']) peer_id = info.get('peer_id', '30.30.0.0') local_id = info.get('local_id', '') fake_ikepolicy = FakeSqlQueryObject(id='foo-ike', name='ike-name') fake_ipsecpolicy = FakeSqlQueryObject(id='foo-ipsec') fake_peer_cidrs_list = [ FakeSqlQueryObject(cidr=cidr, ipsec_site_connection_id='conn-id') for cidr in peer_cidrs] peer_epg_id = 'peer-epg-id' if peer_endpoints else None local_epg_id = 'local-epg-id' if local_endpoints else None fake_ipsec_conn = FakeSqlQueryObject(id='conn-id', peer_id=peer_id, peer_address=peer_address, local_id=local_id, ikepolicy=fake_ikepolicy, ipsecpolicy=fake_ipsecpolicy, peer_ep_group_id=peer_epg_id, local_ep_group_id=local_epg_id, peer_cidrs=fake_peer_cidrs_list) if peer_endpoints: fake_peer_ep_group = FakeSqlQueryObject(id=peer_epg_id) fake_peer_ep_group.endpoints = [ FakeSqlQueryObject(endpoint=ep, endpoint_group_id=peer_epg_id) for ep in peer_endpoints] fake_ipsec_conn.peer_ep_group = fake_peer_ep_group if local_endpoints: fake_local_ep_group = FakeSqlQueryObject(id=local_epg_id) fake_local_ep_group.endpoints = [ FakeSqlQueryObject(endpoint=ep, endpoint_group_id=local_epg_id) for ep in local_endpoints] fake_ipsec_conn.local_ep_group = fake_local_ep_group subnet_id = None else: subnet_id = 'foo-subnet-id' fake_gw_port = {'fixed_ips': [{'ip_address': external_ip}]} fake_router = FakeSqlQueryObject(gw_port=fake_gw_port) fake_vpnservice = FakeSqlQueryObject(id='foo-vpn-id', name='foo-vpn', description='foo-vpn-service', admin_state_up=True, status='active', external_v4_ip=external_ip, external_v6_ip=None, subnet_id=subnet_id, router_id='foo-router-id', project_id='foo-project-id') if local_endpoints: fake_vpnservice.subnet = None else: fake_subnet = FakeSqlQueryObject(id=subnet_id, name='foo-subnet', cidr='9.0.0.0/16', network_id='foo-net-id') fake_vpnservice.subnet = fake_subnet fake_vpnservice.router = fake_router fake_vpnservice.ipsec_site_connections = [fake_ipsec_conn] return fake_vpnservice def build_expected_dict(self, info): """Create the expected dict used in sync operations. The default is to use non-endpoint groups, where the peer CIDRs come from the peer_cidrs arguments, the local CIDRs come from the (sole) subnet CIDR, and there is subnet info. Tests will customize the peer ID and peer CIDRs. """ external_ip = '10.0.0.99' peer_id = info.get('peer_id', '30.30.0.0') peer_cidrs = info.get('peer_cidrs', ['40.4.0.0/24', '50.5.0.0/24']) local_id = info.get('local_id', '') return {'name': 'foo-vpn', 'id': 'foo-vpn-id', 'description': 'foo-vpn-service', 'admin_state_up': True, 'status': 'active', 'external_v4_ip': external_ip, 'external_v6_ip': None, 'router_id': 'foo-router-id', 'subnet': {'cidr': '9.0.0.0/16', 'id': 'foo-subnet-id', 'name': 'foo-subnet', 'network_id': 'foo-net-id'}, 'subnet_id': 'foo-subnet-id', 'external_ip': external_ip, 'project_id': 'foo-project-id', 'tenant_id': 'foo-project-id', 'ipsec_site_connections': [ {'id': 'conn-id', 'peer_id': peer_id, 'external_ip': external_ip, 'peer_address': '10.0.0.2', 'local_id': local_id, 'ikepolicy': {'id': 'foo-ike', 'name': 'ike-name'}, 'ipsecpolicy': {'id': 'foo-ipsec'}, 'peer_ep_group_id': None, 'local_ep_group_id': None, 'peer_cidrs': peer_cidrs, 'local_cidrs': ['9.0.0.0/16'], 'local_ip_vers': 4} ]} def build_expected_dict_for_endpoints(self, info): """Create the expected dict used in sync operations for endpoints. The local and peer CIDRs come from the endpoint groups (with the local CIDR translated from the corresponding subnets specified). Tests will customize CIDRs, and the subnet, which is needed for backward compatibility with agents, during rolling upgrades. """ external_ip = '10.0.0.99' peer_id = '30.30.0.0' local_id = info.get('local_id', '') return {'name': 'foo-vpn', 'id': 'foo-vpn-id', 'description': 'foo-vpn-service', 'admin_state_up': True, 'status': 'active', 'external_v4_ip': external_ip, 'external_v6_ip': None, 'router_id': 'foo-router-id', 'subnet': None, 'subnet_id': None, 'external_ip': external_ip, 'project_id': 'foo-project-id', 'tenant_id': 'foo-project-id', 'ipsec_site_connections': [ {'id': 'conn-id', 'peer_id': peer_id, 'external_ip': external_ip, 'peer_address': '10.0.0.2', 'local_id': local_id, 'ikepolicy': {'id': 'foo-ike', 'name': 'ike-name'}, 'ipsecpolicy': {'id': 'foo-ipsec'}, 'peer_ep_group_id': 'peer-epg-id', 'local_ep_group_id': 'local-epg-id', 'peer_cidrs': info['peers'], 'local_cidrs': info['locals'], 'local_ip_vers': info['vers']} ]} def test_make_vpnservice_dict_peer_id_is_ipaddr(self): """Peer ID as IP should be copied as-is, when creating dict.""" subnet_cidr_map = {} peer_id_as_ip = {'peer_id': '10.0.0.2'} fake_service = self.prepare_dummy_query_objects(peer_id_as_ip) expected_dict = self.build_expected_dict(peer_id_as_ip) actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) # make sure that ipsec_site_conn peer_id is not updated by # _make_vpnservice_dict (bug #1423244) self.assertEqual(peer_id_as_ip['peer_id'], fake_service.ipsec_site_connections[0].peer_id) def test_make_vpnservice_dict_peer_id_is_string(self): """Peer ID as string should have '@' prepended, when creating dict.""" subnet_cidr_map = {} peer_id_as_name = {'peer_id': 'foo.peer.id'} fake_service = self.prepare_dummy_query_objects(peer_id_as_name) expected_peer_id = {'peer_id': '@foo.peer.id'} expected_dict = self.build_expected_dict(expected_peer_id) actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) # make sure that ipsec_site_conn peer_id is not updated by # _make_vpnservice_dict (bug #1423244) self.assertEqual(peer_id_as_name['peer_id'], fake_service.ipsec_site_connections[0].peer_id) def test_make_vpnservice_dict_peer_cidrs_from_peer_cidr_table(self): """Peer CIDRs list populated from peer_cidr table. User provides peer CIDRs as parameters to IPSec site-to-site connection API, and they are stored in the peercidrs table. """ subnet_cidr_map = {} peer_cidrs = {'peer_cidrs': ['80.0.0.0/24', '90.0.0.0/24']} fake_service = self.prepare_dummy_query_objects(peer_cidrs) expected_dict = self.build_expected_dict(peer_cidrs) actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) def test_make_vpnservice_dict_cidrs_from_endpoints(self): """CIDRs list populated from local and peer endpoints. User provides peer and local endpoint group IDs in the IPSec site-to-site connection API. The endpoint groups contains peer CIDRs and local subnets (which will be mapped to CIDRs). """ # Cannot have peer CIDRs specified, when using endpoint group subnet_cidr_map = {'local-sn1': '5.0.0.0/16', 'local-sn2': '5.1.0.0/16'} endpoint_groups = {'peer_cidrs': [], 'peer_endpoints': ['80.0.0.0/24', '90.0.0.0/24'], 'local_endpoints': ['local-sn1', 'local-sn2']} expected_cidrs = {'peers': ['80.0.0.0/24', '90.0.0.0/24'], 'locals': ['5.0.0.0/16', '5.1.0.0/16'], 'vers': 4} fake_service = self.prepare_dummy_query_objects(endpoint_groups) expected_dict = self.build_expected_dict_for_endpoints(expected_cidrs) expected_dict['subnet'] = {'cidr': '5.0.0.0/16'} actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) def test_make_vpnservice_dict_v6_cidrs_from_endpoints(self): """IPv6 CIDRs list populated from local and peer endpoints.""" # Cannot have peer CIDRs specified, when using endpoint group subnet_cidr_map = {'local-sn1': '2002:0a00:0000::/48', 'local-sn2': '2002:1400:0000::/48'} endpoint_groups = {'peer_cidrs': [], 'peer_endpoints': ['2002:5000:0000::/48', '2002:5a00:0000::/48'], 'local_endpoints': ['local-sn1', 'local-sn2']} expected_cidrs = {'peers': ['2002:5000:0000::/48', '2002:5a00:0000::/48'], 'locals': ['2002:0a00:0000::/48', '2002:1400:0000::/48'], 'vers': 6} fake_service = self.prepare_dummy_query_objects(endpoint_groups) expected_dict = self.build_expected_dict_for_endpoints(expected_cidrs) expected_dict['subnet'] = {'cidr': '2002:0a00:0000::/48'} actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) def test_get_external_ip_based_on_ipv4_peer(self): vpnservice = mock.Mock() vpnservice.external_v4_ip = '10.0.0.99' vpnservice.external_v6_ip = '2001::1' ipsec_sitecon = {'id': FAKE_CONN_ID, 'peer_address': '10.0.0.9'} ip_to_use = self.driver.get_external_ip_based_on_peer(vpnservice, ipsec_sitecon) self.assertEqual('10.0.0.99', ip_to_use) def test_get_external_ip_based_on_ipv6_peer(self): vpnservice = mock.Mock() vpnservice.external_v4_ip = '10.0.0.99' vpnservice.external_v6_ip = '2001::1' ipsec_sitecon = {'id': FAKE_CONN_ID, 'peer_address': '2001::5'} ip_to_use = self.driver.get_external_ip_based_on_peer(vpnservice, ipsec_sitecon) self.assertEqual('2001::1', ip_to_use) def test_get_ipv4_gw_ip(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertEqual('10.0.0.99', v4_ip) self.assertIsNone(v6_ip) def test_get_ipv6_gw_ip(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '2001::1'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertIsNone(v4_ip) self.assertEqual('2001::1', v6_ip) def test_get_both_gw_ips(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}, {'ip_address': '2001::1'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertEqual('10.0.0.99', v4_ip) self.assertEqual('2001::1', v6_ip) def test_use_first_gw_ips_when_multiples(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}, {'ip_address': '20.0.0.99'}, {'ip_address': '2001::1'}, {'ip_address': 'fd00::4'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertEqual('10.0.0.99', v4_ip) self.assertEqual('2001::1', v6_ip) def test_store_gw_ips_on_service_create(self): vpnservice = mock.Mock() self.svc_plugin._get_vpnservice.return_value = vpnservice vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}, {'ip_address': '2001::1'}]} ctxt = n_ctx.Context('', 'somebody') vpnservice_dict = {'id': FAKE_SERVICE_ID, 'router_id': FAKE_ROUTER_ID} self.driver.create_vpnservice(ctxt, vpnservice_dict) self.svc_plugin.set_external_tunnel_ips.assert_called_once_with( ctxt, FAKE_SERVICE_ID, v4_ip='10.0.0.99', v6_ip='2001::1') def test_validate_ipsec_policy(self): # Validate IPsec Policy transform_protocol and auth_algorithm ipsec_policy = {'transform_protocol': 'ah-esp'} self.assertRaises(ipsec_validator.IpsecValidationFailure, self.validator.validate_ipsec_policy, self.context, ipsec_policy) auth_algorithm = {'auth_algorithm': 'sha384'} self.assertRaises(ipsec_validator.IpsecValidationFailure, self.validator.validate_ipsec_policy, self.context, auth_algorithm) auth_algorithm = {'auth_algorithm': 'sha512'} self.assertRaises(ipsec_validator.IpsecValidationFailure, self.validator.validate_ipsec_policy, self.context, auth_algorithm) def test_validate_ike_policy(self): # Validate IKE Policy auth_algorithm auth_algorithm = {'auth_algorithm': 'sha384'} self.assertRaises(ipsec_validator.IkeValidationFailure, self.validator.validate_ike_policy, self.context, auth_algorithm) auth_algorithm = {'auth_algorithm': 'sha512'} self.assertRaises(ipsec_validator.IkeValidationFailure, self.validator.validate_ike_policy, self.context, auth_algorithm) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_vyatta_ipsec.py0000666000175000017500000000741513370230615033721 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, 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. # import mock from neutron_lib import context as n_ctx from neutron_lib.plugins import constants from neutron_lib.plugins import directory from oslo_utils import uuidutils from neutron_vpnaas.services.vpn.service_drivers import vyatta_ipsec from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_HOST = 'fake_host' FAKE_SERVICE_ID = _uuid() FAKE_VPN_CONNECTION = { 'vpnservice_id': FAKE_SERVICE_ID } FAKE_ROUTER_ID = _uuid() FAKE_VPN_SERVICE = { 'router_id': FAKE_ROUTER_ID } class TestVyattaDriver(base.BaseTestCase): def setUp(self): super(TestVyattaDriver, self).setUp() mock.patch('neutron.common.rpc.create_connection').start() l3_agent = mock.Mock() l3_agent.host = FAKE_HOST plugin = mock.Mock() plugin.get_l3_agents_hosting_routers.return_value = [l3_agent] directory.add_plugin(constants.CORE, plugin) directory.add_plugin(constants.L3, plugin) service_plugin = mock.Mock() service_plugin.get_l3_agents_hosting_routers.return_value = [l3_agent] self._fake_vpn_router_id = _uuid() service_plugin._get_vpnservice.return_value = { 'router_id': self._fake_vpn_router_id } self.driver = vyatta_ipsec.VyattaIPsecDriver(service_plugin) def _test_update(self, func, args, additional_info=None): ctxt = n_ctx.Context('', 'somebody') with mock.patch.object(self.driver.agent_rpc.client, 'cast' ) as rpc_mock, \ mock.patch.object(self.driver.agent_rpc.client, 'prepare' ) as prepare_mock: prepare_mock.return_value = self.driver.agent_rpc.client func(ctxt, *args) prepare_args = {'server': 'fake_host', 'version': '1.0'} prepare_mock.assert_called_once_with(**prepare_args) rpc_mock.assert_called_once_with(ctxt, 'vpnservice_updated', **additional_info) def test_create_ipsec_site_connection(self): self._test_update(self.driver.create_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_update_ipsec_site_connection(self): self._test_update(self.driver.update_ipsec_site_connection, [FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_delete_ipsec_site_connection(self): self._test_update(self.driver.delete_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_update_vpnservice(self): self._test_update(self.driver.update_vpnservice, [FAKE_VPN_SERVICE, FAKE_VPN_SERVICE], {'router': {'id': FAKE_VPN_SERVICE['router_id']}}) def test_delete_vpnservice(self): self._test_update(self.driver.delete_vpnservice, [FAKE_VPN_SERVICE], {'router': {'id': FAKE_VPN_SERVICE['router_id']}}) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/common/0000775000175000017500000000000013370231105025671 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/common/__init__.py0000666000175000017500000000000013370230606027777 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/common/test_netns_wrapper.py0000666000175000017500000000604313370230606032203 0ustar zuulzuul00000000000000# Copyright (c) 2015 OpenStack Foundation. # 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 mock from neutron.tests import base from neutron_vpnaas.services.vpn.common import netns_wrapper as nswrap class TestNetnsWrapper(base.BaseTestCase): def setUp(self): super(TestNetnsWrapper, self).setUp() patch_methods = ['filter_command', 'execute', 'setup_conf'] for method in patch_methods: self.patch_obj(nswrap, method) patch_classes = ['neutron.common.config.setup_logging', 'os.path.isdir', 'os.path.samefile', 'sys.exit'] for cls in patch_classes: self.patch_cls(cls) self.filter_command.return_value = False self.execute.return_value = 0 self.conf = mock.Mock() self.conf.cmd = 'ls,-al' self.conf.mount_paths = {'/foo': '/dir/foo', '/var': '/dir/var'} self.setup_conf.return_value = self.conf self.conf.rootwrap_config = 'conf' self.isdir.return_value = True self.samefile.return_value = False def patch_obj(self, obj, method): _m = mock.patch.object(obj, method) _mock = _m.start() setattr(self, method, _mock) def patch_cls(self, patch_class): _m = mock.patch(patch_class) mock_name = patch_class.split('.')[-1] _mock = _m.start() setattr(self, mock_name, _mock) def test_netns_wrap_fail_without_netns(self): self.samefile.return_value = True return_val = nswrap.execute_with_mount() self.assertTrue(return_val) def test_netns_wrap(self): self.conf.cmd = 'ls,-al' return_val = nswrap.execute_with_mount() exp_calls = [mock.call(['mount', '--bind', '/dir/foo', '/foo']), mock.call(['mount', '--bind', '/dir/var', '/var']), mock.call('ls,-al')] self.execute.assert_has_calls(exp_calls, any_order=True) self.assertFalse(return_val) def test_netns_wrap_fail_without_cmd(self): self.conf.cmd = None return_val = nswrap.execute_with_mount() self.assertFalse(self.execute.called) self.assertTrue(return_val) def test_netns_wrap_fail_without_mount_paths(self): self.conf.mount_paths = None return_val = nswrap.execute_with_mount() self.assertFalse(self.execute.called) self.assertTrue(return_val) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/0000775000175000017500000000000013370231105027376 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/__init__.py0000666000175000017500000000000013370230606031504 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_cisco_ipsec.py0000666000175000017500000022655313370230615033316 0ustar zuulzuul00000000000000# 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. import copy import operator import mock from neutron_lib import constants from neutron_lib import context from oslo_utils import uuidutils from neutron_vpnaas.services.vpn.device_drivers import ( cisco_csr_rest_client as csr_client) from neutron_vpnaas.services.vpn.device_drivers \ import cisco_ipsec as ipsec_driver from neutron_vpnaas.tests import base import six if six.PY3: from http import client as httplib else: import httplib _uuid = uuidutils.generate_uuid FAKE_HOST = 'fake_host' FAKE_ROUTER_ID = _uuid() FAKE_VPN_SERVICE = { 'id': _uuid(), 'router_id': FAKE_ROUTER_ID, 'admin_state_up': True, 'status': constants.PENDING_CREATE, 'subnet': {'cidr': '10.0.0.0/24'}, 'ipsec_site_connections': [ {'peer_cidrs': ['20.0.0.0/24', '30.0.0.0/24']}, {'peer_cidrs': ['40.0.0.0/24', '50.0.0.0/24']}] } FIND_CFG_FOR_CSRS = ('neutron_vpnaas.services.vpn.device_drivers.cisco_ipsec.' 'find_available_csrs_from_config') class TestCiscoCsrIPSecConnection(base.BaseTestCase): def setUp(self): super(TestCiscoCsrIPSecConnection, self).setUp() self.conn_info = { u'id': '123', u'status': constants.PENDING_CREATE, u'admin_state_up': True, 'psk': 'secret', 'peer_address': '192.168.1.2', 'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'], 'mtu': 1500, 'ike_policy': {'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'pfs': 'Group5', 'ike_version': 'v1', 'lifetime_units': 'seconds', 'lifetime_value': 3600}, 'ipsec_policy': {'transform_protocol': 'ah', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'lifetime_units': 'seconds', 'lifetime_value': 3600}, 'cisco': {'site_conn_id': 'Tunnel0', 'ike_policy_id': 222, 'ipsec_policy_id': 333} } self.csr = mock.Mock(spec=csr_client.CsrRestClient) self.csr.status = 201 # All calls to CSR REST API succeed self.ipsec_conn = ipsec_driver.CiscoCsrIPSecConnection(self.conn_info, self.csr) def test_create_ipsec_site_connection(self): """Ensure all steps are done to create an IPSec site connection. Verify that each of the driver calls occur (in order), and the right information is stored for later deletion. """ expected = ['create_pre_shared_key', 'create_ike_policy', 'create_ipsec_policy', 'create_ipsec_connection', 'create_static_route', 'create_static_route'] expected_rollback_steps = [ ipsec_driver.RollbackStep(action='pre_shared_key', resource_id='123', title='Pre-Shared Key'), ipsec_driver.RollbackStep(action='ike_policy', resource_id=222, title='IKE Policy'), ipsec_driver.RollbackStep(action='ipsec_policy', resource_id=333, title='IPSec Policy'), ipsec_driver.RollbackStep(action='ipsec_connection', resource_id='Tunnel0', title='IPSec Connection'), ipsec_driver.RollbackStep(action='static_route', resource_id='10.1.0.0_24_Tunnel0', title='Static Route'), ipsec_driver.RollbackStep(action='static_route', resource_id='10.2.0.0_24_Tunnel0', title='Static Route')] self.ipsec_conn.create_ipsec_site_connection(mock.Mock(), self.conn_info) client_calls = [c[0] for c in self.csr.method_calls] self.assertEqual(expected, client_calls) self.assertEqual(expected_rollback_steps, self.ipsec_conn.steps) def test_create_ipsec_site_connection_with_rollback(self): """Failure test of IPSec site conn creation that fails and rolls back. Simulate a failure in the last create step (making routes for the peer networks), and ensure that the create steps are called in order (except for create_static_route), and that the delete steps are called in reverse order. At the end, there should be no rollback information for the connection. """ def fake_route_check_fails(*args): if args[0] == 'Static Route': # So that subsequent calls to CSR rest client (for rollback) # will fake as passing. self.csr.status = httplib.NO_CONTENT raise ipsec_driver.CsrResourceCreateFailure(resource=args[0], which=args[1]) with mock.patch.object(ipsec_driver.CiscoCsrIPSecConnection, '_check_create', side_effect=fake_route_check_fails): expected = ['create_pre_shared_key', 'create_ike_policy', 'create_ipsec_policy', 'create_ipsec_connection', 'create_static_route', 'delete_ipsec_connection', 'delete_ipsec_policy', 'delete_ike_policy', 'delete_pre_shared_key'] self.ipsec_conn.create_ipsec_site_connection(mock.Mock(), self.conn_info) client_calls = [c[0] for c in self.csr.method_calls] self.assertEqual(expected, client_calls) self.assertEqual([], self.ipsec_conn.steps) def test_create_verification_with_error(self): """Negative test of create check step had failed.""" self.csr.status = httplib.NOT_FOUND self.assertRaises(ipsec_driver.CsrResourceCreateFailure, self.ipsec_conn._check_create, 'name', 'id') def test_failure_with_invalid_create_step(self): """Negative test of invalid create step (programming error).""" self.ipsec_conn.steps = [] try: self.ipsec_conn.do_create_action('bogus', None, '123', 'Bad Step') except ipsec_driver.CsrResourceCreateFailure: pass else: self.fail('Expected exception with invalid create step') def test_failure_with_invalid_delete_step(self): """Negative test of invalid delete step (programming error).""" self.ipsec_conn.steps = [ipsec_driver.RollbackStep(action='bogus', resource_id='123', title='Bogus Step')] try: self.ipsec_conn.do_rollback() except ipsec_driver.CsrResourceCreateFailure: pass else: self.fail('Expected exception with invalid delete step') def test_delete_ipsec_connection(self): """Perform delete of IPSec site connection and check steps done.""" # Simulate that a create was done with rollback steps stored self.ipsec_conn.steps = [ ipsec_driver.RollbackStep(action='pre_shared_key', resource_id='123', title='Pre-Shared Key'), ipsec_driver.RollbackStep(action='ike_policy', resource_id=222, title='IKE Policy'), ipsec_driver.RollbackStep(action='ipsec_policy', resource_id=333, title='IPSec Policy'), ipsec_driver.RollbackStep(action='ipsec_connection', resource_id='Tunnel0', title='IPSec Connection'), ipsec_driver.RollbackStep(action='static_route', resource_id='10.1.0.0_24_Tunnel0', title='Static Route'), ipsec_driver.RollbackStep(action='static_route', resource_id='10.2.0.0_24_Tunnel0', title='Static Route')] expected = ['delete_static_route', 'delete_static_route', 'delete_ipsec_connection', 'delete_ipsec_policy', 'delete_ike_policy', 'delete_pre_shared_key'] self.ipsec_conn.delete_ipsec_site_connection(mock.Mock(), 123) client_calls = [c[0] for c in self.csr.method_calls] self.assertEqual(expected, client_calls) class TestCiscoCsrIPsecConnectionCreateTransforms(base.BaseTestCase): """Verifies that config info is prepared/transformed correctly.""" def setUp(self): super(TestCiscoCsrIPsecConnectionCreateTransforms, self).setUp() self.conn_info = { u'id': '123', u'status': constants.PENDING_CREATE, u'admin_state_up': True, 'psk': 'secret', 'peer_address': '192.168.1.2', 'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'], 'mtu': 1500, 'ike_policy': {'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'pfs': 'Group5', 'ike_version': 'v1', 'lifetime_units': 'seconds', 'lifetime_value': 3600}, 'ipsec_policy': {'transform_protocol': 'ah', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'lifetime_units': 'seconds', 'lifetime_value': 3600}, 'cisco': {'site_conn_id': 'Tunnel0', 'ike_policy_id': 222, 'ipsec_policy_id': 333} } self.csr = mock.Mock(spec=csr_client.CsrRestClient) self.csr.tunnel_ip = '172.24.4.23' self.ipsec_conn = ipsec_driver.CiscoCsrIPSecConnection(self.conn_info, self.csr) def test_invalid_attribute(self): """Negative test of unknown attribute - programming error.""" self.assertRaises(ipsec_driver.CsrDriverMismatchError, self.ipsec_conn.translate_dialect, 'ike_policy', 'unknown_attr', self.conn_info) def test_driver_unknown_mapping(self): """Negative test of service driver providing unknown value to map.""" self.conn_info['ike_policy']['pfs'] = "unknown_value" self.assertRaises(ipsec_driver.CsrUnknownMappingError, self.ipsec_conn.translate_dialect, 'ike_policy', 'pfs', self.conn_info['ike_policy']) def test_psk_create_info(self): """Ensure that pre-shared key info is created correctly.""" expected = {u'keyring-name': '123', u'pre-shared-key-list': [ {u'key': 'secret', u'encrypted': False, u'peer-address': '192.168.1.2'}]} psk_id = self.conn_info['id'] psk_info = self.ipsec_conn.create_psk_info(psk_id, self.conn_info) self.assertEqual(expected, psk_info) def test_create_ike_policy_info(self): """Ensure that IKE policy info is mapped/created correctly.""" expected = {u'priority-id': 222, u'encryption': u'aes', u'hash': u'sha', u'dhGroup': 5, u'version': u'v1', u'lifetime': 3600} policy_id = self.conn_info['cisco']['ike_policy_id'] policy_info = self.ipsec_conn.create_ike_policy_info(policy_id, self.conn_info) self.assertEqual(expected, policy_info) def test_create_ike_policy_info_different_encryption(self): """Ensure that IKE policy info is mapped/created correctly.""" self.conn_info['ike_policy']['encryption_algorithm'] = 'aes-192' expected = {u'priority-id': 222, u'encryption': u'aes192', u'hash': u'sha', u'dhGroup': 5, u'version': u'v1', u'lifetime': 3600} policy_id = self.conn_info['cisco']['ike_policy_id'] policy_info = self.ipsec_conn.create_ike_policy_info(policy_id, self.conn_info) self.assertEqual(expected, policy_info) def test_create_ike_policy_info_non_defaults(self): """Ensure that IKE policy info with different values.""" self.conn_info['ike_policy'] = { 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-256', 'pfs': 'Group14', 'ike_version': 'v1', 'lifetime_units': 'seconds', 'lifetime_value': 60 } expected = {u'priority-id': 222, u'encryption': u'aes256', u'hash': u'sha', u'dhGroup': 14, u'version': u'v1', u'lifetime': 60} policy_id = self.conn_info['cisco']['ike_policy_id'] policy_info = self.ipsec_conn.create_ike_policy_info(policy_id, self.conn_info) self.assertEqual(expected, policy_info) def test_ipsec_policy_info(self): """Ensure that IPSec policy info is mapped/created correctly. Note: That although the default for anti-replay-window-size on the CSR is 64, we force it to disabled, for OpenStack use. """ expected = {u'policy-id': 333, u'protection-suite': { u'esp-encryption': u'esp-aes', u'esp-authentication': u'esp-sha-hmac', u'ah': u'ah-sha-hmac' }, u'lifetime-sec': 3600, u'pfs': u'group5', u'anti-replay-window-size': u'disable'} ipsec_policy_id = self.conn_info['cisco']['ipsec_policy_id'] policy_info = self.ipsec_conn.create_ipsec_policy_info(ipsec_policy_id, self.conn_info) self.assertEqual(expected, policy_info) def test_ipsec_policy_info_different_encryption(self): """Create IPSec policy with different settings.""" self.conn_info['ipsec_policy']['transform_protocol'] = 'ah-esp' self.conn_info['ipsec_policy']['encryption_algorithm'] = 'aes-192' expected = {u'policy-id': 333, u'protection-suite': { u'esp-encryption': u'esp-192-aes', u'esp-authentication': u'esp-sha-hmac', u'ah': u'ah-sha-hmac' }, u'lifetime-sec': 3600, u'pfs': u'group5', u'anti-replay-window-size': u'disable'} ipsec_policy_id = self.conn_info['cisco']['ipsec_policy_id'] policy_info = self.ipsec_conn.create_ipsec_policy_info(ipsec_policy_id, self.conn_info) self.assertEqual(expected, policy_info) def test_ipsec_policy_info_non_defaults(self): """Create/map IPSec policy info with different values.""" self.conn_info['ipsec_policy'] = {'transform_protocol': 'esp', 'encryption_algorithm': '3des', 'auth_algorithm': 'sha1', 'pfs': 'group14', 'lifetime_units': 'seconds', 'lifetime_value': 120, 'anti-replay-window-size': 'disable'} expected = {u'policy-id': 333, u'protection-suite': { u'esp-encryption': u'esp-3des', u'esp-authentication': u'esp-sha-hmac' }, u'lifetime-sec': 120, u'pfs': u'group14', u'anti-replay-window-size': u'disable'} ipsec_policy_id = self.conn_info['cisco']['ipsec_policy_id'] policy_info = self.ipsec_conn.create_ipsec_policy_info(ipsec_policy_id, self.conn_info) self.assertEqual(expected, policy_info) def test_site_connection_info(self): """Ensure site-to-site connection info is created/mapped correctly.""" expected = {u'vpn-interface-name': 'Tunnel0', u'ipsec-policy-id': 333, u'remote-device': { u'tunnel-ip-address': '192.168.1.2' }, u'mtu': 1500} ipsec_policy_id = self.conn_info['cisco']['ipsec_policy_id'] site_conn_id = self.conn_info['cisco']['site_conn_id'] conn_info = self.ipsec_conn.create_site_connection_info( site_conn_id, ipsec_policy_id, self.conn_info) self.assertEqual(expected, conn_info) def test_static_route_info(self): """Create static route info for peer CIDRs.""" expected = [('10.1.0.0_24_Tunnel0', {u'destination-network': '10.1.0.0/24', u'outgoing-interface': 'Tunnel0'}), ('10.2.0.0_24_Tunnel0', {u'destination-network': '10.2.0.0/24', u'outgoing-interface': 'Tunnel0'})] # self.driver.csr.make_route_id.side_effect = ['10.1.0.0_24_Tunnel0', # '10.2.0.0_24_Tunnel0'] site_conn_id = self.conn_info['cisco']['site_conn_id'] routes_info = self.ipsec_conn.create_routes_info(site_conn_id, self.conn_info) self.assertEqual(2, len(routes_info)) self.assertEqual(expected, routes_info) class TestCiscoCsrIPsecDeviceDriverSyncStatuses(base.BaseTestCase): """Test status/state of services and connections, after sync.""" def setUp(self): super(TestCiscoCsrIPsecDeviceDriverSyncStatuses, self).setUp() for klass in ['neutron.common.rpc.create_connection', 'neutron_lib.context.get_admin_context_without_session', 'oslo_service.loopingcall.FixedIntervalLoopingCall']: mock.patch(klass).start() self.context = context.Context('some_user', 'some_tenant') self.agent = mock.Mock() self.driver = ipsec_driver.CiscoCsrIPsecDriver(self.agent, FAKE_HOST) self.driver.agent_rpc = mock.Mock() self.conn_create = mock.patch.object( ipsec_driver.CiscoCsrIPSecConnection, 'create_ipsec_site_connection').start() self.conn_delete = mock.patch.object( ipsec_driver.CiscoCsrIPSecConnection, 'delete_ipsec_site_connection').start() self.admin_state = mock.patch.object( ipsec_driver.CiscoCsrIPSecConnection, 'set_admin_state').start() self.csr = mock.Mock() self.router_info = { u'router_info': {'rest_mgmt_ip': '2.2.2.2', 'tunnel_ip': '1.1.1.3', 'username': 'me', 'password': 'password', 'timeout': 120, 'outer_if_name': u'GigabitEthernet3.102', 'inner_if_name': u'GigabitEthernet3.101'}} self.service123_data = {u'id': u'123', u'status': constants.DOWN, u'admin_state_up': False} self.service123_data.update(self.router_info) self.conn1_data = {u'id': u'1', u'status': constants.ACTIVE, u'admin_state_up': True, u'mtu': 1500, u'psk': u'secret', u'peer_address': '192.168.1.2', u'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'], u'ike_policy': { u'auth_algorithm': u'sha1', u'encryption_algorithm': u'aes-128', u'pfs': u'Group5', u'ike_version': u'v1', u'lifetime_units': u'seconds', u'lifetime_value': 3600}, u'ipsec_policy': { u'transform_protocol': u'ah', u'encryption_algorithm': u'aes-128', u'auth_algorithm': u'sha1', u'pfs': u'group5', u'lifetime_units': u'seconds', u'lifetime_value': 3600}, u'cisco': {u'site_conn_id': u'Tunnel0'}} # NOTE: For sync, there is mark (trivial), update (tested), # sweep (tested), and report(tested) phases. def test_update_ipsec_connection_create_notify(self): """Notified of connection create request - create.""" # Make the (existing) service self.driver.create_vpn_service(self.service123_data) conn_data = copy.deepcopy(self.conn1_data) conn_data[u'status'] = constants.PENDING_CREATE connection = self.driver.update_connection(self.context, u'123', conn_data) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.PENDING_CREATE, connection.last_status) self.assertEqual(1, self.conn_create.call_count) def test_detect_no_change_to_ipsec_connection(self): """No change to IPSec connection - nop.""" # Make existing service, and connection that was active vpn_service = self.driver.create_vpn_service(self.service123_data) connection = vpn_service.create_connection(self.conn1_data) self.assertFalse(connection.check_for_changes(self.conn1_data)) def test_detect_state_only_change_to_ipsec_connection(self): """Only IPSec connection state changed - update.""" # Make existing service, and connection that was active vpn_service = self.driver.create_vpn_service(self.service123_data) connection = vpn_service.create_connection(self.conn1_data) conn_data = copy.deepcopy(self.conn1_data) conn_data[u'admin_state_up'] = False self.assertFalse(connection.check_for_changes(conn_data)) def test_detect_non_state_change_to_ipsec_connection(self): """Connection change instead of/in addition to state - update.""" # Make existing service, and connection that was active vpn_service = self.driver.create_vpn_service(self.service123_data) connection = vpn_service.create_connection(self.conn1_data) conn_data = copy.deepcopy(self.conn1_data) conn_data[u'ipsec_policy'][u'encryption_algorithm'] = u'aes-256' self.assertTrue(connection.check_for_changes(conn_data)) def test_update_ipsec_connection_changed_admin_down(self): """Notified of connection state change - update. For a connection that was previously created, expect to force connection down on an admin down (only) change. """ # Make existing service, and connection that was active vpn_service = self.driver.create_vpn_service(self.service123_data) vpn_service.create_connection(self.conn1_data) # Simulate that notification of connection update received self.driver.mark_existing_connections_as_dirty() # Modify the connection data for the 'sync' conn_data = copy.deepcopy(self.conn1_data) conn_data[u'admin_state_up'] = False connection = self.driver.update_connection(self.context, '123', conn_data) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.ACTIVE, connection.last_status) self.assertFalse(self.conn_create.called) self.assertFalse(connection.is_admin_up) self.assertTrue(connection.forced_down) self.assertEqual(1, self.admin_state.call_count) def test_update_ipsec_connection_changed_config(self): """Notified of connection changing config - update. Goal here is to detect that the connection is deleted and then created, but not that the specific values have changed, so picking arbitrary value (MTU). """ # Make existing service, and connection that was active vpn_service = self.driver.create_vpn_service(self.service123_data) vpn_service.create_connection(self.conn1_data) # Simulate that notification of connection update received self.driver.mark_existing_connections_as_dirty() # Modify the connection data for the 'sync' conn_data = copy.deepcopy(self.conn1_data) conn_data[u'mtu'] = 9200 connection = self.driver.update_connection(self.context, '123', conn_data) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.ACTIVE, connection.last_status) self.assertEqual(1, self.conn_create.call_count) self.assertEqual(1, self.conn_delete.call_count) self.assertTrue(connection.is_admin_up) self.assertFalse(connection.forced_down) self.assertFalse(self.admin_state.called) def test_update_of_unknown_ipsec_connection(self): """Notified of update of unknown connection - create. Occurs if agent restarts and receives a notification of change to connection, but has no previous record of the connection. Result will be to rebuild the connection. """ # Will have previously created service, but don't know of connection self.driver.create_vpn_service(self.service123_data) # Simulate that notification of connection update received self.driver.mark_existing_connections_as_dirty() conn_data = copy.deepcopy(self.conn1_data) conn_data[u'status'] = constants.DOWN connection = self.driver.update_connection(self.context, u'123', conn_data) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.DOWN, connection.last_status) self.assertEqual(1, self.conn_create.call_count) self.assertTrue(connection.is_admin_up) self.assertFalse(connection.forced_down) self.assertFalse(self.admin_state.called) def test_update_missing_connection_admin_down(self): """Connection not present is in admin down state - nop. If the agent has restarted, and a sync notification occurs with a connection that is in admin down state, recreate the connection, but indicate that the connection is down. """ # Make existing service, but no connection self.driver.create_vpn_service(self.service123_data) conn_data = copy.deepcopy(self.conn1_data) conn_data.update({u'status': constants.DOWN, u'admin_state_up': False}) connection = self.driver.update_connection(self.context, u'123', conn_data) self.assertIsNotNone(connection) self.assertFalse(connection.is_dirty) self.assertEqual(1, self.conn_create.call_count) self.assertFalse(connection.is_admin_up) self.assertTrue(connection.forced_down) self.assertEqual(1, self.admin_state.call_count) def test_update_connection_admin_up(self): """Connection updated to admin up state - record.""" # Make existing service, and connection that was admin down conn_data = copy.deepcopy(self.conn1_data) conn_data.update({u'status': constants.DOWN, u'admin_state_up': False}) service_data = {u'id': u'123', u'status': constants.DOWN, u'admin_state_up': True, u'ipsec_conns': [conn_data]} service_data.update(self.router_info) self.driver.update_service(self.context, service_data) # Simulate that notification of connection update received self.driver.mark_existing_connections_as_dirty() # Now simulate that the notification shows the connection admin up new_conn_data = copy.deepcopy(conn_data) new_conn_data[u'admin_state_up'] = True connection = self.driver.update_connection(self.context, u'123', new_conn_data) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.DOWN, connection.last_status) self.assertTrue(connection.is_admin_up) self.assertFalse(connection.forced_down) self.assertEqual(2, self.admin_state.call_count) def test_update_for_vpn_service_create(self): """Creation of new IPSec connection on new VPN service - create. Service will be created and marked as 'clean', and update processing for connection will occur (create). """ conn_data = copy.deepcopy(self.conn1_data) conn_data[u'status'] = constants.PENDING_CREATE service_data = {u'id': u'123', u'status': constants.PENDING_CREATE, u'admin_state_up': True, u'ipsec_conns': [conn_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) self.assertFalse(vpn_service.is_dirty) self.assertEqual(constants.PENDING_CREATE, vpn_service.last_status) connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.PENDING_CREATE, connection.last_status) self.assertEqual(1, self.conn_create.call_count) self.assertTrue(connection.is_admin_up) self.assertFalse(connection.forced_down) self.assertFalse(self.admin_state.called) def test_update_for_new_connection_on_existing_service(self): """Creating a new IPSec connection on an existing service.""" # Create the service before testing, and mark it dirty prev_vpn_service = self.driver.create_vpn_service(self.service123_data) self.driver.mark_existing_connections_as_dirty() conn_data = copy.deepcopy(self.conn1_data) conn_data[u'status'] = constants.PENDING_CREATE service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [conn_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) # Should reuse the entry and update the status self.assertEqual(prev_vpn_service, vpn_service) self.assertFalse(vpn_service.is_dirty) self.assertEqual(constants.ACTIVE, vpn_service.last_status) connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.PENDING_CREATE, connection.last_status) self.assertEqual(1, self.conn_create.call_count) def test_update_for_vpn_service_with_one_unchanged_connection(self): """Existing VPN service and IPSec connection without any changes - nop. Service and connection will be marked clean. No processing for either, as there are no changes. """ # Create a service and add in a connection that is active prev_vpn_service = self.driver.create_vpn_service(self.service123_data) prev_vpn_service.create_connection(self.conn1_data) self.driver.mark_existing_connections_as_dirty() # Create notification with conn unchanged and service already created service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [self.conn1_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) # Should reuse the entry and update the status self.assertEqual(prev_vpn_service, vpn_service) self.assertFalse(vpn_service.is_dirty) self.assertEqual(constants.ACTIVE, vpn_service.last_status) connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.ACTIVE, connection.last_status) self.assertFalse(self.conn_create.called) def test_update_service_admin_down(self): """VPN service updated to admin down state - force all down. If service is down, then all connections are forced down. """ # Create an "existing" service, prior to notification prev_vpn_service = self.driver.create_vpn_service(self.service123_data) self.driver.mark_existing_connections_as_dirty() service_data = {u'id': u'123', u'status': constants.DOWN, u'admin_state_up': False, u'ipsec_conns': [self.conn1_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) self.assertEqual(prev_vpn_service, vpn_service) self.assertFalse(vpn_service.is_dirty) self.assertFalse(vpn_service.is_admin_up) self.assertEqual(constants.DOWN, vpn_service.last_status) conn = vpn_service.get_connection(u'1') self.assertIsNotNone(conn) self.assertFalse(conn.is_dirty) self.assertTrue(conn.forced_down) self.assertTrue(conn.is_admin_up) def test_update_new_service_admin_down(self): """Unknown VPN service updated to admin down state - nop. Can happen if agent restarts and then gets its first notification of a service that is in the admin down state. Structures will be created, but forced down. """ service_data = {u'id': u'123', u'status': constants.DOWN, u'admin_state_up': False, u'ipsec_conns': [self.conn1_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) self.assertIsNotNone(vpn_service) self.assertFalse(vpn_service.is_dirty) self.assertFalse(vpn_service.is_admin_up) self.assertEqual(constants.DOWN, vpn_service.last_status) conn = vpn_service.get_connection(u'1') self.assertIsNotNone(conn) self.assertFalse(conn.is_dirty) self.assertTrue(conn.forced_down) self.assertTrue(conn.is_admin_up) def test_update_service_admin_up(self): """VPN service updated to admin up state - restore. If service is up now, then connections that are admin up will come up and connections that are admin down, will remain down. """ # Create an "existing" service, prior to notification prev_vpn_service = self.driver.create_vpn_service(self.service123_data) self.driver.mark_existing_connections_as_dirty() conn_data1 = {u'id': u'1', u'status': constants.DOWN, u'admin_state_up': False, u'cisco': {u'site_conn_id': u'Tunnel0'}} conn_data2 = {u'id': u'2', u'status': constants.ACTIVE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel1'}} service_data = {u'id': u'123', u'status': constants.DOWN, u'admin_state_up': True, u'ipsec_conns': [conn_data1, conn_data2]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) self.assertEqual(prev_vpn_service, vpn_service) self.assertFalse(vpn_service.is_dirty) self.assertTrue(vpn_service.is_admin_up) self.assertEqual(constants.DOWN, vpn_service.last_status) conn1 = vpn_service.get_connection(u'1') self.assertIsNotNone(conn1) self.assertFalse(conn1.is_dirty) self.assertTrue(conn1.forced_down) self.assertFalse(conn1.is_admin_up) conn2 = vpn_service.get_connection(u'2') self.assertIsNotNone(conn2) self.assertFalse(conn2.is_dirty) self.assertFalse(conn2.forced_down) self.assertTrue(conn2.is_admin_up) def test_update_of_unknown_service_create(self): """Create of VPN service that is currently unknown - record. If agent is restarted or user changes VPN service to admin up, the notification may contain a VPN service with an IPSec connection that is not in PENDING_CREATE state. """ conn_data = {u'id': u'1', u'status': constants.DOWN, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel0'}} service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [conn_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) self.assertFalse(vpn_service.is_dirty) self.assertEqual(constants.ACTIVE, vpn_service.last_status) connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) self.assertFalse(connection.is_dirty) self.assertEqual(u'Tunnel0', connection.tunnel) self.assertEqual(constants.DOWN, connection.last_status) self.assertEqual(1, self.conn_create.call_count) def _check_connection_for_service(self, count, vpn_service): """Helper to check the connection information for a service.""" connection = vpn_service.get_connection(u'%d' % count) self.assertIsNotNone(connection, "for connection %d" % count) self.assertFalse(connection.is_dirty, "for connection %d" % count) self.assertEqual(u'Tunnel%d' % count, connection.tunnel, "for connection %d" % count) self.assertEqual(constants.PENDING_CREATE, connection.last_status, "for connection %d" % count) return count + 1 def notification_for_two_services_with_two_conns(self): """Helper used by tests to create two services, each with two conns.""" conn1_data = {u'id': u'1', u'status': constants.PENDING_CREATE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel1'}} conn2_data = {u'id': u'2', u'status': constants.PENDING_CREATE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel2'}} service1_data = {u'id': u'123', u'status': constants.PENDING_CREATE, u'admin_state_up': True, u'ipsec_conns': [conn1_data, conn2_data]} service1_data.update(self.router_info) conn3_data = {u'id': u'3', u'status': constants.PENDING_CREATE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel3'}} conn4_data = {u'id': u'4', u'status': constants.PENDING_CREATE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel4'}} service2_data = {u'id': u'456', u'status': constants.PENDING_CREATE, u'admin_state_up': True, u'ipsec_conns': [conn3_data, conn4_data]} service2_data.update(self.router_info) return service1_data, service2_data def test_create_two_connections_on_two_services(self): """High level test of multiple VPN services with connections.""" # Build notification message (service1_data, service2_data) = self.notification_for_two_services_with_two_conns() # Simulate plugin returning notification, when requested self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ service1_data, service2_data] vpn_services = self.driver.update_all_services_and_connections( self.context) self.assertEqual(2, len(vpn_services)) count = 1 for vpn_service in vpn_services: self.assertFalse(vpn_service.is_dirty, "for service %s" % vpn_service) self.assertEqual(constants.PENDING_CREATE, vpn_service.last_status, "for service %s" % vpn_service) count = self._check_connection_for_service(count, vpn_service) count = self._check_connection_for_service(count, vpn_service) self.assertEqual(4, self.conn_create.call_count) def test_sweep_connection_marked_as_clean(self): """Sync updated connection - no action.""" # Create a service and connection vpn_service = self.driver.create_vpn_service(self.service123_data) connection = vpn_service.create_connection(self.conn1_data) self.driver.mark_existing_connections_as_dirty() # Simulate that the update phase visited both of them vpn_service.is_dirty = False connection.is_dirty = False self.driver.remove_unknown_connections(self.context) vpn_service = self.driver.service_state.get(u'123') self.assertIsNotNone(vpn_service) self.assertFalse(vpn_service.is_dirty) connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) self.assertFalse(connection.is_dirty) def test_sweep_connection_dirty(self): """Sync did not update connection - delete.""" # Create a service and connection vpn_service = self.driver.create_vpn_service(self.service123_data) vpn_service.create_connection(self.conn1_data) self.driver.mark_existing_connections_as_dirty() # Simulate that the update phase only visited the service vpn_service.is_dirty = False self.driver.remove_unknown_connections(self.context) vpn_service = self.driver.service_state.get(u'123') self.assertIsNotNone(vpn_service) self.assertFalse(vpn_service.is_dirty) connection = vpn_service.get_connection(u'1') self.assertIsNone(connection) self.assertEqual(1, self.conn_delete.call_count) def test_sweep_service_dirty(self): """Sync did not update service - delete it and all conns.""" # Create a service and connection vpn_service = self.driver.create_vpn_service(self.service123_data) vpn_service.create_connection(self.conn1_data) self.driver.mark_existing_connections_as_dirty() # Both the service and the connection are still 'dirty' self.driver.remove_unknown_connections(self.context) self.assertIsNone(self.driver.service_state.get(u'123')) self.assertEqual(1, self.conn_delete.call_count) def test_sweep_multiple_services(self): """One service and conn updated, one service and conn not.""" # Create two services, each with a connection vpn_service1 = self.driver.create_vpn_service(self.service123_data) vpn_service1.create_connection(self.conn1_data) service456_data = {u'id': u'456', u'status': constants.ACTIVE, u'admin_state_up': False} service456_data.update(self.router_info) conn2_data = {u'id': u'2', u'status': constants.ACTIVE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel0'}} prev_vpn_service2 = self.driver.create_vpn_service(service456_data) prev_connection2 = prev_vpn_service2.create_connection(conn2_data) self.driver.mark_existing_connections_as_dirty() # Simulate that the update phase visited the first service and conn prev_vpn_service2.is_dirty = False prev_connection2.is_dirty = False self.driver.remove_unknown_connections(self.context) self.assertIsNone(self.driver.service_state.get(u'123')) vpn_service2 = self.driver.service_state.get(u'456') self.assertEqual(prev_vpn_service2, vpn_service2) self.assertFalse(vpn_service2.is_dirty) connection2 = vpn_service2.get_connection(u'2') self.assertEqual(prev_connection2, connection2) self.assertFalse(connection2.is_dirty) self.assertEqual(1, self.conn_delete.call_count) def simulate_mark_update_sweep_for_service_with_conn(self, service_state, connection_state): """Create internal structures for single service with connection. Creates a service and corresponding connection. Then, simulates the mark/update/sweep operation by marking both the service and connection as clean and updating their status. Override the REST client created for the service, with a mock, so that all calls can be mocked out. """ conn_data = {u'id': u'1', u'status': connection_state, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel0'}} service_data = {u'id': u'123', u'admin_state_up': True} service_data.update(self.router_info) # Create a service and connection vpn_service = self.driver.create_vpn_service(service_data) vpn_service.csr = self.csr # Mocked REST client connection = vpn_service.create_connection(conn_data) # Simulate that the update phase visited both of them vpn_service.is_dirty = False vpn_service.connections_removed = False vpn_service.last_status = service_state vpn_service.is_admin_up = True connection.is_dirty = False connection.last_status = connection_state connection.is_admin_up = True connection.forced_down = False return vpn_service def test_report_fragment_connection_created(self): """Generate report section for a created connection.""" # Prepare service and connection in PENDING_CREATE state vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.PENDING_CREATE, constants.PENDING_CREATE) # Simulate that CSR has reported the connection is still up self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'UP-ACTIVE'), ] # Get the statuses for connections existing on CSR tunnels = vpn_service.get_ipsec_connections_status() self.assertEqual({u'Tunnel0': constants.ACTIVE}, tunnels) # Check that there is a status for this connection connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) current_status = connection.find_current_status_in(tunnels) self.assertEqual(constants.ACTIVE, current_status) # Create report fragment due to change self.assertNotEqual(connection.last_status, current_status) report_frag = connection.update_status_and_build_report(current_status) self.assertEqual(current_status, connection.last_status) expected = {'1': {'status': constants.ACTIVE, 'updated_pending_status': True}} self.assertEqual(expected, report_frag) def test_report_fragment_connection_unchanged_status(self): """No report section generated for a created connection.""" # Prepare service and connection in ACTIVE state vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.ACTIVE, constants.ACTIVE) # Simulate that CSR has reported the connection is up self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'UP-IDLE'), ] # Get the statuses for connections existing on CSR tunnels = vpn_service.get_ipsec_connections_status() self.assertEqual({u'Tunnel0': constants.ACTIVE}, tunnels) # Check that there is a status for this connection connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) current_status = connection.find_current_status_in(tunnels) self.assertEqual(constants.ACTIVE, current_status) # Should be no report, as no change self.assertEqual(connection.last_status, current_status) report_frag = connection.update_status_and_build_report(current_status) self.assertEqual(current_status, connection.last_status) self.assertEqual({}, report_frag) def test_report_fragment_connection_changed_status(self): """Generate report section for connection with changed state.""" # Prepare service in ACTIVE state and connection in DOWN state vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.ACTIVE, constants.DOWN) # Simulate that CSR has reported the connection is still up self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'UP-NO-IKE'), ] # Get the statuses for connections existing on CSR tunnels = vpn_service.get_ipsec_connections_status() self.assertEqual({u'Tunnel0': constants.ACTIVE}, tunnels) # Check that there is a status for this connection connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) current_status = connection.find_current_status_in(tunnels) self.assertEqual(constants.ACTIVE, current_status) # Create report fragment due to change self.assertNotEqual(connection.last_status, current_status) report_frag = connection.update_status_and_build_report(current_status) self.assertEqual(current_status, connection.last_status) expected = {'1': {'status': constants.ACTIVE, 'updated_pending_status': False}} self.assertEqual(expected, report_frag) def test_report_fragment_connection_failed_create(self): """Failure test of report fragment for conn that failed creation. Normally, without any status from the CSR, the connection report would be skipped, but we need to report back failures. """ # Prepare service and connection in PENDING_CREATE state vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.PENDING_CREATE, constants.PENDING_CREATE) # Simulate that CSR does NOT report the status (no tunnel) self.csr.read_tunnel_statuses.return_value = [] # Get the statuses for connections existing on CSR tunnels = vpn_service.get_ipsec_connections_status() self.assertEqual({}, tunnels) # Check that there is a status for this connection connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) current_status = connection.find_current_status_in(tunnels) self.assertEqual(constants.ERROR, current_status) # Create report fragment due to change self.assertNotEqual(connection.last_status, current_status) report_frag = connection.update_status_and_build_report(current_status) self.assertEqual(current_status, connection.last_status) expected = {'1': {'status': constants.ERROR, 'updated_pending_status': True}} self.assertEqual(expected, report_frag) def test_report_fragment_connection_admin_down(self): """Report for a connection that is in admin down state.""" # Prepare service and connection with previous status ACTIVE, but # with connection admin down conn_data = {u'id': u'1', u'status': constants.ACTIVE, u'admin_state_up': False, u'cisco': {u'site_conn_id': u'Tunnel0'}} service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [conn_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) vpn_service.csr = self.csr # Mocked REST client # Tunnel would have been deleted, so simulate no status self.csr.read_tunnel_statuses.return_value = [] connection = vpn_service.get_connection(u'1') self.assertIsNotNone(connection) self.assertTrue(connection.forced_down) self.assertEqual(constants.ACTIVE, connection.last_status) # Create report fragment due to change report_frag = self.driver.build_report_for_connections_on(vpn_service) self.assertEqual(constants.DOWN, connection.last_status) expected = {'1': {'status': constants.DOWN, 'updated_pending_status': False}} self.assertEqual(expected, report_frag) def test_report_fragment_two_connections(self): """Generate report fragment for two connections on a service.""" # Prepare service with two connections, one ACTIVE, one DOWN conn1_data = {u'id': u'1', u'status': constants.DOWN, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel1'}} conn2_data = {u'id': u'2', u'status': constants.ACTIVE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel2'}} service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [conn1_data, conn2_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) vpn_service.csr = self.csr # Mocked REST client # Simulate that CSR has reported the connections with diff status self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel1', u'UP-IDLE'), (u'Tunnel2', u'DOWN-NEGOTIATING')] # Get the report fragments for the connections report_frag = self.driver.build_report_for_connections_on(vpn_service) expected = {u'1': {u'status': constants.ACTIVE, u'updated_pending_status': False}, u'2': {u'status': constants.DOWN, u'updated_pending_status': False}} self.assertEqual(expected, report_frag) def test_report_service_create(self): """VPN service and IPSec connection created - report.""" # Simulate creation of the service and connection vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.PENDING_CREATE, constants.PENDING_CREATE) # Simulate that the CSR has created the connection self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'UP-ACTIVE'), ] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': True, u'status': constants.ACTIVE, u'ipsec_site_connections': { u'1': {u'status': constants.ACTIVE, u'updated_pending_status': True} } } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.ACTIVE, vpn_service.last_status) self.assertEqual(constants.ACTIVE, vpn_service.get_connection(u'1').last_status) def test_report_service_create_of_first_conn_fails(self): """VPN service and IPSec conn created, but conn failed - report. Since this is the sole IPSec connection on the service, and the create failed (connection in ERROR state), the VPN service's status will be set to DOWN. """ # Simulate creation of the service and connection vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.PENDING_CREATE, constants.PENDING_CREATE) # Simulate that the CSR has no info due to failed create self.csr.read_tunnel_statuses.return_value = [] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': True, u'status': constants.DOWN, u'ipsec_site_connections': { u'1': {u'status': constants.ERROR, u'updated_pending_status': True} } } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.DOWN, vpn_service.last_status) self.assertEqual(constants.ERROR, vpn_service.get_connection(u'1').last_status) def test_report_connection_created_on_existing_service(self): """Creating connection on existing service - report.""" # Simulate existing service and connection create vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.ACTIVE, constants.PENDING_CREATE) # Simulate that the CSR has created the connection self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'UP-IDLE'), ] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': False, u'status': constants.ACTIVE, u'ipsec_site_connections': { u'1': {u'status': constants.ACTIVE, u'updated_pending_status': True} } } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.ACTIVE, vpn_service.last_status) self.assertEqual(constants.ACTIVE, vpn_service.get_connection(u'1').last_status) def test_no_report_no_changes(self): """VPN service with unchanged IPSec connection - no report. Note: No report will be generated if the last connection on the service is deleted. The service (and connection) objects will have been removed by the sweep operation and thus not reported. On the plugin, the service should be changed to DOWN. Likewise, if the service goes to admin down state. """ # Simulate an existing service and connection that are ACTIVE vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.ACTIVE, constants.ACTIVE) # Simulate that the CSR reports the connection still active self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'UP-ACTIVE'), ] report = self.driver.build_report_for_service(vpn_service) self.assertEqual({}, report) # Check that service and connection statuses are still same self.assertEqual(constants.ACTIVE, vpn_service.last_status) self.assertEqual(constants.ACTIVE, vpn_service.get_connection(u'1').last_status) def test_report_sole_connection_goes_down(self): """Only connection on VPN service goes down - report. In addition to reporting the status change and recording the new state for the IPSec connection, the VPN service status will be DOWN. """ # Simulate an existing service and connection that are ACTIVE vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.ACTIVE, constants.ACTIVE) # Simulate that the CSR reports the connection went down self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'DOWN-NEGOTIATING'), ] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': False, u'status': constants.DOWN, u'ipsec_site_connections': { u'1': {u'status': constants.DOWN, u'updated_pending_status': False} } } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.DOWN, vpn_service.last_status) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'1').last_status) def test_report_sole_connection_comes_up(self): """Only connection on VPN service comes up - report. In addition to reporting the status change and recording the new state for the IPSec connection, the VPN service status will be ACTIVE. """ # Simulate an existing service and connection that are DOWN vpn_service = self.simulate_mark_update_sweep_for_service_with_conn( constants.DOWN, constants.DOWN) # Simulate that the CSR reports the connection came up self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel0', u'UP-NO-IKE'), ] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': False, u'status': constants.ACTIVE, u'ipsec_site_connections': { u'1': {u'status': constants.ACTIVE, u'updated_pending_status': False} } } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.ACTIVE, vpn_service.last_status) self.assertEqual(constants.ACTIVE, vpn_service.get_connection(u'1').last_status) def test_report_service_with_two_connections_gone_down(self): """One service with two connections that went down - report. Shows the case where all the connections are down, so that the service should report as DOWN, as well. """ # Simulate one service with two ACTIVE connections conn1_data = {u'id': u'1', u'status': constants.ACTIVE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel1'}} conn2_data = {u'id': u'2', u'status': constants.ACTIVE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel2'}} service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [conn1_data, conn2_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) vpn_service.csr = self.csr # Mocked REST client # Simulate that the CSR has reported that the connections are DOWN self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel1', u'DOWN-NEGOTIATING'), (u'Tunnel2', u'DOWN')] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': False, u'status': constants.DOWN, u'ipsec_site_connections': { u'1': {u'status': constants.DOWN, u'updated_pending_status': False}, u'2': {u'status': constants.DOWN, u'updated_pending_status': False}} } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.DOWN, vpn_service.last_status) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'1').last_status) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'2').last_status) def test_report_service_with_connection_removed(self): """One service with two connections where one is removed - report. With a connection removed and the other connection unchanged, normally there would be nothing to report for the connections, but we need to report any possible change to the service state. In this case, the service was ACTIVE, but since the only ACTIVE connection is deleted and the remaining connection is DOWN, the service will indicate as DOWN. """ # Simulate one service with one connection up, one down conn1_data = {u'id': u'1', u'status': constants.ACTIVE, u'admin_state_up': True, u'mtu': 1500, u'psk': u'secret', u'peer_address': '192.168.1.2', u'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'], u'ike_policy': {u'auth_algorithm': u'sha1', u'encryption_algorithm': u'aes-128', u'pfs': u'Group5', u'ike_version': u'v1', u'lifetime_units': u'seconds', u'lifetime_value': 3600}, u'ipsec_policy': {u'transform_protocol': u'ah', u'encryption_algorithm': u'aes-128', u'auth_algorithm': u'sha1', u'pfs': u'group5', u'lifetime_units': u'seconds', u'lifetime_value': 3600}, u'cisco': {u'site_conn_id': u'Tunnel1'}} conn2_data = {u'id': u'2', u'status': constants.DOWN, u'admin_state_up': True, u'mtu': 1500, u'psk': u'secret', u'peer_address': '192.168.1.2', u'peer_cidrs': ['10.1.0.0/24', '10.2.0.0/24'], u'ike_policy': {u'auth_algorithm': u'sha1', u'encryption_algorithm': u'aes-128', u'pfs': u'Group5', u'ike_version': u'v1', u'lifetime_units': u'seconds', u'lifetime_value': 3600}, u'ipsec_policy': {u'transform_protocol': u'ah', u'encryption_algorithm': u'aes-128', u'auth_algorithm': u'sha1', u'pfs': u'group5', u'lifetime_units': u'seconds', u'lifetime_value': 3600}, u'cisco': {u'site_conn_id': u'Tunnel2'}} service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [conn1_data, conn2_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) self.assertEqual(constants.ACTIVE, vpn_service.last_status) self.assertEqual(constants.ACTIVE, vpn_service.get_connection(u'1').last_status) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'2').last_status) # Simulate that one is deleted self.driver.mark_existing_connections_as_dirty() service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': True, u'ipsec_conns': [conn2_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) vpn_service.csr = self.csr # Mocked REST client self.driver.remove_unknown_connections(self.context) self.assertTrue(vpn_service.connections_removed) self.assertEqual(constants.ACTIVE, vpn_service.last_status) self.assertIsNone(vpn_service.get_connection(u'1')) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'2').last_status) # Simulate that only one connection reports and status is unchanged, # so there will be NO connection info to report. self.csr.read_tunnel_statuses.return_value = [(u'Tunnel2', u'DOWN')] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': False, u'status': constants.DOWN, u'ipsec_site_connections': {} } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.DOWN, vpn_service.last_status) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'2').last_status) def test_report_service_admin_down_with_two_connections(self): """One service admin down, with two connections - report. When the service is admin down, all the connections will report as DOWN. """ # Simulate one service (admin down) with two ACTIVE connections conn1_data = {u'id': u'1', u'status': constants.ACTIVE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel1'}} conn2_data = {u'id': u'2', u'status': constants.ACTIVE, u'admin_state_up': True, u'cisco': {u'site_conn_id': u'Tunnel2'}} service_data = {u'id': u'123', u'status': constants.ACTIVE, u'admin_state_up': False, u'ipsec_conns': [conn1_data, conn2_data]} service_data.update(self.router_info) vpn_service = self.driver.update_service(self.context, service_data) vpn_service.csr = self.csr # Mocked REST client # Since service admin down, connections will have been deleted self.csr.read_tunnel_statuses.return_value = [] report = self.driver.build_report_for_service(vpn_service) expected_report = { u'id': u'123', u'updated_pending_status': False, u'status': constants.DOWN, u'ipsec_site_connections': { u'1': {u'status': constants.DOWN, u'updated_pending_status': False}, u'2': {u'status': constants.DOWN, u'updated_pending_status': False}} } self.assertEqual(expected_report, report) # Check that service and connection statuses are updated self.assertEqual(constants.DOWN, vpn_service.last_status) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'1').last_status) self.assertEqual(constants.DOWN, vpn_service.get_connection(u'2').last_status) def test_report_multiple_services(self): """Status changes for several services - report.""" # Simulate creation of the service and connection (service1_data, service2_data) = self.notification_for_two_services_with_two_conns() vpn_service1 = self.driver.update_service(self.context, service1_data) vpn_service2 = self.driver.update_service(self.context, service2_data) # Simulate that the CSR has created the connections vpn_service1.csr = vpn_service2.csr = self.csr # Mocked REST client self.csr.read_tunnel_statuses.return_value = [ (u'Tunnel1', u'UP-ACTIVE'), (u'Tunnel2', u'DOWN'), (u'Tunnel3', u'DOWN-NEGOTIATING'), (u'Tunnel4', u'UP-IDLE')] report = self.driver.report_status(self.context) expected_report = [{u'id': u'123', u'updated_pending_status': True, u'status': constants.ACTIVE, u'ipsec_site_connections': { u'1': {u'status': constants.ACTIVE, u'updated_pending_status': True}, u'2': {u'status': constants.DOWN, u'updated_pending_status': True}} }, {u'id': u'456', u'updated_pending_status': True, u'status': constants.ACTIVE, u'ipsec_site_connections': { u'3': {u'status': constants.DOWN, u'updated_pending_status': True}, u'4': {u'status': constants.ACTIVE, u'updated_pending_status': True}} }] self.assertEqual(expected_report, sorted(report, key=operator.itemgetter('id'))) # Check that service and connection statuses are updated self.assertEqual(constants.ACTIVE, vpn_service1.last_status) self.assertEqual(constants.ACTIVE, vpn_service1.get_connection(u'1').last_status) self.assertEqual(constants.DOWN, vpn_service1.get_connection(u'2').last_status) self.assertEqual(constants.ACTIVE, vpn_service2.last_status) self.assertEqual(constants.DOWN, vpn_service2.get_connection(u'3').last_status) self.assertEqual(constants.ACTIVE, vpn_service2.get_connection(u'4').last_status) # TODO(pcm) FUTURE - UTs for update action, when supported. def test_vpnservice_updated(self): with mock.patch.object(self.driver, 'sync') as sync: context = mock.Mock() self.driver.vpnservice_updated(context) sync.assert_called_once_with(context, []) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py0000666000175000017500000020630413370230615032126 0ustar zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, 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. import copy import difflib import io import os import socket import mock import netaddr from neutron.agent.l3 import dvr_edge_router from neutron.agent.l3 import dvr_snat_ns from neutron.agent.l3 import legacy_router from neutron.agent.linux import iptables_manager from neutron_lib import constants from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.device_drivers import fedora_strongswan_ipsec from neutron_vpnaas.services.vpn.device_drivers import ipsec as openswan_ipsec from neutron_vpnaas.services.vpn.device_drivers import libreswan_ipsec from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_HOST = 'fake_host' FAKE_ROUTER_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION1_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION2_ID = _uuid() FAKE_IKE_POLICY = { 'ike_version': 'v1', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'lifetime_value': 3600 } FAKE_IPSEC_POLICY = { 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'transform_protocol': 'esp', 'lifetime_value': 3600, 'encapsulation_mode': 'tunnel' } FAKE_VPN_SERVICE = { 'id': _uuid(), 'router_id': FAKE_ROUTER_ID, 'name': 'myvpn', 'admin_state_up': True, 'status': constants.PENDING_CREATE, 'external_ip': '60.0.0.4', 'ipsec_site_connections': [ {'peer_cidrs': ['20.0.0.0/24', '30.0.0.0/24'], 'local_cidrs': ['10.0.0.0/24'], 'local_ip_vers': 4, 'admin_state_up': True, 'id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'external_ip': '60.0.0.4', 'peer_address': '60.0.0.5', 'mtu': 1500, 'peer_id': '60.0.0.5', 'psk': 'password', 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120, 'status': constants.PENDING_CREATE}, {'peer_cidrs': ['40.0.0.0/24', '50.0.0.0/24'], 'local_cidrs': ['11.0.0.0/24'], 'local_ip_vers': 4, 'admin_state_up': True, 'external_ip': '60.0.0.4', 'peer_address': '60.0.0.6', 'peer_id': '60.0.0.6', 'mtu': 1500, 'psk': 'password', 'id': FAKE_IPSEC_SITE_CONNECTION2_ID, 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120, 'status': constants.PENDING_CREATE}] } AUTH_ESP = '''esp # [encryption_algorithm]-[auth_algorithm]-[pfs] phase2alg=aes128-sha1;modp1536''' AUTH_AH = '''ah # AH protocol does not support encryption # [auth_algorithm]-[pfs] phase2alg=sha1;modp1536''' OPENSWAN_CONNECTION_DETAILS = '''# rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) # [mtu] mtu=1500 # [dpd_action] dpdaction=%(dpd_action)s # [dpd_interval] dpddelay=%(dpd_delay)s # [dpd_timeout] dpdtimeout=%(dpd_timeout)s # [auth_mode] authby=secret ###################### # IKEPolicy params ###################### #ike version ikev2=never # [encryption_algorithm]-[auth_algorithm]-[pfs] ike=aes128-sha1;modp1536 # [lifetime_value] ikelifetime=%(ike_lifetime)ss # NOTE: it looks lifetime_units=kilobytes can't be enforced \ (could be seconds, hours, days...) ########################## # IPsecPolicys params ########################## # [transform_protocol] phase2=%(auth_mode)s # [encapsulation_mode] type=%(encapsulation_mode)s # [lifetime_value] lifetime=%(life_time)ss # lifebytes=100000 if lifetime_units=kilobytes (IKEv2 only) ''' IPV4_NEXT_HOP = '''# NOTE: a default route is required for %defaultroute to work... leftnexthop=%defaultroute rightnexthop=%defaultroute''' IPV6_NEXT_HOP = '''# To recognize the given IP addresses in this config # as IPv6 addresses by pluto whack. Default is ipv4 connaddrfamily=ipv6 # openswan can't process defaultroute for ipv6. # Assign gateway address as leftnexthop leftnexthop=%s # rightnexthop is not mandatory for ipsec, so no need in ipv6.''' EXPECTED_OPENSWAN_CONF = """ # Configuration for myvpn config setup nat_traversal=yes virtual_private=%(virtual_privates)s conn %%default ikelifetime=480m keylife=60m keyingtries=%%forever conn %(conn1_id)s %(next_hop)s left=%(left)s leftid=%(left)s auto=start # NOTE:REQUIRED # [subnet] leftsubnet%(local_cidrs1)s # [updown] # What "updown" script to run to adjust routing and/or firewalling when # the status of the connection changes (default "ipsec _updown"). # "--route yes" allows to specify such routing options as mtu and metric. leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ###################### # [peer_address] right=%(right1)s # [peer_id] rightid=%(right1)s # [peer_cidrs] rightsubnets={ %(peer_cidrs1)s } %(conn_details)sconn %(conn2_id)s %(next_hop)s left=%(left)s leftid=%(left)s auto=start # NOTE:REQUIRED # [subnet] leftsubnet%(local_cidrs2)s # [updown] # What "updown" script to run to adjust routing and/or firewalling when # the status of the connection changes (default "ipsec _updown"). # "--route yes" allows to specify such routing options as mtu and metric. leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ###################### # [peer_address] right=%(right2)s # [peer_id] rightid=%(right2)s # [peer_cidrs] rightsubnets={ %(peer_cidrs2)s } %(conn_details)s """ STRONGSWAN_AUTH_ESP = 'esp=aes128-sha1-modp1536' STRONGSWAN_AUTH_AH = 'ah=sha1-modp1536' EXPECTED_IPSEC_OPENSWAN_SECRET_CONF = ''' # Configuration for myvpn 60.0.0.4 60.0.0.5 : PSK "password" 60.0.0.4 60.0.0.6 : PSK "password"''' EXPECTED_IPSEC_STRONGSWAN_CONF = ''' # Configuration for myvpn config setup conn %%default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 authby=psk mobike=no conn %(conn1_id)s keyexchange=ikev1 left=%(left)s leftsubnet=%(local_cidrs1)s leftid=%(left)s leftfirewall=yes right=%(right1)s rightsubnet=%(peer_cidrs1)s rightid=%(right1)s auto=route dpdaction=%(dpd_action)s dpddelay=%(dpd_delay)ss dpdtimeout=%(dpd_timeout)ss ike=%(ike_encryption_algorithm)s-%(ike_auth_algorithm)s-%(ike_pfs)s ikelifetime=%(ike_lifetime)ss %(auth_mode)s lifetime=%(life_time)ss type=%(encapsulation_mode)s conn %(conn2_id)s keyexchange=ikev1 left=%(left)s leftsubnet=%(local_cidrs2)s leftid=%(left)s leftfirewall=yes right=%(right2)s rightsubnet=%(peer_cidrs2)s rightid=%(right2)s auto=route dpdaction=%(dpd_action)s dpddelay=%(dpd_delay)ss dpdtimeout=%(dpd_timeout)ss ike=%(ike_encryption_algorithm)s-%(ike_auth_algorithm)s-%(ike_pfs)s ikelifetime=%(ike_lifetime)ss %(auth_mode)s lifetime=%(life_time)ss type=%(encapsulation_mode)s ''' EXPECTED_STRONGSWAN_DEFAULT_CONF = ''' charon { load_modular = yes plugins { include strongswan.d/charon/*.conf } } include strongswan.d/*.conf ''' EXPECTED_IPSEC_STRONGSWAN_SECRET_CONF = ''' # Configuration for myvpn 60.0.0.4 60.0.0.5 : PSK "password" 60.0.0.4 60.0.0.6 : PSK "password" ''' PLUTO_ACTIVE_STATUS = """000 "%(conn_id)s/0x1": erouted;\n 000 #4: "%(conn_id)s/0x1":500 STATE_QUICK_R2 (IPsec SA established); \ newest IPSEC;""" % { 'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_ACTIVE_STATUS_IKEV2 = """000 "%(conn_id)s/0x1": erouted;\n 000 #4: "%(conn_id)s/0x1":500 STATE_PARENT_R2 (PARENT SA established); \ newest IPSEC;""" % { 'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_MULTIPLE_SUBNETS_ESTABLISHED_STATUS = """000 "%(conn_id1)s/1x1": erouted;\n 000 #4: "%(conn_id1)s/1x1":500 STATE_QUICK_R2 (IPsec SA established); \ newest IPSEC;\n 000 "%(conn_id2)s/2x1": erouted;\n 000 #4: "%(conn_id2)s/2x1":500 STATE_QUICK_R2 (IPsec SA established); \ newest IPSEC;\n""" % { 'conn_id1': FAKE_IPSEC_SITE_CONNECTION1_ID, 'conn_id2': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_ACTIVE_NO_IPSEC_SA_STATUS = """000 "%(conn_id)s/0x1": erouted;\n 000 #258: "%(conn_id)s/0x1":500 STATE_MAIN_R2 (sent MR2, expecting MI3);""" % { 'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_DOWN_STATUS = "000 \"%(conn_id)s/0x1\": unrouted;" % {'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} CHARON_ACTIVE_STATUS = "%(conn_id)s{1}: INSTALLED, TUNNEL" % {'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} CHARON_DOWN_STATUS = "%(conn_id)s{1}: ROUTED, TUNNEL" % {'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} NOT_RUNNING_STATUS = "Command: ['ipsec', 'status'] Exit code: 3 Stdout:" class BaseIPsecDeviceDriver(base.BaseTestCase): def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess, vpnservice=FAKE_VPN_SERVICE): super(BaseIPsecDeviceDriver, self).setUp() for klass in [ 'neutron.common.rpc.create_connection', 'oslo_service.loopingcall.FixedIntervalLoopingCall' ]: mock.patch(klass).start() self._execute = mock.patch.object(ipsec_process, '_execute').start() self.agent = mock.Mock() self.conf = cfg.CONF self.agent.conf = self.conf self.driver = driver( self.agent, FAKE_HOST) self.driver.agent_rpc = mock.Mock() self.ri_kwargs = {'router': {'id': FAKE_ROUTER_ID, 'ha': False}, 'agent_conf': self.conf, 'interface_driver': mock.sentinel.interface_driver} self.iptables = mock.Mock() self.apply_mock = mock.Mock() self.vpnservice = copy.deepcopy(vpnservice) ipsec_process._get_strongswan_piddir = mock.Mock( return_value="/var/run") @staticmethod def generate_diff(a, b): """Generates unified diff of a and b.""" a, b = list(a.splitlines(True)), list(b.splitlines(True)) diff = difflib.unified_diff(a, b, fromfile="expected", tofile="actual") return diff def modify_config_for_test(self, overrides): """Revise service/connection settings to test variations. Must update service, so that dialect mappings occur for any changes that are made. """ ipsec_auth_protocol = overrides.get('ipsec_auth') if ipsec_auth_protocol: auth_proto = {'transform_protocol': ipsec_auth_protocol} for conn in self.vpnservice['ipsec_site_connections']: conn['ipsecpolicy'].update(auth_proto) local_cidrs = overrides.get('local_cidrs') if local_cidrs: for i, conn in enumerate( self.vpnservice['ipsec_site_connections']): conn['local_cidrs'] = local_cidrs[i] local_ip_version = overrides.get('local_ip_vers', 4) for conn in self.vpnservice['ipsec_site_connections']: conn['local_ip_vers'] = local_ip_version peer_cidrs = overrides.get('peer_cidrs') if peer_cidrs: for i, conn in enumerate( self.vpnservice['ipsec_site_connections']): conn['peer_cidrs'] = peer_cidrs[i] peers = overrides.get('peers') if peers: for i, conn in enumerate( self.vpnservice['ipsec_site_connections']): conn['peer_id'] = peers[i] conn['peer_address'] = peers[i] local_ip = overrides.get('local') if local_ip: for conn in self.vpnservice['ipsec_site_connections']: conn['external_ip'] = local_ip local_id = overrides.get('local_id') if local_ip: for conn in self.vpnservice['ipsec_site_connections']: conn['local_id'] = local_id def check_config_file(self, expected, actual): expected = expected.strip() actual = actual.strip() res_diff = self.generate_diff(expected, actual) self.assertEqual(expected, actual, message=''.join(res_diff)) def _test_ipsec_connection_config(self, info): """Check config file string for service/connection. Calls test specific method to create (and override as needed) the expected config file string, generates the config using the test's IPSec template, and then compares the results. """ expected = self.build_ipsec_expected_config_for_test(info) actual = self.process._gen_config_content(self.ipsec_template, self.vpnservice) self.check_config_file(expected, actual) class IPSecDeviceLegacy(BaseIPsecDeviceDriver): def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(IPSecDeviceLegacy, self).setUp(driver, ipsec_process) self._make_router_info_for_test() def _make_router_info_for_test(self): self.router = legacy_router.LegacyRouter(router_id=FAKE_ROUTER_ID, agent=self.agent, **self.ri_kwargs) self.router.router['distributed'] = False self.router.iptables_manager.ipv4['nat'] = self.iptables self.router.iptables_manager.apply = self.apply_mock self.driver.routers[FAKE_ROUTER_ID] = self.router def _test_vpnservice_updated(self, expected_param, **kwargs): with mock.patch.object(self.driver, 'sync') as sync: context = mock.Mock() self.driver.vpnservice_updated(context, **kwargs) sync.assert_called_once_with(context, expected_param) def test_vpnservice_updated(self): self._test_vpnservice_updated([]) def test_vpnservice_updated_with_router_info(self): router_info = {'id': FAKE_ROUTER_ID, 'ha': False} kwargs = {'router': router_info} self._test_vpnservice_updated([router_info], **kwargs) def test_create_router(self): process = mock.Mock(openswan_ipsec.OpenSwanProcess) process.vpnservice = self.vpnservice self.driver.processes = { FAKE_ROUTER_ID: process} self.driver.create_router(self.router) self._test_add_nat_rule() process.enable.assert_called_once_with() def test_destroy_router(self): process_id = _uuid() process = mock.Mock() process.vpnservice = self.vpnservice self.driver.processes = { process_id: process} self.driver.destroy_router(process_id) process.disable.assert_called_once_with() self.assertNotIn(process_id, self.driver.processes) def _test_add_nat_rule(self): self.router.iptables_manager.ipv4['nat'].assert_has_calls([ mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 20.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 30.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 40.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 50.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True) ]) self.router.iptables_manager.apply.assert_called_once_with() def _test_add_nat_rule_with_multiple_locals(self): self.router.iptables_manager.ipv4['nat'].assert_has_calls([ mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 20.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 30.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 20.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 30.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 12.0.0.0/24 -d 40.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 12.0.0.0/24 -d 50.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 13.0.0.0/24 -d 40.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 13.0.0.0/24 -d 50.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True) ]) self.router.iptables_manager.apply.assert_called_once_with() def test_sync(self): fake_vpn_service = FAKE_VPN_SERVICE self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ fake_vpn_service] context = mock.Mock() self.driver._sync_vpn_processes = mock.Mock() self.driver._delete_vpn_processes = mock.Mock() self.driver._cleanup_stale_vpn_processes = mock.Mock() sync_routers = [{'id': fake_vpn_service['router_id']}] sync_router_ids = [fake_vpn_service['router_id']] self.driver.sync(context, sync_routers) self.driver._sync_vpn_processes.assert_called_once_with( [fake_vpn_service], sync_router_ids) self.driver._delete_vpn_processes.assert_called_once_with( sync_router_ids, sync_router_ids) self.driver._cleanup_stale_vpn_processes.assert_called_once_with( sync_router_ids) def test__sync_vpn_processes_new_vpn_service(self): new_vpnservice = self.vpnservice router_id = new_vpnservice['router_id'] self.driver.processes = {} with mock.patch.object(self.driver, 'ensure_process') as ensure_p: ensure_p.side_effect = self.fake_ensure_process self.driver._sync_vpn_processes([new_vpnservice], router_id) self._test_add_nat_rule() self.driver.processes[router_id].update.assert_called_once_with() def test_add_nat_rules_with_multiple_local_subnets(self): """Ensure that add nat rule combinations are correct.""" overrides = {'local_cidrs': [['10.0.0.0/24', '11.0.0.0/24'], ['12.0.0.0/24', '13.0.0.0/24']]} self.modify_config_for_test(overrides) self.driver._update_nat(self.vpnservice, self.driver.add_nat_rule) self._test_add_nat_rule_with_multiple_locals() def test__sync_vpn_processes_router_with_no_vpn(self): """Test _sync_vpn_processes with a router not hosting vpnservice. This test case tests that when a router which doesn't host vpn services is updated, sync_vpn_processes doesn't restart/update the existing vpnservice processes. """ process = mock.Mock() process.vpnservice = self.vpnservice process.connection_status = {} self.driver.processes = { self.vpnservice['router_id']: process} router_id_no_vpn = _uuid() with mock.patch.object(self.driver, 'ensure_process') as ensure_p: self.driver._sync_vpn_processes([self.vpnservice], [router_id_no_vpn]) self.assertEqual(0, ensure_p.call_count) def test__sync_vpn_processes_router_with_no_vpn_and_no_vpn_services(self): """No vpn services running and router not hosting vpn svc.""" router_id_no_vpn = _uuid() self.driver.process_status_cache = {} self.driver.processes = {} with mock.patch.object(self.driver, 'ensure_process') as ensure_p: ensure_p.side_effect = self.fake_ensure_process self.driver._sync_vpn_processes([], [router_id_no_vpn]) self.assertEqual(0, ensure_p.call_count) def test__sync_vpn_processes_router_with_no_vpn_agent_restarted(self): """Test for the router not hosting vpnservice and agent restarted. This test case tests that when a non vpnservice hosted router is updated, _sync_vpn_processes restart/update the existing vpnservices which are not yet stored in driver.processes. """ router_id = FAKE_ROUTER_ID self.driver.process_status_cache = {} self.driver.processes = {} with mock.patch.object(self.driver, 'ensure_process') as ensure_p: ensure_p.side_effect = self.fake_ensure_process self.driver._sync_vpn_processes([self.vpnservice], [router_id]) self._test_add_nat_rule() self.driver.processes[router_id].update.assert_called_once_with() def test_delete_vpn_processes(self): router_id_no_vpn = _uuid() vpn_service_router_id = _uuid() with mock.patch.object(self.driver, 'destroy_process') as (fake_destroy_process): self.driver._delete_vpn_processes([router_id_no_vpn], [vpn_service_router_id]) fake_destroy_process.assert_has_calls( [mock.call(router_id_no_vpn)]) # test that _delete_vpn_processes doesn't delete the # the valid vpn processes with mock.patch.object(self.driver, 'destroy_process') as fake_destroy_process: self.driver._delete_vpn_processes([vpn_service_router_id], [vpn_service_router_id]) self.assertFalse(fake_destroy_process.called) def test_cleanup_stale_vpn_processes(self): stale_vpn_service = {'router_id': _uuid()} active_vpn_service = {'router_id': _uuid()} self.driver.processes = { stale_vpn_service['router_id']: stale_vpn_service, active_vpn_service['router_id']: active_vpn_service} with mock.patch.object(self.driver, 'destroy_process') as destroy_p: self.driver._cleanup_stale_vpn_processes( [active_vpn_service['router_id']]) destroy_p.assert_has_calls( [mock.call(stale_vpn_service['router_id'])]) def fake_ensure_process(self, process_id, vpnservice=None): process = self.driver.processes.get(process_id) if not process: process = mock.Mock() process.vpnservice = self.vpnservice process.connection_status = {} process.status = constants.ACTIVE process.updated_pending_status = True self.driver.processes[process_id] = process elif vpnservice: process.vpnservice = vpnservice process.update_vpnservice(vpnservice) return process def fake_destroy_router(self, process_id): process = self.driver.processes.get(process_id) if process: del self.driver.processes[process_id] def test_sync_update_vpnservice(self): with mock.patch.object(self.driver, 'ensure_process') as ensure_process: ensure_process.side_effect = self.fake_ensure_process new_vpn_service = self.vpnservice updated_vpn_service = copy.deepcopy(new_vpn_service) updated_vpn_service['ipsec_site_connections'][1].update( {'peer_cidrs': ['60.0.0.0/24', '70.0.0.0/24']}) context = mock.Mock() self.driver.process_status_cache = {} self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ new_vpn_service] self.driver.sync(context, [{'id': FAKE_ROUTER_ID}]) process = self.driver.processes[FAKE_ROUTER_ID] self.assertEqual(new_vpn_service, process.vpnservice) self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ updated_vpn_service] self.driver.sync(context, [{'id': FAKE_ROUTER_ID}]) process = self.driver.processes[FAKE_ROUTER_ID] process.update_vpnservice.assert_called_once_with( updated_vpn_service) self.assertEqual(updated_vpn_service, process.vpnservice) def test_sync_removed(self): self.driver.agent_rpc.get_vpn_services_on_host.return_value = [] context = mock.Mock() process_id = _uuid() process = mock.Mock() process.vpnservice = self.vpnservice self.driver.processes = { process_id: process} self.driver.sync(context, []) process.disable.assert_called_once_with() self.assertNotIn(process_id, self.driver.processes) def test_sync_removed_router(self): self.driver.agent_rpc.get_vpn_services_on_host.return_value = [] context = mock.Mock() process_id = _uuid() self.driver.sync(context, [{'id': process_id}]) self.assertNotIn(process_id, self.driver.processes) def test_status_updated_on_connection_admin_down(self): self.driver.process_status_cache = { '1': { 'status': constants.ACTIVE, 'id': 123, 'updated_pending_status': False, 'ipsec_site_connections': { '10': { 'status': constants.ACTIVE, 'updated_pending_status': False, }, '20': { 'status': constants.ACTIVE, 'updated_pending_status': False, } } } } # Simulate that there is no longer status for connection '20' # e.g. connection admin down new_status = { 'ipsec_site_connections': { '10': { 'status': constants.ACTIVE, 'updated_pending_status': False } } } self.driver.update_downed_connections('1', new_status) existing_conn = new_status['ipsec_site_connections'].get('10') self.assertIsNotNone(existing_conn) self.assertEqual(constants.ACTIVE, existing_conn['status']) missing_conn = new_status['ipsec_site_connections'].get('20') self.assertIsNotNone(missing_conn) self.assertEqual(constants.DOWN, missing_conn['status']) def test_status_updated_on_service_admin_down(self): self.driver.process_status_cache = { '1': { 'status': constants.ACTIVE, 'id': 123, 'updated_pending_status': False, 'ipsec_site_connections': { '10': { 'status': constants.ACTIVE, 'updated_pending_status': False, }, '20': { 'status': constants.ACTIVE, 'updated_pending_status': False, } } } } # Simulate that there are no connections now new_status = { 'ipsec_site_connections': {} } self.driver.update_downed_connections('1', new_status) missing_conn = new_status['ipsec_site_connections'].get('10') self.assertIsNotNone(missing_conn) self.assertEqual(constants.DOWN, missing_conn['status']) missing_conn = new_status['ipsec_site_connections'].get('20') self.assertIsNotNone(missing_conn) self.assertEqual(constants.DOWN, missing_conn['status']) def _test_status_handling_for_downed_connection(self, down_status): """Test status handling for downed connection.""" router_id = self.router.router_id connection_id = FAKE_IPSEC_SITE_CONNECTION2_ID self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = down_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.ACTIVE, process_status['status']) self.assertEqual(constants.DOWN, ipsec_site_conn[connection_id]['status']) def _test_status_handling_for_active_connection(self, active_status): """Test status handling for active connection.""" router_id = self.router.router_id connection_id = FAKE_IPSEC_SITE_CONNECTION2_ID self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = active_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[ router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.ACTIVE, process_status['status']) self.assertEqual(constants.ACTIVE, ipsec_site_conn[connection_id]['status']) def _test_status_handling_for_ike_v2_active_connection(self, active_status): """Test status handling for active connection.""" router_id = self.router.router_id connection_id = FAKE_IPSEC_SITE_CONNECTION2_ID ike_policy = {'ike_version': 'v2', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'lifetime_value': 3600} vpn_service = FAKE_VPN_SERVICE for isc in vpn_service["ipsec_site_connections"]: isc['ikepolicy'] = ike_policy self.driver.ensure_process(router_id, vpn_service) self._execute.return_value = active_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[ router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.ACTIVE, process_status['status']) self.assertEqual(constants.ACTIVE, ipsec_site_conn[connection_id]['status']) def _test_connection_names_handling_for_multiple_subnets(self, active_status): """Test connection names handling for multiple subnets.""" router_id = self.router.router_id process = self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = active_status names = process.get_established_connections() self.assertEqual(2, len(names)) def _test_status_handling_for_deleted_connection(self, not_running_status): """Test status handling for deleted connection.""" router_id = self.router.router_id self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = not_running_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.DOWN, process_status['status']) self.assertFalse(ipsec_site_conn) def _test_parse_connection_status(self, not_running_status, active_status, down_status): """Test the status of ipsec-site-connection is parsed correctly.""" router_id = self.router.router_id process = self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = not_running_status self.assertFalse(process.active) # An empty return value to simulate that the process # does not have any status to report. self._execute.return_value = '' self.assertFalse(process.active) self._execute.return_value = active_status self.assertTrue(process.active) self._execute.return_value = down_status self.assertTrue(process.active) def test_get_namespace_for_router(self): namespace = self.driver.get_namespace(FAKE_ROUTER_ID) self.assertEqual('qrouter-' + FAKE_ROUTER_ID, namespace) def test_fail_getting_namespace_for_unknown_router(self): self.assertFalse(self.driver.get_namespace('bogus_id')) def test_add_nat_rule(self): self.driver.add_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.add_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) def test_add_nat_rule_with_no_router(self): self.driver.add_nat_rule( 'bogus_router_id', 'fake_chain', 'fake_rule', True) self.assertFalse(self.iptables.add_rule.called) def test_remove_rule(self): self.driver.remove_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.remove_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) def test_remove_rule_with_no_router(self): self.driver.remove_nat_rule( 'bogus_router_id', 'fake_chain', 'fake_rule') self.assertFalse(self.iptables.remove_rule.called) def test_iptables_apply(self): self.driver.iptables_apply(FAKE_ROUTER_ID) self.apply_mock.assert_called_once_with() def test_iptables_apply_with_no_router(self): self.driver.iptables_apply('bogus_router_id') self.assertFalse(self.apply_mock.called) class IPSecDeviceDVR(BaseIPsecDeviceDriver): def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(IPSecDeviceDVR, self).setUp(driver, ipsec_process) mock.patch.object(dvr_snat_ns.SnatNamespace, 'create').start() self._make_dvr_edge_router_info_for_test() def _make_dvr_edge_router_info_for_test(self): router = dvr_edge_router.DvrEdgeRouter(mock.sentinel.agent, mock.sentinel.myhost, FAKE_ROUTER_ID, **self.ri_kwargs) router.router['distributed'] = True router.snat_namespace = dvr_snat_ns.SnatNamespace(router.router['id'], mock.sentinel.agent, self.driver, mock.ANY) router.snat_namespace.create() router.snat_iptables_manager = iptables_manager.IptablesManager( namespace='snat-' + FAKE_ROUTER_ID, use_ipv6=mock.ANY) router.snat_iptables_manager.ipv4['nat'] = self.iptables router.snat_iptables_manager.apply = self.apply_mock self.driver.routers[FAKE_ROUTER_ID] = router def test_get_namespace_for_dvr_edge_router(self): namespace = self.driver.get_namespace(FAKE_ROUTER_ID) self.assertEqual('snat-' + FAKE_ROUTER_ID, namespace) def test_add_nat_rule_with_dvr_edge_router(self): self.driver.add_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.add_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) def test_iptables_apply_with_dvr_edge_router(self): self.driver.iptables_apply(FAKE_ROUTER_ID) self.apply_mock.assert_called_once_with() def test_remove_rule_with_dvr_edge_router(self): self.driver.remove_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.remove_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) class TestOpenSwanConfigGeneration(BaseIPsecDeviceDriver): """Verify that configuration files are generated correctly. Besides the normal translation of some settings, when creating the config file, the generated file can also vary based on the following special conditions: - IPv6 versus IPv4 - Multiple left subnets versus a single left subnet - IPSec policy using AH transform The tests will focus on these variations. """ def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(TestOpenSwanConfigGeneration, self).setUp( driver, ipsec_process, vpnservice=FAKE_VPN_SERVICE) self.conf.register_opts(openswan_ipsec.openswan_opts, 'openswan') self.conf.set_override('state_path', '/tmp') self.ipsec_template = self.conf.openswan.ipsec_config_template self.process = openswan_ipsec.OpenSwanProcess(self.conf, 'foo-process-id', self.vpnservice, mock.ANY) def build_ipsec_expected_config_for_test(self, info): """Modify OpenSwan ipsec expected config files for test variations.""" auth_mode = info.get('ipsec_auth', AUTH_ESP) conn_details = OPENSWAN_CONNECTION_DETAILS % {'auth_mode': auth_mode, 'dpd_action': 'hold', 'dpd_delay': 30, 'dpd_timeout': 120, 'ike_lifetime': 3600, 'life_time': 3600, 'encapsulation_mode': 'tunnel'} virtual_privates = [] # Convert local CIDRs into assignment strings. IF more than one, # pluralize the attribute name and enclose in brackets. cidrs = info.get('local_cidrs', [['10.0.0.0/24'], ['11.0.0.0/24']]) local_cidrs = [] for cidr in cidrs: if len(cidr) == 2: local_cidrs.append("s={ %s }" % ' '.join(cidr)) else: local_cidrs.append("=%s" % cidr[0]) for net in cidr: version = netaddr.IPNetwork(net).version virtual_privates.append('%%v%s:%s' % (version, net)) # Convert peer CIDRs into space separated strings cidrs = info.get('peer_cidrs', [['20.0.0.0/24', '30.0.0.0/24'], ['40.0.0.0/24', '50.0.0.0/24']]) for cidr in cidrs: for net in cidr: version = netaddr.IPNetwork(net).version virtual_privates.append('%%v%s:%s' % (version, net)) peer_cidrs = [' '.join(cidr) for cidr in cidrs] local_ip = info.get('local', '60.0.0.4') version = info.get('local_ip_vers', 4) next_hop = IPV4_NEXT_HOP if version == 4 else IPV6_NEXT_HOP % local_ip peer_ips = info.get('peers', ['60.0.0.5', '60.0.0.6']) virtual_privates.sort() return EXPECTED_OPENSWAN_CONF % { 'virtual_privates': ','.join(virtual_privates), 'next_hop': next_hop, 'local_cidrs1': local_cidrs[0], 'local_cidrs2': local_cidrs[1], 'local_ver': version, 'peer_cidrs1': peer_cidrs[0], 'peer_cidrs2': peer_cidrs[1], 'left': local_ip, 'right1': peer_ips[0], 'right2': peer_ips[1], 'conn1_id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'conn2_id': FAKE_IPSEC_SITE_CONNECTION2_ID, 'conn_details': conn_details} def test_connections_with_esp_transform_protocol(self): """Test config file with IPSec policy using ESP.""" self._test_ipsec_connection_config({}) def test_connections_with_ah_transform_protocol(self): """Test config file with IPSec policy using ESP.""" overrides = {'ipsec_auth': 'ah'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) info = {'ipsec_auth': AUTH_AH} self._test_ipsec_connection_config(info) def test_connections_with_multiple_left_subnets(self): """Test multiple local subnets. The configure uses the 'leftsubnets' attribute, instead of the 'leftsubnet' attribute. """ overrides = {'local_cidrs': [['10.0.0.0/24', '11.0.0.0/24'], ['12.0.0.0/24', '13.0.0.0/24']]} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_config_files_with_ipv6_addresses(self): """Test creating config files using IPv6 addressing.""" overrides = {'local_cidrs': [['2002:0a00::/48'], ['2002:0b00::/48']], 'local_ip_vers': 6, 'peer_cidrs': [['2002:1400::/48', '2002:1e00::/48'], ['2002:2800::/48', '2002:3200::/48']], 'local': '2002:3c00:0004::', 'peers': ['2002:3c00:0005::', '2002:3c00:0006::'], 'local_id': '2002:3c00:0004::'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_secrets_config_file(self): expected = EXPECTED_IPSEC_OPENSWAN_SECRET_CONF actual = self.process._gen_config_content( self.conf.openswan.ipsec_secret_template, self.vpnservice) self.check_config_file(expected, actual) class IPsecStrongswanConfigGeneration(BaseIPsecDeviceDriver): def setUp(self, driver=strongswan_ipsec.StrongSwanDriver, ipsec_process=strongswan_ipsec.StrongSwanProcess): super(IPsecStrongswanConfigGeneration, self).setUp( driver, ipsec_process, vpnservice=FAKE_VPN_SERVICE) self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') self.conf.set_override('state_path', '/tmp') self.ipsec_template = self.conf.strongswan.ipsec_config_template self.process = strongswan_ipsec.StrongSwanProcess(self.conf, 'foo-process-id', self.vpnservice, mock.ANY) def build_ipsec_expected_config_for_test(self, info): cidrs = info.get('local_cidrs', [['10.0.0.0/24'], ['11.0.0.0/24']]) local_cidrs = [','.join(cidr) for cidr in cidrs] cidrs = info.get('peer_cidrs', [['20.0.0.0/24', '30.0.0.0/24'], ['40.0.0.0/24', '50.0.0.0/24']]) peer_cidrs = [','.join(cidr) for cidr in cidrs] local_ip = info.get('local', '60.0.0.4') peer_ips = info.get('peers', ['60.0.0.5', '60.0.0.6']) auth_mode = info.get('ipsec_auth', STRONGSWAN_AUTH_ESP) return EXPECTED_IPSEC_STRONGSWAN_CONF % { 'local_cidrs1': local_cidrs[0], 'local_cidrs2': local_cidrs[1], 'peer_cidrs1': peer_cidrs[0], 'peer_cidrs2': peer_cidrs[1], 'left': local_ip, 'right1': peer_ips[0], 'right2': peer_ips[1], 'dpd_action': 'hold', 'dpd_delay': 30, 'dpd_timeout': 120, 'ike_encryption_algorithm': 'aes128', 'ike_auth_algorithm': 'sha1', 'ike_pfs': 'modp1536', 'ike_lifetime': 3600, 'life_time': 3600, 'auth_mode': auth_mode, 'encapsulation_mode': 'tunnel', 'conn1_id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'conn2_id': FAKE_IPSEC_SITE_CONNECTION2_ID} def test_ipsec_config_file_with_esp(self): self._test_ipsec_connection_config({}) def test_ipsec_config_file_with_ah(self): overrides = {'ipsec_auth': 'ah'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) info = {'ipsec_auth': STRONGSWAN_AUTH_AH} self._test_ipsec_connection_config(info) def test_ipsec_config_file_for_v6(self): overrides = {'local_cidrs': [['2002:0a00::/48'], ['2002:0b00::/48']], 'peer_cidrs': [['2002:1400::/48', '2002:1e00::/48'], ['2002:2800::/48', '2002:3200::/48']], 'local': '2002:3c00:0004::', 'peers': ['2002:3c00:0005::', '2002:3c00:0006::'], 'local_id': '2002:3c00:0004::'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_strongswan_default_config_file(self): expected = EXPECTED_STRONGSWAN_DEFAULT_CONF actual = self.process._gen_config_content( self.conf.strongswan.strongswan_config_template, self.vpnservice) self.check_config_file(expected, actual) def test_secrets_config_file(self): expected = EXPECTED_IPSEC_STRONGSWAN_SECRET_CONF actual = self.process._gen_config_content( self.conf.strongswan.ipsec_secret_template, self.vpnservice) self.check_config_file(expected, actual) class TestOpenSwanProcess(IPSecDeviceLegacy): _test_timeout = 1 _test_backoff = 2 _test_retries = 5 def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(TestOpenSwanProcess, self).setUp(driver, ipsec_process) self.conf.register_opts(openswan_ipsec.openswan_opts, 'openswan') self.conf.set_override('state_path', '/tmp') cfg.CONF.register_opts(openswan_ipsec.pluto_opts, 'pluto') cfg.CONF.set_override('shutdown_check_timeout', self._test_timeout, group='pluto') cfg.CONF.set_override('shutdown_check_back_off', self._test_backoff, group='pluto') cfg.CONF.set_override('shutdown_check_retries', self._test_retries, group='pluto') self.addCleanup(cfg.CONF.reset) self.os_remove = mock.patch('os.remove').start() self.process = openswan_ipsec.OpenSwanProcess(self.conf, 'foo-process-id', self.vpnservice, mock.ANY) def test__resolve_fqdn(self): with mock.patch.object(socket, 'getaddrinfo') as mock_getaddr_info: mock_getaddr_info.return_value = [(2, 1, 6, '', ('172.168.1.2', 0))] resolved_ip_addr = self.process._resolve_fqdn('fqdn.foo.addr') self.assertEqual('172.168.1.2', resolved_ip_addr) def _test_get_nexthop_helper(self, address, _resolve_fqdn_side_effect, expected_ip_cmd, expected_nexthop): with mock.patch.object(self.process, '_resolve_fqdn') as fake_resolve_fqdn: fake_resolve_fqdn.side_effect = _resolve_fqdn_side_effect returned_next_hop = self.process._get_nexthop(address, 'fake-conn-id') _resolve_fqdn_expected_call_count = ( 1 if _resolve_fqdn_side_effect else 0) self.assertEqual(_resolve_fqdn_expected_call_count, fake_resolve_fqdn.call_count) self._execute.assert_called_once_with(expected_ip_cmd) self.assertEqual(expected_nexthop, returned_next_hop) def test__get_nexthop_peer_addr_is_ipaddr(self): gw_addr = '10.0.0.1' self._execute.return_value = '172.168.1.2 via %s' % gw_addr peer_address = '172.168.1.2' expected_ip_cmd = ['ip', 'route', 'get', peer_address] self._test_get_nexthop_helper(peer_address, None, expected_ip_cmd, gw_addr) def test__get_nexthop_peer_addr_is_valid_fqdn(self): peer_address = 'foo.peer.addr' expected_ip_cmd = ['ip', 'route', 'get', '172.168.1.2'] gw_addr = '10.0.0.1' self._execute.return_value = '172.168.1.2 via %s' % gw_addr def _fake_resolve_fqdn(address): return '172.168.1.2' self._test_get_nexthop_helper(peer_address, _fake_resolve_fqdn, expected_ip_cmd, gw_addr) def test__get_nexthop_gw_not_present(self): peer_address = '172.168.1.2' expected_ip_cmd = ['ip', 'route', 'get', '172.168.1.2'] self._execute.return_value = ' ' self._test_get_nexthop_helper(peer_address, None, expected_ip_cmd, peer_address) def test__get_nexthop_fqdn_peer_addr_is_not_resolved(self): self.process.connection_status = {} expected_connection_status_dict = ( {'fake-conn-id': {'status': constants.ERROR, 'updated_pending_status': True}}) self.assertRaises(vpnaas.VPNPeerAddressNotResolved, self.process._get_nexthop, 'foo.peer.addr', 'fake-conn-id') self.assertEqual(expected_connection_status_dict, self.process.connection_status) self.process.connection_status = ( {'fake-conn-id': {'status': constants.PENDING_CREATE, 'updated_pending_status': False}}) self.assertRaises(vpnaas.VPNPeerAddressNotResolved, self.process._get_nexthop, 'foo.peer.addr', 'fake-conn-id') self.assertEqual(expected_connection_status_dict, self.process.connection_status) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') def test_no_cleanups(self, cleanup_mock, hop_mock): # Not an "awesome test" but more of a check box item. Basically, # what happens if we didn't need to clean up any files. with mock.patch.object(self.process, '_process_running', return_value=True) as query_mock: self.process.start() self.assertEqual(1, query_mock.call_count) # This is really what is being tested here. If process is # running, we shouldn't attempt a cleanup. self.assertFalse(cleanup_mock.called) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('os.path.exists', return_value=True) def test_cleanup_files(self, exists_mock, hop_mock): # Tests the 'bones' of things really and kind of check-box-item-bogus # test - this really needs exercising through a higher level test. with mock.patch.object(self.process, '_process_running', return_value=False) as query_mock: fake_path = '/fake/path/run' self.process.pid_path = fake_path self.process.pid_file = '%s.pid' % fake_path self.process.start() self.assertEqual(1, query_mock.call_count) self.assertEqual(2, self.os_remove.call_count) self.os_remove.assert_has_calls([mock.call('%s.pid' % fake_path), mock.call('%s.ctl' % fake_path)]) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._process_running', return_value=False) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') @mock.patch('eventlet.sleep') def test_restart_process_not_running(self, sleep_mock, cleanup_mock, query_mock, hop_mock): self.process.restart() # Really what is being tested - retry configuration exists and that # we do the right things when process check is false. self.assertTrue(query_mock.called) self.assertTrue(cleanup_mock.called) self.assertFalse(sleep_mock.called) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._process_running', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') @mock.patch('eventlet.sleep') def test_restart_process_doesnt_stop(self, sleep_mock, cleanup_mock, query_mock, hop_mock): self.process.restart() # Really what is being tested - retry configuration exists and that # we do the right things when process check is True. self.assertEqual(self._test_retries + 1, query_mock.call_count) self.assertFalse(cleanup_mock.called) self.assertEqual(self._test_retries, sleep_mock.call_count) calls = [mock.call(1), mock.call(2), mock.call(4), mock.call(8), mock.call(16)] sleep_mock.assert_has_calls(calls) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._process_running', side_effect=[True, True, False, False]) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') @mock.patch('eventlet.sleep') def test_restart_process_retry_until_stop(self, sleep_mock, cleanup_mock, query_mock, hop_mock): self.process.restart() # Really what is being tested - retry configuration exists and that # we do the right things when process check is True a few times and # then returns False. self.assertEqual(4, query_mock.call_count) self.assertTrue(cleanup_mock.called) self.assertEqual(2, sleep_mock.call_count) def test_process_running_no_pid(self): with mock.patch('os.path.exists', return_value=False): self.assertFalse( self.process._process_running()) # open() is used elsewhere, so we need to inject a mocked open into the # module to be tested. @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=IOError) def test_process_running_open_failure(self, mock_open, mock_exists): self.assertFalse(self.process._process_running()) self.assertTrue(mock_exists.called) self.assertTrue(mock_open.called) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'invalid'), IOError]) def test_process_running_bogus_pid(self, mock_open, mock_exists): with mock.patch.object(openswan_ipsec.LOG, 'error'): self.assertFalse(self.process._process_running()) self.assertTrue(mock_exists.called) self.assertEqual(2, mock_open.call_count) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'134'), io.StringIO(u'')]) def test_process_running_no_cmdline(self, mock_open, mock_exists): with mock.patch.object(openswan_ipsec.LOG, 'error') as log_mock: self.assertFalse(self.process._process_running()) self.assertFalse(log_mock.called) self.assertEqual(2, mock_open.call_count) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'134'), io.StringIO(u'ps ax')]) def test_process_running_cmdline_mismatch(self, mock_open, mock_exists): with mock.patch.object(openswan_ipsec.LOG, 'error') as log_mock: self.assertFalse(self.process._process_running()) self.assertFalse(log_mock.called) self.assertEqual(2, mock_open.call_count) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'134'), io.StringIO(u'/usr/libexec/ipsec/pluto -ctlbase' '/some/foo/path')]) def test_process_running_cmdline_match(self, mock_open, mock_exists): self.process.pid_path = '/some/foo/path' with mock.patch.object(openswan_ipsec.LOG, 'error') as log_mock: self.assertTrue(self.process._process_running()) self.assertTrue(log_mock.called) def test_status_handling_for_downed_connection(self): """Test status handling for downed connection.""" self._test_status_handling_for_downed_connection(PLUTO_DOWN_STATUS) def test_status_handling_for_connection_with_no_ipsec_sa(self): """Test status handling for downed connection.""" self._test_status_handling_for_downed_connection( PLUTO_ACTIVE_NO_IPSEC_SA_STATUS) def test_status_handling_for_active_connection(self): """Test status handling for active connection.""" self._test_status_handling_for_active_connection(PLUTO_ACTIVE_STATUS) def test_status_handling_for_ike_v2_active_connection(self): """Test status handling for active connection.""" self._test_status_handling_for_ike_v2_active_connection( PLUTO_ACTIVE_STATUS_IKEV2) def test_status_handling_for_deleted_connection(self): """Test status handling for deleted connection.""" self._test_status_handling_for_deleted_connection(NOT_RUNNING_STATUS) def test_connection_names_handling_for_multiple_subnets(self): """Test connection names handling for multiple subnets.""" self._test_connection_names_handling_for_multiple_subnets( PLUTO_MULTIPLE_SUBNETS_ESTABLISHED_STATUS) def test_parse_connection_status(self): """Test the status of ipsec-site-connection parsed correctly.""" self._test_parse_connection_status(NOT_RUNNING_STATUS, PLUTO_ACTIVE_STATUS, PLUTO_DOWN_STATUS) class TestLibreSwanProcess(base.BaseTestCase): def setUp(self): super(TestLibreSwanProcess, self).setUp() self.vpnservice = copy.deepcopy(FAKE_VPN_SERVICE) self.ipsec_process = libreswan_ipsec.LibreSwanProcess(cfg.CONF, 'foo-process-id', self.vpnservice, mock.ANY) @mock.patch('os.remove') @mock.patch('os.path.exists', return_value=True) def test_ensure_configs_on_restart(self, exists_mock, remove_mock): openswan_ipsec.OpenSwanProcess.ensure_configs = mock.Mock() with mock.patch.object( self.ipsec_process, '_execute' ) as fake_execute, mock.patch.object( self.ipsec_process, '_ipsec_execute' ) as fake_ipsec_execute, mock.patch.object( self.ipsec_process, '_ensure_needed_files' ) as fake_ensure_needed_files: self.ipsec_process.ensure_configs() expected = [mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process._get_config_filename( 'ipsec.secrets')]), mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process.log_dir])] fake_execute.assert_has_calls(expected) self.assertEqual(2, fake_execute.call_count) expected = [mock.call(['_stackmanager', 'start']), mock.call(['checknss'])] fake_ipsec_execute.assert_has_calls(expected) self.assertEqual(2, fake_ipsec_execute.call_count) self.assertTrue(fake_ensure_needed_files.called) self.assertTrue(exists_mock.called) self.assertTrue(remove_mock.called) @mock.patch('os.remove') @mock.patch('os.path.exists', return_value=False) def test_ensure_configs(self, exists_mock, remove_mock): openswan_ipsec.OpenSwanProcess.ensure_configs = mock.Mock() with mock.patch.object( self.ipsec_process, '_execute' ) as fake_execute, mock.patch.object( self.ipsec_process, '_ipsec_execute' ) as fake_ipsec_execute, mock.patch.object( self.ipsec_process, '_ensure_needed_files' ) as fake_ensure_needed_files: self.ipsec_process.ensure_configs() expected = [mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process._get_config_filename( 'ipsec.secrets')]), mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process.log_dir])] fake_execute.assert_has_calls(expected) self.assertEqual(2, fake_execute.call_count) expected = [mock.call(['_stackmanager', 'start']), mock.call(['checknss'])] fake_ipsec_execute.assert_has_calls(expected) self.assertEqual(2, fake_ipsec_execute.call_count) self.assertTrue(fake_ensure_needed_files.called) self.assertTrue(exists_mock.called) self.assertFalse(remove_mock.called) exists_mock.reset_mock() remove_mock.reset_mock() with mock.patch.object( self.ipsec_process, '_execute' ) as fake_execute, mock.patch.object( self.ipsec_process, '_ipsec_execute' ) as fake_ipsec_execute, mock.patch.object( self.ipsec_process, '_ensure_needed_files' ) as fake_ensure_needed_files: fake_ipsec_execute.side_effect = [None, RuntimeError, None] self.ipsec_process.ensure_configs() expected = [mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process._get_config_filename( 'ipsec.secrets')]), mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process.log_dir])] fake_execute.assert_has_calls(expected) self.assertEqual(2, fake_execute.call_count) expected = [mock.call(['_stackmanager', 'start']), mock.call(['checknss']), mock.call(['initnss'])] self.assertEqual(3, fake_ipsec_execute.call_count) fake_ipsec_execute.assert_has_calls(expected) self.assertTrue(fake_ensure_needed_files.called) self.assertTrue(exists_mock.called) self.assertFalse(remove_mock.called) class IPsecStrongswanDeviceDriverLegacy(IPSecDeviceLegacy): def setUp(self, driver=strongswan_ipsec.StrongSwanDriver, ipsec_process=strongswan_ipsec.StrongSwanProcess): super(IPsecStrongswanDeviceDriverLegacy, self).setUp(driver, ipsec_process) self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') self.conf.set_override('state_path', '/tmp') self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ self.vpnservice] def test_status_handling_for_downed_connection(self): """Test status handling for downed connection.""" self._test_status_handling_for_downed_connection(CHARON_DOWN_STATUS) def test_status_handling_for_active_connection(self): """Test status handling for active connection.""" self._test_status_handling_for_active_connection(CHARON_ACTIVE_STATUS) def test_status_handling_for_deleted_connection(self): """Test status handling for deleted connection.""" self._test_status_handling_for_deleted_connection(NOT_RUNNING_STATUS) def test_parse_connection_status(self): """Test the status of ipsec-site-connection parsed correctly.""" self._test_parse_connection_status(NOT_RUNNING_STATUS, CHARON_ACTIVE_STATUS, CHARON_DOWN_STATUS) class IPsecStrongswanDeviceDriverDVR(IPSecDeviceDVR): def setUp(self, driver=strongswan_ipsec.StrongSwanDriver, ipsec_process=strongswan_ipsec.StrongSwanProcess): super(IPsecStrongswanDeviceDriverDVR, self).setUp(driver, ipsec_process) class IPsecFedoraStrongswanDeviceDriverLegacy( IPsecStrongswanDeviceDriverLegacy): def setUp(self, driver=fedora_strongswan_ipsec.FedoraStrongSwanDriver, ipsec_process=fedora_strongswan_ipsec.FedoraStrongSwanProcess): super(IPsecFedoraStrongswanDeviceDriverLegacy, self).setUp(driver, ipsec_process) class IPsecFedoraStrongswanDeviceDriverDVR(IPSecDeviceDVR): def setUp(self, driver=fedora_strongswan_ipsec.FedoraStrongSwanDriver, ipsec_process=fedora_strongswan_ipsec.FedoraStrongSwanProcess): super(IPsecFedoraStrongswanDeviceDriverDVR, self).setUp(driver, ipsec_process) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_vyatta_ipsec.py0000666000175000017500000001542013370230615033513 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, 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. # import sys import mock from neutron.agent.l3 import legacy_router from oslo_utils import uuidutils from neutron_vpnaas.tests import base with mock.patch.dict(sys.modules, { 'networking_brocade': mock.Mock(), 'networking_brocade.vyatta': mock.Mock(), 'networking_brocade.vyatta.common': mock.Mock(), 'networking_brocade.vyatta.vrouter': mock.Mock(), 'networking_brocade.vyatta.vpn': mock.Mock(), }): from networking_brocade.vyatta.common import vrouter_config from networking_brocade.vyatta.vpn import config as vyatta_vpn_config from neutron_vpnaas.services.vpn.device_drivers import vyatta_ipsec _uuid = uuidutils.generate_uuid FAKE_HOST = 'fake_host' class TestNeutronServerAPI(base.BaseTestCase): def setUp(self): super(TestNeutronServerAPI, self).setUp() get_client_mock = mock.patch( 'neutron.common.rpc.get_client').start() self.client = get_client_mock.return_value self.api = vyatta_ipsec.NeutronServerAPI('fake-topic') def test_get_vpn_services_on_host(self): fake_context = mock.Mock() svc_connections = [ self._make_svc_connection(), self._make_svc_connection() ] vpn_services_on_host = [{ vyatta_ipsec._KEY_CONNECTIONS: svc_connections }] cctxt = self.client.prepare.return_value cctxt.call.return_value = vpn_services_on_host vpn_services = self.api.get_vpn_services_on_host( fake_context, FAKE_HOST) cctxt.call.assert_called_with( fake_context, 'get_vpn_services_on_host', host=FAKE_HOST) validate_func = vyatta_vpn_config.validate_svc_connection for connection in svc_connections: validate_func.assert_any_call(connection) self.assertEqual(len(vpn_services_on_host), len(vpn_services)) def test_update_status(self): context = mock.Mock() fake_status = 'fake-status' cctxt = self.client.prepare.return_value self.api.update_status(context, 'fake-status') cctxt.cast.assert_called_once_with( context, 'update_status', status=fake_status) @staticmethod def _make_svc_connection(): return { vyatta_ipsec._KEY_IKEPOLICY: { 'encryption_algorithm': 'aes-256', 'lifetime_units': 'seconds', }, vyatta_ipsec._KEY_ESPPOLICY: { 'encryption_algorithm': 'aes-256', 'lifetime_units': 'seconds', 'transform_protocol': 'esp', 'pfs': 'dh-group2', 'encapsulation_mode': 'tunnel' }, 'dpd_action': 'hold', } class TestVyattaDeviceDriver(base.BaseTestCase): def setUp(self): super(TestVyattaDeviceDriver, self).setUp() mock.patch('oslo_service.loopingcall.DynamicLoopingCall').start() self.server_api = mock.patch( 'neutron_vpnaas.services.vpn.device_drivers' '.vyatta_ipsec.NeutronServerAPI').start() self.agent = mock.Mock() self.driver = vyatta_ipsec.VyattaIPSecDriver(self.agent, FAKE_HOST) def test_create_router(self): router_id = _uuid() router = mock.Mock(legacy_router.LegacyRouter) router.router_id = router_id vrouter_svc_list = [self._make_vrouter_svc()] parse_vrouter_config = mock.Mock() parse_vrouter_config.return_value = vrouter_svc_list with mock.patch.object(vrouter_config, 'parse_config'), \ mock.patch.object(vyatta_vpn_config, 'parse_vrouter_config', parse_vrouter_config), \ mock.patch.object(self.driver, 'get_router_resources', mock.MagicMock()): self.driver.create_router(router) svc_cache = self.driver._svc_cache self.assertEqual(1, len(svc_cache)) self.assertEqual(router_id, svc_cache[0]['router_id']) ipsec_connections = svc_cache[0]['ipsec_site_connections'] self.assertEqual( '172.24.4.234', ipsec_connections[0]['peer_address']) def test_destroy_router(self): router_id = _uuid() get_router_resources = mock.Mock() vrouter_svc = self._make_vrouter_svc() vrouter_svc['router_id'] = router_id svc_cache = [vrouter_svc] svc_delete = mock.Mock() with mock.patch.object(self.driver, 'get_router_resources', get_router_resources), \ mock.patch.object(self.driver, '_svc_delete', svc_delete), \ mock.patch.object(self.driver, '_svc_cache', svc_cache): self.driver.destroy_router(router_id) self.assertNotIn(vrouter_svc, svc_cache) svc_delete.assert_called_with(vrouter_svc, mock.ANY) def test_sync(self): router_id = _uuid() self.agent.router_info = { router_id: mock.Mock() } to_del = [self._make_svc()] to_change = [ (self._make_svc(), self._make_svc()), ] to_add = [self._make_svc()] svc_diff = mock.Mock() svc_diff.return_value = ( to_del, to_change, to_add, ) svc_delete = mock.Mock() svc_add = mock.Mock() with mock.patch.object(self.driver, '_svc_diff', svc_diff), \ mock.patch.object(self.driver, '_svc_delete', svc_delete), \ mock.patch.object(self.driver, '_svc_add', svc_add): self.driver.sync(mock.Mock(), None) for svc in to_add: svc_add.assert_any_call(svc, mock.ANY) for svc in to_del: svc_delete.assert_any_call(svc, mock.ANY) for old, new in to_change: svc_delete.assert_any_call(old, mock.ANY) svc_add.assert_any_call(new, mock.ANY) @staticmethod def _make_vrouter_svc(): return { 'id': _uuid(), vyatta_ipsec._KEY_CONNECTIONS: [{ 'peer_address': '172.24.4.234', }] } @staticmethod def _make_svc(): return { 'router_id': _uuid() } ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_cisco_csr_rest_client.pyneutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_cisco_csr_rest_clie0000666000175000017500000022051313370230615034372 0ustar zuulzuul00000000000000# 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. # import random import re import requests from requests import exceptions as r_exc from requests_mock.contrib import fixture as mock_fixture from neutron_vpnaas.services.vpn.device_drivers import ( cisco_csr_rest_client as csr_client) from neutron_vpnaas.tests import base dummy_policy_id = 'dummy-ipsec-policy-id-name' TEST_VRF = 'nrouter-123456' BASE_URL = 'https://%s:55443/api/v1/' LOCAL_URL = 'https://localhost:55443/api/v1/' URI_HOSTNAME = 'global/host-name' URI_USERS = 'global/local-users' URI_AUTH = 'auth/token-services' URI_INTERFACE_GE1 = 'interfaces/GigabitEthernet1' URI_PSK = 'vrf/' + TEST_VRF + '/vpn-svc/ike/keyrings' URI_PSK_ID = URI_PSK + '/%s' URI_IKE_POLICY = 'vpn-svc/ike/policies' URI_IKE_POLICY_ID = URI_IKE_POLICY + '/%s' URI_IPSEC_POLICY = 'vpn-svc/ipsec/policies' URI_IPSEC_POLICY_ID = URI_IPSEC_POLICY + '/%s' URI_IPSEC_CONN = 'vrf/' + TEST_VRF + '/vpn-svc/site-to-site' URI_IPSEC_CONN_ID = URI_IPSEC_CONN + '/%s' URI_KEEPALIVE = 'vpn-svc/ike/keepalive' URI_ROUTES = 'vrf/' + TEST_VRF + '/routing-svc/static-routes' URI_ROUTES_ID = URI_ROUTES + '/%s' URI_SESSIONS = 'vrf/' + TEST_VRF + '/vpn-svc/site-to-site/active/sessions' # Note: Helper functions to test reuse of IDs. def generate_pre_shared_key_id(): return random.randint(100, 200) def generate_ike_policy_id(): return random.randint(200, 300) def generate_ipsec_policy_id(): return random.randint(300, 400) class CiscoCsrBaseTestCase(base.BaseTestCase): """Helper methods to register mock intercepts - used by child classes.""" def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): super(CiscoCsrBaseTestCase, self).setUp() self.base_url = BASE_URL % host self.requests = self.useFixture(mock_fixture.Fixture()) info = {'rest_mgmt_ip': host, 'tunnel_ip': tunnel_ip, 'vrf': 'nrouter-123456', 'username': 'stack', 'password': 'cisco', 'timeout': timeout} self.csr = csr_client.CsrRestClient(info) def _register_local_get(self, uri, json=None, result_code=requests.codes.OK): self.requests.register_uri( 'GET', LOCAL_URL + uri, status_code=result_code, json=json) def _register_local_post(self, uri, resource_id, result_code=requests.codes.CREATED): self.requests.register_uri( 'POST', LOCAL_URL + uri, status_code=result_code, headers={'location': LOCAL_URL + uri + '/' + str(resource_id)}) def _register_local_delete(self, uri, resource_id, json=None, result_code=requests.codes.NO_CONTENT): self.requests.register_uri( 'DELETE', LOCAL_URL + uri + '/' + str(resource_id), status_code=result_code, json=json) def _register_local_delete_by_id(self, resource_id, result_code=requests.codes.NO_CONTENT): local_resource_re = re.compile(LOCAL_URL + '.+%s$' % resource_id) self.requests.register_uri( 'DELETE', local_resource_re, status_code=result_code) def _register_local_put(self, uri, resource_id, result_code=requests.codes.NO_CONTENT): self.requests.register_uri('PUT', LOCAL_URL + uri + '/' + resource_id, status_code=result_code) def _register_local_get_not_found(self, uri, resource_id, result_code=requests.codes.NOT_FOUND): self.requests.register_uri( 'GET', LOCAL_URL + uri + '/' + str(resource_id), status_code=result_code) def _helper_register_auth_request(self): self.requests.register_uri('POST', LOCAL_URL + URI_AUTH, status_code=requests.codes.OK, json={'token-id': 'dummy-token'}) def _helper_register_psk_post(self, psk_id): self._register_local_post(URI_PSK, psk_id) def _helper_register_ike_policy_post(self, policy_id): self._register_local_post(URI_IKE_POLICY, policy_id) def _helper_register_ipsec_policy_post(self, policy_id): self._register_local_post(URI_IPSEC_POLICY, policy_id) def _helper_register_tunnel_post(self, tunnel): self._register_local_post(URI_IPSEC_CONN, tunnel) class TestCsrLoginRestApi(CiscoCsrBaseTestCase): """Test logging into CSR to obtain token-id.""" def test_get_token(self): """Obtain the token and its expiration time.""" self._helper_register_auth_request() self.assertTrue(self.csr.authenticate()) self.assertEqual(requests.codes.OK, self.csr.status) self.assertIsNotNone(self.csr.token) def test_unauthorized_token_request(self): """Negative test of invalid user/password.""" self.requests.register_uri('POST', LOCAL_URL + URI_AUTH, status_code=requests.codes.UNAUTHORIZED) self.csr.auth = ('stack', 'bogus') self.assertIsNone(self.csr.authenticate()) self.assertEqual(requests.codes.UNAUTHORIZED, self.csr.status) def _simulate_wrong_host(self, request): if 'wrong-host' in request.url: raise r_exc.ConnectionError() def test_non_existent_host(self): """Negative test of request to non-existent host.""" self.requests.add_matcher(self._simulate_wrong_host) self.csr.host = 'wrong-host' self.csr.token = 'Set by some previously successful access' self.assertIsNone(self.csr.authenticate()) self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) self.assertIsNone(self.csr.token) def _simulate_token_timeout(self, request): raise r_exc.Timeout() def test_timeout_on_token_access(self): """Negative test of a timeout on a request.""" self.requests.add_matcher(self._simulate_token_timeout) self.assertIsNone(self.csr.authenticate()) self.assertEqual(requests.codes.REQUEST_TIMEOUT, self.csr.status) self.assertIsNone(self.csr.token) class TestCsrGetRestApi(CiscoCsrBaseTestCase): """Test CSR GET REST API.""" def test_valid_rest_gets(self): """Simple GET requests. First request will do a post to get token (login). Assumes that there are two interfaces on the CSR. """ self._helper_register_auth_request() self._register_local_get(URI_HOSTNAME, json={u'kind': u'object#host-name', u'host-name': u'Router'}) self._register_local_get(URI_USERS, json={u'kind': u'collection#local-user', u'users': ['peter', 'paul', 'mary']}) actual = self.csr.get_request(URI_HOSTNAME) self.assertEqual(requests.codes.OK, self.csr.status) self.assertIn('host-name', actual) self.assertIsNotNone(actual['host-name']) actual = self.csr.get_request(URI_USERS) self.assertEqual(requests.codes.OK, self.csr.status) self.assertIn('users', actual) class TestCsrPostRestApi(CiscoCsrBaseTestCase): """Test CSR POST REST API.""" def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Setup for each test in this suite. Each test case will have a normal authentication mock response registered here, although they may replace it, as needed. """ super(TestCsrPostRestApi, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() def test_post_requests(self): """Simple POST requests (repeatable). First request will do a post to get token (login). Assumes that there are two interfaces (Ge1 and Ge2) on the CSR. """ interface_re = re.compile('https://localhost:55443/.*/interfaces/' 'GigabitEthernet\d/statistics') self.requests.register_uri('POST', interface_re, status_code=requests.codes.NO_CONTENT) actual = self.csr.post_request( 'interfaces/GigabitEthernet1/statistics', payload={'action': 'clear'}) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) self.assertIsNone(actual) actual = self.csr.post_request( 'interfaces/GigabitEthernet2/statistics', payload={'action': 'clear'}) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) self.assertIsNone(actual) def test_post_with_location(self): """Create a user and verify that location returned.""" self.requests.register_uri( 'POST', LOCAL_URL + URI_USERS, status_code=requests.codes.CREATED, headers={'location': LOCAL_URL + URI_USERS + '/test-user'}) location = self.csr.post_request( URI_USERS, payload={'username': 'test-user', 'password': 'pass12345', 'privilege': 15}) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_USERS + '/test-user', location) def test_post_missing_required_attribute(self): """Negative test of POST with missing mandatory info.""" self.requests.register_uri('POST', LOCAL_URL + URI_USERS, status_code=requests.codes.BAD_REQUEST) self.csr.post_request(URI_USERS, payload={'password': 'pass12345', 'privilege': 15}) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) def test_post_invalid_attribute(self): """Negative test of POST with invalid info.""" self.requests.register_uri('POST', LOCAL_URL + URI_USERS, status_code=requests.codes.BAD_REQUEST) self.csr.post_request(URI_USERS, payload={'username': 'test-user', 'password': 'pass12345', 'privilege': 20}) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) def test_post_already_exists(self): """Negative test of a duplicate POST. Uses the lower level _do_request() API to just perform the POST and obtain the response, without any error processing. """ self.requests.register_uri( 'POST', LOCAL_URL + URI_USERS, status_code=requests.codes.CREATED, headers={'location': LOCAL_URL + URI_USERS + '/test-user'}) location = self.csr._do_request( 'POST', URI_USERS, payload={'username': 'test-user', 'password': 'pass12345', 'privilege': 15}, more_headers=csr_client.HEADER_CONTENT_TYPE_JSON) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_USERS + '/test-user', location) self.csr.post_request(URI_USERS, payload={'username': 'test-user', 'password': 'pass12345', 'privilege': 20}) self.requests.register_uri( 'POST', LOCAL_URL + URI_USERS, status_code=requests.codes.NOT_FOUND, json={u'error-code': -1, u'error-message': u'user test-user already exists'}) self.csr._do_request( 'POST', URI_USERS, payload={'username': 'test-user', 'password': 'pass12345', 'privilege': 15}, more_headers=csr_client.HEADER_CONTENT_TYPE_JSON) # Note: For local-user, a 404 error is returned. For # site-to-site connection a 400 is returned. self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) def test_post_changing_value(self): """Negative test of a POST trying to change a value.""" self.requests.register_uri( 'POST', LOCAL_URL + URI_USERS, status_code=requests.codes.CREATED, headers={'location': LOCAL_URL + URI_USERS + '/test-user'}) location = self.csr.post_request( URI_USERS, payload={'username': 'test-user', 'password': 'pass12345', 'privilege': 15}) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_USERS + '/test-user', location) self.requests.register_uri( 'POST', LOCAL_URL + URI_USERS, status_code=requests.codes.NOT_FOUND, json={u'error-code': -1, u'error-message': u'user test-user already exists'}) actual = self.csr.post_request(URI_USERS, payload={'username': 'test-user', 'password': 'changed', 'privilege': 15}) self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) expected = {u'error-code': -1, u'error-message': u'user test-user already exists'} self.assertDictSupersetOf(expected, actual) class TestCsrPutRestApi(CiscoCsrBaseTestCase): """Test CSR PUT REST API.""" def _save_resources(self): self._register_local_get(URI_HOSTNAME, json={u'kind': u'object#host-name', u'host-name': u'Router'}) interface_info = {u'kind': u'object#interface', u'description': u'Changed description', u'if-name': 'interfaces/GigabitEthernet1', u'proxy-arp': True, u'subnet-mask': u'255.255.255.0', u'icmp-unreachable': True, u'nat-direction': u'', u'icmp-redirects': True, u'ip-address': u'192.168.200.1', u'verify-unicast-source': False, u'type': u'ethernet'} self._register_local_get(URI_INTERFACE_GE1, json=interface_info) details = self.csr.get_request(URI_HOSTNAME) if self.csr.status != requests.codes.OK: self.fail("Unable to save original host name") self.original_host = details['host-name'] details = self.csr.get_request(URI_INTERFACE_GE1) if self.csr.status != requests.codes.OK: self.fail("Unable to save interface Ge1 description") self.original_if = details self.csr.token = None def _restore_resources(self, user, password): """Restore the host name and interface description. Must restore the user and password, so that authentication token can be obtained (as some tests corrupt auth info). Will also clear token, so that it gets a fresh token. """ self._register_local_put('global', 'host-name') self._register_local_put('interfaces', 'GigabitEthernet1') self.csr.auth = (user, password) self.csr.token = None payload = {'host-name': self.original_host} self.csr.put_request(URI_HOSTNAME, payload=payload) if self.csr.status != requests.codes.NO_CONTENT: self.fail("Unable to restore host name after test") payload = {'description': self.original_if['description'], 'if-name': self.original_if['if-name'], 'ip-address': self.original_if['ip-address'], 'subnet-mask': self.original_if['subnet-mask'], 'type': self.original_if['type']} self.csr.put_request(URI_INTERFACE_GE1, payload=payload) if self.csr.status != requests.codes.NO_CONTENT: self.fail("Unable to restore I/F Ge1 description after test") def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Setup for each test in this suite. Each test case will have a normal authentication mock response registered here, although they may replace it, as needed. In addition, resources are saved, before each test is run, and restored, after each test completes. """ super(TestCsrPutRestApi, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() self._save_resources() self.addCleanup(self._restore_resources, 'stack', 'cisco') def test_put_requests(self): """Simple PUT requests (repeatable). First request will do a post to get token (login). Assumes that there are two interfaces on the CSR (Ge1 and Ge2). """ self._register_local_put('interfaces', 'GigabitEthernet1') self._register_local_put('global', 'host-name') actual = self.csr.put_request(URI_HOSTNAME, payload={'host-name': 'TestHost'}) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) self.assertIsNone(actual) actual = self.csr.put_request(URI_HOSTNAME, payload={'host-name': 'TestHost2'}) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) self.assertIsNone(actual) def test_change_interface_description(self): """Test that interface description can be changed. This was a problem with an earlier version of the CSR image and is here to prevent regression. """ self._register_local_put('interfaces', 'GigabitEthernet1') payload = {'description': u'Changed description', 'if-name': self.original_if['if-name'], 'ip-address': self.original_if['ip-address'], 'subnet-mask': self.original_if['subnet-mask'], 'type': self.original_if['type']} actual = self.csr.put_request(URI_INTERFACE_GE1, payload=payload) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) self.assertIsNone(actual) actual = self.csr.get_request(URI_INTERFACE_GE1) self.assertEqual(requests.codes.OK, self.csr.status) self.assertIn('description', actual) self.assertEqual(u'Changed description', actual['description']) def ignore_test_change_to_empty_interface_description(self): """Test that interface description can be changed to empty string. This is here to prevent regression, where the CSR was rejecting an attempt to set the description to an empty string. """ self._register_local_put('interfaces', 'GigabitEthernet1') payload = {'description': '', 'if-name': self.original_if['if-name'], 'ip-address': self.original_if['ip-address'], 'subnet-mask': self.original_if['subnet-mask'], 'type': self.original_if['type']} actual = self.csr.put_request(URI_INTERFACE_GE1, payload=payload) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) self.assertIsNone(actual) actual = self.csr.get_request(URI_INTERFACE_GE1) self.assertEqual(requests.codes.OK, self.csr.status) self.assertIn('description', actual) self.assertEqual('', actual['description']) class TestCsrDeleteRestApi(CiscoCsrBaseTestCase): """Test CSR DELETE REST API.""" def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Setup for each test in this suite. Each test case will have a normal authentication mock response registered here, although they may replace it, as needed. """ super(TestCsrDeleteRestApi, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() def _make_dummy_user(self): """Create a user that will be later deleted.""" self.requests.register_uri( 'POST', LOCAL_URL + URI_USERS, status_code=requests.codes.CREATED, headers={'location': LOCAL_URL + URI_USERS + '/dummy'}) self.csr.post_request(URI_USERS, payload={'username': 'dummy', 'password': 'dummy', 'privilege': 15}) self.assertEqual(requests.codes.CREATED, self.csr.status) def test_delete_requests(self): """Simple DELETE requests (creating entry first).""" self._register_local_delete(URI_USERS, 'dummy') self._make_dummy_user() self.csr.token = None # Force login self.csr.delete_request(URI_USERS + '/dummy') self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) # Delete again, but without logging in this time self._make_dummy_user() self.csr.delete_request(URI_USERS + '/dummy') self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) def test_delete_non_existent_entry(self): """Negative test of trying to delete a non-existent user.""" expected = {u'error-code': -1, u'error-message': u'user unknown not found'} self._register_local_delete(URI_USERS, 'unknown', result_code=requests.codes.NOT_FOUND, json=expected) actual = self.csr.delete_request(URI_USERS + '/unknown') self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) self.assertDictSupersetOf(expected, actual) def test_delete_not_allowed(self): """Negative test of trying to delete the host-name.""" self._register_local_delete( 'global', 'host-name', result_code=requests.codes.METHOD_NOT_ALLOWED) self.csr.delete_request(URI_HOSTNAME) self.assertEqual(requests.codes.METHOD_NOT_ALLOWED, self.csr.status) class TestCsrRestApiFailures(CiscoCsrBaseTestCase): """Test failure cases common for all REST APIs. Uses the lower level _do_request() to just perform the operation and get the result, without any error handling. """ def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=0.1): """Setup for each test in this suite. Each test case will have a normal authentication mock response registered here, although they may replace it, as needed. """ super(TestCsrRestApiFailures, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() def _simulate_timeout(self, request): if URI_HOSTNAME in request.path_uri: raise r_exc.Timeout() def test_request_for_non_existent_resource(self): """Negative test of non-existent resource on REST request.""" self.requests.register_uri('POST', LOCAL_URL + 'no/such/request', status_code=requests.codes.NOT_FOUND) self.csr.post_request('no/such/request') self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) # The result is HTTP 404 message, so no error content to check def _simulate_get_timeout(self, request): """Will raise exception for any host request to this resource.""" if URI_HOSTNAME in request.path_url: raise r_exc.Timeout() def test_timeout_during_request(self): """Negative test of timeout during REST request.""" self.requests.add_matcher(self._simulate_get_timeout) self.csr._do_request('GET', URI_HOSTNAME) self.assertEqual(requests.codes.REQUEST_TIMEOUT, self.csr.status) def _simulate_auth_failure(self, request): """First time auth POST is done, re-report unauthorized.""" if URI_AUTH in request.path_url and not self.called_once: self.called_once = True resp = requests.Response() resp.status_code = requests.codes.UNAUTHORIZED return resp def test_token_expired_on_request(self): """Token expired before trying a REST request. First, the token is set to a bogus value, to force it to try to authenticate on the GET request. Second, a mock that runs once, will simulate an auth failure. Third, the normal auth mock will simulate success. """ self._register_local_get(URI_HOSTNAME, json={u'kind': u'object#host-name', u'host-name': u'Router'}) self.called_once = False self.requests.add_matcher(self._simulate_auth_failure) self.csr.token = '123' # These are 44 characters, so won't match actual = self.csr._do_request('GET', URI_HOSTNAME) self.assertEqual(requests.codes.OK, self.csr.status) self.assertIn('host-name', actual) self.assertIsNotNone(actual['host-name']) def test_failed_to_obtain_token_for_request(self): """Negative test of unauthorized user for REST request.""" self.csr.auth = ('stack', 'bogus') self._register_local_get(URI_HOSTNAME, result_code=requests.codes.UNAUTHORIZED) self.csr._do_request('GET', URI_HOSTNAME) self.assertEqual(requests.codes.UNAUTHORIZED, self.csr.status) class TestCsrRestIkePolicyCreate(CiscoCsrBaseTestCase): """Test IKE policy create REST requests.""" def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Setup for each test in this suite. Each test case will have a normal authentication and post mock response registered, although the test may replace them, if needed. """ super(TestCsrRestIkePolicyCreate, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() self._helper_register_ike_policy_post(2) def _helper_register_ike_policy_get(self): content = {u'kind': u'object#ike-policy', u'priority-id': u'2', u'version': u'v1', u'local-auth-method': u'pre-share', u'encryption': u'aes256', u'hash': u'sha', u'dhGroup': 5, u'lifetime': 3600} self._register_local_get(URI_IKE_POLICY_ID % '2', json=content) def test_create_delete_ike_policy(self): """Create and then delete IKE policy.""" self._helper_register_ike_policy_get() policy_info = {u'priority-id': u'2', u'encryption': u'aes256', u'hash': u'sha', u'dhGroup': 5, u'lifetime': 3600} location = self.csr.create_ike_policy(policy_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IKE_POLICY_ID % '2', location) # Check the hard-coded items that get set as well... actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) expected_policy = {u'kind': u'object#ike-policy', u'version': u'v1', u'local-auth-method': u'pre-share'} expected_policy.update(policy_info) self.assertEqual(expected_policy, actual) # Now delete and verify the IKE policy is gone self._register_local_delete(URI_IKE_POLICY, 2) self._register_local_get_not_found(URI_IKE_POLICY, 2) self.csr.delete_ike_policy(2) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) def test_create_ike_policy_with_defaults(self): """Create IKE policy using defaults for all optional values.""" policy = {u'kind': u'object#ike-policy', u'priority-id': u'2', u'version': u'v1', u'local-auth-method': u'pre-share', u'encryption': u'des', u'hash': u'sha', u'dhGroup': 1, u'lifetime': 86400} self._register_local_get(URI_IKE_POLICY_ID % '2', json=policy) policy_info = {u'priority-id': u'2'} location = self.csr.create_ike_policy(policy_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IKE_POLICY_ID % '2', location) # Check the hard-coded items that get set as well... actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) expected_policy = {u'kind': u'object#ike-policy', u'version': u'v1', u'encryption': u'des', u'hash': u'sha', u'dhGroup': 1, u'lifetime': 86400, # Lower level sets this, but it is the default u'local-auth-method': u'pre-share'} expected_policy.update(policy_info) self.assertEqual(expected_policy, actual) def test_create_duplicate_ike_policy(self): """Negative test of trying to create a duplicate IKE policy.""" self._helper_register_ike_policy_get() policy_info = {u'priority-id': u'2', u'encryption': u'aes', u'hash': u'sha', u'dhGroup': 5, u'lifetime': 3600} location = self.csr.create_ike_policy(policy_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IKE_POLICY_ID % '2', location) self.requests.register_uri( 'POST', LOCAL_URL + URI_IKE_POLICY, status_code=requests.codes.BAD_REQUEST, json={u'error-code': -1, u'error-message': u'policy 2 exist, not allow to ' u'update policy using POST method'}) location = self.csr.create_ike_policy(policy_info) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) expected = {u'error-code': -1, u'error-message': u'policy 2 exist, not allow to ' u'update policy using POST method'} self.assertDictSupersetOf(expected, location) class TestCsrRestIPSecPolicyCreate(CiscoCsrBaseTestCase): """Test IPSec policy create REST requests.""" def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Set up for each test in this suite. Each test case will have a normal authentication and post mock response registered, although the test may replace them, if needed. """ super(TestCsrRestIPSecPolicyCreate, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() self._helper_register_ipsec_policy_post(123) def _helper_register_ipsec_policy_get(self, override=None): content = {u'kind': u'object#ipsec-policy', u'mode': u'tunnel', u'policy-id': u'123', u'protection-suite': { u'esp-encryption': u'esp-256-aes', u'esp-authentication': u'esp-sha-hmac', u'ah': u'ah-sha-hmac', }, u'anti-replay-window-size': u'Disable', u'lifetime-sec': 120, u'pfs': u'group5', u'lifetime-kb': 4608000, u'idle-time': None} if override: content.update(override) self._register_local_get(URI_IPSEC_POLICY + '/123', json=content) def test_create_delete_ipsec_policy(self): """Create and then delete IPSec policy.""" policy_info = { u'policy-id': u'123', u'protection-suite': { u'esp-encryption': u'esp-256-aes', u'esp-authentication': u'esp-sha-hmac', u'ah': u'ah-sha-hmac', }, u'lifetime-sec': 120, u'pfs': u'group5', u'anti-replay-window-size': u'disable' } location = self.csr.create_ipsec_policy(policy_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_POLICY + '/123', location) # Check the hard-coded items that get set as well... self._helper_register_ipsec_policy_get() actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) expected_policy = {u'kind': u'object#ipsec-policy', u'mode': u'tunnel', u'lifetime-kb': 4608000, u'idle-time': None} expected_policy.update(policy_info) # CSR will respond with capitalized value expected_policy[u'anti-replay-window-size'] = u'Disable' self.assertEqual(expected_policy, actual) # Now delete and verify the IPSec policy is gone self._register_local_delete(URI_IPSEC_POLICY, 123) self._register_local_get_not_found(URI_IPSEC_POLICY, 123) self.csr.delete_ipsec_policy('123') self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) def test_create_ipsec_policy_with_defaults(self): """Create IPSec policy with default for all optional values.""" policy_info = {u'policy-id': u'123'} location = self.csr.create_ipsec_policy(policy_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_POLICY + '/123', location) # Check the hard-coded items that get set as well... expected_policy = {u'kind': u'object#ipsec-policy', u'mode': u'tunnel', u'policy-id': u'123', u'protection-suite': {}, u'lifetime-sec': 3600, u'pfs': u'Disable', u'anti-replay-window-size': u'None', u'lifetime-kb': 4608000, u'idle-time': None} self._register_local_get(URI_IPSEC_POLICY + '/123', json=expected_policy) actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_policy, actual) def test_create_ipsec_policy_with_uuid(self): """Create IPSec policy using UUID for id.""" # Override normal POST response w/one that has a different policy ID self._helper_register_ipsec_policy_post(dummy_policy_id) policy_info = { u'policy-id': u'%s' % dummy_policy_id, u'protection-suite': { u'esp-encryption': u'esp-256-aes', u'esp-authentication': u'esp-sha-hmac', u'ah': u'ah-sha-hmac', }, u'lifetime-sec': 120, u'pfs': u'group5', u'anti-replay-window-size': u'disable' } location = self.csr.create_ipsec_policy(policy_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_POLICY_ID % dummy_policy_id, location) # Check the hard-coded items that get set as well... expected_policy = {u'kind': u'object#ipsec-policy', u'mode': u'tunnel', u'lifetime-kb': 4608000, u'idle-time': None} expected_policy.update(policy_info) # CSR will respond with capitalized value expected_policy[u'anti-replay-window-size'] = u'Disable' self._register_local_get(URI_IPSEC_POLICY_ID % dummy_policy_id, json=expected_policy) actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_policy, actual) def test_create_ipsec_policy_without_ah(self): """Create IPSec policy.""" policy_info = { u'policy-id': u'123', u'protection-suite': { u'esp-encryption': u'esp-aes', u'esp-authentication': u'esp-sha-hmac', }, u'lifetime-sec': 120, u'pfs': u'group5', u'anti-replay-window-size': u'128' } location = self.csr.create_ipsec_policy(policy_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_POLICY_ID % '123', location) # Check the hard-coded items that get set as well... self._helper_register_ipsec_policy_get( override={u'anti-replay-window-size': u'128', u'protection-suite': { u'esp-encryption': u'esp-aes', u'esp-authentication': u'esp-sha-hmac'}}) actual = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) expected_policy = {u'kind': u'object#ipsec-policy', u'mode': u'tunnel', u'lifetime-kb': 4608000, u'idle-time': None} expected_policy.update(policy_info) self.assertEqual(expected_policy, actual) def test_invalid_ipsec_policy_lifetime(self): """Failure test of IPSec policy with unsupported lifetime.""" # Override normal POST response with one that indicates bad request self.requests.register_uri('POST', LOCAL_URL + URI_IPSEC_POLICY, status_code=requests.codes.BAD_REQUEST) policy_info = { u'policy-id': u'123', u'protection-suite': { u'esp-encryption': u'esp-aes', u'esp-authentication': u'esp-sha-hmac', u'ah': u'ah-sha-hmac', }, u'lifetime-sec': 119, u'pfs': u'group5', u'anti-replay-window-size': u'128' } self.csr.create_ipsec_policy(policy_info) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) def test_create_ipsec_policy_with_invalid_name(self): """Failure test of creating IPSec policy with name too long.""" # Override normal POST response with one that indicates bad request self.requests.register_uri('POST', LOCAL_URL + URI_IPSEC_POLICY, status_code=requests.codes.BAD_REQUEST) policy_info = {u'policy-id': u'policy-name-is-too-long-32-chars'} self.csr.create_ipsec_policy(policy_info) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) class TestCsrRestPreSharedKeyCreate(CiscoCsrBaseTestCase): """Test Pre-shared key (PSK) create REST requests.""" def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Set up for each test in this suite. Each test case will have a normal authentication and post mock response registered, although the test may replace them, if needed. """ super(TestCsrRestPreSharedKeyCreate, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() self._helper_register_psk_post(5) def _helper_register_psk_get(self, override=None): content = {u'kind': u'object#ike-keyring', u'keyring-name': u'5', u'pre-shared-key-list': [ {u'key': u'super-secret', u'encrypted': False, u'peer-address': u'10.10.10.20 255.255.255.0'} ]} if override: content.update(override) self._register_local_get(URI_PSK_ID % '5', json=content) def test_create_delete_pre_shared_key(self): """Create and then delete a keyring entry for pre-shared key.""" psk_info = {u'keyring-name': u'5', u'pre-shared-key-list': [ {u'key': u'super-secret', u'encrypted': False, u'peer-address': u'10.10.10.20/24'} ]} location = self.csr.create_pre_shared_key(psk_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_PSK_ID % '5', location) # Check the hard-coded items that get set as well... self._helper_register_psk_get() content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) expected_policy = {u'kind': u'object#ike-keyring'} expected_policy.update(psk_info) # Note: the peer CIDR is returned as an IP and mask expected_policy[u'pre-shared-key-list'][0][u'peer-address'] = ( u'10.10.10.20 255.255.255.0') self.assertEqual(expected_policy, content) # Now delete and verify pre-shared key is gone self._register_local_delete(URI_PSK, 5) self._register_local_get_not_found(URI_PSK, 5) self.csr.delete_pre_shared_key('5') self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) def test_create_pre_shared_key_with_fqdn_peer(self): """Create pre-shared key using FQDN for peer address.""" psk_info = {u'keyring-name': u'5', u'pre-shared-key-list': [ {u'key': u'super-secret', u'encrypted': False, u'peer-address': u'cisco.com'} ]} location = self.csr.create_pre_shared_key(psk_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_PSK_ID % '5', location) # Check the hard-coded items that get set as well... self._helper_register_psk_get( override={u'pre-shared-key-list': [ {u'key': u'super-secret', u'encrypted': False, u'peer-address': u'cisco.com'} ]} ) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) expected_policy = {u'kind': u'object#ike-keyring'} expected_policy.update(psk_info) self.assertEqual(expected_policy, content) class TestCsrRestIPSecConnectionCreate(CiscoCsrBaseTestCase): """Test IPSec site-to-site connection REST requests. This requires us to have first created an IKE policy, IPSec policy, and pre-shared key, so it's more of an integration test, when used with a real CSR (as we can't mock out these pre-conditions). """ def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Setup for each test in this suite. Each test case will have a normal authentication mock response registered here, although they may replace it, as needed. """ super(TestCsrRestIPSecConnectionCreate, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() self.route_id = '10.1.0.0_24_GigabitEthernet1' def _make_psk_for_test(self): psk_id = generate_pre_shared_key_id() self._remove_resource_for_test(self.csr.delete_pre_shared_key, psk_id) self._helper_register_psk_post(psk_id) psk_info = {u'keyring-name': u'%d' % psk_id, u'pre-shared-key-list': [ {u'key': u'super-secret', u'encrypted': False, u'peer-address': u'10.10.10.20/24'} ]} self.csr.create_pre_shared_key(psk_info) if self.csr.status != requests.codes.CREATED: self.fail("Unable to create PSK for test case") self.addCleanup(self._remove_resource_for_test, self.csr.delete_pre_shared_key, psk_id) return psk_id def _make_ike_policy_for_test(self): policy_id = generate_ike_policy_id() self._remove_resource_for_test(self.csr.delete_ike_policy, policy_id) self._helper_register_ike_policy_post(policy_id) policy_info = {u'priority-id': u'%d' % policy_id, u'encryption': u'aes', u'hash': u'sha', u'dhGroup': 5, u'lifetime': 3600} self.csr.create_ike_policy(policy_info) if self.csr.status != requests.codes.CREATED: self.fail("Unable to create IKE policy for test case") self.addCleanup(self._remove_resource_for_test, self.csr.delete_ike_policy, policy_id) return policy_id def _make_ipsec_policy_for_test(self): policy_id = generate_ipsec_policy_id() self._remove_resource_for_test(self.csr.delete_ipsec_policy, policy_id) self._helper_register_ipsec_policy_post(policy_id) policy_info = { u'policy-id': u'%d' % policy_id, u'protection-suite': { u'esp-encryption': u'esp-aes', u'esp-authentication': u'esp-sha-hmac', u'ah': u'ah-sha-hmac', }, u'lifetime-sec': 120, u'pfs': u'group5', u'anti-replay-window-size': u'disable' } self.csr.create_ipsec_policy(policy_info) if self.csr.status != requests.codes.CREATED: self.fail("Unable to create IPSec policy for test case") self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_policy, policy_id) return policy_id def _remove_resource_for_test(self, delete_resource, resource_id): self._register_local_delete_by_id(resource_id) delete_resource(resource_id) def _prepare_for_site_conn_create(self, skip_psk=False, skip_ike=False, skip_ipsec=False): """Create the policies and PSK so can then create site conn.""" if not skip_psk: ike_policy_id = self._make_psk_for_test() else: ike_policy_id = generate_ike_policy_id() if not skip_ike: self._make_ike_policy_for_test() if not skip_ipsec: ipsec_policy_id = self._make_ipsec_policy_for_test() else: ipsec_policy_id = generate_ipsec_policy_id() # Note: Use same ID number for tunnel and IPSec policy, so that when # GET tunnel info, the mocks can infer the IPSec policy ID from the # tunnel number. return (ike_policy_id, ipsec_policy_id, ipsec_policy_id) def _helper_register_ipsec_conn_get(self, tunnel, override=None): # Use same number, to allow mock to generate IPSec policy ID ipsec_policy_id = tunnel[6:] content = {u'kind': u'object#vpn-site-to-site', u'vpn-interface-name': u'%s' % tunnel, u'ip-version': u'ipv4', u'vpn-type': u'site-to-site', u'ipsec-policy-id': u'%s' % ipsec_policy_id, u'ike-profile-id': None, u'mtu': 1500, u'tunnel-vrf': TEST_VRF, u'local-device': { u'ip-address': '10.3.0.1/24', u'tunnel-ip-address': '10.10.10.10' }, u'remote-device': { u'tunnel-ip-address': '10.10.10.20' }} if override: content.update(override) self._register_local_get(URI_IPSEC_CONN_ID % tunnel, json=content) def test_create_delete_ipsec_connection(self): """Create and then delete an IPSec connection.""" ike_policy_id, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create()) tunnel_name = u'Tunnel%s' % tunnel_id self._helper_register_tunnel_post(tunnel_name) self._register_local_post(URI_ROUTES, self.route_id) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'mtu': 1500, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } expected_connection = {u'kind': u'object#vpn-site-to-site', u'ike-profile-id': None, u'vpn-type': u'site-to-site', u'mtu': 1500, u'tunnel-vrf': TEST_VRF, u'ip-version': u'ipv4'} expected_connection.update(connection_info) location = self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_CONN_ID % tunnel_name, location) # Check the hard-coded items that get set as well... self._helper_register_ipsec_conn_get(tunnel_name) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_connection, content) # Now delete and verify that site-to-site connection is gone self._register_local_delete_by_id(tunnel_name) self._register_local_delete_by_id(ipsec_policy_id) self._register_local_delete_by_id(ike_policy_id) self._register_local_get_not_found(URI_IPSEC_CONN, tunnel_name) # Only delete connection. Cleanup will take care of prerequisites self.csr.delete_ipsec_connection(tunnel_name) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) def test_create_ipsec_connection_with_no_tunnel_subnet(self): """Create an IPSec connection without an IP address on tunnel.""" _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create()) tunnel_name = u'Tunnel%s' % tunnel_id self._helper_register_tunnel_post(tunnel_name) self._register_local_post(URI_ROUTES, self.route_id) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'local-device': {u'ip-address': u'GigabitEthernet3', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } expected_connection = {u'kind': u'object#vpn-site-to-site', u'ike-profile-id': None, u'vpn-type': u'site-to-site', u'mtu': 1500, u'tunnel-vrf': TEST_VRF, u'ip-version': u'ipv4'} expected_connection.update(connection_info) location = self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_CONN_ID % tunnel_name, location) # Check the hard-coded items that get set as well... self._helper_register_ipsec_conn_get(tunnel_name, override={ u'local-device': { u'ip-address': u'GigabitEthernet3', u'tunnel-ip-address': u'10.10.10.10' }}) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_connection, content) def test_create_ipsec_connection_no_pre_shared_key(self): """Test of connection create without associated pre-shared key. The CSR will create the connection, but will not be able to pass traffic without the pre-shared key. """ _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create(skip_psk=True)) tunnel_name = u'Tunnel%s' % tunnel_id self._helper_register_tunnel_post(tunnel_name) self._register_local_post(URI_ROUTES, self.route_id) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'mtu': 1500, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } expected_connection = {u'kind': u'object#vpn-site-to-site', u'ike-profile-id': None, u'tunnel-vrf': TEST_VRF, u'vpn-type': u'site-to-site', u'ip-version': u'ipv4'} expected_connection.update(connection_info) location = self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_CONN_ID % tunnel_name, location) # Check the hard-coded items that get set as well... self._helper_register_ipsec_conn_get(tunnel_name) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_connection, content) def test_create_ipsec_connection_with_default_ike_policy(self): """Test of connection create without IKE policy (uses default). Without an IKE policy, the CSR will use a built-in default IKE policy setting for the connection. """ _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create(skip_ike=True)) tunnel_name = u'Tunnel%s' % tunnel_id self._helper_register_tunnel_post(tunnel_name) self._register_local_post(URI_ROUTES, self.route_id) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'mtu': 1500, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } expected_connection = {u'kind': u'object#vpn-site-to-site', u'ike-profile-id': None, u'tunnel-vrf': TEST_VRF, u'vpn-type': u'site-to-site', u'ip-version': u'ipv4'} expected_connection.update(connection_info) location = self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_CONN_ID % tunnel_name, location) # Check the hard-coded items that get set as well... self._helper_register_ipsec_conn_get(tunnel_name) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_connection, content) def test_set_ipsec_connection_admin_state_changes(self): """Create IPSec connection in admin down state.""" _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create()) tunnel_name = u'Tunnel%s' % tunnel_id self._helper_register_tunnel_post(tunnel_name) self._register_local_post(URI_ROUTES, self.route_id) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'mtu': 1500, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } location = self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_CONN_ID % tunnel_name, location) state_url = location + "/state" state_uri = URI_IPSEC_CONN_ID % tunnel_name + '/state' # Note: When created, the tunnel will be in admin 'up' state # Note: Line protocol state will be down, unless have an active conn. expected_state = {u'kind': u'object#vpn-site-to-site-state', u'vpn-interface-name': tunnel_name, u'line-protocol-state': u'down', u'enabled': False} self._register_local_put(URI_IPSEC_CONN_ID % tunnel_name, 'state') self.csr.set_ipsec_connection_state(tunnel_name, admin_up=False) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) self._register_local_get(state_uri, json=expected_state) content = self.csr.get_request(state_url, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_state, content) self.csr.set_ipsec_connection_state(tunnel_name, admin_up=True) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) expected_state = {u'kind': u'object#vpn-site-to-site-state', u'vpn-interface-name': tunnel_name, u'line-protocol-state': u'down', u'enabled': True} self._register_local_get(state_uri, json=expected_state) content = self.csr.get_request(state_url, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_state, content) def test_create_ipsec_connection_missing_ipsec_policy(self): """Negative test of connection create without IPSec policy.""" _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create(skip_ipsec=True)) tunnel_name = u'Tunnel%s' % tunnel_id self._register_local_post(URI_IPSEC_CONN, tunnel_name, result_code=requests.codes.BAD_REQUEST) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, 'Tunnel%d' % tunnel_id) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) def _determine_conflicting_ip(self): content = {u'kind': u'object#interface', u'subnet-mask': u'255.255.255.0', u'ip-address': u'10.5.0.2'} self._register_local_get('interfaces/GigabitEthernet3', json=content) details = self.csr.get_request('interfaces/GigabitEthernet3') if self.csr.status != requests.codes.OK: self.fail("Unable to obtain interface GigabitEthernet3's IP") if_ip = details.get('ip-address') if not if_ip: self.fail("No IP address for GigabitEthernet3 interface") return '.'.join(if_ip.split('.')[:3]) + '.10' def test_create_ipsec_connection_conficting_tunnel_ip(self): """Negative test of connection create with conflicting tunnel IP. Find out the IP of a local interface (GigabitEthernet3) and create an IP that is on the same subnet. Note: this interface needs to be up. """ conflicting_ip = self._determine_conflicting_ip() _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create()) tunnel_name = u'Tunnel%s' % tunnel_id self._register_local_post(URI_IPSEC_CONN, tunnel_name, result_code=requests.codes.BAD_REQUEST) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'local-device': {u'ip-address': u'%s/24' % conflicting_ip, u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) def test_create_ipsec_connection_with_max_mtu(self): """Create an IPSec connection with max MTU value.""" _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create()) tunnel_name = u'Tunnel%s' % tunnel_id self._helper_register_tunnel_post(tunnel_name) self._register_local_post(URI_ROUTES, self.route_id) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'mtu': 9192, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } expected_connection = {u'kind': u'object#vpn-site-to-site', u'ike-profile-id': None, u'tunnel-vrf': TEST_VRF, u'vpn-type': u'site-to-site', u'ip-version': u'ipv4'} expected_connection.update(connection_info) location = self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_CONN_ID % tunnel_name, location) # Check the hard-coded items that get set as well... self._helper_register_ipsec_conn_get(tunnel_name, override={ u'mtu': 9192}) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_connection, content) def test_create_ipsec_connection_with_bad_mtu(self): """Negative test of connection create with unsupported MTU value.""" _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create()) tunnel_name = u'Tunnel%s' % tunnel_id self._register_local_post(URI_IPSEC_CONN, tunnel_name, result_code=requests.codes.BAD_REQUEST) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'mtu': 9193, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.BAD_REQUEST, self.csr.status) def test_status_when_no_tunnels_exist(self): """Get status, when there are no tunnels.""" content = {u'kind': u'collection#vpn-active-sessions', u'items': []} self._register_local_get(URI_SESSIONS, json=content) tunnels = self.csr.read_tunnel_statuses() self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual([], tunnels) def test_status_for_one_tunnel(self): """Get status of one tunnel.""" # Create the IPsec site-to-site connection first _, ipsec_policy_id, tunnel_id = ( self._prepare_for_site_conn_create()) tunnel_name = u'Tunnel%s' % tunnel_id self._helper_register_tunnel_post(tunnel_name) self._register_local_post(URI_ROUTES, self.route_id) connection_info = { u'vpn-interface-name': tunnel_name, u'ipsec-policy-id': u'%d' % ipsec_policy_id, u'local-device': {u'ip-address': u'10.3.0.1/24', u'tunnel-ip-address': u'10.10.10.10'}, u'remote-device': {u'tunnel-ip-address': u'10.10.10.20'} } location = self.csr.create_ipsec_connection(connection_info) self.addCleanup(self._remove_resource_for_test, self.csr.delete_ipsec_connection, tunnel_name) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_IPSEC_CONN_ID % tunnel_name, location) # Now, check the status content = {u'kind': u'collection#vpn-active-sessions', u'items': [{u'status': u'DOWN-NEGOTIATING', u'vpn-interface-name': tunnel_name}, ]} self._register_local_get(URI_SESSIONS, json=content) self._helper_register_ipsec_conn_get(tunnel_name) tunnels = self.csr.read_tunnel_statuses() self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual([(tunnel_name, u'DOWN-NEGOTIATING'), ], tunnels) class TestCsrRestIkeKeepaliveCreate(CiscoCsrBaseTestCase): """Test IKE keepalive REST requests. Note: On the Cisco CSR, the IKE keepalive for v1 is a global configuration that applies to all VPN tunnels to specify Dead Peer Detection information. As a result, this REST API is not used in the OpenStack device driver, and the keepalive will default to zero (disabled). """ def _save_dpd_info(self): details = self.csr.get_request(URI_KEEPALIVE) if self.csr.status == requests.codes.OK: self.dpd = details self.addCleanup(self._restore_dpd_info) elif self.csr.status != requests.codes.NOT_FOUND: self.fail("Unable to save original DPD info") def _restore_dpd_info(self): payload = {'interval': self.dpd['interval'], 'retry': self.dpd['retry']} self.csr.put_request(URI_KEEPALIVE, payload=payload) if self.csr.status != requests.codes.NO_CONTENT: self.fail("Unable to restore DPD info after test") def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Set up for each test in this suite. Each test case will have a normal authentication, get, and put mock responses registered, although the test may replace them, if needed. Dead Peer Detection settings will be saved for each test, and restored afterwards. """ super(TestCsrRestIkeKeepaliveCreate, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() self._helper_register_keepalive_get() self._register_local_put('vpn-svc/ike', 'keepalive') self._save_dpd_info() self.csr.token = None def _helper_register_keepalive_get(self, override=None): content = {u'interval': 60, u'retry': 4, u'periodic': True} if override: content.update(override) self._register_local_get(URI_KEEPALIVE, json=content) def test_configure_ike_keepalive(self): """Set IKE keep-alive (aka Dead Peer Detection) for the CSR.""" keepalive_info = {'interval': 60, 'retry': 4} self.csr.configure_ike_keepalive(keepalive_info) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) content = self.csr.get_request(URI_KEEPALIVE) self.assertEqual(requests.codes.OK, self.csr.status) expected = {'periodic': False} expected.update(keepalive_info) self.assertDictSupersetOf(expected, content) def test_disable_ike_keepalive(self): """Disable IKE keep-alive (aka Dead Peer Detection) for the CSR.""" keepalive_info = {'interval': 0, 'retry': 4} self.csr.configure_ike_keepalive(keepalive_info) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) class TestCsrRestStaticRoute(CiscoCsrBaseTestCase): """Test static route REST requests. A static route is added for the peer's private network. Would create a route for each of the peer CIDRs specified for the VPN connection. """ def setUp(self, host='localhost', tunnel_ip='10.10.10.10', timeout=None): """Set up for each test in this suite. Each test case will have a normal authentication mock response registered, although the test may replace it, if needed. """ super(TestCsrRestStaticRoute, self).setUp(host, tunnel_ip, timeout) self._helper_register_auth_request() def test_create_delete_static_route(self): """Create and then delete a static route for the tunnel.""" expected_id = '10.1.0.0_24_GigabitEthernet1' self._register_local_post(URI_ROUTES, resource_id=expected_id) cidr = u'10.1.0.0/24' interface = u'GigabitEthernet1' route_info = {u'destination-network': cidr, u'outgoing-interface': interface} location = self.csr.create_static_route(route_info) self.assertEqual(requests.codes.CREATED, self.csr.status) self.assertIn(URI_ROUTES_ID % expected_id, location) # Check the hard-coded items that get set as well... expected_route = {u'destination-network': u'10.1.0.0/24', u'kind': u'object#static-route', u'next-hop-router': None, u'outgoing-interface': u'GigabitEthernet1', u'admin-distance': 1} self._register_local_get(URI_ROUTES_ID % expected_id, json=expected_route) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.OK, self.csr.status) self.assertEqual(expected_route, content) # Now delete and verify that static route is gone self._register_local_delete(URI_ROUTES, expected_id) self._register_local_get_not_found(URI_ROUTES, expected_id) route_id = csr_client.make_route_id(cidr, interface) self.csr.delete_static_route(route_id) self.assertEqual(requests.codes.NO_CONTENT, self.csr.status) content = self.csr.get_request(location, full_url=True) self.assertEqual(requests.codes.NOT_FOUND, self.csr.status) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/services/vpn/test_vyatta_vpn_service.py0000666000175000017500000000350213370230615031734 0ustar zuulzuul00000000000000# Copyright 2015 Brocade Communications System, 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. import mock from neutron.conf.agent import common as agent_config from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.services.vpn import vyatta_vpn_service from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_ROUTER_ID = _uuid() class TestVyattaVPNService(base.BaseTestCase): def setUp(self): super(TestVyattaVPNService, self).setUp() self.conf = cfg.CONF agent_config.register_root_helper(self.conf) self.ri_kwargs = {'root_helper': self.conf.AGENT.root_helper, 'agent_conf': self.conf, 'interface_driver': mock.sentinel.interface_driver} self.agent = mock.Mock() self.vyatta_service = vyatta_vpn_service.VyattaVPNService( self.agent) self.l3_agent = self.vyatta_service.l3_agent def test_get_router_client(self): self.vyatta_service.get_router_client(FAKE_ROUTER_ID) self.l3_agent.get_router_client.assert_called_once_with(FAKE_ROUTER_ID) def test_get_router(self): self.vyatta_service.get_router(FAKE_ROUTER_ID) self.l3_agent.get_router.assert_called_once_with(FAKE_ROUTER_ID) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/db/0000775000175000017500000000000013370231105022340 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/db/__init__.py0000666000175000017500000000000013370230606024446 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/db/vpn/0000775000175000017500000000000013370231105023143 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/db/vpn/__init__.py0000666000175000017500000000000013370230606025251 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/db/vpn/test_vpn_validator.py0000666000175000017500000006031013370230606027433 0ustar zuulzuul00000000000000# Copyright 2015, 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. import socket import mock from neutron.db import l3_db from neutron_lib import context as n_ctx from neutron_lib import exceptions as nexception from neutron_lib.plugins import constants as nconstants from neutron_lib.plugins import directory from oslo_utils import uuidutils from sqlalchemy.orm import query from neutron_vpnaas.db.vpn import vpn_validator from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.common import constants as v_constants from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_ROUTER_ID = _uuid() FAKE_ROUTER = {l3_db.EXTERNAL_GW_INFO: FAKE_ROUTER_ID} FAKE_SUBNET_ID = _uuid() IPV4 = 4 IPV6 = 6 class TestVpnValidation(base.BaseTestCase): def setUp(self): super(TestVpnValidation, self).setUp() self.l3_plugin = mock.Mock() self.core_plugin = mock.Mock() directory.add_plugin(nconstants.CORE, self.core_plugin) directory.add_plugin(nconstants.L3, self.l3_plugin) self.context = n_ctx.Context('some_user', 'some_tenant') self.validator = vpn_validator.VpnReferenceValidator() self.router = mock.Mock() self.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}]} 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_resolve_peer_address_with_ipaddress(self): ipsec_sitecon = {'peer_address': '10.0.0.9'} self.validator._validate_peer_address = mock.Mock() self.validator.resolve_peer_address(ipsec_sitecon, self.router) self.assertEqual('10.0.0.9', ipsec_sitecon['peer_address']) self.validator._validate_peer_address.assert_called_once_with( IPV4, self.router) def test_resolve_peer_address_with_fqdn(self): with mock.patch.object(socket, 'getaddrinfo') as mock_getaddr_info: mock_getaddr_info.return_value = [(2, 1, 6, '', ('10.0.0.9', 0))] ipsec_sitecon = {'peer_address': 'fqdn.peer.addr'} self.validator._validate_peer_address = mock.Mock() self.validator.resolve_peer_address(ipsec_sitecon, self.router) self.assertEqual('10.0.0.9', ipsec_sitecon['peer_address']) self.validator._validate_peer_address.assert_called_once_with( IPV4, self.router) def test_resolve_peer_address_with_invalid_fqdn(self): with mock.patch.object(socket, 'getaddrinfo') as mock_getaddr_info: def getaddr_info_failer(*args, **kwargs): raise socket.gaierror() mock_getaddr_info.side_effect = getaddr_info_failer ipsec_sitecon = {'peer_address': 'fqdn.invalid'} self.assertRaises(vpnaas.VPNPeerAddressNotResolved, self.validator.resolve_peer_address, ipsec_sitecon, self.router) def helper_validate_peer_address(self, fixed_ips, ip_version, expected_exception=False): self.router.id = FAKE_ROUTER_ID self.router.gw_port = {'fixed_ips': fixed_ips} try: self.validator._validate_peer_address(ip_version, self.router) if expected_exception: self.fail("No exception raised for invalid peer address") except vpnaas.ExternalNetworkHasNoSubnet: if not expected_exception: self.fail("exception for valid peer address raised") def test_validate_peer_address(self): # validate ipv4 peer_address with ipv4 gateway fixed_ips = [{'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV4) # validate ipv6 peer_address with ipv6 gateway fixed_ips = [{'ip_address': '2001::1'}] self.helper_validate_peer_address(fixed_ips, IPV6) # validate ipv6 peer_address with both ipv4 and ipv6 gateways fixed_ips = [{'ip_address': '2001::1'}, {'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV6) # validate ipv4 peer_address with both ipv4 and ipv6 gateways fixed_ips = [{'ip_address': '2001::1'}, {'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV4) # validate ipv4 peer_address with ipv6 gateway fixed_ips = [{'ip_address': '2001::1'}] self.helper_validate_peer_address(fixed_ips, IPV4, expected_exception=True) # validate ipv6 peer_address with ipv4 gateway fixed_ips = [{'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV6, expected_exception=True) def test_defaults_for_ipsec_site_connections_on_update(self): """Check that defaults are used for any values not specified.""" ipsec_sitecon = {} prev_connection = {'peer_cidrs': [{'cidr': '10.0.0.0/24'}, {'cidr': '20.0.0.0/24'}], 'local_ep_group_id': None, 'peer_ep_group_id': None, 'dpd_action': 'clear', 'dpd_timeout': 500, 'dpd_interval': 250} self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, prev_connection) expected = { 'peer_cidrs': ['10.0.0.0/24', '20.0.0.0/24'], 'local_ep_group_id': None, 'peer_ep_group_id': None, 'dpd_action': 'clear', 'dpd_timeout': 500, 'dpd_interval': 250 } self.assertEqual(expected, ipsec_sitecon) ipsec_sitecon = {'dpd': {'timeout': 200}} local_epg_id = _uuid() peer_epg_id = _uuid() prev_connection = {'peer_cidrs': [], 'local_ep_group_id': local_epg_id, 'peer_ep_group_id': peer_epg_id, 'dpd_action': 'clear', 'dpd_timeout': 500, 'dpd_interval': 100} self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, prev_connection) expected = { 'peer_cidrs': [], 'local_ep_group_id': local_epg_id, 'peer_ep_group_id': peer_epg_id, '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 = {'dpd_action': 'hold', 'dpd_interval': 100, 'dpd_timeout': 100} self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError, self.validator._check_dpd, ipsec_sitecon) ipsec_sitecon = {'dpd_action': 'hold', 'dpd_interval': 100, 'dpd_timeout': 99} self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError, self.validator._check_dpd, ipsec_sitecon) def test_bad_mtu_for_ipsec_connection(self): """Failure test of invalid MTU values for IPSec conn create/update.""" ip_version_limits = self.validator.IP_MIN_MTU for version, limit in ip_version_limits.items(): ipsec_sitecon = {'mtu': limit - 1} self.assertRaises( vpnaas.IPsecSiteConnectionMtuError, self.validator._check_mtu, self.context, ipsec_sitecon['mtu'], version) def test_endpoints_all_cidrs_in_endpoint_group(self): """All endpoints in the endpoint group are valid CIDRs.""" endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24', '20.20.20.0/24']} try: self.validator.validate_endpoint_group(self.context, endpoint_group) except Exception: self.fail("All CIDRs in endpoint_group should be valid") def test_endpoints_all_subnets_in_endpoint_group(self): """All endpoints in the endpoint group are valid subnets.""" endpoint_group = {'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), _uuid()]} try: self.validator.validate_endpoint_group(self.context, endpoint_group) except Exception: self.fail("All subnets in endpoint_group should be valid") def test_mixed_endpoint_types_in_endpoint_group(self): """Fail when mixing types of endpoints in endpoint group.""" endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24', _uuid()]} self.assertRaises(vpnaas.InvalidEndpointInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) endpoint_group = {'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), '10.10.10.0/24']} self.assertRaises(vpnaas.InvalidEndpointInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_missing_endpoints_for_endpoint_group(self): endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': []} self.assertRaises(vpnaas.MissingEndpointForEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_fail_bad_cidr_in_endpoint_group(self): """Testing catches bad CIDR. Just check one case, as CIDR validator used has good test coverage. """ endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.10/24', '20.20.20.1']} self.assertRaises(vpnaas.InvalidEndpointInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_unknown_subnet_in_endpoint_group(self): subnet_id = _uuid() self.core_plugin.get_subnet.side_effect = nexception.SubnetNotFound( subnet_id=subnet_id) endpoint_group = {'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [subnet_id]} self.assertRaises(vpnaas.NonExistingSubnetInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_fail_subnets_not_on_same_router_for_endpoint_group(self): """Detect when local endpoints not on the same router.""" subnet1 = {'id': _uuid(), 'ip_version': 4} subnet2 = {'id': _uuid(), 'ip_version': 4} router = _uuid() multiple_subnets = [subnet1, subnet2] port_mock = mock.patch.object(self.core_plugin, "get_ports").start() port_mock.side_effect = ['dummy info', None] self.assertRaises(vpnaas.SubnetIsNotConnectedToRouter, self.validator._check_local_subnets_on_router, self.context, router, multiple_subnets) def test_ipsec_conn_local_endpoints_same_ip_version(self): """Check local endpoint subnets all have same IP version.""" endpoint_group_id = _uuid() subnet1 = {'ip_version': 4} subnet2 = {'ip_version': 4} single_subnet = [subnet1] version = self.validator._check_local_endpoint_ip_versions( endpoint_group_id, single_subnet) self.assertEqual(4, version) multiple_subnets = [subnet1, subnet2] version = self.validator._check_local_endpoint_ip_versions( endpoint_group_id, multiple_subnets) self.assertEqual(4, version) def test_fail_ipsec_conn_local_endpoints_mixed_ip_version(self): """Fail when mixed IP versions in local endpoints.""" endpoint_group_id = _uuid() subnet1 = {'ip_version': 6} subnet2 = {'ip_version': 4} mixed_subnets = [subnet1, subnet2] self.assertRaises(vpnaas.MixedIPVersionsForIPSecEndpoints, self.validator._check_local_endpoint_ip_versions, endpoint_group_id, mixed_subnets) def test_ipsec_conn_peer_endpoints_same_ip_version(self): """Check all CIDRs have the same IP version.""" endpoint_group_id = _uuid() one_cidr = ['2002:0a00::/48'] version = self.validator._check_peer_endpoint_ip_versions( endpoint_group_id, one_cidr) self.assertEqual(6, version) multiple_cidr = ['10.10.10.0/24', '20.20.20.0/24'] version = self.validator._check_peer_endpoint_ip_versions( endpoint_group_id, multiple_cidr) self.assertEqual(4, version) def test_fail_ipsec_conn_peer_endpoints_mixed_ip_version(self): """Fail when mixed IP versions in peer endpoints.""" endpoint_group_id = _uuid() mixed_cidrs = ['10.10.10.0/24', '2002:1400::/48'] self.assertRaises(vpnaas.MixedIPVersionsForIPSecEndpoints, self.validator._check_peer_endpoint_ip_versions, endpoint_group_id, mixed_cidrs) def test_fail_ipsec_conn_locals_and_peers_different_ip_version(self): """Ensure catch when local and peer IP versions are not the same.""" self.assertRaises(vpnaas.MixedIPVersionsForIPSecConnection, self.validator._validate_compatible_ip_versions, 4, 6) def test_fail_ipsec_conn_no_subnet_requiring_endpoint_groups(self): """When no subnet, connection must use endpoints. This means both endpoint groups must be present, and peer_cidrs cannot be used. """ subnet = None ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpnaas.PeerCidrsInvalid, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': None, 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpnaas.MissingRequiredEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': None} self.assertRaises(vpnaas.MissingRequiredEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': None, 'peer_ep_group_id': None} self.assertRaises(vpnaas.MissingRequiredEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) def test_fail_ipsec_conn_subnet_requiring_peer_cidrs(self): """When legacy mode, no endpoint groups. This means neither endpoint group can be specified, and the peer_cidrs must be present. """ subnet = {'id': FAKE_SUBNET_ID} ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': None, 'peer_ep_group_id': None} self.assertRaises(vpnaas.MissingPeerCidrs, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': None} self.assertRaises(vpnaas.InvalidEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': None, 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpnaas.InvalidEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpnaas.InvalidEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) def test_ipsec_conn_get_local_subnets(self): subnet1 = _uuid() subnet2 = _uuid() expected_subnets = [subnet1, subnet2] local_epg = {'id': _uuid(), 'type': v_constants.SUBNET_ENDPOINT, 'endpoints': expected_subnets} query_mock = mock.patch.object(query.Query, 'all').start() query_mock.return_value = expected_subnets subnets = self.validator._get_local_subnets(self.context, local_epg) self.assertEqual(expected_subnets, subnets) def test_ipsec_conn_get_peer_cidrs(self): expected_cidrs = ['10.10.10.10/24', '20.20.20.20/24'] peer_epg = {'id': 'should-be-cidrs', 'type': v_constants.CIDR_ENDPOINT, 'endpoints': expected_cidrs} cidrs = self.validator._get_peer_cidrs(peer_epg) self.assertEqual(expected_cidrs, cidrs) def test_ipsec_conn_check_peer_cidrs(self): peer_cidrs = ['10.10.10.0/24', '20.20.20.0/24'] try: self.validator._check_peer_cidrs(peer_cidrs) except Exception: self.fail("All peer cidrs format should be valid") def test_fail_ipsec_conn_peer_cidrs_with_invalid_format(self): peer_cidrs = ['invalid_cidr'] self.assertRaises(vpnaas.IPsecSiteConnectionPeerCidrError, self.validator._check_peer_cidrs, peer_cidrs) peer_cidrs = ['192/24'] self.assertRaises(vpnaas.IPsecSiteConnectionPeerCidrError, self.validator._check_peer_cidrs, peer_cidrs) def test_fail_ipsec_conn_endpoint_group_types(self): local_epg = {'id': 'should-be-subnets', 'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.10/24', '20.20.20.20/24']} self.assertRaises(vpnaas.WrongEndpointGroupType, self.validator._get_local_subnets, self.context, local_epg) peer_epg = {'id': 'should-be-cidrs', 'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), _uuid()]} self.assertRaises(vpnaas.WrongEndpointGroupType, self.validator._get_peer_cidrs, peer_epg) def test_validate_ipsec_conn_for_endpoints(self): """Check upper-level validation method for endpoint groups. Tests the happy path for doing general validation of the IPSec connection, calling all the sub-checks for an endpoint group case. """ subnet1 = {'id': _uuid(), 'ip_version': 4} subnet2 = {'id': _uuid(), 'ip_version': 4} local_subnets = [subnet1, subnet2] local_epg_id = _uuid() local_epg = {'id': local_epg_id, 'type': v_constants.SUBNET_ENDPOINT, 'endpoints': local_subnets} # Mock getting the subnets from the IDs query_mock = mock.patch.object(query.Query, 'all').start() query_mock.return_value = local_subnets # Mock that subnet is on router port_mock = mock.patch.object(self.core_plugin, "get_ports").start() port_mock.side_effect = ['dummy info', 'more dummy info'] peer_epg_id = _uuid() peer_cidrs = ['10.10.10.10/24', '20.20.20.20/24'] peer_epg = {'id': peer_epg_id, 'type': v_constants.CIDR_ENDPOINT, 'endpoints': peer_cidrs} ipsec_sitecon = {'local_ep_group_id': local_epg_id, 'local_epg_subnets': local_epg, 'peer_ep_group_id': peer_epg_id, 'peer_epg_cidrs': peer_epg, 'mtu': 2000, 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120} local_version = None vpnservice = {'router_id': _uuid()} self.validator.validate_ipsec_site_connection( self.context, ipsec_sitecon, local_version, vpnservice) # NOTE: Following are tests for the older API, providing some additional # coverage. def test_ipsec_conn_peer_cidrs_same_ip_version(self): """Check legacy peer_cidrs have same IP version.""" one_cidr = ['2002:0a00::/48'] version = self.validator._check_peer_cidrs_ip_versions(one_cidr) self.assertEqual(6, version) multiple_cidrs = ['10.10.10.0/24', '20.20.20.0/24'] version = self.validator._check_peer_cidrs_ip_versions(multiple_cidrs) self.assertEqual(4, version) def test_fail_ipsec_conn_peer_cidrs_mixed_ip_version(self): mixed_cidrs = ['2002:0a00::/48', '20.20.20.0/24'] self.assertRaises(vpnaas.MixedIPVersionsForPeerCidrs, self.validator._check_peer_cidrs_ip_versions, mixed_cidrs) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py0000666000175000017500000031675413370230606026053 0ustar zuulzuul00000000000000# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. # (c) Copyright 2015 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. import contextlib import copy import os import mock from neutron.api import extensions as api_extensions from neutron.common import config 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 as nextensions from neutron.scheduler import l3_agent_scheduler from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_plugin from neutron.tests.unit.extensions import test_l3 as test_l3_plugin from neutron_lib import constants as lib_constants from neutron_lib import context from neutron_lib.exceptions import l3 as l3_exception from neutron_lib.plugins import constants as nconstants from neutron_lib.plugins import directory from oslo_db import exception as db_exc from oslo_utils import uuidutils import webob.exc from neutron_vpnaas.db.vpn import vpn_db from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.services.vpn.common import constants from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.tests import base from neutron_vpnaas import extensions 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" FLAVOR_PLUGIN_KLASS = "neutron.services.flavors.flavors_plugin.FlavorsPlugin" ROOTDIR = os.path.normpath(os.path.join( os.path.dirname(__file__), '..', '..', '..', '..')) extensions_path = ':'.join(extensions.__path__ + nextensions.__path__) _uuid = uuidutils.generate_uuid class TestVpnCorePlugin(test_l3_plugin.TestL3NatIntPlugin, l3_agentschedulers_db.L3AgentSchedulerDbMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin): def __init__(self, configfile=None): super(TestVpnCorePlugin, self).__init__() self.router_scheduler = l3_agent_scheduler.ChanceScheduler() class VPNTestMixin(object): resource_prefix_map = dict( (k.replace('_', '-'), "/vpn") for k in vpnaas.RESOURCE_ATTRIBUTE_MAP ) def _create_ikepolicy(self, fmt, name='ikepolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', phase1_negotiation_mode='main', lifetime_units='seconds', lifetime_value=3600, ike_version='v1', pfs='group5', expected_res_status=None, **kwargs): data = {'ikepolicy': { 'name': name, 'auth_algorithm': auth_algorithm, 'encryption_algorithm': encryption_algorithm, 'phase1_negotiation_mode': phase1_negotiation_mode, 'lifetime': { 'units': lifetime_units, 'value': lifetime_value}, 'ike_version': ike_version, 'pfs': pfs, 'tenant_id': self._tenant_id }} if kwargs.get('description') is not None: data['ikepolicy']['description'] = kwargs['description'] ikepolicy_req = self.new_create_request('ikepolicies', data, fmt) ikepolicy_res = ikepolicy_req.get_response(self.ext_api) if expected_res_status: self.assertEqual(ikepolicy_res.status_int, expected_res_status) return ikepolicy_res @contextlib.contextmanager def ikepolicy(self, fmt=None, name='ikepolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', phase1_negotiation_mode='main', lifetime_units='seconds', lifetime_value=3600, ike_version='v1', pfs='group5', do_delete=True, **kwargs): if not fmt: fmt = self.fmt res = self._create_ikepolicy(fmt, name, auth_algorithm, encryption_algorithm, phase1_negotiation_mode, lifetime_units, lifetime_value, ike_version, pfs, **kwargs) if res.status_int >= 400: raise webob.exc.HTTPClientError(code=res.status_int) ikepolicy = self.deserialize(fmt or self.fmt, res) yield ikepolicy if do_delete: self._delete('ikepolicies', ikepolicy['ikepolicy']['id']) def _create_ipsecpolicy(self, fmt, name='ipsecpolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', encapsulation_mode='tunnel', transform_protocol='esp', lifetime_units='seconds', lifetime_value=3600, pfs='group5', expected_res_status=None, **kwargs): data = {'ipsecpolicy': {'name': name, 'auth_algorithm': auth_algorithm, 'encryption_algorithm': encryption_algorithm, 'encapsulation_mode': encapsulation_mode, 'transform_protocol': transform_protocol, 'lifetime': {'units': lifetime_units, 'value': lifetime_value}, 'pfs': pfs, 'tenant_id': self._tenant_id}} if kwargs.get('description') is not None: data['ipsecpolicy']['description'] = kwargs['description'] ipsecpolicy_req = self.new_create_request('ipsecpolicies', data, fmt) ipsecpolicy_res = ipsecpolicy_req.get_response(self.ext_api) if expected_res_status: self.assertEqual(ipsecpolicy_res.status_int, expected_res_status) return ipsecpolicy_res @contextlib.contextmanager def ipsecpolicy(self, fmt=None, name='ipsecpolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', encapsulation_mode='tunnel', transform_protocol='esp', lifetime_units='seconds', lifetime_value=3600, pfs='group5', do_delete=True, **kwargs): if not fmt: fmt = self.fmt res = self._create_ipsecpolicy(fmt, name, auth_algorithm, encryption_algorithm, encapsulation_mode, transform_protocol, lifetime_units, lifetime_value, pfs, **kwargs) if res.status_int >= 400: raise webob.exc.HTTPClientError(code=res.status_int) ipsecpolicy = self.deserialize(fmt or self.fmt, res) yield ipsecpolicy if do_delete: self._delete('ipsecpolicies', ipsecpolicy['ipsecpolicy']['id']) def _create_vpnservice(self, fmt, name, admin_state_up, router_id, subnet_id, expected_res_status=None, **kwargs): tenant_id = kwargs.get('tenant_id', self._tenant_id) data = {'vpnservice': {'name': name, 'subnet_id': subnet_id, 'router_id': router_id, 'admin_state_up': admin_state_up, 'tenant_id': tenant_id}} if kwargs.get('description') is not None: data['vpnservice']['description'] = kwargs['description'] if kwargs.get('flavor_id') is not None: data['vpnservice']['flavor_id'] = kwargs['flavor_id'] vpnservice_req = self.new_create_request('vpnservices', data, fmt) if (kwargs.get('set_context') and 'tenant_id' in kwargs): # create a specific auth context for this request vpnservice_req.environ['neutron.context'] = context.Context( '', kwargs['tenant_id']) vpnservice_res = vpnservice_req.get_response(self.ext_api) if expected_res_status: self.assertEqual(vpnservice_res.status_int, expected_res_status) return vpnservice_res @contextlib.contextmanager def vpnservice(self, fmt=None, name='vpnservice1', subnet=None, router=None, admin_state_up=True, do_delete=True, plug_subnet=True, external_subnet_cidr='192.168.100.0/24', external_router=True, **kwargs): if not fmt: fmt = self.fmt with test_db_plugin.optional_ctx(subnet, self.subnet) as tmp_subnet, \ test_db_plugin.optional_ctx(router, self.router) as tmp_router, \ self.subnet(cidr=external_subnet_cidr) as public_sub: if external_router: self._set_net_external( public_sub['subnet']['network_id']) self._add_external_gateway_to_router( tmp_router['router']['id'], public_sub['subnet']['network_id']) tmp_router['router']['external_gateway_info'] = { 'network_id': public_sub['subnet']['network_id']} if plug_subnet: self._router_interface_action( 'add', tmp_router['router']['id'], tmp_subnet['subnet']['id'], None) res = self._create_vpnservice(fmt, name, admin_state_up, router_id=(tmp_router['router'] ['id']), subnet_id=(tmp_subnet['subnet'] ['id']), **kwargs) vpnservice = self.deserialize(fmt or self.fmt, res) if res.status_int < 400: yield vpnservice if do_delete and vpnservice.get('vpnservice'): self._delete('vpnservices', vpnservice['vpnservice']['id']) if plug_subnet: self._router_interface_action( 'remove', tmp_router['router']['id'], tmp_subnet['subnet']['id'], None) if external_router: external_gateway = tmp_router['router'].get( 'external_gateway_info') if external_gateway: network_id = external_gateway['network_id'] self._remove_external_gateway_from_router( tmp_router['router']['id'], network_id) if res.status_int >= 400: raise webob.exc.HTTPClientError( code=res.status_int, detail=vpnservice) self._delete('subnets', public_sub['subnet']['id']) if not subnet: self._delete('subnets', tmp_subnet['subnet']['id']) def _create_ipsec_site_connection(self, fmt, name='test', peer_address='192.168.1.10', peer_id='192.168.1.10', peer_cidrs=None, mtu=1500, psk='abcdefg', initiator='bi-directional', dpd_action='hold', dpd_interval=30, dpd_timeout=120, vpnservice_id='fake_id', ikepolicy_id='fake_id', ipsecpolicy_id='fake_id', admin_state_up=True, local_ep_group_id=None, peer_ep_group_id=None, expected_res_status=None, **kwargs): data = { 'ipsec_site_connection': {'name': name, 'peer_address': peer_address, 'peer_id': peer_id, 'peer_cidrs': peer_cidrs, 'mtu': mtu, 'psk': psk, 'initiator': initiator, 'dpd': { 'action': dpd_action, 'interval': dpd_interval, 'timeout': dpd_timeout, }, 'vpnservice_id': vpnservice_id, 'ikepolicy_id': ikepolicy_id, 'ipsecpolicy_id': ipsecpolicy_id, 'admin_state_up': admin_state_up, 'tenant_id': self._tenant_id, 'local_ep_group_id': local_ep_group_id, 'peer_ep_group_id': peer_ep_group_id} } if kwargs.get('description') is not None: data['ipsec_site_connection'][ 'description'] = kwargs['description'] ipsec_site_connection_req = self.new_create_request( 'ipsec-site-connections', data, fmt ) ipsec_site_connection_res = ipsec_site_connection_req.get_response( self.ext_api ) if expected_res_status: self.assertEqual( ipsec_site_connection_res.status_int, expected_res_status ) return ipsec_site_connection_res @contextlib.contextmanager def ipsec_site_connection(self, fmt=None, name='ipsec_site_connection1', peer_address='192.168.1.10', peer_id='192.168.1.10', peer_cidrs=None, mtu=1500, psk='abcdefg', initiator='bi-directional', dpd_action='hold', dpd_interval=30, dpd_timeout=120, vpnservice=None, ikepolicy=None, ipsecpolicy=None, admin_state_up=True, do_delete=True, local_ep_group_id=None, peer_ep_group_id=None, **kwargs): if not fmt: fmt = self.fmt with test_db_plugin.optional_ctx(vpnservice, self.vpnservice ) as tmp_vpnservice, \ test_db_plugin.optional_ctx(ikepolicy, self.ikepolicy ) as tmp_ikepolicy, \ test_db_plugin.optional_ctx(ipsecpolicy, self.ipsecpolicy ) as tmp_ipsecpolicy: vpnservice_id = tmp_vpnservice['vpnservice']['id'] ikepolicy_id = tmp_ikepolicy['ikepolicy']['id'] ipsecpolicy_id = tmp_ipsecpolicy['ipsecpolicy']['id'] if not peer_cidrs and not local_ep_group_id: # Must be legacy usage - pick default to use peer_cidrs = ['10.0.0.0/24'] res = self._create_ipsec_site_connection(fmt, name, peer_address, peer_id, peer_cidrs, mtu, psk, initiator, dpd_action, dpd_interval, dpd_timeout, vpnservice_id, ikepolicy_id, ipsecpolicy_id, admin_state_up, local_ep_group_id, peer_ep_group_id, **kwargs) if res.status_int >= 400: raise webob.exc.HTTPClientError(code=res.status_int) ipsec_site_connection = self.deserialize( fmt or self.fmt, res ) yield ipsec_site_connection if do_delete: self._delete( 'ipsec-site-connections', ipsec_site_connection[ '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 = directory.get_plugin(nconstants.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 = lib_constants.ACTIVE class VPNPluginDbTestCase(VPNTestMixin, test_l3_plugin.L3NatTestCaseMixin, base.NeutronDbPluginV2TestCase): def setUp(self, core_plugin=None, vpnaas_plugin=DB_VPN_PLUGIN_KLASS, vpnaas_provider=None): if not vpnaas_provider: vpnaas_provider = ( nconstants.VPN + ':vpnaas:neutron_vpnaas.services.vpn.' 'service_drivers.ipsec.IPsecVPNDriver:default') bits = vpnaas_provider.split(':') vpnaas_provider = { 'service_type': bits[0], 'name': bits[1], 'driver': bits[2] } if len(bits) == 4: vpnaas_provider['default'] = True # override the default service provider self.service_providers = ( mock.patch.object(sdb.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = [vpnaas_provider] # force service type manager to reload configuration: sdb.ServiceTypeManager._instance = None service_plugins = { 'vpnaas_plugin': vpnaas_plugin, 'flavors_plugin': FLAVOR_PLUGIN_KLASS} plugin_str = ('neutron_vpnaas.tests.unit.db.vpn.' 'test_vpn_db.TestVpnCorePlugin') super(VPNPluginDbTestCase, self).setUp( plugin_str, service_plugins=service_plugins ) self._subnet_id = _uuid() self.core_plugin = TestVpnCorePlugin() self.plugin = vpn_plugin.VPNPlugin() ext_mgr = api_extensions.PluginAwareExtensionManager( extensions_path, {nconstants.CORE: self.core_plugin, nconstants.VPN: self.plugin} ) app = config.load_paste_app('extensions_test_app') self.ext_api = api_extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) class TestVpnaas(VPNPluginDbTestCase): def setUp(self, **kwargs): # TODO(armax): this is far from being a unit test case, as it tests # that multiple parties (core + vpn) are integrated properly and # should be replaced by API test that do not rely on so much mocking. # NOTE(armax): make sure that the callbacks needed by this test are # registered, as they may get wiped out depending by the order in # which imports, subscriptions and mocks occur. super(TestVpnaas, self).setUp(**kwargs) vpn_db.subscribe() def _check_policy(self, policy, keys, lifetime): for k, v in keys: self.assertEqual(policy[k], v) for k, v in lifetime.items(): self.assertEqual(policy['lifetime'][k], v) def test_create_ikepolicy(self): """Test case to create an ikepolicy.""" name = "ikepolicy1" description = 'ipsec-ikepolicy' keys = [('name', name), ('description', 'ipsec-ikepolicy'), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ikepolicy(name=name, description=description) as ikepolicy: self._check_policy(ikepolicy['ikepolicy'], keys, lifetime) def test_delete_ikepolicy(self): """Test case to delete an ikepolicy.""" with self.ikepolicy(do_delete=False) as ikepolicy: req = self.new_delete_request('ikepolicies', ikepolicy['ikepolicy']['id']) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, 204) def test_show_ikepolicy(self): """Test case to show or get an ikepolicy.""" name = "ikepolicy1" description = 'ipsec-ikepolicy' keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ikepolicy(name=name, description=description) as ikepolicy: req = self.new_show_request('ikepolicies', ikepolicy['ikepolicy']['id'], fmt=self.fmt) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self._check_policy(res['ikepolicy'], keys, lifetime) def test_list_ikepolicies(self): """Test case to list all ikepolicies.""" name = "ikepolicy_list" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ikepolicy(name=name) as ikepolicy: keys.append(('id', ikepolicy['ikepolicy']['id'])) req = self.new_list_request('ikepolicies') res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(len(res), 1) for k, v in keys: self.assertEqual(res['ikepolicies'][0][k], v) for k, v in lifetime.items(): self.assertEqual(res['ikepolicies'][0]['lifetime'][k], v) def test_list_ikepolicies_with_sort_emulated(self): """Test case to list all ikepolicies.""" with self.ikepolicy(name='ikepolicy1') as ikepolicy1, \ self.ikepolicy(name='ikepolicy2') as ikepolicy2, \ self.ikepolicy(name='ikepolicy3') as ikepolicy3: self._test_list_with_sort('ikepolicy', (ikepolicy3, ikepolicy2, ikepolicy1), [('name', 'desc')], 'ikepolicies') def test_list_ikepolicies_with_pagination_emulated(self): """Test case to list all ikepolicies with pagination.""" with self.ikepolicy(name='ikepolicy1') as ikepolicy1, \ self.ikepolicy(name='ikepolicy2') as ikepolicy2, \ self.ikepolicy(name='ikepolicy3') as ikepolicy3: self._test_list_with_pagination('ikepolicy', (ikepolicy1, ikepolicy2, ikepolicy3), ('name', 'asc'), 2, 2, 'ikepolicies') def test_list_ikepolicies_with_pagination_reverse_emulated(self): """Test case to list all ikepolicies with reverse pagination.""" with self.ikepolicy(name='ikepolicy1') as ikepolicy1, \ self.ikepolicy(name='ikepolicy2') as ikepolicy2, \ self.ikepolicy(name='ikepolicy3') as ikepolicy3: self._test_list_with_pagination_reverse('ikepolicy', (ikepolicy1, ikepolicy2, ikepolicy3), ('name', 'asc'), 2, 2, 'ikepolicies') def test_update_ikepolicy(self): """Test case to update an ikepolicy.""" name = "new_ikepolicy1" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id), ('lifetime', {'units': 'seconds', 'value': 60})] with self.ikepolicy(name=name) as ikepolicy: data = {'ikepolicy': {'name': name, 'lifetime': {'units': 'seconds', 'value': 60}}} req = self.new_update_request("ikepolicies", data, ikepolicy['ikepolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['ikepolicy'][k], v) def test_create_ikepolicy_with_invalid_values(self): """Test case to test invalid values.""" name = 'ikepolicy1' self._create_ikepolicy(name=name, fmt=self.fmt, auth_algorithm='md5', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, auth_algorithm=200, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, encryption_algorithm='des', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, encryption_algorithm=100, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, phase1_negotiation_mode='aggressive', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, phase1_negotiation_mode=-100, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, ike_version='v6', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, ike_version=500, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, pfs='group1', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, pfs=120, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_units='Megabytes', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_units=20000, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_value=-20, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_value='Megabytes', expected_res_status=400) def test_create_ipsecpolicy(self): """Test case to create an ipsecpolicy.""" name = "ipsecpolicy1" description = 'my-ipsecpolicy' keys = [('name', name), ('description', 'my-ipsecpolicy'), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ipsecpolicy(name=name, description=description) as ipsecpolicy: self._check_policy(ipsecpolicy['ipsecpolicy'], keys, lifetime) def test_delete_ipsecpolicy(self): """Test case to delete an ipsecpolicy.""" with self.ipsecpolicy(do_delete=False) as ipsecpolicy: req = self.new_delete_request('ipsecpolicies', ipsecpolicy['ipsecpolicy']['id']) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, 204) def test_show_ipsecpolicy(self): """Test case to show or get an ipsecpolicy.""" name = "ipsecpolicy1" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ipsecpolicy(name=name) as ipsecpolicy: req = self.new_show_request('ipsecpolicies', ipsecpolicy['ipsecpolicy']['id'], fmt=self.fmt) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self._check_policy(res['ipsecpolicy'], keys, lifetime) def test_list_ipsecpolicies(self): """Test case to list all ipsecpolicies.""" name = "ipsecpolicy_list" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ipsecpolicy(name=name) as ipsecpolicy: keys.append(('id', ipsecpolicy['ipsecpolicy']['id'])) req = self.new_list_request('ipsecpolicies') res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(len(res), 1) self._check_policy(res['ipsecpolicies'][0], keys, lifetime) def test_list_ipsecpolicies_with_sort_emulated(self): """Test case to list all ipsecpolicies.""" with self.ipsecpolicy(name='ipsecpolicy1') as ipsecpolicy1, \ self.ipsecpolicy(name='ipsecpolicy2') as ipsecpolicy2, \ self.ipsecpolicy(name='ipsecpolicy3') as ipsecpolicy3: self._test_list_with_sort('ipsecpolicy', (ipsecpolicy3, ipsecpolicy2, ipsecpolicy1), [('name', 'desc')], 'ipsecpolicies') def test_list_ipsecpolicies_with_pagination_emulated(self): """Test case to list all ipsecpolicies with pagination.""" with self.ipsecpolicy(name='ipsecpolicy1') as ipsecpolicy1, \ self.ipsecpolicy(name='ipsecpolicy2') as ipsecpolicy2, \ self.ipsecpolicy(name='ipsecpolicy3') as ipsecpolicy3: self._test_list_with_pagination('ipsecpolicy', (ipsecpolicy1, ipsecpolicy2, ipsecpolicy3), ('name', 'asc'), 2, 2, 'ipsecpolicies') def test_list_ipsecpolicies_with_pagination_reverse_emulated(self): """Test case to list all ipsecpolicies with reverse pagination.""" with self.ipsecpolicy(name='ipsecpolicy1') as ipsecpolicy1, \ self.ipsecpolicy(name='ipsecpolicy2') as ipsecpolicy2, \ self.ipsecpolicy(name='ipsecpolicy3') as ipsecpolicy3: self._test_list_with_pagination_reverse('ipsecpolicy', (ipsecpolicy1, ipsecpolicy2, ipsecpolicy3), ('name', 'asc'), 2, 2, 'ipsecpolicies') def test_update_ipsecpolicy(self): """Test case to update an ipsecpolicy.""" name = "new_ipsecpolicy1" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id), ('lifetime', {'units': 'seconds', 'value': 60})] with self.ipsecpolicy(name=name) as ipsecpolicy: data = {'ipsecpolicy': {'name': name, 'lifetime': {'units': 'seconds', 'value': 60}}} req = self.new_update_request("ipsecpolicies", data, ipsecpolicy['ipsecpolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['ipsecpolicy'][k], v) def test_update_ipsecpolicy_lifetime(self): with self.ipsecpolicy() as ipsecpolicy: data = {'ipsecpolicy': {'lifetime': {'units': 'seconds'}}} req = self.new_update_request("ipsecpolicies", data, ipsecpolicy['ipsecpolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(res['ipsecpolicy']['lifetime']['units'], 'seconds') data = {'ipsecpolicy': {'lifetime': {'value': 60}}} req = self.new_update_request("ipsecpolicies", data, ipsecpolicy['ipsecpolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(res['ipsecpolicy']['lifetime']['value'], 60) def test_create_ipsecpolicy_with_invalid_values(self): """Test case to test invalid values.""" name = 'ipsecpolicy1' self._create_ipsecpolicy( fmt=self.fmt, name=name, auth_algorithm='md5', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, auth_algorithm=100, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, encryption_algorithm='des', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, encryption_algorithm=200, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, transform_protocol='abcd', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, transform_protocol=500, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, encapsulation_mode='unsupported', expected_res_status=400) self._create_ipsecpolicy(name=name, fmt=self.fmt, encapsulation_mode=100, expected_res_status=400) self._create_ipsecpolicy(name=name, fmt=self.fmt, pfs='group9', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, pfs=-1, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, lifetime_units='minutes', expected_res_status=400) self._create_ipsecpolicy(fmt=self.fmt, name=name, lifetime_units=100, expected_res_status=400) self._create_ipsecpolicy(fmt=self.fmt, name=name, lifetime_value=-800, expected_res_status=400) self._create_ipsecpolicy(fmt=self.fmt, name=name, lifetime_value='Megabytes', expected_res_status=400) 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': 'PENDING_CREATE', '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_delete_router_interface_in_use_by_vpnservice(self): """Test delete router interface 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._router_interface_action('remove', router['router']['id'], subnet['subnet']['id'], None, expected_code=webob.exc. HTTPConflict.code) def test_delete_router_interface_not_in_use_by_vpnservice(self): """Test delete router interface not in use by vpn service.""" with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router1, self.router() as router2, \ self.vpnservice(subnet=subnet, router=router1), \ self.port(subnet=subnet) as port: self._router_interface_action('add', router2['router']['id'], None, port['port']['id'], expected_code=webob.exc. HTTPOk.code) self._router_interface_action('remove', router1['router']['id'], subnet['subnet']['id'], None, expected_code=webob.exc. HTTPConflict.code) self._router_interface_action('remove', router2['router']['id'], None, port['port']['id'], expected_code=webob.exc. HTTPOk.code) def test_delete_external_gateway_interface_in_use_by_vpnservice(self): """Test delete external gateway interface in use by vpn service.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.subnet(cidr='11.0.0.0/24') as public_sub: self._set_net_external( public_sub['subnet']['network_id']) self._add_external_gateway_to_router( router['router']['id'], public_sub['subnet']['network_id']) with self.vpnservice(subnet=subnet, router=router): self._remove_external_gateway_from_router( router['router']['id'], public_sub['subnet']['network_id'], expected_code=webob.exc.HTTPConflict.code) def test_router_update_after_ipsec_site_connection(self): """Test case to update router after vpn connection.""" rname1 = "router_one" rname2 = "router_two" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router(name=rname1) as r: with self.vpnservice(subnet=subnet, router=r ) as vpnservice: self.ipsec_site_connection( name='connection1', vpnservice=vpnservice ) body = self._show('routers', r['router']['id']) self.assertEqual(body['router']['name'], rname1) body = self._update('routers', r['router']['id'], {'router': {'name': rname2}}) body = self._show('routers', r['router']['id']) self.assertEqual(body['router']['name'], rname2) def test_update_vpnservice(self): """Test case to update a vpnservice.""" name = 'new_vpnservice1' keys = [('name', name)] with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(name=name, subnet=subnet, router=router) as vpnservice: keys.append(('subnet_id', vpnservice['vpnservice']['subnet_id'])) keys.append(('router_id', vpnservice['vpnservice']['router_id'])) data = {'vpnservice': {'name': name}} self._set_active(vpn_models.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 keys: self.assertEqual(res['vpnservice'][k], v) def test_update_vpnservice_with_invalid_state(self): """Test case to update a vpnservice in invalid state .""" name = 'new_vpnservice1' keys = [('name', name)] with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(name=name, subnet=subnet, router=router) as vpnservice: keys.append(('subnet_id', vpnservice['vpnservice']['subnet_id'])) keys.append(('router_id', vpnservice['vpnservice']['router_id'])) data = {'vpnservice': {'name': name}} req = self.new_update_request( 'vpnservices', data, vpnservice['vpnservice']['id']) res = req.get_response(self.ext_api) self.assertEqual(400, res.status_int) res = self.deserialize(self.fmt, res) self.assertIn(vpnservice['vpnservice']['id'], res['NeutronError']['message']) def test_delete_vpnservice(self): """Test case to delete a vpnservice.""" with self.vpnservice(name='vpnserver', do_delete=False) as vpnservice: req = self.new_delete_request('vpnservices', vpnservice['vpnservice']['id']) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, 204) def test_show_vpnservice(self): """Test case to show or get a vpnservice.""" name = "vpnservice1" keys = [('name', name), ('description', ''), ('admin_state_up', True), ('status', 'PENDING_CREATE')] with self.vpnservice(name=name) as vpnservice: req = self.new_show_request('vpnservices', vpnservice['vpnservice']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['vpnservice'][k], v) def test_list_vpnservices(self): """Test case to list all vpnservices.""" name = "vpnservice_list" keys = [('name', name), ('description', ''), ('admin_state_up', True), ('status', 'PENDING_CREATE')] with self.vpnservice(name=name) as vpnservice: keys.append(('subnet_id', vpnservice['vpnservice']['subnet_id'])) keys.append(('router_id', vpnservice['vpnservice']['router_id'])) req = self.new_list_request('vpnservices') res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(len(res), 1) for k, v in keys: self.assertEqual(res['vpnservices'][0][k], v) def test_list_vpnservices_with_sort_emulated(self): """Test case to list all vpnservices with sorting.""" with self.subnet() as subnet: with self.router() as router: with self.vpnservice(name='vpnservice1', subnet=subnet, router=router, external_subnet_cidr='192.168.10.0/24' ) as vpnservice1, \ self.vpnservice(name='vpnservice2', subnet=subnet, router=router, plug_subnet=False, external_router=False, external_subnet_cidr='192.168.11.0/24' ) as vpnservice2, \ self.vpnservice(name='vpnservice3', subnet=subnet, router=router, plug_subnet=False, external_router=False, external_subnet_cidr='192.168.13.0/24' ) as vpnservice3: self._test_list_with_sort('vpnservice', (vpnservice3, vpnservice2, vpnservice1), [('name', 'desc')]) def test_list_vpnservice_with_pagination_emulated(self): """Test case to list all vpnservices with pagination.""" with self.subnet() as subnet: with self.router() as router: with self.vpnservice(name='vpnservice1', subnet=subnet, router=router, external_subnet_cidr='192.168.10.0/24' ) as vpnservice1, \ self.vpnservice(name='vpnservice2', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.20.0/24', external_router=False ) as vpnservice2, \ self.vpnservice(name='vpnservice3', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.30.0/24', external_router=False ) as vpnservice3: self._test_list_with_pagination('vpnservice', (vpnservice1, vpnservice2, vpnservice3), ('name', 'asc'), 2, 2) def test_list_vpnservice_with_pagination_reverse_emulated(self): """Test case to list all vpnservices with reverse pagination.""" with self.subnet() as subnet: with self.router() as router: with self.vpnservice(name='vpnservice1', subnet=subnet, router=router, external_subnet_cidr='192.168.10.0/24' ) as vpnservice1, \ self.vpnservice(name='vpnservice2', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.11.0/24', external_router=False ) as vpnservice2, \ self.vpnservice(name='vpnservice3', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.12.0/24', external_router=False ) as vpnservice3: self._test_list_with_pagination_reverse('vpnservice', (vpnservice1, vpnservice2, vpnservice3), ('name', 'asc'), 2, 2) def test_create_ipsec_site_connection_with_invalid_values(self): """Test case to create an ipsec_site_connection with invalid values.""" name = 'connection1' self._create_ipsec_site_connection( fmt=self.fmt, name=name, peer_cidrs='myname', expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, mtu=-100, expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, dpd_action='unsupported', expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, dpd_interval=-1, expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, dpd_timeout=-200, expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, initiator='unsupported', expected_status_int=400) def _test_create_ipsec_site_connection(self, 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 is not None: params.update(setup_overrides) keys = {'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': 'PENDING_CREATE', 'admin_state_up': True} if key_overrides is not None: keys.update(key_overrides) dpd = {'action': 'hold', 'interval': 40, 'timeout': 120} with self.ikepolicy(name=params['ikename']) as ikepolicy, \ self.ipsecpolicy(name=params['ipsecname']) as ipsecpolicy, \ self.subnet(cidr=params['subnet_cidr'], ip_version=params['subnet_version']) as subnet, \ self.router() as router: with self.vpnservice(name=params['vpnsname'], subnet=subnet, router=router) as vpnservice1: keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id'] keys['vpnservice_id'] = vpnservice1['vpnservice']['id'] try: with self.ipsec_site_connection( self.fmt, keys['name'], keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], dpd['action'], dpd['interval'], dpd['timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=keys['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'], keys, dpd) except webob.exc.HTTPClientError as ce: self.assertEqual(ce.code, expected_status_int) self._delete('subnets', subnet['subnet']['id']) 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_delete_ipsec_site_connection(self): """Test case to delete a ipsec_site_connection.""" with self.ipsec_site_connection( do_delete=False) 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) 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}) 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': 2000}, overrides=ipv6_settings) 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( overrides={'make_active': False}, expected_status_int=400) def test_update_ipsec_site_connection_peer_cidrs(self): """Test updating an ipsec_site_connection for peer_cidrs.""" new_peers = {'peer_cidrs': ['192.168.4.0/24', '192.168.5.0/24']} self._test_update_ipsec_site_connection( update=new_peers) def _test_update_ipsec_site_connection(self, update={'name': 'new name'}, overrides=None, expected_status_int=200): """Creates and then updates ipsec_site_connection.""" keys = {'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 is not None: keys.update(overrides) with self.ikepolicy(name=keys['ikename']) as ikepolicy, \ self.ipsecpolicy(name=keys['ipsecname']) as ipsecpolicy, \ self.subnet(cidr=keys['subnet_cidr'], ip_version=keys['subnet_version']) as subnet, \ self.router() as router: with self.vpnservice(name=keys['vpnsname'], subnet=subnet, router=router) as vpnservice1: ext_gw = router['router']['external_gateway_info'] if ext_gw: self._create_subnet(self.fmt, net_id=ext_gw['network_id'], ip_version=6, cidr='2001:db8::/32') keys['vpnservice_id'] = vpnservice1['vpnservice']['id'] keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id'] with self.ipsec_site_connection( self.fmt, keys['name'], keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], keys['action'], keys['interval'], keys['timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=keys['description'] ) as ipsec_site_connection: data = {'ipsec_site_connection': update} if keys.get('make_active', None): self._set_active( vpn_models.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) actual = res_dict['ipsec_site_connection'] for k, v in update.items(): # Sort lists before checking equality if isinstance(actual[k], list): self.assertEqual(v, sorted(actual[k])) else: self.assertEqual(v, actual[k]) self._delete('networks', subnet['subnet']['network_id']) def test_show_ipsec_site_connection(self): """Test case to show a ipsec_site_connection.""" ikename = "ikepolicy1" ipsecname = "ipsecpolicy1" vpnsname = "vpnservice1" name = "connection1" description = "my-ipsec-connection" keys = {'name': name, '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': 'PENDING_CREATE', 'admin_state_up': True} dpd = {'action': 'hold', 'interval': 40, 'timeout': 120} with self.ikepolicy(name=ikename) as ikepolicy, \ self.ipsecpolicy(name=ipsecname) as ipsecpolicy, \ self.subnet() as subnet, \ self.router() as router: with self.vpnservice(name=vpnsname, subnet=subnet, router=router) as vpnservice1: keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id'] keys['vpnservice_id'] = vpnservice1['vpnservice']['id'] with self.ipsec_site_connection( self.fmt, name, keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], dpd['action'], dpd['interval'], dpd['timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=description, ) as ipsec_site_connection: req = self.new_show_request( 'ipsec-site-connections', ipsec_site_connection[ 'ipsec_site_connection']['id'], fmt=self.fmt ) res = self.deserialize( self.fmt, req.get_response(self.ext_api) ) self._check_ipsec_site_connection( res['ipsec_site_connection'], keys, dpd) def test_list_ipsec_site_connections_with_sort_emulated(self): """Test case to list all ipsec_site_connections with sort.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.vpnservice(subnet=subnet, router=router ) as vpnservice: with self.ipsec_site_connection(name='connection1', vpnservice=vpnservice ) as conn1, \ self.ipsec_site_connection(name='connection2', vpnservice=vpnservice ) as conn2, \ self.ipsec_site_connection(name='connection3', vpnservice=vpnservice ) as conn3: self._test_list_with_sort('ipsec-site-connection', (conn3, conn2, conn1), [('name', 'desc')]) def test_list_ipsec_site_connections_with_pagination_emulated(self): """Test case to list all ipsec_site_connections with pagination.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.vpnservice(subnet=subnet, router=router ) as vpnservice: with self.ipsec_site_connection( name='ipsec_site_connection1', vpnservice=vpnservice) as conn1, \ self.ipsec_site_connection( name='ipsec_site_connection1', vpnservice=vpnservice) as conn2, \ self.ipsec_site_connection( name='ipsec_site_connection1', vpnservice=vpnservice) as conn3: self._test_list_with_pagination( 'ipsec-site-connection', (conn1, conn2, conn3), ('name', 'asc'), 2, 2) def test_list_ipsec_site_conns_with_pagination_reverse_emulated(self): """Test to list all ipsec_site_connections with reverse pagination.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.vpnservice(subnet=subnet, router=router ) as vpnservice: with self.ipsec_site_connection(name='connection1', vpnservice=vpnservice ) as conn1, \ self.ipsec_site_connection(name='connection2', vpnservice=vpnservice ) as conn2, \ self.ipsec_site_connection(name='connection3', vpnservice=vpnservice ) as conn3: self._test_list_with_pagination_reverse( 'ipsec-site-connection', (conn1, conn2, conn3), ('name', 'asc'), 2, 2 ) def test_create_vpn(self): """Test case to create a vpn.""" vpns_name = "vpnservice1" ike_name = "ikepolicy1" ipsec_name = "ipsecpolicy1" name1 = "ipsec_site_connection1" with self.ikepolicy(name=ike_name) as ikepolicy, \ self.ipsecpolicy(name=ipsec_name) as ipsecpolicy, \ self.vpnservice(name=vpns_name) as vpnservice: vpnservice_id = vpnservice['vpnservice']['id'] ikepolicy_id = ikepolicy['ikepolicy']['id'] ipsecpolicy_id = ipsecpolicy['ipsecpolicy']['id'] with self.ipsec_site_connection( self.fmt, name1, '192.168.1.10', '192.168.1.10', ['192.168.2.0/24', '192.168.3.0/24'], 1500, 'abcdef', 'bi-directional', 'hold', 30, 120, vpnservice, ikepolicy, ipsecpolicy, True ) as vpnconn1: vpnservice_req = self.new_show_request( 'vpnservices', vpnservice_id, fmt=self.fmt) vpnservice_updated = self.deserialize( self.fmt, vpnservice_req.get_response(self.ext_api) ) self.assertEqual( vpnservice_updated['vpnservice']['id'], vpnconn1['ipsec_site_connection']['vpnservice_id'] ) ikepolicy_req = self.new_show_request('ikepolicies', ikepolicy_id, fmt=self.fmt) ikepolicy_res = self.deserialize( self.fmt, ikepolicy_req.get_response(self.ext_api) ) self.assertEqual( ikepolicy_res['ikepolicy']['id'], vpnconn1['ipsec_site_connection']['ikepolicy_id']) ipsecpolicy_req = self.new_show_request( 'ipsecpolicies', ipsecpolicy_id, fmt=self.fmt) ipsecpolicy_res = self.deserialize( self.fmt, ipsecpolicy_req.get_response(self.ext_api) ) self.assertEqual( ipsecpolicy_res['ipsecpolicy']['id'], vpnconn1['ipsec_site_connection']['ipsecpolicy_id'] ) def test_delete_ikepolicy_inuse(self): """Test case to delete an ikepolicy, that is in use.""" vpns_name = "vpnservice1" ike_name = "ikepolicy1" ipsec_name = "ipsecpolicy1" name1 = "ipsec_site_connection1" with self.ikepolicy(name=ike_name) as ikepolicy: with self.ipsecpolicy(name=ipsec_name) as ipsecpolicy: with self.vpnservice(name=vpns_name) as vpnservice: with self.ipsec_site_connection( self.fmt, name1, '192.168.1.10', '192.168.1.10', ['192.168.2.0/24', '192.168.3.0/24'], 1500, 'abcdef', 'bi-directional', 'hold', 30, 120, vpnservice, ikepolicy, ipsecpolicy, True ): delete_req = self.new_delete_request( 'ikepolicies', ikepolicy['ikepolicy']['id'] ) delete_res = delete_req.get_response(self.ext_api) self.assertEqual(409, delete_res.status_int) def test_delete_ipsecpolicy_inuse(self): """Test case to delete an ipsecpolicy, that is in use.""" vpns_name = "vpnservice1" ike_name = "ikepolicy1" ipsec_name = "ipsecpolicy1" name1 = "ipsec_site_connection1" with self.ikepolicy(name=ike_name) as ikepolicy: with self.ipsecpolicy(name=ipsec_name) as ipsecpolicy: with self.vpnservice(name=vpns_name) as vpnservice: with self.ipsec_site_connection( self.fmt, name1, '192.168.1.10', '192.168.1.10', ['192.168.2.0/24', '192.168.3.0/24'], 1500, 'abcdef', 'bi-directional', 'hold', 30, 120, vpnservice, ikepolicy, ipsecpolicy, True ): delete_req = self.new_delete_request( 'ipsecpolicies', ipsecpolicy['ipsecpolicy']['id'] ) delete_res = delete_req.get_response(self.ext_api) self.assertEqual(409, delete_res.status_int) def test_router_in_use_by_vpnaas(self): """Check that exception raised, if router in use by VPNaaS.""" with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(subnet=subnet, router=router): self.assertRaises(l3_exception.RouterInUse, self.plugin.check_router_in_use, context.get_admin_context(), router['router']['id']) def test_subnet_in_use_by_vpnaas(self): """Check that exception raised, if subnet in use by VPNaaS.""" with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(subnet=subnet, router=router): self.assertRaises(vpnaas.SubnetInUseByVPNService, self.plugin.check_subnet_in_use, context.get_admin_context(), subnet['subnet']['id'], router['router']['id']) def test_check_router_has_no_vpn(self): vpn_plugin = mock.Mock() directory.add_plugin('VPN', vpn_plugin) kwargs = {'context': mock.ANY, 'router': {'id': 'foo_id'}} self.assertTrue(vpn_db.migration_callback( mock.ANY, mock.ANY, mock.ANY, **kwargs)) vpn_plugin.check_router_in_use.assert_called_once_with( mock.ANY, 'foo_id') # Note: Below are new database related tests that only exercise the database # instead of going through the client API. The intent here is to (eventually) # convert all the database tests to this method, for faster, more granular # tests. # TODO(pcm): Put helpers in another module for sharing class NeutronResourcesMixin(object): def create_network(self, overrides=None): """Create database entry for network.""" network_info = {'network': {'name': 'my-net', 'tenant_id': self.tenant_id, 'admin_state_up': True, 'shared': False}} if overrides: network_info['network'].update(overrides) return self.core_plugin.create_network(self.context, network_info) def create_subnet(self, overrides=None): """Create database entry for subnet.""" subnet_info = {'subnet': {'name': 'my-subnet', 'tenant_id': self.tenant_id, 'ip_version': 4, 'enable_dhcp': True, 'dns_nameservers': None, 'host_routes': None, 'allocation_pools': None}} if overrides: subnet_info['subnet'].update(overrides) return self.core_plugin.create_subnet(self.context, subnet_info) def create_router(self, overrides=None, gw=None): """Create database entry for router with optional gateway.""" router_info = { 'router': { 'name': 'my-router', 'tenant_id': self.tenant_id, 'admin_state_up': True, } } if overrides: router_info['router'].update(overrides) if gw: gw_info = { 'external_gateway_info': { 'network_id': gw['net_id'], 'external_fixed_ips': [{'subnet_id': gw['subnet_id'], 'ip_address': gw['ip']}], } } router_info['router'].update(gw_info) return self.l3_plugin.create_router(self.context, router_info) def create_router_port_for_subnet(self, router, subnet): """Creates port on router for subnet specified.""" port = {'port': { 'tenant_id': self.tenant_id, 'network_id': subnet['network_id'], 'fixed_ips': [ {'ip_address': subnet['gateway_ip'], 'subnet_id': subnet['id']} ], 'mac_address': lib_constants.ATTR_NOT_SPECIFIED, 'admin_state_up': True, 'device_id': router['id'], 'device_owner': lib_constants.DEVICE_OWNER_ROUTER_INTF, 'name': '' }} return self.core_plugin.create_port(self.context, port) def create_basic_topology(self): """Setup networks, subnets, and a router for testing VPN.""" public_net = self.create_network(overrides={'name': 'public', 'router:external': True}) private_net = self.create_network(overrides={'name': 'private'}) overrides = {'name': 'private-subnet', 'cidr': '10.2.0.0/24', 'gateway_ip': '10.2.0.1', 'network_id': private_net['id']} private_subnet = self.create_subnet(overrides=overrides) overrides = {'name': 'public-subnet', 'cidr': '192.168.100.0/24', 'gateway_ip': '192.168.100.1', 'allocation_pools': [{'start': '192.168.100.2', 'end': '192.168.100.254'}], 'network_id': public_net['id']} public_subnet = self.create_subnet(overrides=overrides) gw_info = {'net_id': public_net['id'], 'subnet_id': public_subnet['id'], 'ip': '192.168.100.5'} router = self.create_router(gw=gw_info) self.create_router_port_for_subnet(router, private_subnet) return (private_subnet, router) class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin): def setUp(self): # Setup the core plugin self.plugin_str = ('neutron_vpnaas.tests.unit.db.vpn.' 'test_vpn_db.TestVpnCorePlugin') super(TestVpnDatabase, self).setUp(self.plugin_str) # Get the plugins self.core_plugin = directory.get_plugin() self.l3_plugin = directory.get_plugin(nconstants.L3) # Create VPN database instance self.plugin = vpn_db.VPNPluginDb() self.tenant_id = _uuid() self.context = context.get_admin_context() def prepare_service_info(self, private_subnet, router): subnet_id = private_subnet['id'] if private_subnet else None return {'vpnservice': {'tenant_id': self.tenant_id, 'name': 'my-service', 'description': 'new service', 'subnet_id': subnet_id, 'router_id': router['id'], 'flavor_id': None, 'admin_state_up': True}} def test_create_vpnservice(self): private_subnet, router = self.create_basic_topology() info = self.prepare_service_info(private_subnet, router) expected = {'admin_state_up': True, 'external_v4_ip': None, 'external_v6_ip': None, 'status': 'PENDING_CREATE'} expected.update(info['vpnservice']) new_service = self.plugin.create_vpnservice(self.context, info) self.assertDictSupersetOf(expected, new_service) def test_create_vpn_service_without_subnet(self): """Create service w/o subnet (will use endpoint groups for conn).""" private_subnet, router = self.create_basic_topology() info = self.prepare_service_info(private_subnet=None, router=router) expected = {'admin_state_up': True, 'external_v4_ip': None, 'external_v6_ip': None, 'status': 'PENDING_CREATE'} expected.update(info['vpnservice']) new_service = self.plugin.create_vpnservice(self.context, info) self.assertDictSupersetOf(expected, new_service) def test_update_external_tunnel_ips(self): """Verify that external tunnel IPs can be set.""" private_subnet, router = self.create_basic_topology() info = self.prepare_service_info(private_subnet, router) expected = {'admin_state_up': True, 'external_v4_ip': None, 'external_v6_ip': None, 'status': 'PENDING_CREATE'} expected.update(info['vpnservice']) new_service = self.plugin.create_vpnservice(self.context, info) self.assertDictSupersetOf(expected, new_service) external_v4_ip = '192.168.100.5' external_v6_ip = 'fd00:1000::4' expected.update({'external_v4_ip': external_v4_ip, 'external_v6_ip': external_v6_ip}) mod_service = self.plugin.set_external_tunnel_ips(self.context, new_service['id'], v4_ip=external_v4_ip, v6_ip=external_v6_ip) self.assertDictSupersetOf(expected, mod_service) def prepare_endpoint_info(self, group_type, endpoints): return {'endpoint_group': {'tenant_id': self.tenant_id, 'name': 'my endpoint group', 'description': 'my description', 'type': group_type, 'endpoints': endpoints}} def test_endpoint_group_create_with_cidrs(self): """Verify create endpoint group using CIDRs.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] new_endpoint_group = self.plugin.create_endpoint_group(self.context, info) self._compare_groups(expected, new_endpoint_group) def test_endpoint_group_create_with_subnets(self): """Verify create endpoint group using subnets.""" # Skip validation for subnets, as validation is checked in other tests mock.patch.object(self.l3_plugin, "get_subnet").start() private_subnet, router = self.create_basic_topology() private_net2 = self.create_network(overrides={'name': 'private2'}) overrides = {'name': 'private-subnet2', 'cidr': '10.1.0.0/24', 'gateway_ip': '10.1.0.1', 'network_id': private_net2['id']} private_subnet2 = self.create_subnet(overrides=overrides) self.create_router_port_for_subnet(router, private_subnet2) info = self.prepare_endpoint_info(constants.SUBNET_ENDPOINT, [private_subnet['id'], private_subnet2['id']]) expected = info['endpoint_group'] new_endpoint_group = self.plugin.create_endpoint_group(self.context, info) self._compare_groups(expected, new_endpoint_group) def test_endpoint_group_create_with_vlans(self): """Verify endpoint group using VLANs.""" info = self.prepare_endpoint_info(constants.VLAN_ENDPOINT, ['100', '200', '300']) expected = info['endpoint_group'] new_endpoint_group = self.plugin.create_endpoint_group(self.context, info) self._compare_groups(expected, new_endpoint_group) def _compare_groups(self, expected_group, actual_group): # Callers may want to reuse passed dicts later expected_group = copy.deepcopy(expected_group) actual_group = copy.deepcopy(actual_group) # We need to compare endpoints separately because their order is # not defined check_endpoints = 'endpoints' in expected_group expected_endpoints = set(expected_group.pop('endpoints', [])) actual_endpoints = set(actual_group.pop('endpoints', [])) self.assertDictSupersetOf(expected_group, actual_group) if check_endpoints: self.assertEqual(expected_endpoints, actual_endpoints) def helper_create_endpoint_group(self, info): """Create endpoint group database entry and verify OK.""" group = info['endpoint_group'] try: actual = self.plugin.create_endpoint_group(self.context, info) except db_exc.DBError as e: self.fail("Endpoint create in prep for test failed: %s" % e) self._compare_groups(group, actual) self.assertIn('id', actual) return actual['id'] def check_endpoint_group_entry(self, endpoint_group_id, expected_info, should_exist=True): try: endpoint_group = self.plugin.get_endpoint_group(self.context, endpoint_group_id) is_found = True except vpnaas.VPNEndpointGroupNotFound: is_found = False except Exception as e: self.fail("Unexpected exception getting endpoint group: %s" % e) if should_exist != is_found: self.fail("Endpoint group should%(expected)s exist, but " "did%(actual)s exist" % {'expected': '' if should_exist else ' not', 'actual': '' if is_found else ' not'}) if is_found: self._compare_groups(expected_info, endpoint_group) def test_delete_endpoint_group(self): """Test that endpoint group is deleted.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] group_id = self.helper_create_endpoint_group(info) self.check_endpoint_group_entry(group_id, expected, should_exist=True) self.plugin.delete_endpoint_group(self.context, group_id) self.check_endpoint_group_entry(group_id, expected, should_exist=False) self.assertRaises(vpnaas.VPNEndpointGroupNotFound, self.plugin.delete_endpoint_group, self.context, group_id) def test_show_endpoint_group(self): """Test showing a single endpoint group.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] group_id = self.helper_create_endpoint_group(info) self.check_endpoint_group_entry(group_id, expected, should_exist=True) actual = self.plugin.get_endpoint_group(self.context, group_id) self._compare_groups(expected, actual) def test_fail_showing_non_existent_endpoint_group(self): """Test failure to show non-existent endpoint group.""" self.assertRaises(vpnaas.VPNEndpointGroupNotFound, self.plugin.get_endpoint_group, self.context, uuidutils.generate_uuid()) def test_list_endpoint_groups(self): """Test listing multiple endpoint groups.""" # Skip validation for subnets, as validation is checked in other tests mock.patch.object(self.l3_plugin, "get_subnet").start() info1 = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected1 = info1['endpoint_group'] group_id1 = self.helper_create_endpoint_group(info1) self.check_endpoint_group_entry(group_id1, expected1, should_exist=True) info2 = self.prepare_endpoint_info(constants.SUBNET_ENDPOINT, [uuidutils.generate_uuid(), uuidutils.generate_uuid()]) expected2 = info2['endpoint_group'] group_id2 = self.helper_create_endpoint_group(info2) self.check_endpoint_group_entry(group_id2, expected2, should_exist=True) expected1.update({'id': group_id1}) expected2.update({'id': group_id2}) expected_groups = [expected1, expected2] actual_groups = self.plugin.get_endpoint_groups(self.context, fields=('type', 'tenant_id', 'endpoints', 'name', 'description', 'id')) for expected_group, actual_group in zip(expected_groups, actual_groups): self._compare_groups(expected_group, actual_group) def test_update_endpoint_group(self): """Test updating endpoint group information.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] group_id = self.helper_create_endpoint_group(info) self.check_endpoint_group_entry(group_id, expected, should_exist=True) group_updates = {'endpoint_group': {'name': 'new name', 'description': 'new description'}} updated_group = self.plugin.update_endpoint_group(self.context, group_id, group_updates) # Check what was returned, and what is stored in database self._compare_groups(group_updates['endpoint_group'], updated_group) expected.update(group_updates['endpoint_group']) self.check_endpoint_group_entry(group_id, expected, should_exist=True) def test_fail_updating_non_existent_group(self): """Test fail updating a non-existent group.""" group_updates = {'endpoint_group': {'name': 'new name'}} self.assertRaises( vpnaas.VPNEndpointGroupNotFound, self.plugin.update_endpoint_group, self.context, _uuid(), group_updates) def prepare_ike_policy_info(self): return {'ikepolicy': {'tenant_id': self.tenant_id, 'name': 'ike policy', 'description': 'my ike policy', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'main', 'lifetime': {'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5'}} def test_create_ike_policy(self): """Create IKE policy with all settings specified.""" info = self.prepare_ike_policy_info() expected = info['ikepolicy'] new_ike_policy = self.plugin.create_ikepolicy(self.context, info) self.assertDictSupersetOf(expected, new_ike_policy) def prepare_ipsec_policy_info(self): return {'ipsecpolicy': {'tenant_id': self.tenant_id, 'name': 'ipsec policy', 'description': 'my ipsec policy', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'transform_protocol': 'esp', 'lifetime': {'units': 'seconds', 'value': 3600}, 'pfs': 'group5'}} def test_create_ipsec_policy(self): """Create IPsec policy with all settings specified.""" info = self.prepare_ipsec_policy_info() expected = info['ipsecpolicy'] new_ipsec_policy = self.plugin.create_ipsecpolicy(self.context, info) self.assertDictSupersetOf(expected, new_ipsec_policy) def create_vpn_service(self, with_subnet=True): private_subnet, router = self.create_basic_topology() if not with_subnet: private_subnet = None info = self.prepare_service_info(private_subnet, router) return self.plugin.create_vpnservice(self.context, info) def create_ike_policy(self): info = self.prepare_ike_policy_info() return self.plugin.create_ikepolicy(self.context, info) def create_ipsec_policy(self): info = self.prepare_ipsec_policy_info() return self.plugin.create_ipsecpolicy(self.context, info) def create_endpoint_group(self, group_type, endpoints): info = self.prepare_endpoint_info(group_type=group_type, endpoints=endpoints) return self.plugin.create_endpoint_group(self.context, info) def prepare_connection_info(self, service_id, ike_policy_id, ipsec_policy_id, local_id=''): """Creates connection request dictionary. The peer_cidrs, local_ep_group_id, and peer_ep_group_id are set to defaults. Caller must then fill in either CIDRs or endpoints, before creating a connection. """ return {'ipsec_site_connection': {'name': 'my connection', 'description': 'my description', 'peer_id': '192.168.1.10', 'peer_address': '192.168.1.10', 'peer_cidrs': [], 'local_id': local_id, 'mtu': 1500, 'psk': 'shhhh!!!', 'initiator': 'bi-directional', 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120, 'vpnservice_id': service_id, 'ikepolicy_id': ike_policy_id, 'ipsecpolicy_id': ipsec_policy_id, 'admin_state_up': True, 'tenant_id': self._tenant_id, 'local_ep_group_id': None, 'peer_ep_group_id': None}} def build_expected_connection_result(self, info): """Create the expected IPsec connection dict from the request info. The DPD information is stored and converted to a nested dict, instead of individual fields. """ expected = copy.copy(info['ipsec_site_connection']) expected['dpd'] = {'action': expected['dpd_action'], 'interval': expected['dpd_interval'], 'timeout': expected['dpd_timeout']} del expected['dpd_action'] del expected['dpd_interval'] del expected['dpd_timeout'] expected['status'] = 'PENDING_CREATE' return expected def prepare_for_ipsec_connection_create(self, with_subnet=True): service = self.create_vpn_service(with_subnet) ike_policy = self.create_ike_policy() ipsec_policy = self.create_ipsec_policy() return self.prepare_connection_info(service['id'], ike_policy['id'], ipsec_policy['id']) def test_create_ipsec_site_connection_with_peer_cidrs(self): """Create connection using old API with peer CIDRs specified.""" info = self.prepare_for_ipsec_connection_create() info['ipsec_site_connection']['peer_cidrs'] = ['10.1.0.0/24', '10.2.0.0/24'] expected = self.build_expected_connection_result(info) new_conn = self.plugin.create_ipsec_site_connection(self.context, info) self.assertDictSupersetOf(expected, new_conn) def test_create_ipsec_site_connection_with_endpoint_groups(self): """Create connection using new API with endpoint groups.""" # Skip validation, which is tested separately mock.patch.object(self.plugin, '_get_validator').start() local_net = self.create_network(overrides={'name': 'local'}) overrides = {'name': 'local-subnet', 'cidr': '30.0.0.0/24', 'gateway_ip': '30.0.0.1', 'network_id': local_net['id']} local_subnet = self.create_subnet(overrides=overrides) info = self.prepare_for_ipsec_connection_create(with_subnet=False) local_ep_group = self.create_endpoint_group( group_type='subnet', endpoints=[local_subnet['id']]) peer_ep_group = self.create_endpoint_group( group_type='cidr', endpoints=['20.1.0.0/24', '20.2.0.0/24']) info['ipsec_site_connection'].update( {'local_ep_group_id': local_ep_group['id'], 'peer_ep_group_id': peer_ep_group['id']}) expected = self.build_expected_connection_result(info) new_conn = self.plugin.create_ipsec_site_connection(self.context, info) self.assertDictSupersetOf(expected, new_conn) def test_fail_endpoint_group_delete_when_in_use_by_ipsec_conn(self): """Ensure endpoint group is not deleted from under IPSec connection.""" # Skip validation, which is tested separately mock.patch.object(self.plugin, '_get_validator').start() local_net = self.create_network(overrides={'name': 'local'}) overrides = {'name': 'local-subnet', 'cidr': '30.0.0.0/24', 'gateway_ip': '30.0.0.1', 'network_id': local_net['id']} local_subnet = self.create_subnet(overrides=overrides) info = self.prepare_for_ipsec_connection_create(with_subnet=False) local_ep_group = self.create_endpoint_group( group_type='subnet', endpoints=[local_subnet['id']]) peer_ep_group = self.create_endpoint_group( group_type='cidr', endpoints=['20.1.0.0/24', '20.2.0.0/24']) info['ipsec_site_connection'].update( {'local_ep_group_id': local_ep_group['id'], 'peer_ep_group_id': peer_ep_group['id']}) self.plugin.create_ipsec_site_connection(self.context, info) self.assertRaises(vpnaas.EndpointGroupInUse, self.plugin.delete_endpoint_group, self.context, local_ep_group['id']) self.assertRaises(vpnaas.EndpointGroupInUse, self.plugin.delete_endpoint_group, self.context, peer_ep_group['id']) unused_ep_group = self.create_endpoint_group( group_type=constants.CIDR_ENDPOINT, endpoints=['30.0.0.0/24']) self.plugin.delete_endpoint_group(self.context, unused_ep_group['id']) def test_fail_subnet_delete_when_in_use_by_endpoint_group(self): """Ensure don't delete subnet from under endpoint group.""" # mock.patch.object(self.plugin, '_get_validator').start() local_net = self.create_network(overrides={'name': 'local'}) overrides = {'name': 'local-subnet', 'cidr': '30.0.0.0/24', 'gateway_ip': '30.0.0.1', 'network_id': local_net['id']} local_subnet = self.create_subnet(overrides=overrides) self.create_endpoint_group(group_type='subnet', endpoints=[local_subnet['id']]) self.assertRaises(vpnaas.SubnetInUseByEndpointGroup, self.plugin.check_subnet_in_use_by_endpoint_group, self.context, local_subnet['id']) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/0000775000175000017500000000000013370231105022455 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/__init__.py0000666000175000017500000000000013370230606024563 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/api/0000775000175000017500000000000013370231105023226 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/api/__init__.py0000666000175000017500000000000013370230606025334 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/api/base.py0000666000175000017500000001431513370230606024525 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2016 Hewlett Packard Enterprise Development Company LP # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.common.utils import data_utils from neutron_tempest_plugin.api import base from neutron_tempest_plugin import config from neutron_vpnaas.tests.tempest.api import clients CONF = config.CONF class BaseNetworkTest(base.BaseNetworkTest): @classmethod def resource_setup(cls): super(BaseNetworkTest, cls).resource_setup() cls.vpnservices = [] cls.ikepolicies = [] cls.ipsecpolicies = [] cls.ipsec_site_connections = [] cls.endpoint_groups = [] @classmethod def get_client_manager(cls, credential_type=None, roles=None, force_new=None): manager = super(BaseNetworkTest, cls).get_client_manager( credential_type=credential_type, roles=roles, force_new=force_new) # Neutron uses a different clients manager than the one in the Tempest return clients.Manager(manager.credentials) @classmethod def resource_cleanup(cls): if CONF.service_available.neutron: # Clean up ipsec connections for ipsec_site_connection in cls.ipsec_site_connections: cls._try_delete_resource( cls.client.delete_ipsec_site_connection, ipsec_site_connection['id']) # Clean up ipsec endpoint group for endpoint_group in cls.endpoint_groups: cls._try_delete_resource(cls.client.delete_endpoint_group, endpoint_group['id']) # Clean up ipsec policies for ipsecpolicy in cls.ipsecpolicies: cls._try_delete_resource(cls.client.delete_ipsecpolicy, ipsecpolicy['id']) # Clean up ike policies for ikepolicy in cls.ikepolicies: cls._try_delete_resource(cls.client.delete_ikepolicy, ikepolicy['id']) # Clean up vpn services for vpnservice in cls.vpnservices: cls._try_delete_resource(cls.client.delete_vpnservice, vpnservice['id']) super(BaseNetworkTest, cls).resource_cleanup() @classmethod def create_vpnservice(cls, subnet_id, router_id, name=None): """Wrapper utility that returns a test vpn service.""" if name is None: name = data_utils.rand_name("vpnservice-") body = cls.client.create_vpnservice( subnet_id=subnet_id, router_id=router_id, admin_state_up=True, name=name) vpnservice = body['vpnservice'] cls.vpnservices.append(vpnservice) return vpnservice @classmethod def create_vpnservice_no_subnet(cls, router_id): """Wrapper utility that returns a test vpn service.""" body = cls.client.create_vpnservice( router_id=router_id, admin_state_up=True, name=data_utils.rand_name("vpnservice-")) vpnservice = body['vpnservice'] cls.vpnservices.append(vpnservice) return vpnservice @classmethod def create_ikepolicy(cls, name): """Wrapper utility that returns a test ike policy.""" body = cls.client.create_ikepolicy(name=name) ikepolicy = body['ikepolicy'] cls.ikepolicies.append(ikepolicy) return ikepolicy @classmethod def create_ipsecpolicy(cls, name): """Wrapper utility that returns a test ipsec policy.""" body = cls.client.create_ipsecpolicy(name=name) ipsecpolicy = body['ipsecpolicy'] cls.ipsecpolicies.append(ipsecpolicy) return ipsecpolicy @classmethod def create_ipsec_site_connection(cls, ikepolicy_id, ipsecpolicy_id, vpnservice_id, psk="secret", peer_address="172.24.4.233", peer_id="172.24.4.233", peer_cidrs=None, name=None): """Wrapper utility that returns a test vpn connection.""" if peer_cidrs is None: peer_cidrs = ['1.1.1.0/24', '2.2.2.0/24'] if name is None: name = data_utils.rand_name("ipsec_site_connection-") body = cls.client.create_ipsec_site_connection( psk=psk, initiator="bi-directional", ipsecpolicy_id=ipsecpolicy_id, admin_state_up=True, mtu=1500, ikepolicy_id=ikepolicy_id, vpnservice_id=vpnservice_id, peer_address=peer_address, peer_id=peer_id, peer_cidrs=peer_cidrs, name=name) ipsec_site_connection = body['ipsec_site_connection'] cls.ipsec_site_connections.append(ipsec_site_connection) return ipsec_site_connection @classmethod def create_endpoint_group(cls, name, type, endpoints): """Wrapper utility that returns a test ipsec policy.""" body = cls.client.create_endpoint_group( endpoints=endpoints, type=type, description='endpoint type:' + type, name=name) endpoint_group = body['endpoint_group'] cls.endpoint_groups.append(endpoint_group) return endpoint_group class BaseAdminNetworkTest(BaseNetworkTest): credentials = ['primary', 'admin'] @classmethod def setup_clients(cls): super(BaseAdminNetworkTest, cls).setup_clients() cls.admin_client = cls.os_admin.network_client cls.identity_admin_client = cls.os_admin.tenants_client neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/api/clients.py0000666000175000017500000000462413370230606025256 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2016 Hewlett Packard Enterprise Development Company # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_tempest_plugin.api import clients as manager from neutron_tempest_plugin import config from neutron_tempest_plugin.services.network.json import network_client CONF = config.CONF class NetworkClient(network_client.NetworkClientJSON): def pluralize(self, resource_name): resource_plural_map = { 'ikepolicy': 'ikepolicies', 'ipsecpolicy': 'ipsecpolicies' } if resource_name in resource_plural_map: return resource_plural_map.get(resource_name) return super(NetworkClient, self).pluralize(resource_name) def get_uri(self, plural_name): # get service prefix from resource name service_resource_prefix_list = [ 'vpnservices', 'ikepolicies', 'ipsecpolicies', 'ipsec_site_connections', 'endpoint_groups', ] if plural_name in service_resource_prefix_list: plural_name = plural_name.replace("_", "-") service_prefix = 'vpn' uri = '%s/%s/%s' % (self.uri_prefix, service_prefix, plural_name) return uri return super(NetworkClient, self).get_uri(plural_name) class Manager(manager.Manager): def __init__(self, credentials=None, service=None): super(Manager, self).__init__(credentials=credentials) self.network_client = NetworkClient( self.auth_provider, CONF.network.catalog_type, CONF.network.region or CONF.identity.region, endpoint_type=CONF.network.endpoint_type, build_interval=CONF.network.build_interval, build_timeout=CONF.network.build_timeout, **self.default_params) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/api/test_vpnaas.py0000666000175000017500000011607213370230615026145 0ustar zuulzuul00000000000000# Copyright 2012,2016 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib.db import constants as db_const from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc from tempest import test from neutron_tempest_plugin import config from neutron_vpnaas.tests.tempest.api import base CONF = config.CONF _LONG_NAME = 'x' * (db_const.NAME_FIELD_SIZE + 1) _LONG_DESCRIPTION = 'y' * (db_const.DESCRIPTION_FIELD_SIZE + 1) class VPNaaSTestJSON(base.BaseAdminNetworkTest): """ Tests the following operations in the Neutron API using the REST client for Neutron: List, Show, Create, Delete, and Update VPN Service List, Show, Create, Delete, and Update IKE policy List, Show, Create, Delete, and Update IPSec policy """ @classmethod def resource_setup(cls): if not test.is_extension_enabled('vpnaas', 'network'): msg = "vpnaas extension not enabled." raise cls.skipException(msg) super(VPNaaSTestJSON, cls).resource_setup() cls.ext_net_id = CONF.network.public_network_id network_name = data_utils.rand_name('network-') cls.network = cls.create_network(network_name) cls.subnet = cls.create_subnet(cls.network) cls.router = cls.create_router( data_utils.rand_name("router"), external_network_id=CONF.network.public_network_id) cls.create_router_interface(cls.router['id'], cls.subnet['id']) cls.vpnservice = cls.create_vpnservice(cls.subnet['id'], cls.router['id']) vpnservice2 = cls.create_vpnservice_no_subnet(cls.router['id']) cls.vpnservice_no_subnet = vpnservice2 cls.ikepolicy = cls.create_ikepolicy( data_utils.rand_name("ike-policy-")) cls.ipsecpolicy = cls.create_ipsecpolicy( data_utils.rand_name("ipsec-policy-")) cls.endpoint_group_local = cls.create_endpoint_group( data_utils.rand_name("endpoint-group-local-"), 'subnet', cls.subnet['id']) cls.endpoint_group_remote = cls.create_endpoint_group( data_utils.rand_name("endpoint-group-remote-"), 'cidr', ["10.101.0.0/24", "10.102.0.0/24"]) cls.ipsec_site_connection = cls.create_ipsec_site_connection( cls.ikepolicy['id'], cls.ipsecpolicy['id'], cls.vpnservice['id']) def _delete_ike_policy(self, ike_policy_id): # Deletes a ike policy and verifies if it is deleted or not ike_list = list() all_ike = self.client.list_ikepolicies() for ike in all_ike['ikepolicies']: ike_list.append(ike['id']) if ike_policy_id in ike_list: self.client.delete_ikepolicy(ike_policy_id) # Asserting that the policy is not found in list after deletion ikepolicies = self.client.list_ikepolicies() ike_id_list = list() for i in ikepolicies['ikepolicies']: ike_id_list.append(i['id']) self.assertNotIn(ike_policy_id, ike_id_list) def _delete_ipsec_policy(self, ipsec_policy_id): # Deletes an ike policy if it exists try: self.client.delete_ipsecpolicy(ipsec_policy_id) except lib_exc.NotFound: pass def _delete_ipsec_site_connection(self, conn_id): # Deletes an ipsec site connection if it exists try: self.client.delete_ipsec_site_connection(conn_id) except lib_exc.NotFound: pass def _assertExpected(self, expected, actual): # Check if not expected keys/values exists in actual response body for key, value in expected.items(): self.assertIn(key, actual) self.assertEqual(value, actual[key]) def _delete_vpn_service(self, vpn_service_id): self.client.delete_vpnservice(vpn_service_id) # Asserting if vpn service is found in the list after deletion body = self.client.list_vpnservices() vpn_services = [vs['id'] for vs in body['vpnservices']] self.assertNotIn(vpn_service_id, vpn_services) def _delete_endpoint_group(self, endpoint_group_id): # Delete a endpoint-group and verifies if it is deleted or not endpoint_group_list = list() all_endpoint = self.client.list_endpoint_groups() for endpoint in all_endpoint['endpoint_groups']: endpoint_group_list.append(endpoint['id']) if endpoint_group_id in endpoint_group_list: self.client.delete_endpoint_group(endpoint_group_id) # Asserting that the endpoint is not found in list after deletion endpoint_group = self.client.list_endpoint_groups() for e in endpoint_group['endpoint_groups']: endpoint_group_list.append(e['id']) self.assertNotIn(endpoint_group_list, endpoint_group_id) def _get_tenant_id(self): """ Returns the tenant_id of the client current user """ # TODO(jroovers) This is a temporary workaround to get the tenant_id # of the the current client. Replace this once tenant_isolation for # neutron is fixed. body = self.client.show_network(self.network['id']) return body['network']['tenant_id'] @decorators.attr(type='smoke') def test_admin_create_ipsec_policy_for_tenant(self): tenant_id = self._get_tenant_id() # Create IPSec policy for the newly created tenant name = data_utils.rand_name('ipsec-policy') body = (self.admin_client. create_ipsecpolicy(name=name, tenant_id=tenant_id)) ipsecpolicy = body['ipsecpolicy'] self.assertIsNotNone(ipsecpolicy['id']) self.addCleanup(self.admin_client.delete_ipsecpolicy, ipsecpolicy['id']) # Assert that created ipsec policy is found in API list call body = self.client.list_ipsecpolicies() ipsecpolicies = [policy['id'] for policy in body['ipsecpolicies']] self.assertIn(ipsecpolicy['id'], ipsecpolicies) @decorators.attr(type='smoke') def test_admin_create_vpn_service_for_tenant(self): tenant_id = self._get_tenant_id() # Create vpn service for the newly created tenant network2 = self.create_network() subnet2 = self.create_subnet(network2) router2 = self.create_router(data_utils.rand_name('router-'), external_network_id=self.ext_net_id) self.create_router_interface(router2['id'], subnet2['id']) name = data_utils.rand_name('vpn-service') body = self.admin_client.create_vpnservice( subnet_id=subnet2['id'], router_id=router2['id'], name=name, admin_state_up=True, tenant_id=tenant_id) vpnservice = body['vpnservice'] self.assertIsNotNone(vpnservice['id']) self.addCleanup(self.admin_client.delete_vpnservice, vpnservice['id']) # Assert that created vpnservice is found in API list call body = self.client.list_vpnservices() vpn_services = [vs['id'] for vs in body['vpnservices']] self.assertIn(vpnservice['id'], vpn_services) @decorators.attr(type='smoke') def test_admin_create_ike_policy_for_tenant(self): tenant_id = self._get_tenant_id() # Create IKE policy for the newly created tenant name = data_utils.rand_name('ike-policy') body = (self.admin_client. create_ikepolicy(name=name, ike_version="v1", encryption_algorithm="aes-128", auth_algorithm="sha1", tenant_id=tenant_id)) ikepolicy = body['ikepolicy'] self.assertIsNotNone(ikepolicy['id']) self.addCleanup(self.admin_client.delete_ikepolicy, ikepolicy['id']) # Assert that created ike policy is found in API list call body = self.client.list_ikepolicies() ikepolicies = [ikp['id'] for ikp in body['ikepolicies']] self.assertIn(ikepolicy['id'], ikepolicies) @decorators.attr(type='smoke') def test_list_vpn_services(self): # Verify the VPN service exists in the list of all VPN services body = self.client.list_vpnservices() vpnservices = body['vpnservices'] self.assertIn(self.vpnservice['id'], [v['id'] for v in vpnservices]) @decorators.attr(type='smoke') def test_create_update_delete_vpn_service(self): # Creates a VPN service and sets up deletion network1 = self.create_network() subnet1 = self.create_subnet(network1) router1 = self.create_router(data_utils.rand_name('router-'), external_network_id=self.ext_net_id) self.create_router_interface(router1['id'], subnet1['id']) name = data_utils.rand_name('vpn-service1') body = self.client.create_vpnservice(subnet_id=subnet1['id'], router_id=router1['id'], name=name, admin_state_up=True) vpnservice = body['vpnservice'] self.addCleanup(self._delete_vpn_service, vpnservice['id']) # Assert if created vpnservices are not found in vpnservices list body = self.client.list_vpnservices() vpn_services = [vs['id'] for vs in body['vpnservices']] self.assertIsNotNone(vpnservice['id']) self.assertIn(vpnservice['id'], vpn_services) # TODO(raies): implement logic to update vpnservice # VPNaaS client function to update is implemented. # But precondition is that current state of vpnservice # should be "ACTIVE" not "PENDING*" @decorators.attr(type='smoke') def test_show_vpn_service(self): # Verifies the details of a vpn service body = self.client.show_vpnservice(self.vpnservice['id']) vpnservice = body['vpnservice'] self.assertEqual(self.vpnservice['id'], vpnservice['id']) self.assertEqual(self.vpnservice['name'], vpnservice['name']) self.assertEqual(self.vpnservice['description'], vpnservice['description']) self.assertEqual(self.vpnservice['router_id'], vpnservice['router_id']) self.assertEqual(self.vpnservice['subnet_id'], vpnservice['subnet_id']) self.assertEqual(self.vpnservice['tenant_id'], vpnservice['tenant_id']) valid_status = ["ACTIVE", "DOWN", "BUILD", "ERROR", "PENDING_CREATE", "PENDING_UPDATE", "PENDING_DELETE"] self.assertIn(vpnservice['status'], valid_status) @decorators.attr(type='smoke') def test_list_ike_policies(self): # Verify the ike policy exists in the list of all IKE policies body = self.client.list_ikepolicies() ikepolicies = body['ikepolicies'] self.assertIn(self.ikepolicy['id'], [i['id'] for i in ikepolicies]) @decorators.attr(type='smoke') def test_create_update_delete_ike_policy(self): # Creates a IKE policy name = data_utils.rand_name('ike-policy') body = (self.client.create_ikepolicy( name=name, ike_version="v1", encryption_algorithm="aes-128", auth_algorithm="sha1")) ikepolicy = body['ikepolicy'] self.assertIsNotNone(ikepolicy['id']) self.addCleanup(self._delete_ike_policy, ikepolicy['id']) # Update IKE Policy new_ike = {'name': data_utils.rand_name("New-IKE"), 'description': "Updated ike policy", 'encryption_algorithm': "aes-256", 'ike_version': "v2", 'pfs': "group14", 'lifetime': {'units': "seconds", 'value': 2000}} self.client.update_ikepolicy(ikepolicy['id'], **new_ike) # Confirm that update was successful by verifying using 'show' body = self.client.show_ikepolicy(ikepolicy['id']) ike_policy = body['ikepolicy'] for key, value in new_ike.items(): self.assertIn(key, ike_policy) self.assertEqual(value, ike_policy[key]) # Verification of ike policy delete self.client.delete_ikepolicy(ikepolicy['id']) body = self.client.list_ikepolicies() ikepolicies = [ikp['id'] for ikp in body['ikepolicies']] self.assertNotIn(ike_policy['id'], ikepolicies) @decorators.attr(type='smoke') def test_show_ike_policy(self): # Verifies the details of a ike policy body = self.client.show_ikepolicy(self.ikepolicy['id']) ikepolicy = body['ikepolicy'] self.assertEqual(self.ikepolicy['id'], ikepolicy['id']) self.assertEqual(self.ikepolicy['name'], ikepolicy['name']) self.assertEqual(self.ikepolicy['description'], ikepolicy['description']) self.assertEqual(self.ikepolicy['encryption_algorithm'], ikepolicy['encryption_algorithm']) self.assertEqual(self.ikepolicy['auth_algorithm'], ikepolicy['auth_algorithm']) self.assertEqual(self.ikepolicy['tenant_id'], ikepolicy['tenant_id']) self.assertEqual(self.ikepolicy['pfs'], ikepolicy['pfs']) self.assertEqual(self.ikepolicy['phase1_negotiation_mode'], ikepolicy['phase1_negotiation_mode']) self.assertEqual(self.ikepolicy['ike_version'], ikepolicy['ike_version']) @decorators.attr(type='smoke') def test_list_ipsec_policies(self): # Verify the ipsec policy exists in the list of all ipsec policies body = self.client.list_ipsecpolicies() ipsecpolicies = body['ipsecpolicies'] self.assertIn(self.ipsecpolicy['id'], [i['id'] for i in ipsecpolicies]) @decorators.attr(type='smoke') def test_create_update_delete_ipsec_policy(self): # Creates an ipsec policy ipsec_policy_body = {'name': data_utils.rand_name('ipsec-policy'), 'pfs': 'group5', 'encryption_algorithm': "aes-128", 'auth_algorithm': 'sha1'} resp_body = self.client.create_ipsecpolicy(**ipsec_policy_body) ipsecpolicy = resp_body['ipsecpolicy'] self.addCleanup(self._delete_ipsec_policy, ipsecpolicy['id']) self._assertExpected(ipsec_policy_body, ipsecpolicy) # Verification of ipsec policy update new_ipsec = {'description': 'Updated ipsec policy', 'pfs': 'group2', 'name': data_utils.rand_name("New-IPSec"), 'encryption_algorithm': "aes-256", 'lifetime': {'units': "seconds", 'value': '2000'}} body = self.client.update_ipsecpolicy(ipsecpolicy['id'], **new_ipsec) updated_ipsec_policy = body['ipsecpolicy'] self._assertExpected(new_ipsec, updated_ipsec_policy) # Verification of ipsec policy delete self.client.delete_ipsecpolicy(ipsecpolicy['id']) self.assertRaises(lib_exc.NotFound, self.client.delete_ipsecpolicy, ipsecpolicy['id']) @decorators.attr(type='smoke') def test_show_ipsec_policy(self): # Verifies the details of an ipsec policy body = self.client.show_ipsecpolicy(self.ipsecpolicy['id']) ipsecpolicy = body['ipsecpolicy'] self._assertExpected(self.ipsecpolicy, ipsecpolicy) @decorators.attr(type=['negative', 'smoke']) def test_create_vpnservice_long_name(self): """ Test excessively long name. Without REST checks, this call would return 500 INTERNAL SERVER error on internal db failure instead. """ name = _LONG_NAME self.assertRaises( lib_exc.BadRequest, self.client.create_vpnservice, subnet_id=self.subnet['id'], router_id=self.router['id'], name=name, admin_state_up=True) @decorators.attr(type=['negative', 'smoke']) def test_create_vpnservice_long_description(self): name = data_utils.rand_name('vpn-service1') description = _LONG_DESCRIPTION self.assertRaises( lib_exc.BadRequest, self.client.create_vpnservice, subnet_id=self.subnet['id'], router_id=self.router['id'], name=name, description=description, admin_state_up=True) @decorators.attr(type='smoke') def test_list_vpn_connections(self): # Verify the VPN service exists in the list of all VPN services body = self.client.list_ipsec_site_connections() ipsec_site_connections = body['ipsec_site_connections'] self.assertIn(self.ipsec_site_connection['id'], [v['id'] for v in ipsec_site_connections]) @decorators.attr(type='smoke') def test_create_delete_vpn_connection_with_legacy_mode(self): # Verify create VPN connection name = data_utils.rand_name("ipsec_site_connection-") body = self.client.create_ipsec_site_connection( ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", peer_cidrs=['10.1.1.0/24', '10.2.2.0/24'], name=name, mtu=1500, admin_state_up=True, initiator="bi-directional", psk="secret") ipsec_site_connection = body['ipsec_site_connection'] self.assertEqual(ipsec_site_connection['name'], name) self.assertEqual(ipsec_site_connection['mtu'], 1500) self.addCleanup(self._delete_ipsec_site_connection, ipsec_site_connection['id']) # Verification of IPsec connection delete self.client.delete_ipsec_site_connection(ipsec_site_connection['id']) body = self.client.list_ipsec_site_connections() ipsec_site_connections = body['ipsec_site_connections'] self.assertNotIn(ipsec_site_connection['id'], [v['id'] for v in ipsec_site_connections]) @decorators.attr(type=['negative', 'smoke']) def test_create_vpn_connection_missing_peer_cidr(self): # Verify create VPN connection with JSON missing peer cidr # in legacy mode name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", name=name, mtu=1500, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_vpn_service_subnet_not_on_router(self): # Verify create VPN service with a subnet not on router tenant_id = self._get_tenant_id() # Create vpn service for the newly created tenant network2 = self.create_network() subnet2 = self.create_subnet(network2) router2 = self.create_router(data_utils.rand_name('router-'), external_network_id=self.ext_net_id) self.addCleanup(self.admin_client.delete_router, router2['id']) self.addCleanup(self.admin_client.delete_network, network2['id']) name = data_utils.rand_name('vpn-service') self.assertRaises( lib_exc.BadRequest, self.admin_client.create_vpnservice, subnet_id=subnet2['id'], router_id=router2['id'], name=name, admin_state_up=True, tenant_id=tenant_id) @decorators.attr(type=['negative', 'smoke']) def test_create_vpn_connection_small_MTU(self): # Verify create VPN connection with small MTU name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", peer_cidrs=['10.1.1.0/24', '10.2.2.0/24'], name=name, mtu=63, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_vpn_connection_small_dpd(self): # Verify create VPN connection with small dpd name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", peer_cidrs=['10.1.1.0/24', '10.2.2.0/24'], name=name, dpd=59, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_vpn_connection_wrong_peer_cidr(self): # Verify create VPN connection with wrong peer cidr name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", peer_cidrs=['1.0.0.0/33'], name=name, mtu=1500, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_connection_with_cidr_and_endpoint_group(self): tenant_id = self._get_tenant_id() # Create endpoint group for the newly created tenant name = data_utils.rand_name('endpoint_group') subnet_id = self.subnet['id'] body = self.client.create_endpoint_group( tenant_id=tenant_id, name=name, type='subnet', endpoints=subnet_id) endpoint_group_local = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group_local['id']) name = data_utils.rand_name('endpoint_group') body = self.client.create_endpoint_group( tenant_id=tenant_id, name=name, type='cidr', endpoints=["10.103.0.0/24", "10.104.0.0/24"]) endpoint_group_remote = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group_remote['id']) # Create connections name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice_no_subnet['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", peer_cidr="10.1.0.0/24", peer_ep_group_id=endpoint_group_local['id'], local_ep_group_id=endpoint_group_remote['id'], name=name, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_vpn_connection_with_missing_remote_endpoint_group(self): # Verify create VPN connection without subnet in vpnservice # and has only local endpoint group tenant_id = self._get_tenant_id() # Create endpoint group for the newly created tenant tenant_id = self._get_tenant_id() name = data_utils.rand_name('endpoint_group') subnet_id = self.subnet['id'] body = self.client.create_endpoint_group( tenant_id=tenant_id, name=name, type='subnet', endpoints=subnet_id) endpoint_group = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group['id']) # Create connections name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice_no_subnet['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", local_ep_group_id=endpoint_group['id'], name=name, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_vpn_connection_with_missing_local_endpoint_group(self): # Verify create VPN connection without subnet in vpnservice # and only have only local endpoint group tenant_id = self._get_tenant_id() # Create endpoint group for the newly created tenant tenant_id = self._get_tenant_id() name = data_utils.rand_name('endpoint_group') body = self.client.create_endpoint_group( tenant_id=tenant_id, name=name, type='cidr', endpoints=["10.101.0.0/24", "10.102.0.0/24"]) endpoint_group = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group['id']) # Create connections name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice_no_subnet['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", peer_ep_group_id=endpoint_group['id'], name=name, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_connection_with_mix_ip_endpoint_group(self): tenant_id = self._get_tenant_id() # Create endpoint group for the newly created tenant name = data_utils.rand_name('endpoint_group') subnet_id = self.subnet['id'] body = self.client.create_endpoint_group( tenant_id=tenant_id, name=name, type='subnet', endpoints=subnet_id) endpoint_group_local = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group_local['id']) name_v6 = data_utils.rand_name('endpoint_group') body_v6 = self.client.create_endpoint_group( tenant_id=tenant_id, name=name_v6, type='cidr', endpoints=["fec0:101::/64", "fec0:102::/64"]) endpoint_group_remote = body_v6['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group_remote['id']) # Create connections name = data_utils.rand_name("ipsec_site_connection-") self.assertEqual(endpoint_group_local['type'], 'subnet') self.assertEqual(endpoint_group_remote['type'], 'cidr') self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice_no_subnet['id'], peer_address="172.24.4.233", peer_id="172.24.4.233", peer_ep_group_id=endpoint_group_local['id'], local_ep_group_id=endpoint_group_remote['id'], name=name, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_connection_with_subnet_and_remote_endpoint_group(self): tenant_id = self._get_tenant_id() # Create endpoint group for the newly created tenant name = data_utils.rand_name('endpoint_group') body = self.client.create_endpoint_group( tenant_id=tenant_id, name=name, type='cidr', endpoints=["10.101.0.0/24", "10.102.0.0/24"]) endpoint_group = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group['id']) # Create connections name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice['id'], peer_address="172.24.4.233", peer_ep_group_id=endpoint_group['id'], name=name, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type=['negative', 'smoke']) def test_create_connection_with_subnet_and_local_endpoint_group(self): tenant_id = self._get_tenant_id() # Create endpoint group for the newly created tenant name = data_utils.rand_name('endpoint_group') subnet_id = self.subnet['id'] body = self.client.create_endpoint_group( tenant_id=tenant_id, name=name, type='subnet', endpoints=subnet_id) endpoint_group = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group['id']) # Create connections name = data_utils.rand_name("ipsec_site_connection-") self.assertRaises( lib_exc.BadRequest, self.client.create_ipsec_site_connection, ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice['id'], peer_address="172.24.4.233", local_ep_group_id=endpoint_group['id'], name=name, admin_state_up=True, initiator="bi-directional", psk="secret") @decorators.attr(type='smoke') def test_create_update_delete_endpoint_group(self): # Creates a endpoint-group name = data_utils.rand_name('endpoint_group') body = (self.client.create_endpoint_group( name=name, type='cidr', endpoints=["10.2.0.0/24", "10.3.0.0/24"])) endpoint_group = body['endpoint_group'] self.assertIsNotNone(endpoint_group['id']) self.addCleanup(self._delete_endpoint_group, endpoint_group['id']) # Update endpoint-group body = {'name': data_utils.rand_name("new_endpoint_group")} self.client.update_endpoint_group(endpoint_group['id'], name=name) # Confirm that update was successful by verifying using 'show' body = self.client.show_endpoint_group(endpoint_group['id']) endpoint_group = body['endpoint_group'] self.assertEqual(name, endpoint_group['name']) # Verification of endpoint-group delete endpoint_group_id = endpoint_group['id'] self.client.delete_endpoint_group(endpoint_group['id']) body = self.client.list_endpoint_groups() endpoint_group = [enp['id'] for enp in body['endpoint_groups']] self.assertNotIn(endpoint_group_id, endpoint_group) @decorators.attr(type='smoke') def test_admin_create_endpoint_group_for_tenant(self): # Create endpoint group for the newly created tenant tenant_id = self._get_tenant_id() name = data_utils.rand_name('endpoint_group') body = (self.client. create_endpoint_group( name=name, type='cidr', endpoints=["10.2.0.0/24", "10.3.0.0/24"], tenant_id=tenant_id)) endpoint_group = body['endpoint_group'] self.assertIsNotNone(endpoint_group['id']) self.addCleanup(self._delete_endpoint_group, endpoint_group['id']) # Assert that created endpoint group is found in API list call endpoint_group_id = endpoint_group['id'] self.client.delete_endpoint_group(endpoint_group['id']) body = self.client.list_endpoint_groups() endpoint_group = [enp['id'] for enp in body['endpoint_groups']] self.assertNotIn(endpoint_group_id, endpoint_group) @decorators.attr(type='smoke') def test_show_endpoint_group(self): # Verifies the details of an endpoint group body = self.client.show_endpoint_group(self.endpoint_group_local['id']) endpoint_group = body['endpoint_group'] self.assertEqual(self.endpoint_group_local['id'], endpoint_group['id']) self.assertEqual(self.endpoint_group_local['name'], endpoint_group['name']) self.assertEqual(self.endpoint_group_local['description'], endpoint_group['description']) self.assertEqual(self.endpoint_group_local['tenant_id'], endpoint_group['tenant_id']) self.assertEqual(self.endpoint_group_local['type'], endpoint_group['type']) self.assertEqual(self.endpoint_group_local['endpoints'], endpoint_group['endpoints']) # Verifies the details of an endpoint group body = self.client.show_endpoint_group( self.endpoint_group_remote['id']) endpoint_group = body['endpoint_group'] #endpoint_group_remote = endpoint_group['id'] self.assertEqual(self.endpoint_group_remote['id'], endpoint_group['id']) self.assertEqual(self.endpoint_group_remote['name'], endpoint_group['name']) self.assertEqual(self.endpoint_group_remote['description'], endpoint_group['description']) self.assertEqual(self.endpoint_group_remote['tenant_id'], endpoint_group['tenant_id']) self.assertEqual(self.endpoint_group_remote['type'], endpoint_group['type']) self.assertEqual(self.endpoint_group_remote['endpoints'], endpoint_group['endpoints']) @decorators.attr(type='smoke') def test_create_delete_vpn_connection_with_ep_group(self): # Creates a endpoint-group with type cidr name = data_utils.rand_name('endpoint_group') body = self.client.create_endpoint_group( name=name, type='cidr', endpoints=["10.2.0.0/24", "10.3.0.0/24"]) endpoint_group_remote = body['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group_remote['id']) # Creates a endpoint-group with type subnet name = data_utils.rand_name('endpoint_group') subnet_id = self.subnet['id'] body2 = self.client.create_endpoint_group( name=name, type='subnet', endpoints=subnet_id) endpoint_group_local = body2['endpoint_group'] self.addCleanup(self._delete_endpoint_group, endpoint_group_local['id']) # Verify create VPN connection name = data_utils.rand_name("ipsec_site_connection-") body = self.client.create_ipsec_site_connection( ipsecpolicy_id=self.ipsecpolicy['id'], ikepolicy_id=self.ikepolicy['id'], vpnservice_id=self.vpnservice_no_subnet['id'], peer_ep_group_id=endpoint_group_remote['id'], local_ep_group_id=endpoint_group_local['id'], name=name, mtu=1500, admin_state_up=True, initiator="bi-directional", peer_address="172.24.4.233", peer_id="172.24.4.233", psk="secret") ipsec_site_connection = body['ipsec_site_connection'] self.assertEqual(ipsec_site_connection['name'], name) self.assertEqual(ipsec_site_connection['mtu'], 1500) self.addCleanup(self._delete_ipsec_site_connection, ipsec_site_connection['id']) # Verification of IPsec connection delete self.client.delete_ipsec_site_connection(ipsec_site_connection['id']) body = self.client.list_ipsec_site_connections() ipsec_site_connections = body['ipsec_site_connections'] self.assertNotIn(ipsec_site_connection['id'], [v['id'] for v in ipsec_site_connections]) @decorators.attr(type=['negative', 'smoke']) def test_fail_create_endpoint_group_when_wrong_type(self): # Creates a endpoint-group with wrong type name = data_utils.rand_name('endpoint_group') self.assertRaises( lib_exc.BadRequest, self.client.create_endpoint_group, name=name, type='subnet', endpoints=["10.2.0.0/24", "10.3.0.0/24"]) @decorators.attr(type=['negative', 'smoke']) def test_fail_create_endpoint_group_when_provide_subnet_id_with_cidr(self): # Creates a endpoint-group when provide subnet id with type cidr name = data_utils.rand_name('endpoint_group') subnet_id = self.subnet['id'] self.assertRaises( lib_exc.BadRequest, self.client.create_endpoint_group, name=name, type='cidr', endpoints=subnet_id) @decorators.attr(type=['negative', 'smoke']) def test_fail_create_endpoint_group_with_mixed_IP_version(self): # Creates a endpoint-group with mixed IP version name = data_utils.rand_name('endpoint_group') self.assertRaises( lib_exc.BadRequest, self.client.create_endpoint_group, name=name, type='cidr', endpoints=["10.2.0.0/24", "2000::1"]) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/services/0000775000175000017500000000000013370231105024300 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/services/__init__.py0000666000175000017500000000000013370230606026406 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/README.rst0000666000175000017500000000032313370230606024151 0ustar zuulzuul00000000000000=============================================== Tempest Integration of neutron-vpnaas =============================================== This directory contains Tempest tests to cover the neutron-vpnaas project. neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/scenario/0000775000175000017500000000000013370231105024260 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/scenario/__init__.py0000666000175000017500000000000013370230606026366 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/scenario/base.py0000666000175000017500000000150313370230606025552 0ustar zuulzuul00000000000000# Copyright (c) 2017 Midokura SARL # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_tempest_plugin.scenario import base from neutron_vpnaas.tests.tempest.api import base as base_api class BaseTempestTestCase(base_api.BaseNetworkTest, base.BaseTempestTestCase): pass neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/scenario/test_vpnaas.py0000666000175000017500000001554413370230615027201 0ustar zuulzuul00000000000000# Copyright (c) 2017 Midokura SARL # 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 netaddr from tempest.common import utils from tempest.common import waiters from tempest.lib.common import ssh from tempest.lib.common.utils import data_utils from tempest.lib import decorators from neutron_tempest_plugin import config from neutron_tempest_plugin.scenario import constants from neutron_vpnaas.tests.tempest.scenario import base CONF = config.CONF class Vpnaas(base.BaseTempestTestCase): """Test the following topology +-------------------+ | public | | network | | | +-+---------------+-+ | | | | +-------+-+ +-+-------+ | LEFT | | RIGHT | | router | <--VPN--> | router | | | | | +----+----+ +----+----+ | | +----+----+ +----+----+ | LEFT | | RIGHT | | network | | network | | | | | +---------+ +---------+ """ credentials = ['primary', 'admin'] @classmethod @utils.requires_ext(extension="vpnaas", service="network") def resource_setup(cls): super(Vpnaas, cls).resource_setup() # common cls.keypair = cls.create_keypair() cls.secgroup = cls.os_primary.network_client.create_security_group( name=data_utils.rand_name('secgroup-'))['security_group'] cls.security_groups.append(cls.secgroup) cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id']) cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id']) cls.ikepolicy = cls.create_ikepolicy( data_utils.rand_name("ike-policy-")) cls.ipsecpolicy = cls.create_ipsecpolicy( data_utils.rand_name("ipsec-policy-")) # LEFT cls.router = cls.create_router( data_utils.rand_name('left-router'), admin_state_up=True, external_network_id=CONF.network.public_network_id) cls.network = cls.create_network(network_name='left-network') cls.subnet = cls.create_subnet(cls.network, name='left-subnet') cls.create_router_interface(cls.router['id'], cls.subnet['id']) # RIGHT cls._right_network, cls._right_subnet, cls._right_router = \ cls._create_right_network() @classmethod def _create_right_network(cls): router = cls.create_router( data_utils.rand_name('right-router'), admin_state_up=True, external_network_id=CONF.network.public_network_id) network = cls.create_network(network_name='right-network') subnet = cls.create_subnet(network, cidr=netaddr.IPNetwork('10.10.0.0/24'), name='right-subnet') cls.create_router_interface(router['id'], subnet['id']) return network, subnet, router def _create_server(self, create_floating_ip=True, network=None): if network is None: network = self.network port = self.create_port(network, security_groups=[self.secgroup['id']]) if create_floating_ip: fip = self.create_and_associate_floatingip(port['id']) else: fip = None server = self.create_server( flavor_ref=CONF.compute.flavor_ref, image_ref=CONF.compute.image_ref, key_name=self.keypair['name'], networks=[{'port': port['id']}])['server'] waiters.wait_for_server_status(self.os_primary.servers_client, server['id'], constants.SERVER_STATUS_ACTIVE) return {'port': port, 'fip': fip, 'server': server} def _setup_vpn(self): sites = [ dict(name="left", network=self.network, subnet=self.subnet, router=self.router), dict(name="right", network=self._right_network, subnet=self._right_subnet, router=self._right_router), ] psk = data_utils.rand_name('mysecret') for i in range(0, 2): site = sites[i] site['vpnservice'] = self.create_vpnservice( site['subnet']['id'], site['router']['id'], name=data_utils.rand_name('%s-vpnservice' % site['name'])) for i in range(0, 2): site = sites[i] vpnservice = site['vpnservice'] peer = sites[1 - i] peer_address = peer['vpnservice']['external_v4_ip'] self.create_ipsec_site_connection( self.ikepolicy['id'], self.ipsecpolicy['id'], vpnservice['id'], peer_address=peer_address, peer_id=peer_address, peer_cidrs=[peer['subnet']['cidr']], psk=psk, name=data_utils.rand_name( '%s-ipsec-site-connection' % site['name'])) @decorators.idempotent_id('aa932ab2-63aa-49cf-a2a0-8ae71ac2bc24') def test_vpnaas(self): # RIGHT right_server = self._create_server(network=self._right_network, create_floating_ip=False) # LEFT left_server = self._create_server() ssh_client = ssh.Client(left_server['fip']['floating_ip_address'], CONF.validation.image_ssh_user, pkey=self.keypair['private_key']) # check LEFT -> RIGHT connectivity via VPN self.check_remote_connectivity(ssh_client, right_server['port']['fixed_ips'][0]['ip_address'], should_succeed=False) self._setup_vpn() self.check_remote_connectivity(ssh_client, right_server['port']['fixed_ips'][0]['ip_address']) # Assign a floating-ip and check connectivity. # This is NOT via VPN. fip = self.create_and_associate_floatingip(right_server['port']['id']) self.check_remote_connectivity(ssh_client, fip['floating_ip_address']) # check LEFT -> RIGHT connectivity via VPN again, to ensure # the above floating-ip doesn't interfere the traffic. self.check_remote_connectivity(ssh_client, right_server['port']['fixed_ips'][0]['ip_address']) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/tempest/plugin.py0000666000175000017500000000213413370230606024334 0ustar zuulzuul00000000000000# Copyright 2015 # 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 os from tempest.test_discover import plugins import neutron_vpnaas class VPNTempestPlugin(plugins.TempestPlugin): def load_tests(self): base_path = os.path.split(os.path.dirname( os.path.abspath(neutron_vpnaas.__file__)))[0] test_dir = "neutron_vpnaas/tests/tempest" full_test_dir = os.path.join(base_path, test_dir) return full_test_dir, base_path def register_opts(self, conf): pass def get_opt_lists(self): pass neutron-vpnaas-12.0.1/neutron_vpnaas/tests/contrib/0000775000175000017500000000000013370231105022434 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/contrib/filters.template0000666000175000017500000000137313370230606025654 0ustar zuulzuul00000000000000# neutron-rootwrap command filters to support functional testing. It # is NOT intended to be used outside of a test environment. # # This file should be owned by (and only-writable by) the root user [Filters] # '$BASE_PATH' is intended to be replaced with the expected tox path # (e.g. /opt/stack/new/neutron/.tox/dsvm-functional) by the neutron # functional jenkins job. This ensures that tests can kill the # processes that they launch with their containing tox environment's # python. kill_tox_python: KillFilter, root, $BASE_PATH/bin/python, -9 # enable ping from namespace ping_filter: CommandFilter, ping, root # enable curl from namespace curl_filter: CommandFilter, curl, root tee_filter: CommandFilter, tee, root tee_kill: KillFilter, root, tee, -9 neutron-vpnaas-12.0.1/neutron_vpnaas/tests/contrib/README0000666000175000017500000000022713370230606023324 0ustar zuulzuul00000000000000The files in this directory are intended for use by the infra jobs that run the various functional test suite in the gate for the neutron-vpnaas repo. neutron-vpnaas-12.0.1/neutron_vpnaas/tests/contrib/functional-test-rootwrap.conf0000666000175000017500000000223213370230606030303 0ustar zuulzuul00000000000000# Configuration for neutron-rootwrap # This file should be owned by (and only-writable by) the root user [DEFAULT] # List of directories to load filter definitions from (separated by ','). # These directories MUST all be only writeable by root ! filters_path=/etc/neutron/rootwrap.d,/usr/share/neutron/rootwrap # List of directories to search executables in, in case filters do not # explicitely specify a full path (separated by ',') # If not specified, defaults to system PATH environment variable. # These directories MUST all be only writeable by root ! exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin # Enable logging to syslog # Default value is False use_syslog=False # Which syslog facility to use. # Valid values include auth, authpriv, syslog, local0, local1... # Default value is 'syslog' syslog_log_facility=syslog # Which messages to log. # INFO means log all usage # ERROR means only log unsuccessful attempts syslog_log_level=ERROR [xenapi] # XenAPI configuration is only required by the L2 agent if it is to # target a XenServer/XCP compute host's dom0. xenapi_connection_url= xenapi_connection_username=root xenapi_connection_password= neutron-vpnaas-12.0.1/neutron_vpnaas/tests/contrib/gate_hook.sh0000777000175000017500000000175313370230606024750 0ustar zuulzuul00000000000000#!/usr/bin/env bash set -ex VENV=${1:-"dsvm-functional"} DEVSTACK_LOCAL_CONFIG="NETWORK_API_EXTENSIONS=all" DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas" export DEVSTACK_LOCAL_CONFIG case $VENV in dsvm-functional | dsvm-functional-sswan) # The following need to be set before sourcing # configure_for_func_testing. GATE_DEST=$BASE/new GATE_STACK_USER=stack NEUTRON_PATH=$GATE_DEST/neutron PROJECT_NAME=neutron-vpnaas NEUTRON_VPN_PATH=$GATE_DEST/$PROJECT_NAME DEVSTACK_PATH=$GATE_DEST/devstack IS_GATE=True USE_CONSTRAINT_ENV=False source $NEUTRON_VPN_PATH/tools/configure_for_vpn_func_testing.sh # Make the workspace owned by the stack user sudo chown -R $STACK_USER:$STACK_USER $BASE configure_host_for_vpn_func_testing ;; api|tempest) $BASE/new/devstack-gate/devstack-vm-gate.sh ;; esac neutron-vpnaas-12.0.1/neutron_vpnaas/tests/contrib/functional-testing.filters0000666000175000017500000000407113370230606027654 0ustar zuulzuul00000000000000# neutron-rootwrap command filters to support functional testing. It # is NOT intended to be used outside of a test environment. # # This file should be owned by (and only-writable by) the root user [Filters] # enable ping from namespace ping_filter: CommandFilter, ping, root ping6_filter: CommandFilter, ping6, root # enable curl from namespace curl_filter: CommandFilter, curl, root tee_filter: CommandFilter, tee, root tee_kill: KillFilter, root, tee, -9 nc_filter: CommandFilter, nc, root # netcat has different binaries depending on linux distribution nc_kill: KillFilter, root, nc, -9 ncbsd_kill: KillFilter, root, nc.openbsd, -9 ncat_kill: KillFilter, root, ncat, -9 ss_filter: CommandFilter, ss, root # arping arping: CommandFilter, arping, root # l3_agent sysctl: CommandFilter, sysctl, root route: CommandFilter, route, root radvd: CommandFilter, radvd, root # metadata proxy metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root # RHEL invocation of the metadata proxy will report /usr/bin/python kill_metadata: KillFilter, root, python, -9 kill_metadata7: KillFilter, root, python2.7, -9 kill_radvd_usr: KillFilter, root, /usr/sbin/radvd, -9, -HUP kill_radvd: KillFilter, root, /sbin/radvd, -9, -HUP # ip_lib ip: IpFilter, ip, root find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.* ip_exec: IpNetnsExecFilter, ip, root # For ip monitor kill_ip_monitor: KillFilter, root, ip, -9 # ovs_lib (if OVSInterfaceDriver is used) ovs-vsctl: CommandFilter, ovs-vsctl, root # iptables_manager iptables-save: CommandFilter, iptables-save, root iptables-restore: CommandFilter, iptables-restore, root ip6tables-save: CommandFilter, ip6tables-save, root ip6tables-restore: CommandFilter, ip6tables-restore, root # Keepalived keepalived: CommandFilter, keepalived, root kill_keepalived: KillFilter, root, /usr/sbin/keepalived, -HUP, -15, -9 # l3 agent to delete floatingip's conntrack state conntrack: CommandFilter, conntrack, root # keepalived state change monitor keepalived_state_change: CommandFilter, neutron-keepalived-state-change, root neutron-vpnaas-12.0.1/neutron_vpnaas/tests/contrib/post_test_hook.sh0000777000175000017500000000220113370230615026041 0ustar zuulzuul00000000000000#!/usr/bin/env bash set -xe NEUTRON_VPNAAS_DIR="$BASE/new/neutron-vpnaas" TEMPEST_CONFIG_DIR="$BASE/new/tempest/etc" SCRIPTS_DIR="/usr/os-testr-env/bin" VENV=${1:-"dsvm-functional"} function generate_testr_results { # Give job user rights to access tox logs sudo -H -u $owner chmod o+rw . sudo -H -u $owner chmod o+rw -R .stestr if [ -f ".stestr/0" ] ; then .tox/$VENV/bin/subunit-1to2 < .stestr/0 > ./stestr.subunit $SCRIPTS_DIR/subunit2html ./stestr.subunit testr_results.html gzip -9 ./stestr.subunit gzip -9 ./testr_results.html sudo mv ./*.gz /opt/stack/logs/ fi } case $VENV in dsvm-functional | dsvm-functional-sswan) owner=stack sudo_env= # Set owner permissions according to job's requirements. cd $NEUTRON_VPNAAS_DIR sudo chown -R $owner:stack $NEUTRON_VPNAAS_DIR echo "Running neutron $VENV test suite" set +e sudo -H -u $owner $sudo_env tox -e $VENV testr_exit_code=$? set -e # Collect and parse results generate_testr_results exit $testr_exit_code ;; esac neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/0000775000175000017500000000000013370231105023136 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/__init__.py0000666000175000017500000000000013370230606025244 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/common/0000775000175000017500000000000013370231105024426 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/common/README0000666000175000017500000000037013370230606025315 0ustar zuulzuul00000000000000This area holds tests that are run for all functional jobs. The load_tests() method in each implementation specific area will include tests in this area. Do not place a test module in this area, if it is specific to one particular implementation. neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/common/__init__.py0000666000175000017500000000000013370230606026534 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/common/test_scenario.py0000666000175000017500000007243513370230615027664 0ustar zuulzuul00000000000000# 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 collections import copy import functools import mock import netaddr from neutron.agent.common import ovs_lib from neutron.agent.l3 import agent as neutron_l3_agent from neutron.agent.l3 import l3_agent_extensions_manager as ext_manager from neutron.agent.l3 import namespaces as n_namespaces from neutron.agent.l3 import router_info from neutron.agent import l3_agent as l3_agent_main from neutron.agent.linux import external_process from neutron.agent.linux import interface from neutron.agent.linux import ip_lib from neutron.agent.linux import utils as linux_utils from neutron.common import utils as common_utils from neutron.conf.agent import common as agent_config from neutron.conf import common as common_config from neutron.services.provider_configuration import serviceprovider_opts from neutron.tests.common import l3_test_common from neutron.tests.common import net_helpers from neutron.tests.functional import base from neutron_lib import constants from neutron_lib.utils import net as n_utils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import uuidutils import testtools from neutron_vpnaas.services.vpn import agent as vpn_agent from neutron_vpnaas.services.vpn.agent import vpn_agent_opts from neutron_vpnaas.services.vpn.device_drivers import ipsec _uuid = uuidutils.generate_uuid FAKE_IKE_POLICY = { 'auth_algorithm': 'sha1', "ike_version": "v1", 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'phase1_negotiation_mode': 'main', 'lifetime_units': 'seconds', 'lifetime_value': 3600 } FAKE_IPSEC_POLICY = { "encapsulation_mode": "tunnel", "encryption_algorithm": "aes-128", "pfs": "group5", "lifetime_units": "seconds", "lifetime_value": 3600, "transform_protocol": "esp", "auth_algorithm": "sha1", } FAKE_VPN_SERVICE = { "id": _uuid(), "router_id": _uuid(), "status": constants.PENDING_CREATE, "admin_state_up": True, 'external_ip': "172.24.4.8" } FAKE_IPSEC_CONNECTION = { "vpnservice_id": _uuid(), "status": "PENDING_CREATE", "psk": "969022489", "initiator": "bi-directional", "admin_state_up": True, "auth_mode": "psk", 'external_ip': "172.24.4.8", "peer_cidrs": ["10.100.255.224/28"], "mtu": 1500, "dpd_action": "hold", "dpd_interval": 30, "dpd_timeout": 120, "route_mode": "static", "ikepolicy": FAKE_IKE_POLICY, "ipsecpolicy": FAKE_IPSEC_POLICY, "peer_address": "172.24.4.8", "peer_id": "172.24.4.8", "id": _uuid() } FAKE_IKE_POLICY_SHA256 = { 'auth_algorithm': 'sha256', "ike_version": "v1", 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'phase1_negotiation_mode': 'main', 'lifetime_units': 'seconds', 'lifetime_value': 3600 } FAKE_IPSEC_POLICY_SHA256 = { "encapsulation_mode": "tunnel", "encryption_algorithm": "aes-128", "pfs": "group5", "lifetime_units": "seconds", "lifetime_value": 3600, "transform_protocol": "esp", "auth_algorithm": "sha256", } FAKE_IPSEC_CONNECTION_SHA256 = { "vpnservice_id": _uuid(), "status": "PENDING_CREATE", "psk": "969022489", "initiator": "bi-directional", "admin_state_up": True, "auth_mode": "psk", 'external_ip': "172.24.4.8", "peer_cidrs": ["10.100.255.224/28"], "mtu": 1500, "dpd_action": "hold", "dpd_interval": 30, "dpd_timeout": 120, "route_mode": "static", "ikepolicy": FAKE_IKE_POLICY_SHA256, "ipsecpolicy": FAKE_IPSEC_POLICY_SHA256, "peer_address": "172.24.4.8", "peer_id": "172.24.4.8", "id": _uuid() } PUBLIC_NET = netaddr.IPNetwork('19.4.4.0/24') PRIVATE_NET = netaddr.IPNetwork('35.4.0.0/16') FAKE_PUBLIC_SUBNET_ID = _uuid() FAKE_PRIVATE_SUBNET_ID = _uuid() MAC_BASE = cfg.CONF.base_mac.split(':') FAKE_ROUTER = { 'enable_snat': True, 'gw_port': { 'network_id': _uuid(), 'subnets': [ { 'cidr': str(PUBLIC_NET), 'gateway_ip': str(PUBLIC_NET[1]), 'id': FAKE_PUBLIC_SUBNET_ID } ], 'fixed_ips': [ { 'subnet_id': FAKE_PUBLIC_SUBNET_ID, 'prefixlen': PUBLIC_NET.prefixlen, } ], }, 'distributed': False, '_floatingips': [], 'routes': [] } def get_ovs_bridge(br_name): return ovs_lib.OVSBridge(br_name) Vm = collections.namedtuple('Vm', ['namespace', 'port_ip']) class SiteInfo(object): """Holds info on the router, ports, service, and connection.""" def __init__(self, public_net, private_nets): self.public_net = public_net self.private_nets = private_nets self.generate_router_info() self._prepare_vpn_service_info() def _generate_private_interface_for_router(self, subnet): subnet_id = _uuid() return { 'id': _uuid(), 'admin_state_up': True, 'network_id': _uuid(), 'mtu': 1500, 'mac_address': n_utils.get_random_mac(MAC_BASE), 'subnets': [ { 'ipv6_ra_mode': None, 'cidr': str(subnet), 'gateway_ip': str(subnet[1]), 'id': subnet_id, 'ipv6_address_mode': None } ], 'fixed_ips': [ { 'subnet_id': subnet_id, 'prefixlen': 24, 'ip_address': str(subnet[4]) } ] } def generate_router_info(self): self.info = copy.deepcopy(FAKE_ROUTER) self.info['id'] = _uuid() self.info['project_id'] = _uuid() self.info['_interfaces'] = [ self._generate_private_interface_for_router(subnet) for subnet in self.private_nets] self.info['gw_port']['id'] = _uuid() self.info['gw_port']['fixed_ips'][0]['ip_address'] = str( self.public_net) self.info['gw_port']['mac_address'] = ( n_utils.get_random_mac(MAC_BASE)) self.info['ha'] = False def _prepare_vpn_service_info(self): self.vpn_service = copy.deepcopy(FAKE_VPN_SERVICE) self.vpn_service.update({'id': _uuid(), 'router_id': self.info['id'], 'external_ip': str(self.public_net)}) def prepare_ipsec_conn_info(self, peer, connection=FAKE_IPSEC_CONNECTION, local_id=None, peer_id=None): ipsec_connection = copy.deepcopy(connection) local_cidrs = [str(s) for s in self.private_nets] peer_cidrs = [str(s) for s in peer.private_nets] ipsec_connection.update({ 'id': _uuid(), 'vpnservice_id': self.vpn_service['id'], 'external_ip': self.vpn_service['external_ip'], 'peer_cidrs': peer_cidrs, 'peer_address': peer.vpn_service['external_ip'], 'peer_id': peer.vpn_service['external_ip'], 'local_cidrs': local_cidrs, 'local_ip_vers': 4 }) if local_id: ipsec_connection['local_id'] = local_id if peer_id: ipsec_connection['peer_id'] = peer_id self.vpn_service['ipsec_site_connections'] = [ipsec_connection] class SiteInfoWithHaRouter(SiteInfo): def __init__(self, public_net, private_nets, host, failover_host): self.host = host self.failover_host = failover_host self.get_ns_name = mock.patch.object(n_namespaces.RouterNamespace, '_get_ns_name').start() super(SiteInfoWithHaRouter, self).__init__(public_net, private_nets) def generate_router_info(self): super(SiteInfoWithHaRouter, self).generate_router_info() self.info['ha'] = True self.info['ha_vr_id'] = 1 self.info[constants.HA_INTERFACE_KEY] = ( l3_test_common.get_ha_interface()) # Mock router namespace name, for when router is created self.get_ns_name.return_value = "qrouter-{0}-{1}".format( self.info['id'], self.host) def generate_backup_router_info(self): # Clone router info, using different HA interface (using same ID) info = copy.deepcopy(self.info) info[constants.HA_INTERFACE_KEY] = ( l3_test_common.get_ha_interface(ip='169.254.192.2', mac='22:22:22:22:22:22')) # Mock router namespace name, for when router is created self.get_ns_name.return_value = "qrouter-{0}-{1}".format( info['id'], self.failover_host) return info class TestIPSecBase(base.BaseSudoTestCase): NESTED_NAMESPACE_SEPARATOR = '@' def setUp(self): super(TestIPSecBase, self).setUp() mock.patch('neutron.agent.l3.agent.L3PluginApi').start() mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.' 'IPsecVpnDriverApi').start() # avoid report_status running periodically mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall').start() # Both the vpn agents try to use execute_rootwrap_daemon's socket # simultaneously during test cleanup, but execute_rootwrap_daemon has # limitations with simultaneous reads. So avoid using # root_helper_daemon and instead use root_helper # https://bugs.launchpad.net/neutron/+bug/1482622 cfg.CONF.set_override('root_helper_daemon', None, group='AGENT') # Mock the method below because it causes Exception: # RuntimeError: Second simultaneous read on fileno 5 detected. # Unless you really know what you're doing, make sure that only # one greenthread can read any particular socket. Consider using # a pools.Pool. If you do know what you're doing and want to disable # this error, call eventlet.debug.hub_prevent_multiple_readers(False) # Can reproduce the exception in the test only ip_lib.send_ip_addr_adv_notif = mock.Mock() self.conf = self._configure_agent('agent1') self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1', self.conf) self.vpn_agent = vpn_agent.L3WithVPNaaS(self.conf) self.driver = self.vpn_agent.device_drivers[0] self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[]) self.driver.report_status = mock.Mock() self.private_nets = list(PRIVATE_NET.subnet(24)) def _connect_agents(self, agent1, agent2): """Simulate both agents in the same host. For packet flow between resources connected to these two agents, agent's ovs bridges are connected through patch ports. """ br_int_1 = get_ovs_bridge(agent1.conf.ovs_integration_bridge) br_int_2 = get_ovs_bridge(agent2.conf.ovs_integration_bridge) net_helpers.create_patch_ports(br_int_1, br_int_2) br_ex_1 = get_ovs_bridge(agent1.conf.external_network_bridge) br_ex_2 = get_ovs_bridge(agent2.conf.external_network_bridge) net_helpers.create_patch_ports(br_ex_1, br_ex_2) def _get_config_opts(self): """Register default config options""" config = cfg.ConfigOpts() config.register_opts(common_config.core_opts) config.register_opts(common_config.core_cli_opts) config.register_opts(serviceprovider_opts, 'service_providers') config.register_opts(vpn_agent_opts, 'vpnagent') config.register_opts(ipsec.ipsec_opts, 'ipsec') config.register_opts(ipsec.openswan_opts, 'openswan') logging.register_options(config) agent_config.register_process_monitor_opts(config) ext_manager.register_opts(config) return config def _configure_agent(self, host): """Override specific config options""" config = self._get_config_opts() l3_agent_main.register_opts(config) cfg.CONF.set_override('debug', True) agent_config.setup_logging() config.set_override('extensions', ['vpnaas'], 'agent') config.set_override( 'interface_driver', 'neutron.agent.linux.interface.OVSInterfaceDriver') br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge br_ex = self.useFixture(net_helpers.OVSBridgeFixture()).bridge config.set_override('ovs_integration_bridge', br_int.br_name) config.set_override('external_network_bridge', br_ex.br_name) temp_dir = self.get_new_temp_dir() get_temp_file_path = functools.partial(self.get_temp_file_path, root=temp_dir) config.set_override('state_path', temp_dir.path) config.set_override('metadata_proxy_socket', get_temp_file_path('metadata_proxy')) config.set_override('ha_confs_path', get_temp_file_path('ha_confs')) config.set_override('external_pids', get_temp_file_path('external/pids')) config.set_override('host', host) ipsec_config_base_dir = '%s/%s' % (temp_dir.path, 'ipsec') config.set_override('config_base_dir', ipsec_config_base_dir, group='ipsec') # Assign ip address to br-ex port because it is a gateway ex_port = ip_lib.IPDevice(br_ex.br_name) ex_port.addr.add(str(PUBLIC_NET[1])) return config def _setup_failover_agent(self): self.failover_agent = self._configure_agent('agent2') self._connect_agents(self.vpn_agent, self.failover_agent) self.failover_driver = self.failover_agent.device_drivers[0] self.failover_driver.agent_rpc.get_vpn_services_on_host = ( mock.Mock(return_value=[])) self.failover_driver.report_status = mock.Mock() def create_router(self, agent, info): """Create router for agent from router info.""" self.addCleanup(agent._safe_router_removed, info['id']) # Generate unique internal and external router device names using the # agent's hostname. This is to allow multiple HA router replicas to # co-exist on the same machine, otherwise they'd all use the same # device names and OVS would freak out(OVS won't allow a port with # same name connected to two bridges). def _append_suffix(dev_name): # If dev_name = 'xyz123' and the suffix is 'agent2' then the result # will be 'xy-nt2' return "{0}-{1}".format(dev_name[:-4], agent.host[-3:]) def get_internal_device_name(port_id): return _append_suffix( (n_namespaces.INTERNAL_DEV_PREFIX + port_id) [:interface.LinuxInterfaceDriver.DEV_NAME_LEN]) def get_external_device_name(port_id): return _append_suffix( (n_namespaces.EXTERNAL_DEV_PREFIX + port_id) [:interface.LinuxInterfaceDriver.DEV_NAME_LEN]) mock_get_internal_device_name = mock.patch.object( router_info.RouterInfo, 'get_internal_device_name').start() mock_get_internal_device_name.side_effect = get_internal_device_name mock_get_external_device_name = mock.patch.object( router_info.RouterInfo, 'get_external_device_name').start() mock_get_external_device_name.side_effect = get_external_device_name # NOTE(huntxu): with commit 88f5e11d8bf, neutron plugs new ports as # dead vlan(4095). During functional tests, all the ports are untagged. # So need to remove such tag during functional testing. original_plug_new = interface.OVSInterfaceDriver.plug_new def plug_new(self, *args, **kwargs): original_plug_new(self, *args, **kwargs) bridge = (kwargs.get('bridge') or args[4] or self.conf.ovs_integration_bridge) device_name = kwargs.get('device_name') or args[2] ovsbr = ovs_lib.OVSBridge(bridge) ovsbr.clear_db_attribute('Port', device_name, 'tag') with mock.patch( 'neutron.agent.linux.interface.OVSInterfaceDriver.plug_new', autospec=True ) as ovs_plug_new: ovs_plug_new.side_effect = plug_new agent._process_added_router(info) return agent.router_info[info['id']] def _port_first_ip_cidr(self, port): fixed_ip = port['fixed_ips'][0] return common_utils.ip_to_cidr(fixed_ip['ip_address'], fixed_ip['prefixlen']) def create_ports_for(self, site): """Creates namespaces and ports for simulated VM. There will be a unique namespace for each port, which is representing a VM for the test. """ bridge = get_ovs_bridge(self.vpn_agent.conf.ovs_integration_bridge) site.vm = [] for internal_port in site.router.internal_ports: router_ip_cidr = self._port_first_ip_cidr(internal_port) port_ip_cidr = net_helpers.increment_ip_cidr(router_ip_cidr, 1) client_ns = self.useFixture( net_helpers.NamespaceFixture()).ip_wrapper namespace = client_ns.namespace port = self.useFixture( net_helpers.OVSPortFixture(bridge, namespace)).port port.addr.add(port_ip_cidr) port.route.add_gateway(router_ip_cidr.partition('/')[0]) site.vm.append(Vm(namespace, port_ip_cidr.partition('/')[0])) def create_site(self, public_net, private_nets, l3ha=False): """Build router(s), namespaces, and ports for a site. For HA, we'll create a backup router and wait for both routers to be ready, so that we can test pings after failover. """ if l3ha: site = SiteInfoWithHaRouter(public_net, private_nets, self.agent.host, self.failover_agent.host) else: site = SiteInfo(public_net, private_nets) site.router = self.create_router(self.agent, site.info) if l3ha: backup_info = site.generate_backup_router_info() site.backup_router = self.create_router(self.failover_agent, backup_info) linux_utils.wait_until_true( lambda: site.router.ha_state == 'master') linux_utils.wait_until_true( lambda: site.backup_router.ha_state == 'backup') self.create_ports_for(site) return site def prepare_ipsec_site_connections(self, site1, site2): """Builds info for connections in both directions in prep for sync.""" site1.prepare_ipsec_conn_info(site2) site2.prepare_ipsec_conn_info(site1) def prepare_ipsec_site_connections_sha256(self, site1, site2): """Builds info for connections in both directions in prep for sync.""" site1.prepare_ipsec_conn_info(site2, FAKE_IPSEC_CONNECTION_SHA256) site2.prepare_ipsec_conn_info(site1, FAKE_IPSEC_CONNECTION_SHA256) def prepare_ipsec_site_connections_local_id(self, site1, site2): """Builds info for connections in both directions in prep for sync.""" site1.prepare_ipsec_conn_info(site2, local_id='@site1.com', peer_id='@site2.com') site2.prepare_ipsec_conn_info(site1, local_id='@site2.com', peer_id='@site1.com') def sync_to_create_ipsec_connections(self, site1, site2): """Perform a sync, so that connections are created.""" # Provide service info to sync self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[site1.vpn_service, site2.vpn_service]) local_router_id = site1.router.router_id peer_router_id = site2.router.router_id self.driver.sync(mock.Mock(), [{'id': local_router_id}, {'id': peer_router_id}]) self.agent._process_updated_router(site1.router.router) self.agent._process_updated_router(site2.router.router) self.addCleanup(self.driver._delete_vpn_processes, [local_router_id, peer_router_id], []) def sync_failover_agent(self, site): """Perform a sync on failover agent associated w/backup router.""" self.failover_driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[site.vpn_service]) self.failover_driver.sync(mock.Mock(), [{'id': site.backup_router.router_id}]) def check_ping(self, from_site, to_site, instance=0, success=True): if success: net_helpers.assert_ping(from_site.vm[instance].namespace, to_site.vm[instance].port_ip, timeout=8, count=4) else: net_helpers.assert_no_ping(from_site.vm[instance].namespace, to_site.vm[instance].port_ip, timeout=8, count=4) def _failover_ha_router(self, router1, router2): """Cause a failover of HA router. Fail the agent1's HA router. Agent1's HA router will transition to backup and agent2's HA router will become master. Wait for the failover to complete. """ device_name = router1.get_ha_device_name() ha_device = ip_lib.IPDevice(device_name, router1.ns_name) ha_device.link.set_down() linux_utils.wait_until_true(lambda: router2.ha_state == 'master') linux_utils.wait_until_true(lambda: router1.ha_state == 'backup') def _ipsec_process_exists(self, conf, router, pid_files): """Check if *Swan process has started up.""" for pid_file in pid_files: pm = external_process.ProcessManager( conf, "ipsec", router.ns_name, pid_file=pid_file) if pm.active: break return pm.active def _wait_for_ipsec_startup(self, router, driver, conf, should_run=True): """Wait for new IPSec process on failover agent to start up.""" # check for both strongswan and openswan processes path = driver.processes[router.router_id].config_dir pid_files = ['%s/var/run/charon.pid' % path, '%s/var/run/pluto.pid' % path] linux_utils.wait_until_true( lambda: should_run == self._ipsec_process_exists( conf, router, pid_files)) @staticmethod def _update_vpnservice(site, **kwargs): site.vpn_service.update(kwargs) @staticmethod def _update_ipsec_connection(site, **kwargs): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection.update(kwargs) class TestIPSecScenario(TestIPSecBase): @testtools.skip('bug/1598466') def test_single_ipsec_connection(self): site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_single_ipsec_connection_sha256(self): site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections_sha256(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_single_ipsec_connection_local_id(self): site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections_local_id(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_ipsec_site_connections_with_mulitple_subnets(self): """Check with a pair of subnets on each end of connection.""" site1 = self.create_site(PUBLIC_NET[4], self.private_nets[1:3]) site2 = self.create_site(PUBLIC_NET[5], self.private_nets[3:5]) # Just check from each VM, not every combination for i in [0, 1]: self.check_ping(site1, site2, instance=i, success=False) self.check_ping(site2, site1, instance=i, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) for i in [0, 1]: self.check_ping(site1, site2, instance=i) self.check_ping(site2, site1, instance=i) @testtools.skip('bug/1598466') def test_ipsec_site_connections_with_l3ha_routers(self): """Test ipsec site connection with HA routers. This test creates two agents. First agent will have Legacy and HA routers. Second agent will host only HA router. We setup ipsec connection between legacy and HA router. When HA router is created, agent1 will have master router and agent2 will have backup router. Ipsec connection will be established between legacy router and agent1's master HA router. Then we fail the agent1's master HA router. Agent1's HA router will transition to backup and agent2's HA router will become master. Now ipsec connection will be established between legacy router and agent2's master HA router """ self._setup_failover_agent() site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]], l3ha=True) # No ipsec connection between legacy router and HA routers self.check_ping(site1, site2, 0, success=False) self.check_ping(site2, site1, 0, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.sync_failover_agent(site2) # Test ipsec connection between legacy router and agent2's HA router self.check_ping(site1, site2, 0) self.check_ping(site2, site1, 0) self._failover_ha_router(site2.router, site2.backup_router) self._wait_for_ipsec_startup(site2.backup_router, self.failover_driver, self.failover_agent.conf) # Test ipsec connection between legacy router and agent2's HA router self.check_ping(site1, site2, 0) self.check_ping(site2, site1, 0) @testtools.skip('bug/1598466') def _test_admin_state_up(self, update_method): # Create ipsec connection between two sites site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) # Disable resource on one of the sites and check that # ping no longer passes. update_method(site1, admin_state_up=False) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2, 0, success=False) self.check_ping(site2, site1, 0, success=False) # Validate that ipsec process for the disabled site was terminated. self._wait_for_ipsec_startup(site1.router, self.driver, self.vpn_agent.conf, should_run=False) # Change admin_state_up of the disabled resource back to True and # check that everything works again. update_method(site1, admin_state_up=True) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_ipsec_site_connections_update_admin_state_up(self): """Test updating admin_state_up of ipsec site connections.""" self._test_admin_state_up(self._update_ipsec_connection) @testtools.skip('bug/1598466') def test_vpnservice_update_admin_state_up(self): """Test updating admin_state_up of a vpn service.""" self._test_admin_state_up(self._update_vpnservice) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/common/test_migrations_sync.py0000666000175000017500000000435413370230606031264 0ustar zuulzuul00000000000000# Copyright 2015 Mirantis 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 oslo_config import cfg from neutron.db.migration.alembic_migrations import external from neutron.db.migration import cli as migration from neutron.tests.functional.db import test_migrations from neutron.tests.unit import testlib_api from neutron_vpnaas.db.models import head EXTERNAL_TABLES = set(external.TABLES) - set(external.VPNAAS_TABLES) VERSION_TABLE = 'alembic_version_vpnaas' class _TestModelsMigrationsVPNAAS(test_migrations._TestModelsMigrations): def db_sync(self, engine): cfg.CONF.set_override('connection', engine.url, group='database') for conf in migration.get_alembic_configs(): self.alembic_config = conf self.alembic_config.neutron_config = cfg.CONF migration.do_alembic_command(conf, 'upgrade', 'heads') def get_metadata(self): return head.get_metadata() def include_object(self, object_, name, type_, reflected, compare_to): if type_ == 'table' and (name.startswith('alembic') or name == VERSION_TABLE or name in EXTERNAL_TABLES): return False if type_ == 'index' and reflected and name.startswith("idx_autoinc_"): return False return True class TestModelsMigrationsMysql(testlib_api.MySQLTestCaseMixin, _TestModelsMigrationsVPNAAS, testlib_api.SqlTestCaseLight): pass class TestModelsMigrationsPostgresql(testlib_api.PostgreSQLTestCaseMixin, _TestModelsMigrationsVPNAAS, testlib_api.SqlTestCaseLight): pass neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/openswan/0000775000175000017500000000000013370231105024770 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/openswan/README0000666000175000017500000000020013370230606025647 0ustar zuulzuul00000000000000This area holds tests for the OpenSwan implementation (only). It will also run tests in neutron_vpnaas/tests/functional/common. neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/openswan/__init__.py0000666000175000017500000000171213370230606027111 0ustar zuulzuul00000000000000# 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 os def load_tests(loader, tests, pattern): this_dir = os.path.dirname(__file__) openswan_tests = loader.discover(start_dir=this_dir, pattern=pattern) tests.addTests(openswan_tests) common_dir = os.path.abspath(os.path.join(this_dir, "../common")) common_tests = loader.discover(start_dir=common_dir, pattern=pattern) tests.addTests(common_tests) return tests neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py0000666000175000017500000001251413370230615031620 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. import mock from neutron.agent.linux import ip_lib from neutron.agent.linux import utils as linux_utils from neutron_vpnaas.services.vpn.device_drivers import ipsec from neutron_vpnaas.tests.functional.common import test_scenario from oslo_config import cfg class TestOpenSwanDeviceDriver(test_scenario.TestIPSecBase): """Test the OpenSwan reference implementation of the device driver.""" # NOTE: Tests may be added/removed/changed, when this is fleshed out # in future commits. def _ping_mtu(self, from_site, to_site, size, instance=0): """Pings ip address using packets of given size and with DF=1. In order to ping it uses following cli command: ip netns exec ping -c 4 -M do -s """ namespace = from_site.vm[instance].namespace ip = to_site.vm[instance].port_ip try: cmd = ['ping', '-c', 4, '-M', 'do', '-s', size, ip] cmd = ip_lib.add_namespace_to_cmd(cmd, namespace) linux_utils.execute(cmd, run_as_root=True) return True except RuntimeError: return False def test_process_created_on_ipsec_connection_create(self): """Check that pluto process is running.""" pass def test_connection_status_with_one_side_of_ipsec_connection(self): """Check status of connection, with only one end created. Expect that the status will indicate that the connection is down. """ pass def test_process_gone_on_ipsec_connection_delete(self): """Verify that there is no longer a process, upon deletion.""" pass def test_cached_status_on_create_and_delete(self): """Test that the status is cached.""" pass def test_status_reporting(self): """Test status reported correctly to agent.""" pass def _override_mtu_for_site(self, site, mtu): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['mtu'] = mtu def test_ipsec_site_connections_mtu_enforcement(self): """Test that mtu of ipsec site connections is enforced.""" site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) # Set up non-default mtu value self._override_mtu_for_site(site1, 1200) self._override_mtu_for_site(site2, 1200) self.sync_to_create_ipsec_connections(site1, site2) # Validate that ip packets with 1172 (1200) bytes of data pass self.assertTrue(self._ping_mtu(site1, site2, 1172)) self.assertTrue(self._ping_mtu(site2, site1, 1172)) # Validate that ip packets with 1173 (1201) bytes of data are dropped self.assertFalse(self._ping_mtu(site1, site2, 1173)) self.assertFalse(self._ping_mtu(site2, site1, 1173)) def test_no_config_change_skip_restart(self): """Test when config is not changed, then restart should be skipped""" site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) with mock.patch.object(ipsec.OpenSwanProcess, 'start') as my_start: ipsec.OpenSwanProcess.active = mock.patch.object( ipsec.OpenSwanProcess, 'active', return_value=True).start() ipsec.OpenSwanProcess._config_changed = mock.patch.object( ipsec.OpenSwanProcess, '_config_changed', return_value=False).start() self.sync_to_create_ipsec_connections(site1, site2) # when restart_check_config is not set, start will be # called in each sync self.assertEqual(2, my_start.call_count) my_start.reset_mock() cfg.CONF.set_override('restart_check_config', True, group='pluto') self.sync_to_create_ipsec_connections(site1, site2) # after restart_check_config enabled, then start will # not be called, since no config changes my_start.assert_not_called() ipsec.OpenSwanProcess.active.stop() ipsec.OpenSwanProcess._config_changed.stop() cfg.CONF.set_override('restart_check_config', False, group='pluto') neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/strongswan/0000775000175000017500000000000013370231105025343 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/strongswan/README0000666000175000017500000000020213370230606026224 0ustar zuulzuul00000000000000This area holds tests for the StrongSwan implementation (only). It will also run tests in neutron_vpnaas/tests/functional/common. neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/strongswan/__init__.py0000666000175000017500000000167513370230606027474 0ustar zuulzuul00000000000000# 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 os def load_tests(loader, tests, pattern): this_dir = os.path.dirname(__file__) strongswan_tests = loader.discover(start_dir=this_dir, pattern=pattern) tests.addTests(strongswan_tests) common_dir = os.path.join(this_dir, "../common") common_tests = loader.discover(start_dir=common_dir, pattern=pattern) tests.addTests(common_tests) return tests neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/strongswan/test_strongswan_driver.py0000666000175000017500000002277513370230615032560 0ustar zuulzuul00000000000000# Copyright (c) 2015 Canonical, 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. import os import mock from neutron.agent.l3 import agent as neutron_l3_agent from neutron.agent.l3 import legacy_router from neutron.conf.agent.l3 import config as l3_config from neutron.tests.functional import base from neutron_lib import constants from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.services.vpn import agent as vpn_agent from neutron_vpnaas.services.vpn.device_drivers import ipsec from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec from neutron_vpnaas.tests.functional.common import test_scenario _uuid = uuidutils.generate_uuid FAKE_ROUTER_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION1_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION2_ID = _uuid() FAKE_IKE_POLICY = { 'ike_version': 'v1', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5' } FAKE_IPSEC_POLICY = { 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5' } FAKE_VPN_SERVICE = { 'id': _uuid(), 'router_id': FAKE_ROUTER_ID, 'name': 'myvpn', 'admin_state_up': True, 'status': constants.PENDING_CREATE, 'external_ip': '50.0.0.4', 'subnet': {'cidr': '10.0.0.0/24'}, 'ipsec_site_connections': [ {'peer_cidrs': ['20.0.0.0/24', '30.0.0.0/24'], 'id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'external_ip': '50.0.0.4', 'peer_address': '30.0.0.5', 'peer_id': '30.0.0.5', 'psk': 'password', 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'status': constants.PENDING_CREATE}, {'peer_cidrs': ['40.0.0.0/24', '50.0.0.0/24'], 'external_ip': '50.0.0.4', 'peer_address': '50.0.0.5', 'peer_id': '50.0.0.5', 'psk': 'password', 'id': FAKE_IPSEC_SITE_CONNECTION2_ID, 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'status': constants.PENDING_CREATE}] } DESIRED_CONN_STATUS = {FAKE_IPSEC_SITE_CONNECTION1_ID: {'status': 'DOWN', 'updated_pending_status': False}, FAKE_IPSEC_SITE_CONNECTION2_ID: {'status': 'DOWN', 'updated_pending_status': False}} FAKE_IKE_POLICY2 = { 'ike_version': 'v1', 'encryption_algorithm': 'aes-256', 'auth_algorithm': 'sha1', 'pfs': 'group2', 'lifetime_value': 1800 } FAKE_IPSEC_POLICY2 = { 'encryption_algorithm': 'aes-256', 'auth_algorithm': 'sha1', 'pfs': 'group2', 'transform_protocol': 'esp', 'lifetime_value': 1800, 'encapsulation_mode': 'tunnel' } class TestStrongSwanDeviceDriver(base.BaseSudoTestCase): """Test the StrongSwan reference implementation of the device driver.""" def setUp(self): super(TestStrongSwanDeviceDriver, self).setUp() self.conf = cfg.CONF self.conf.register_opts(l3_config.OPTS) self.conf.register_opts(ipsec.ipsec_opts, 'ipsec') self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') self.conf.set_override('state_path', '/tmp') ri_kwargs = {'router': {'id': FAKE_ROUTER_ID}, 'agent_conf': self.conf, 'interface_driver': mock.sentinel.interface_driver} self.router = legacy_router.LegacyRouter(router_id=FAKE_ROUTER_ID, agent=mock.Mock(), **ri_kwargs) self.router.router['distributed'] = False self.router_id = FAKE_VPN_SERVICE['router_id'] looping_call_p = mock.patch( 'oslo_service.loopingcall.FixedIntervalLoopingCall') looping_call_p.start() vpn_service = mock.Mock() vpn_service.conf = self.conf self.driver = strongswan_ipsec.StrongSwanDriver( vpn_service, host=mock.sentinel.host) self.driver.routers[FAKE_ROUTER_ID] = self.router self.driver.agent_rpc = mock.Mock() self.driver._update_nat = mock.Mock() self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ FAKE_VPN_SERVICE] self.addCleanup(self.driver.destroy_router, self.router_id) self.router.router_namespace.create() self.addCleanup(self.router.router_namespace.delete) def test_process_lifecycle(self): """ Lifecycle test that validates that the strongswan process could be launched, that a connection could be successfully initiated through it, and then that it could be terminated and clean up after itself. """ process = self.driver.ensure_process(self.router_id, FAKE_VPN_SERVICE) process.enable() self.assertTrue(process.active) self.assertIn(self.router_id, self.driver.processes) self.assertEqual(DESIRED_CONN_STATUS, process.connection_status) self.assertIsNotNone(process.namespace) conf_dir = os.path.join(self.conf.ipsec.config_base_dir, self.router_id) self.assertTrue(os.path.exists(conf_dir)) process.disable() self.assertFalse(process.active) self.assertFalse(process.connection_status) self.assertFalse(os.path.exists(conf_dir)) class TestStrongSwanScenario(test_scenario.TestIPSecBase): def setUp(self): super(TestStrongSwanScenario, self).setUp() self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') VPNAAS_STRONGSWAN_DEVICE = ('neutron_vpnaas.services.vpn.' 'device_drivers.strongswan_ipsec.' 'StrongSwanDriver') cfg.CONF.set_override('vpn_device_driver', [VPNAAS_STRONGSWAN_DEVICE], 'vpnagent') self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1', self.conf) self.vpn_agent = vpn_agent.L3WithVPNaaS(self.conf) vpn_service = mock.Mock() vpn_service.conf = self.conf self.driver = strongswan_ipsec.StrongSwanDriver( vpn_service, host=mock.sentinel.host) def _override_ikepolicy_for_site(self, site, ikepolicy): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['ikepolicy'] = ikepolicy def _override_ipsecpolicy_for_site(self, site, ipsecpolicy): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['ipsecpolicy'] = ipsecpolicy def _override_dpd_for_site(self, site, dpdaction, dpddelay, dpdtimeout): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['dpd_action'] = dpdaction ipsec_connection['dpd_interval'] = dpddelay ipsec_connection['dpd_timeout'] = dpdtimeout def _override_auth_algorithm_for_site(self, site, auth): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['ipsecpolicy']['auth_algorithm'] = auth ipsec_connection['ikepolicy']['auth_algorithm'] = auth def test_strongswan_connection_with_non_default_value(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._override_ikepolicy_for_site(site1, FAKE_IKE_POLICY2) self._override_ikepolicy_for_site(site2, FAKE_IKE_POLICY2) self._override_ipsecpolicy_for_site(site1, FAKE_IPSEC_POLICY2) self._override_ipsecpolicy_for_site(site2, FAKE_IPSEC_POLICY2) self._override_dpd_for_site(site1, 'hold', 60, 240) self._override_dpd_for_site(site2, 'hold', 60, 240) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) def test_strongswan_connection_with_sha256(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._override_auth_algorithm_for_site(site1, 'sha256') self._override_auth_algorithm_for_site(site2, 'sha256') self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/strongswan/test_netns_wrapper.py0000666000175000017500000000431313370230606031653 0ustar zuulzuul00000000000000# Copyright (c) 2015 Canonical, 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. import re from neutron.agent.linux import utils from neutron.conf.agent import common as config from neutron.tests.common import net_helpers from neutron.tests.functional import base WRAPPER_SCRIPT = 'neutron-vpn-netns-wrapper' STATUS_PATTERN = re.compile('Command:.*ip.*addr.*show.*Exit code: 0') class TestNetnsWrapper(base.BaseSudoTestCase): def setUp(self): super(TestNetnsWrapper, self).setUp() config.setup_logging() self.fake_ns = 'func-8f1b728c-6eca-4042-9b6b-6ef66ab9352a' self.mount_paths = ('--mount_paths=/etc:/var/lib/neutron' '/vpnaas/%(ns)s/etc,/var/run:/var/lib' '/neutron/vpnaas/%(ns)s/var/run') self.fake_pth = self.mount_paths % {'ns': self.fake_ns} def test_netns_wrap_success(self): client_ns = self.useFixture(net_helpers.NamespaceFixture()).ip_wrapper ns = client_ns.namespace pth = self.mount_paths % {'ns': ns} cmd = WRAPPER_SCRIPT, pth, '--cmd=ip,addr,show' output = client_ns.netns.execute(cmd) self.assertTrue(STATUS_PATTERN.search(output)) def test_netns_wrap_fail_without_netns(self): cmd = [WRAPPER_SCRIPT, self.fake_pth, '--cmd=ip,addr,show'] self.assertRaises(RuntimeError, utils.execute, cmd=cmd, run_as_root=True) def test_netns_wrap_unauthorized_command(self): cmd = [WRAPPER_SCRIPT, self.fake_pth, '--cmd=nofiltercommand'] self.assertRaises(RuntimeError, utils.execute, cmd=cmd, run_as_root=True) neutron-vpnaas-12.0.1/neutron_vpnaas/tests/functional/requirements.txt0000666000175000017500000000050013370230606026424 0ustar zuulzuul00000000000000# Additional requirements for functional tests # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. psutil>=1.1.1,<2.0.0 psycopg2 PyMySQL>=0.6.2 # MIT License neutron-vpnaas-12.0.1/releasenotes/0000775000175000017500000000000013370231105017261 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/notes/0000775000175000017500000000000013370231105020411 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/notes/libreswan-driver-works-with-3.19+-7e1fc79ac6c7efe5.yaml0000666000175000017500000000023113370230606032012 0ustar zuulzuul00000000000000--- fixes: - The libreswan driver of neutron-vpnaas can now also work with Libreswan 3.19+ (bug `#1711456 `_). neutron-vpnaas-12.0.1/releasenotes/notes/.placeholder0000666000175000017500000000000013370230606022671 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/notes/config-file-generation-0dcf19f5d8baaf5d.yaml0000666000175000017500000000043713370230606030341 0ustar zuulzuul00000000000000--- prelude: > Generation of sample Neutron VPNaaS configuration files. features: - Neutron VPNaaS no longer includes static example configuration files. Instead, use tools/generate_config_file_samples.sh to generate them. The files are generated with a .sample extension. neutron-vpnaas-12.0.1/releasenotes/notes/vpn-l3-extension-5cab632bb2c38531.yaml0000666000175000017500000000130213370230615026631 0ustar zuulzuul00000000000000--- features: - | VPNaaS support in neutron L3 agent is now implemented as L3 agent extension. We no longer have a separate binary ``neutron-vpn-agent``. To enable VPNaaS support in L3 agent, ensure to specify ``vpnaas`` to the configuration ``extensions`` in ``[AGENT]`` section of the L3 agent config file. upgrade: - | The separate L3 agent binary ``neutron-vpn-agent`` for VPNaaS support is dropped and VPNaaS support is now implemented as L3 agent extension. When upgrading your deployment to Queens, ensure to specify ``vpnaas`` to ``[AGENT] extensions`` configuration of the L3 agent config file and run ``neutron-l3-agent`` instead of ``neutron-vpn-agent``. neutron-vpnaas-12.0.1/releasenotes/notes/oslo-reports-3059c2e10e1b35b5.yaml0000666000175000017500000000034113370230606026072 0ustar zuulzuul00000000000000--- prelude: > Neutron VPNaaS is integrated with Guru Meditation Reports library. features: - Neutron VPNaaS services should respond to SIGUSR2 signal by dumping valuable debug information to standard error output. neutron-vpnaas-12.0.1/releasenotes/notes/flavor-framework-integration-f68d28bd35ce2643.yaml0000666000175000017500000000053113370230606031334 0ustar zuulzuul00000000000000--- prelude: > Flavor framework integration. features: - Neutron VPNaaS is now integrated with Neutron flavor framework. Multiple VPN service providers might be configured at the same time. A flavor of service type VPN associated with a profile containing a driver is used to find the provider for a newly created VPN service. neutron-vpnaas-12.0.1/releasenotes/source/0000775000175000017500000000000013370231105020561 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/unreleased.rst0000666000175000017500000000016013370230606023446 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: neutron-vpnaas-12.0.1/releasenotes/source/locale/0000775000175000017500000000000013370231105022020 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/locale/fr/0000775000175000017500000000000013370231105022427 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000013370231105024214 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000666000175000017500000000235213370230606027256 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: Neutron VPNaaS Release Notes 9.0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-10-24 16:01+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 05:51+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.7.3\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "7.0.2" msgstr "7.0.2" msgid "8.0.0" msgstr "8.0.0" msgid "9.0.0" msgstr "9.0.0" msgid "9.0.0.0rc1" msgstr "9.0.0.0rc1" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "Liberty Series Release Notes" msgstr "Note de release pour Liberty" msgid "Mitaka Series Release Notes" msgstr "Note de release pour Mitaka" msgid "Neutron VPNaaS Release Notes" msgstr "Note de release de Neutron VPNaaS" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Other Notes" msgstr "Autres notes" msgid "Start using reno to manage release notes." msgstr "Commence à utiliser reno pour la gestion des notes de release" neutron-vpnaas-12.0.1/releasenotes/source/pike.rst0000666000175000017500000000021713370230606022252 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike neutron-vpnaas-12.0.1/releasenotes/source/conf.py0000666000175000017500000002201713370230606022071 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # 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. # Neutron VPNaaS Release Notes documentation build configuration file, created # by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Neutron VPNaaS Release Notes' copyright = u'2015, Neutron VPNaaS Developers' # Release notes are version independent. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'NeutronVPNaaSReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'NeutronVPNaaSReleaseNotes.tex', u'Neutron VPNaaS Release Notes Documentation', u'Neutron VPNaaS Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'neutronvpnaasreleasenotes', u'Neutron VPNaaS Release Notes ' 'Documentation', [u'Neutron VPNaaS Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'NeutronVPNaaSReleaseNotes', u'Neutron VPNaaS Release Notes ' 'Documentation', u'Neutron VPNaaS Developers', 'NeutronVPNaaSReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/neutron-vpnaas' bug_project = 'neutron' bug_tag = 'doc' neutron-vpnaas-12.0.1/releasenotes/source/_templates/0000775000175000017500000000000013370231105022716 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/_templates/.placeholder0000666000175000017500000000000013370230606025176 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/_static/0000775000175000017500000000000013370231105022207 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/_static/.placeholder0000666000175000017500000000000013370230606024467 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/releasenotes/source/ocata.rst0000666000175000017500000000023013370230606022404 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata neutron-vpnaas-12.0.1/releasenotes/source/newton.rst0000666000175000017500000000023213370230606022631 0ustar zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton neutron-vpnaas-12.0.1/releasenotes/source/mitaka.rst0000666000175000017500000000023213370230606022565 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka neutron-vpnaas-12.0.1/releasenotes/source/liberty.rst0000666000175000017500000000022213370230606022770 0ustar zuulzuul00000000000000============================== Liberty Series Release Notes ============================== .. release-notes:: :branch: origin/stable/liberty neutron-vpnaas-12.0.1/releasenotes/source/index.rst0000666000175000017500000000027113370230615022431 0ustar zuulzuul00000000000000============================== Neutron VPNaaS Release Notes ============================== .. toctree:: :maxdepth: 1 unreleased pike ocata newton mitaka liberty neutron-vpnaas-12.0.1/devstack/0000775000175000017500000000000013370231105016374 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/devstack/plugin.sh0000666000175000017500000000640213370230615020237 0ustar zuulzuul00000000000000# plugin.sh - DevStack plugin.sh dispatch script template VPNAAS_XTRACE=$(set +o | grep xtrace) set -o xtrace # Source L3 agent extension management LIBDIR=$DEST/neutron-vpnaas/devstack/lib source $LIBDIR/l3_agent NEUTRON_L3_CONF=${NEUTRON_L3_CONF:-$Q_L3_CONF_FILE} function neutron_vpnaas_install { setup_develop $NEUTRON_VPNAAS_DIR if is_service_enabled q-l3 neutron-l3; then neutron_agent_vpnaas_install_agent_packages fi } function neutron_agent_vpnaas_install_agent_packages { install_package $IPSEC_PACKAGE if is_ubuntu && [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then install_package apparmor sudo ln -sf /etc/apparmor.d/usr.lib.ipsec.charon /etc/apparmor.d/disable/ sudo ln -sf /etc/apparmor.d/usr.lib.ipsec.stroke /etc/apparmor.d/disable/ # NOTE: Due to https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1387220 # one must use 'sudo start apparmor ACTION=reload' for Ubuntu 14.10 restart_service apparmor fi } function neutron_vpnaas_configure_common { cp $NEUTRON_VPNAAS_DIR/etc/neutron_vpnaas.conf.sample $NEUTRON_VPNAAS_CONF neutron_server_config_add $NEUTRON_VPNAAS_CONF neutron_service_plugin_class_add $VPN_PLUGIN neutron_deploy_rootwrap_filters $NEUTRON_VPNAAS_DIR inicomment $NEUTRON_VPNAAS_CONF service_providers service_provider iniadd $NEUTRON_VPNAAS_CONF service_providers service_provider $NEUTRON_VPNAAS_SERVICE_PROVIDER } function neutron_vpnaas_configure_agent { plugin_agent_add_l3_agent_extension vpnaas configure_l3_agent if [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then if is_fedora; then iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.fedora_strongswan_ipsec.FedoraStrongSwanDriver else iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver fi elif [[ "$IPSEC_PACKAGE" == "libreswan" ]]; then iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.libreswan_ipsec.LibreSwanDriver else iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver $NEUTRON_VPNAAS_DEVICE_DRIVER fi } function neutron_vpnaas_configure_db { $NEUTRON_BIN_DIR/neutron-db-manage --subproject neutron-vpnaas --config-file $NEUTRON_CONF upgrade head } function neutron_vpnaas_generate_config_files { # Uses oslo config generator to generate VPNaaS sample configuration files (cd $NEUTRON_VPNAAS_DIR && exec sudo ./tools/generate_config_file_samples.sh) } # Main plugin processing # NOP for pre-install step if [[ "$1" == "stack" && "$2" == "install" ]]; then echo_summary "Installing neutron-vpnaas" neutron_vpnaas_install elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then neutron_vpnaas_generate_config_files neutron_vpnaas_configure_common if is_service_enabled q-svc neutron-api; then echo_summary "Configuring neutron-vpnaas on controller" neutron_vpnaas_configure_db fi if is_service_enabled q-l3 neutron-l3; then echo_summary "Configuring neutron-vpnaas agent" neutron_vpnaas_configure_agent fi # NOP for clean step fi $VPNAAS_XTRACE neutron-vpnaas-12.0.1/devstack/upgrade/0000775000175000017500000000000013370231105020023 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/devstack/upgrade/settings0000777000175000017500000000005413370230606021617 0ustar zuulzuul00000000000000register_project_for_upgrade neutron-vpnaas neutron-vpnaas-12.0.1/devstack/upgrade/shutdown.sh0000777000175000017500000000227413370230606022251 0ustar zuulzuul00000000000000#!/bin/bash # # 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. # # ``upgrade-neutron-vpnaas`` set -o errexit source $GRENADE_DIR/grenaderc source $GRENADE_DIR/functions source $BASE_DEVSTACK_DIR/functions source $BASE_DEVSTACK_DIR/stackrc # needed for status directory # TODO(kevinbenton): figure out best way to source this from devstack plugin function neutron_vpnaas_stop { local ipsec_data_dir=$DATA_DIR/neutron/ipsec local pids if [ -d $ipsec_data_dir ]; then pids=$(find $ipsec_data_dir -name 'pluto.pid' -exec cat {} \;) fi if [ -n "$pids" ]; then sudo kill $pids fi stop_process neutron-vpnaas } ENABLED_SERVICES+=,neutron-vpnaas set -o xtrace neutron_vpnaas_stop neutron-vpnaas-12.0.1/devstack/upgrade/resources.sh0000777000175000017500000000105713370230606022406 0ustar zuulzuul00000000000000#!/bin/bash # # 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. # neutron-vpnaas-12.0.1/devstack/upgrade/upgrade.sh0000777000175000017500000000105713370230606022023 0ustar zuulzuul00000000000000#!/bin/bash # # 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. # neutron-vpnaas-12.0.1/devstack/README.md0000666000175000017500000000017413370230606017664 0ustar zuulzuul00000000000000This directory contains the neutron-vpnaas devstack plugin. Please see the devref for how to set up VPNaaS with devstack. neutron-vpnaas-12.0.1/devstack/local_AIO.conf.sample0000666000175000017500000000075313370230606022321 0ustar zuulzuul00000000000000[[local|localrc]] disable_service n-net enable_service q-svc enable_service q-agent enable_service q-dhcp enable_service q-l3 enable_service q-meta enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas FIXED_RANGE=10.1.0.0/24 FIXED_NETWORK_SIZE=256 NETWORK_GATEWAY=10.1.0.1 PRIVATE_SUBNET_NAME=privateA PUBLIC_SUBNET_NAME=public-subnet FLOATING_RANGE=172.24.4.0/24 PUBLIC_NETWORK_GATEWAY=172.24.4.10 Q_FLOATING_ALLOCATION_POOL="start=172.24.4.11,end=172.24.4.29" neutron-vpnaas-12.0.1/devstack/settings0000666000175000017500000000125513370230606020171 0ustar zuulzuul00000000000000# Settings for the VPNaaS devstack plugin # Plugin VPN_PLUGIN=${VPN_PLUGIN:-"vpnaas"} # Service Driver NEUTRON_VPNAAS_SERVICE_PROVIDER=${NEUTRON_VPNAAS_SERVICE_PROVIDER:-"VPN:strongswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default"} # Device driver IPSEC_PACKAGE=${IPSEC_PACKAGE:-"strongswan"} NEUTRON_VPNAAS_DEVICE_DRIVER=${NEUTRON_VPNAAS_DEVICE_DRIVER:-"neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec:StrongSwanDriver"} # Config files NEUTRON_CONF_DIR=${NEUTRON_CONF_DIR:-"/etc/neutron"} NEUTRON_VPNAAS_DIR=$DEST/neutron-vpnaas NEUTRON_VPNAAS_CONF_FILE=neutron_vpnaas.conf NEUTRON_VPNAAS_CONF=$NEUTRON_CONF_DIR/$NEUTRON_VPNAAS_CONF_FILE neutron-vpnaas-12.0.1/devstack/local.conf.sample0000666000175000017500000000062013370230606021622 0ustar zuulzuul00000000000000[[local|localrc]] enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas disable_service n-net enable_service q-svc enable_service q-agt enable_service q-dhcp enable_service q-l3 enable_service q-meta # Optional, to enable tempest configuration as part of devstack enable_service tempest # IPsec driver to use. Optional, defaults to strongswan. IPSEC_PACKAGE="strongswan" neutron-vpnaas-12.0.1/devstack/lib/0000775000175000017500000000000013370231105017142 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/devstack/lib/l3_agent0000666000175000017500000000114413370230606020570 0ustar zuulzuul00000000000000# This file is completely based on one in the neutron repository here: # http://git.openstack.org/cgit/openstack/neutron/tree/devstack/lib/l2_agent NEUTRON_L3_CONF=${NEUTRON_L3_CONF:-$Q_L3_CONF_FILE} function plugin_agent_add_l3_agent_extension { local l3_agent_extension=$1 if [[ -z "$L3_AGENT_EXTENSIONS" ]]; then L3_AGENT_EXTENSIONS=$l3_agent_extension elif [[ ! ,${L3_AGENT_EXTENSIONS}, =~ ,${l3_agent_extension}, ]]; then L3_AGENT_EXTENSIONS+=",$l3_agent_extension" fi } function configure_l3_agent { iniset $NEUTRON_L3_CONF AGENT extensions "$L3_AGENT_EXTENSIONS" } neutron-vpnaas-12.0.1/CONTRIBUTING.rst0000666000175000017500000000027413370230606017243 0ustar zuulzuul00000000000000Please see the Neutron CONTRIBUTING.rst file for how to contribute to neutron-vpnaas: `Neutron CONTRIBUTING.rst `_ neutron-vpnaas-12.0.1/.coveragerc0000666000175000017500000000015113370230606016715 0ustar zuulzuul00000000000000[run] branch = True source = neutron_vpnaas omit = neutron_vpnaas/tests/* [report] ignore_errors = True neutron-vpnaas-12.0.1/playbooks/0000775000175000017500000000000013370231105016573 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/playbooks/legacy/0000775000175000017500000000000013370231105020037 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-rally/0000775000175000017500000000000013370231105025107 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-rally/run.yaml0000666000175000017500000000406113370230606026607 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-neutron-vpnaas-dsvm-rally from old job gate-neutron-vpnaas-dsvm-rally-ubuntu-xenial tasks: - name: Ensure legacy workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack-infra/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ git://git.openstack.org \ openstack-infra/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x cat << 'EOF' >>"/tmp/dg-local.conf" [[local|localrc]] enable_plugin neutron-vpnaas git://git.openstack.org/openstack/neutron-vpnaas enable_plugin rally git://git.openstack.org/openstack/rally EOF executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PYTHONUNBUFFERED=true export DEVSTACK_GATE_NEUTRON=1 export RALLY_SCENARIO=neutron-vpnaas export BRANCH_OVERRIDE=default if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE fi export PROJECTS="openstack/neutron-vpnaas $PROJECTS" export PROJECTS="openstack/rally $PROJECTS" function post_test_hook { $BASE/new/rally/tests/ci/rally-gate.sh } export -f post_test_hook cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-rally/post.yaml0000666000175000017500000000620513370230606026772 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*nose_results.html - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testr_results.html.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.testrepository/tmp* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testrepository.subunit.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}/tox' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.tox/*/log/* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/rally-plot/** - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/rally-plot/extra/index.html - --include=*/ - --exclude=* - --prune-empty-dirs neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-functional-sswan/0000775000175000017500000000000013370231105027257 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-functional-sswan/run.yaml0000666000175000017500000000362613370230606030765 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-neutron-vpnaas-dsvm-functional-sswan from old job gate-neutron-vpnaas-dsvm-functional-sswan-ubuntu-xenial tasks: - name: Ensure legacy workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack-infra/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ git://git.openstack.org \ openstack-infra/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PYTHONUNBUFFERED=true export DEVSTACK_GATE_UNSTACK=1 export DEVSTACK_GATE_TEMPEST=0 export DEVSTACK_GATE_EXERCISES=0 export DEVSTACK_GATE_NEUTRON=1 export DEVSTACK_GATE_INSTALL_TESTONLY=1 export BRANCH_OVERRIDE=default if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE fi function gate_hook { bash -xe $BASE/new/neutron-vpnaas/neutron_vpnaas/tests/contrib/gate_hook.sh dsvm-functional-sswan } export -f gate_hook function post_test_hook { bash -xe $BASE/new/neutron-vpnaas/neutron_vpnaas/tests/contrib/post_test_hook.sh dsvm-functional-sswan } export -f post_test_hook cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-functional-sswan/post.yaml0000666000175000017500000000455113370230606031144 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*nose_results.html - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testr_results.html.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.testrepository/tmp* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testrepository.subunit.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}/tox' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.tox/*/log/* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-tempest/0000775000175000017500000000000013370231105025445 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-tempest/run.yaml0000666000175000017500000000443613370230615027153 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-neutron-dsvm-tempest-vpnaas from old job gate-neutron-dsvm-tempest-vpnaas-ubuntu-xenial tasks: - name: Ensure legacy workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack-infra/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ git://git.openstack.org \ openstack-infra/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x cat << 'EOF' >>"/tmp/dg-local.conf" [[local|localrc]] enable_plugin neutron-tempest-plugin git://git.openstack.org/openstack/neutron-tempest-plugin EOF executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PYTHONUNBUFFERED=true export DEVSTACK_GATE_TEMPEST=1 export DEVSTACK_GATE_TEMPEST_REGEX="^neutron_vpnaas\." export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1 export DEVSTACK_GATE_NEUTRON=1 export PROJECTS="openstack/neutron-tempest-plugin $PROJECTS" export BRANCH_OVERRIDE=default if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE fi function gate_hook { bash -xe $BASE/new/neutron-vpnaas/neutron_vpnaas/tests/contrib/gate_hook.sh tempest } export -f gate_hook function post_test_hook { bash -xe $BASE/new/neutron-vpnaas/neutron_vpnaas/tests/contrib/post_test_hook.sh tempest } export -f post_test_hook cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' neutron-vpnaas-12.0.1/playbooks/legacy/neutron-vpnaas-dsvm-tempest/post.yaml0000666000175000017500000000455113370230615027332 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*nose_results.html - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testr_results.html.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.testrepository/tmp* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testrepository.subunit.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}/tox' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.tox/*/log/* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/0000775000175000017500000000000013370231105021324 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/PKG-INFO0000664000175000017500000000302213370231104022415 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: neutron-vpnaas Version: 12.0.1 Summary: OpenStack Networking VPN as a Service Home-page: https://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Welcome! ======== This package contains the code for the Neutron VPN as a Service (VPNaaS) service. This includes third-party drivers. This package requires Neutron to run. External Resources: =================== The homepage for Neutron is: https://launchpad.net/neutron. Use this site for asking for help, and filing bugs. We use a single Launchpad page for all Neutron projects. Code is available on git.openstack.org at: https://git.openstack.org/cgit/openstack/neutron-vpnaas. Please refer to Neutron documentation for more information: `Neutron README.rst `_ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/dependency_links.txt0000664000175000017500000000000113370231104025371 0ustar zuulzuul00000000000000 neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/entry_points.txt0000664000175000017500000000225113370231104024621 0ustar zuulzuul00000000000000[console_scripts] neutron-vpn-netns-wrapper = neutron_vpnaas.services.vpn.common.netns_wrapper:main neutron-vyatta-agent = neutron_vpnaas.cmd.eventlet.vyatta_agent:main [device_drivers] neutron.services.vpn.device_drivers.cisco_ipsec.CiscoCsrIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.cisco_ipsec:CiscoCsrIPsecDriver neutron.services.vpn.device_drivers.ipsec.OpenSwanDriver = neutron_vpnaas.services.vpn.device_drivers.ipsec:OpenSwanDriver neutron.services.vpn.device_drivers.vyatta_ipsec.VyattaIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.vyatta_ipsec:VyattaIPsecDriver [neutron.agent.l3.extensions] vpnaas = neutron_vpnaas.services.vpn.agent:L3WithVPNaaS [neutron.db.alembic_migrations] neutron-vpnaas = neutron_vpnaas.db.migration:alembic_migrations [neutron.service_plugins] neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin vpnaas = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin [oslo.config.opts] neutron.vpnaas = neutron_vpnaas.opts:list_opts neutron.vpnaas.agent = neutron_vpnaas.opts:list_agent_opts [tempest.test_plugins] neutron_vpnaas_tests = neutron_vpnaas.tests.tempest.plugin:VPNTempestPlugin neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/SOURCES.txt0000664000175000017500000002471413370231105023220 0ustar zuulzuul00000000000000.coveragerc .mailmap .pylintrc .stestr.conf .testr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst TESTING.rst babel.cfg requirements.txt setup.cfg setup.py test-requirements.txt tox.ini devstack/README.md devstack/local.conf.sample devstack/local_AIO.conf.sample devstack/plugin.sh devstack/settings devstack/lib/l3_agent devstack/upgrade/resources.sh devstack/upgrade/settings devstack/upgrade/shutdown.sh devstack/upgrade/upgrade.sh doc/source/conf.py doc/source/index.rst doc/source/devref/devstack.rst doc/source/devref/index.rst doc/source/devref/multiple-local-subnets.rst doc/source/devref/team.rst doc/source/devref/testing-with-devstack.rst doc/source/devref/vpnaas-rally-test.rst doc/source/devref/vpnaas-tempest-test.rst etc/README.txt etc/neutron/rootwrap.d/vpnaas.filters etc/oslo-config-generator/neutron_vpnaas.conf etc/oslo-config-generator/vpn_agent.ini neutron_vpnaas/__init__.py neutron_vpnaas/_i18n.py neutron_vpnaas/opts.py neutron_vpnaas/version.py neutron_vpnaas.egg-info/PKG-INFO neutron_vpnaas.egg-info/SOURCES.txt neutron_vpnaas.egg-info/dependency_links.txt neutron_vpnaas.egg-info/entry_points.txt neutron_vpnaas.egg-info/not-zip-safe neutron_vpnaas.egg-info/pbr.json neutron_vpnaas.egg-info/requires.txt neutron_vpnaas.egg-info/top_level.txt neutron_vpnaas/cmd/__init__.py neutron_vpnaas/cmd/eventlet/__init__.py neutron_vpnaas/cmd/eventlet/vyatta_agent.py neutron_vpnaas/db/__init__.py neutron_vpnaas/db/migration/__init__.py neutron_vpnaas/db/migration/alembic_migrations/README neutron_vpnaas/db/migration/alembic_migrations/__init__.py neutron_vpnaas/db/migration/alembic_migrations/env.py neutron_vpnaas/db/migration/alembic_migrations/script.py.mako neutron_vpnaas/db/migration/alembic_migrations/versions/3ea02b2a773e_add_index_tenant_id.py neutron_vpnaas/db/migration/alembic_migrations/versions/CONTRACT_HEAD neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD neutron_vpnaas/db/migration/alembic_migrations/versions/kilo_release.py neutron_vpnaas/db/migration/alembic_migrations/versions/start_neutron_vpnaas.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/2c82e782d734_drop_tenant_id_in_cisco_csr_identifier_.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/333dfd6afaa2_populate_vpn_service_table_fields.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/56893333aa52_fix_identifier_map_fk.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/24f28869838b_add_fields_to_vpn_service_table.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/30018084ed99_initial.py neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/2cb4ee992b41_multiple_local_subnets.py neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/28ee739a7e4b_multiple_local_subnets.py neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/41b509d10b5e_vpnaas_endpoint_groups.py neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/b6a2519ab7dc_rename_tenant_to_project.py neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/52783a36bd67_support_local_id.py neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/fe637dc3f042_support_sha256.py neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/38893903cbde_add_auth_algorithm_sha384_and_sha512.py neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/95601446dbcc_add_flavor_id_to_vpnservices.py neutron_vpnaas/db/models/__init__.py neutron_vpnaas/db/models/head.py neutron_vpnaas/db/vpn/__init__.py neutron_vpnaas/db/vpn/vpn_db.py neutron_vpnaas/db/vpn/vpn_models.py neutron_vpnaas/db/vpn/vpn_validator.py neutron_vpnaas/extensions/__init__.py neutron_vpnaas/extensions/vpn_endpoint_groups.py neutron_vpnaas/extensions/vpn_flavors.py neutron_vpnaas/extensions/vpnaas.py neutron_vpnaas/services/__init__.py neutron_vpnaas/services/vpn/__init__.py neutron_vpnaas/services/vpn/agent.py neutron_vpnaas/services/vpn/plugin.py neutron_vpnaas/services/vpn/vpn_service.py neutron_vpnaas/services/vpn/vyatta_agent.py neutron_vpnaas/services/vpn/vyatta_vpn_service.py neutron_vpnaas/services/vpn/common/__init__.py neutron_vpnaas/services/vpn/common/constants.py neutron_vpnaas/services/vpn/common/netns_wrapper.py neutron_vpnaas/services/vpn/common/topics.py neutron_vpnaas/services/vpn/device_drivers/__init__.py neutron_vpnaas/services/vpn/device_drivers/cisco_csr_rest_client.py neutron_vpnaas/services/vpn/device_drivers/cisco_ipsec.py neutron_vpnaas/services/vpn/device_drivers/fedora_strongswan_ipsec.py neutron_vpnaas/services/vpn/device_drivers/ipsec.py neutron_vpnaas/services/vpn/device_drivers/libreswan_ipsec.py neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py neutron_vpnaas/services/vpn/device_drivers/vyatta_ipsec.py neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.secret.template neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.template neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.secret.template neutron_vpnaas/services/vpn/device_drivers/template/strongswan/strongswan.conf.template neutron_vpnaas/services/vpn/service_drivers/__init__.py neutron_vpnaas/services/vpn/service_drivers/base_ipsec.py neutron_vpnaas/services/vpn/service_drivers/cisco_csr_db.py neutron_vpnaas/services/vpn/service_drivers/cisco_ipsec.py neutron_vpnaas/services/vpn/service_drivers/cisco_validator.py neutron_vpnaas/services/vpn/service_drivers/driver_validator.py neutron_vpnaas/services/vpn/service_drivers/ipsec.py neutron_vpnaas/services/vpn/service_drivers/ipsec_validator.py neutron_vpnaas/services/vpn/service_drivers/vyatta_ipsec.py neutron_vpnaas/tests/__init__.py neutron_vpnaas/tests/base.py neutron_vpnaas/tests/contrib/README neutron_vpnaas/tests/contrib/filters.template neutron_vpnaas/tests/contrib/functional-test-rootwrap.conf neutron_vpnaas/tests/contrib/functional-testing.filters neutron_vpnaas/tests/contrib/gate_hook.sh neutron_vpnaas/tests/contrib/post_test_hook.sh neutron_vpnaas/tests/functional/__init__.py neutron_vpnaas/tests/functional/requirements.txt neutron_vpnaas/tests/functional/common/README neutron_vpnaas/tests/functional/common/__init__.py neutron_vpnaas/tests/functional/common/test_migrations_sync.py neutron_vpnaas/tests/functional/common/test_scenario.py neutron_vpnaas/tests/functional/openswan/README neutron_vpnaas/tests/functional/openswan/__init__.py neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py neutron_vpnaas/tests/functional/strongswan/README neutron_vpnaas/tests/functional/strongswan/__init__.py neutron_vpnaas/tests/functional/strongswan/test_netns_wrapper.py neutron_vpnaas/tests/functional/strongswan/test_strongswan_driver.py neutron_vpnaas/tests/tempest/README.rst neutron_vpnaas/tests/tempest/__init__.py neutron_vpnaas/tests/tempest/plugin.py neutron_vpnaas/tests/tempest/api/__init__.py neutron_vpnaas/tests/tempest/api/base.py neutron_vpnaas/tests/tempest/api/clients.py neutron_vpnaas/tests/tempest/api/test_vpnaas.py neutron_vpnaas/tests/tempest/scenario/__init__.py neutron_vpnaas/tests/tempest/scenario/base.py neutron_vpnaas/tests/tempest/scenario/test_vpnaas.py neutron_vpnaas/tests/tempest/services/__init__.py neutron_vpnaas/tests/unit/__init__.py neutron_vpnaas/tests/unit/db/__init__.py neutron_vpnaas/tests/unit/db/vpn/__init__.py neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py neutron_vpnaas/tests/unit/db/vpn/test_vpn_validator.py neutron_vpnaas/tests/unit/extensions/__init__.py neutron_vpnaas/tests/unit/extensions/test_vpn_endpoint_groups.py neutron_vpnaas/tests/unit/extensions/test_vpnaas.py neutron_vpnaas/tests/unit/services/__init__.py neutron_vpnaas/tests/unit/services/vpn/__init__.py neutron_vpnaas/tests/unit/services/vpn/test_plugin.py neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py neutron_vpnaas/tests/unit/services/vpn/test_vyatta_vpn_service.py neutron_vpnaas/tests/unit/services/vpn/common/__init__.py neutron_vpnaas/tests/unit/services/vpn/common/test_netns_wrapper.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/__init__.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_cisco_csr_rest_client.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_cisco_ipsec.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_vyatta_ipsec.py neutron_vpnaas/tests/unit/services/vpn/service_drivers/__init__.py neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_cisco_ipsec.py neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_vyatta_ipsec.py playbooks/legacy/neutron-vpnaas-dsvm-functional-sswan/post.yaml playbooks/legacy/neutron-vpnaas-dsvm-functional-sswan/run.yaml playbooks/legacy/neutron-vpnaas-dsvm-rally/post.yaml playbooks/legacy/neutron-vpnaas-dsvm-rally/run.yaml playbooks/legacy/neutron-vpnaas-dsvm-tempest/post.yaml playbooks/legacy/neutron-vpnaas-dsvm-tempest/run.yaml rally-jobs/__init__.py rally-jobs/plugins/__init__.py rally-jobs/plugins/test_vpn_connectivity.py rally-jobs/plugins/test_vpn_status.py rally-jobs/plugins/test_vpn_tenant_scenario.py rally-jobs/plugins/vpn_base.py rally-jobs/plugins/vpn_utils.py rally-jobs/rally-configs/args_template.json rally-jobs/rally-configs/rally_config_dvr.yaml rally-jobs/rally-configs/rally_config_non_dvr.yaml releasenotes/notes/.placeholder releasenotes/notes/config-file-generation-0dcf19f5d8baaf5d.yaml releasenotes/notes/flavor-framework-integration-f68d28bd35ce2643.yaml releasenotes/notes/libreswan-driver-works-with-3.19+-7e1fc79ac6c7efe5.yaml releasenotes/notes/oslo-reports-3059c2e10e1b35b5.yaml releasenotes/notes/vpn-l3-extension-5cab632bb2c38531.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po tools/check_i18n.py tools/check_i18n_test_case.txt tools/check_unit_test_structure.sh tools/clean.sh tools/configure_for_vpn_func_testing.sh tools/deploy_rootwrap.sh tools/generate_config_file_samples.sh tools/i18n_cfg.py tools/install_venv.py tools/install_venv_common.py tools/ostestr_compat_shim.sh tools/test_script.sh tools/tox_install.sh tools/with_venv.shneutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/top_level.txt0000664000175000017500000000001713370231104024053 0ustar zuulzuul00000000000000neutron_vpnaas neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/not-zip-safe0000664000175000017500000000000113370231104023551 0ustar zuulzuul00000000000000 neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/requires.txt0000664000175000017500000000063113370231104023723 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 requests>=2.14.2 Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 netaddr>=0.7.18 SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 alembic>=0.8.10 six>=1.10.0 neutron-lib>=1.13.0 oslo.concurrency>=3.25.0 oslo.config>=5.1.0 oslo.db>=4.27.0 oslo.log>=3.36.0 oslo.messaging>=5.29.0 oslo.reports>=1.18.0 oslo.serialization!=2.19.1,>=2.18.0 oslo.service!=1.28.1,>=1.24.0 oslo.utils>=3.33.0 neutron-vpnaas-12.0.1/neutron_vpnaas.egg-info/pbr.json0000664000175000017500000000005613370231104023002 0ustar zuulzuul00000000000000{"git_version": "3f36a0a", "is_release": true}neutron-vpnaas-12.0.1/PKG-INFO0000664000175000017500000000302213370231105015662 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: neutron-vpnaas Version: 12.0.1 Summary: OpenStack Networking VPN as a Service Home-page: https://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Welcome! ======== This package contains the code for the Neutron VPN as a Service (VPNaaS) service. This includes third-party drivers. This package requires Neutron to run. External Resources: =================== The homepage for Neutron is: https://launchpad.net/neutron. Use this site for asking for help, and filing bugs. We use a single Launchpad page for all Neutron projects. Code is available on git.openstack.org at: https://git.openstack.org/cgit/openstack/neutron-vpnaas. Please refer to Neutron documentation for more information: `Neutron README.rst `_ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 neutron-vpnaas-12.0.1/.mailmap0000666000175000017500000000111613370230606016217 0ustar zuulzuul00000000000000# Format is: # # lawrancejing Jiajun Liu Zhongyue Luo Kun Huang Zhenguo Niu Isaku Yamahata Isaku Yamahata Morgan Fainberg neutron-vpnaas-12.0.1/setup.py0000666000175000017500000000200613370230606016307 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) neutron-vpnaas-12.0.1/doc/0000775000175000017500000000000013370231105015335 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/doc/source/0000775000175000017500000000000013370231105016635 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/doc/source/conf.py0000666000175000017500000001746013370230615020153 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 OpenStack Foundation. # # 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. # # Keystone documentation build configuration file, created by # sphinx-quickstart on Tue May 18 13:50:15 2010. # # This file is execfile()'d with the current directory set to it's containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import subprocess import sys import warnings # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) sys.path.insert(0, ROOT_DIR) # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.graphviz', 'sphinx.ext.todo', 'openstackdocstheme',] todo_include_todos = True # Add any paths that contain templates here, relative to this directory. templates_path = [] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master doctree document. master_doc = 'index' # General information about the project. project = u'Neutron VPNaaS' copyright = u'2011-present, OpenStack Foundation.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # Version info from neutron_vpnaas.version import version_info as neutron_vpnaas_version release = neutron_vpnaas_version.release_string() # The short X.Y version. version = neutron_vpnaas_version.version_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. # unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['neutron_vpnaas.'] # -- Options for man page output -------------------------------------------- # Grouping the document tree for man pages. # List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual' #man_pages = [ # ('man/neutron-server', 'neutron-server', u'Neutron Server', # [u'OpenStack'], 1) #] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = ['_theme'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. #htmlhelp_basename = 'neutrondoc' # -- Options for LaTeX output ------------------------------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, # documentclass [howto/manual]). #latex_documents = [ # ('index', 'Neutron.tex', u'Neutron Documentation', # u'Neutron development team', 'manual'), #] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/neutron-vpnaas' bug_project = 'neutron' bug_tag = 'doc' neutron-vpnaas-12.0.1/doc/source/devref/0000775000175000017500000000000013370231105020110 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/doc/source/devref/vpnaas-tempest-test.rst0000666000175000017500000000401613370230615024576 0ustar zuulzuul00000000000000==================== VPNaaS Tempest Tests ==================== This contains the tempest test codes for the Neutron VPN as a Service (VPNaaS) service. The tests currently require tempest to be installed via devstack or standalone. It is assumed that you also have Neutron with the Neutron VPNaaS service installed. These tests could also be run against a multinode openstack. Please see /neutron-vpnaas/devstack/README.md for the required devstack configuration settings for Neutron-VPNaaS. How to test: ============ As a tempest plugin, the steps to run tests by hands are: 1. Setup a local working environment for running tempest :: tempest init ${your_tempest_dir} 2. Enter ${your_tempest_dir} :: cd ${your_tempest_dir} 3. Check neutron_vpnaas_tests exist in tempest plugins: :: tempest list-plugins +----------------------+--------------------------------------------------------+ | Name | EntryPoint | +----------------------+--------------------------------------------------------+ | neutron_tests | neutron_tempest_plugin.plugin:NeutronTempestPlugin | | neutron_vpnaas_tests | neutron_vpnaas.tests.tempest.plugin:VPNTempestPlugin | +----------------------+--------------------------------------------------------+ 4. Run neutron_vpnaas tests: :: tempest run --regex "^neutron_vpnaas.tests.tempest.api\." Usage in gate: ============== In the jenkins gate, devstack-gate/devstack-vm-gate-wrap.sh will invoke tempest with proper configurations, such as: :: DEVSTACK_GATE_TEMPEST=1 DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1 DEVSTACK_GATE_TEMPEST_REGEX="^neutron_vpnaas.tests.tempest.api\." The actual raw command in gate running under the tempest code directory is: :: tox -eall-plugin -- "^neutron_vpnaas.tests.tempest.api\." External Resources: =================== For more information on the tempest, see: neutron-vpnaas-12.0.1/doc/source/devref/team.rst0000666000175000017500000000507413370230615021605 0ustar zuulzuul00000000000000===================================== Core reviewers and Driver maintainers ===================================== Core reviewers -------------- The `Neutron VPNaaS Core Reviewer Team `_ is responsible for many things that same as `Neutron team `_. Driver maintainers ------------------ The driver maintainers are supposed to try: - Test the driver - Fix bugs in the driver - Keep the driver up-to-date for Neutron - Keep the driver up-to-date for its backend - Review relevant patches The following is a list of drivers and their maintainers. It includes both of in-tree and out-of-tree drivers. (alphabetical order) +----------------------------+---------------------------+------------------+ | Driver | Contact person | IRC nick | +============================+===========================+==================+ | CiscoCsrIPsecDriver | ??? | ??? | +----------------------------+---------------------------+------------------+ | FedoraStrongSwanDriver | ??? | ??? | +----------------------------+---------------------------+------------------+ | LibreSwanDriver | ??? | ??? | +----------------------------+---------------------------+------------------+ | MidonetIPsecVPNDriver [#]_ | YAMAMOTO Takashi | yamamoto | +----------------------------+---------------------------+------------------+ | NSXvIPsecVpnDriver [#]_ | Roey Chen | roeyc | +----------------------------+---------------------------+------------------+ | OpenSwanDriver | Lingxian Kong | kong | +----------------------------+---------------------------+------------------+ | | Lingxian Kong | kong | | StrongSwanDriver +---------------------------+------------------+ | | Cao Xuan Hoang | hoangcx | +----------------------------+---------------------------+------------------+ | VyattaIPsecDriver | ??? | ??? | +----------------------------+---------------------------+------------------+ .. [#] networking-midonet: https://docs.openstack.org/networking-midonet/latest/install/installation.html#vpnaas .. [#] vmware-nsx: Maintained under the vmware-nsx repository - https://github.com/openstack/vmware-nsx neutron-vpnaas-12.0.1/doc/source/devref/multiple-local-subnets.rst0000666000175000017500000002663713370230615025273 0ustar zuulzuul00000000000000================================= Multiple Local Subnets for VPNaaS ================================= As originally implemented, an VPN IPSec connection could have one or more peer subnets specified, but only **one** local subnet. To support multiple local subnets, multiple IPSec connections would be needed. With the multiple local subnet support, three goals are addressed. First, there can be multiple local and peer endpoints for a single IPSec connection. Second, validation enforces that the same IP version is used for all endpoints (to reduce complexity and ease testing). Third, the "what is connected" is separated from the "how to connect", so that other flavors of VPN (as they are developed) can use some of this mechanism. Design Notes ------------ There were three proposals considered, to support multiple local subnets. Proposal A was to just add the local subnets to the IPSec connection API. That would be the quickest way, and addresses the first two goals, but not the third. Proposal B was to create a new API that specifies of the local subnets and peer CIDRs, and reference those in the connection API. This would separate the "what is connected" from the "how to connect", and again addresses the first two goals (only). Proposal C, which was the *selected proposal*, creates a new API that represents the "endpoint groups" for VPN connections, in the same manner as proposal B. The added flexibility here, though, which meets goal three, is to also include the endpoint group "type", thus allowing subnets (local) and CIDRs (peer) to be used for IPSec, but routers, networks, and VLANs to be used for other VPN types (BGP, L2, direct connection). Additional types can be added in the future as needed. Client CLI API -------------- The originally implemented client CLI APIs (which are still available for backward compatibility) for an IPsec connection are:: neutron vpn-service-create ROUTER SUBNET neutron ipsec-site-connection-create --vpnservice-id VPNSERVICE --ikepolicy-id IKEPOLICY --ipsecpolicy-id IPSECPOLICY --peer-address PEER_ADDRESS --peer-id PEER_ID --peer-cidr PEER_CIDRS --dpd action=ACTION,interval=INTERVAL,timeout=TIMEOUT --initiator {bi-directional | response-only} --mtu MTU --psk PSK Changes to the API, to support multiple local subnets, are shown in **bold** text:: neutron vpn-service-create ROUTER **neutron vpn-endpoint-groups-create** **--name OPTIONAL-NAME** **--description OPTIONAL-DESCRIPTION** **--ep-type={subnet,cidr,network,vlan,router}** **--ep-value=[list-of-endpoints-of-type]** neutron ipsec-site-connection-create --vpnservice-id VPNSERVICE --ikepolicy-id IKEPOLICY --ipsecpolicy-id IPSECPOLICY --peer-address PEER_ADDRESS --peer-id PEER_ID --dpd action=ACTION,interval=INTERVAL,timeout=TIMEOUT --initiator {bi-directional | response-only} --mtu MTU --psk PSK **--local-endpoints ENDPOINT-GROUPS-UUID** **--peer-endpoints ENDPOINT-GROUPS-UUID** The SUBNET in the original service API is optional, and will be used as an indicator of whether or not the multiple local subnets feature is active. See the 'Backward Compatibility' section, below, for details. For the endpoint groups, the --ep-type value is a string, so that other types can be supported in the future. The endpoint groups API would enforce that the endpoint values are all of the same type, and match the endpoint type specified. The connection APIs, would then provide additional validation. For example, with IPSec, the endpoint type must be 'subnet' for local, and 'cidr' for peer, all the endpoints should be of the same IP version, and for the local endpoint, all subnets would be on the same router. For BGP VPN with dynamic routing, only a local endpoint group would be specified, and the type would be 'network'. The ROUTER may also be able to be removed, in the future, and can be determined, when the connections are created. Note: Using --ep-type, as --endpoint-type is already used elsewhere, and --type is too generic. Using --ep-value, as --endpoint is already in use, --end-point could be easily mistyped as --endpoint, and --value is too generic. Examples -------- The original APIs to create one side of an IPSec connection with only one local and peer subnet:: neutron vpn-ikepolicy-create ikepolicy neutron vpn-ipsecpolicy-create ipsecpolicy neutron vpn-service-create --name myvpn router1 privateA neutron ipsec-site-connection-create --name vpnconnection1 --vpnservice-id myvpn --ikepolicy-id ikepolicy --ipsecpolicy-id ipsecpolicy --peer-address 172.24.4.13 --peer-id 172.24.4.13 --peer-cidr 10.3.0.0/24 --psk secret The local CIDR is obtained from the subnet, privateA. In this example, that would be 10.1.0.0/24 (because that's how privateA was created). Using the multiple local subnet feature, the APIs (with changes shown in **bold** below:: neutron vpn-ikepolicy-create ikepolicy neutron vpn-ipsecpolicy-create ipsecpolicy neutron vpn-service-create --name myvpn router1 **neutron vpn-endpoint-group-create** **--name local-eps** **--ep-type=subnet** **--ep-value=privateA** **--ep-value=privateB** **neutron vpn-endpoint-group-create** **--name peer-eps** **--ep-type=cidr** **--ep-vallue=10.3.0.0/24** neutron ipsec-site-connection-create --name vpnconnection1 --vpnservice-id myvpn --ikepolicy-id ikepolicy --ipsecpolicy-id ipsecpolicy --peer-address 172.24.4.13 --psk secret **--local-endpoints local-eps** **--peer-endpoints peer-eps** The subnets privateA and privateB are used for local endpoints and the 10.3.0.0/24 CIDR is used for the peer endpoint. Database -------- The vpn_endpoints table contains single endpoint entries and a reference to the containing endpoint group. The vpn_endpoint_groups table defines the group, specifying the endpoint type. Database Migration ------------------ For an older database, the first subnet, in the subnet entry of the service table can be placed in an endpoint group that will be used for the local endpoints of the connection. The CIDRs from the connection can be placed into another endpoint group for the peer endpoints. Backwards Compatibility ----------------------- Operators would like to see this new capability provided, with backward compatibility support. The implication, as I see it, is to provide the ability for end users to be able to switch to the new API at any time, versus being forced to use the new API immediately, upon upgrade to the new release containing this feature. This would apply to both manual API use, and client apps/scripting-tools that would be used to configure VPNaaS. There are several attributes that are involve here. One is the subnet ID attribute in the VPN service API. The other is the peer CIDR attribute in the IPSec connection API. Both would be specified by endpoint groups in the new API, and these groups would be called out in the IPSec connection API. A plan to meet the backward compatibility goal of allowing both APIs to be used at once involves taking the following steps. For VPN service: - Make the subnet ID attribute optional. - If subnet ID is specified for create, consider old API mode. - If subnet ID specified for create, create endpoint group and store ID. - For delete, if subnet ID exists, delete corresponding endpoint group. - For show/list, if subnet ID exists, show the ID in output. - Subnet ID is not mutable, so no change for update API. For IPSec site to site connection: - For create, if old API mode, only allow peer-cidr attribute. - For create, if not old API mode, require local/peer endpoint group IDs attributes. - For create, if peer-cidr specified, create endpoint group and store ID. - For create, reject endpoint group ID attributes, if old API mode. - For create, reject peer-cidr attribute, if not old API mode. - For create, if old API mode, lookup subnet in service, find containing endpoint group ID and store. - For delete, if old API mode, delete endpoint group for peer. - For update of CIDRs (old mode), will delete endpoint group and create new one. (note 1) - For update of endpoint-group IDs (new mode), will allow different groups to be specified. (note 1,2) - For show/list, if old API mode, only display the peer CIDR values from peer endpoint group. - For show/list, if not old API mode, also show local subnets from local endpoint group. Note 1: Implication is that connection is torn down and re-created (as is done currently). Note 2: Users would create a new endpoint group, and then select that group, when modifying the IPSec connection. For endpoint groups: - For delete, if subnet, and (sole) subnet ID is used in a VPN service (old mode), reject request. - Updates are not supported, so no action required. (note 2) Note 2: Allowing updates would require deletion/recreation of connection using endpoint group. Avoiding that complexity. The thought here is to use endpoint groups under the hood, but if the old API was being used, treat the endpoint groups as if they never existed. Deleting connections and services would remove any endpoint groups, unlike with the new API, where they are independent. Migration can be used to move any VPNaaS configurations using the old schema to the new schema. This would look at VPN services and for any with a subnet ID, an endpoint group would be created and the group ID stored in any existing IPSec connections for that service. Likewise, any peer CIDRs in a connection would be copied into a new endpoint group and the group ID stored in the connection. The subnet ID field would then be removed from the VPN service table, and the peer CIDRs table would be removed. This migration could be done at the time of the new API release, in which case all tenants with existing VPNaaS configurations would use the new API to manage them (but could use old for new configurations). Alternatively, the migration could be deferred until the old API is removed, to ensure all existing configurations conform to the new schema. Migration tools can then be created to manually migrate individual tenants, as desired. Stories ------- For the endpoint groups, stories can cover: - CRUD API for the endpoint groups. - Database support for new tables. - Migration creation of new tables. - Validation of endpoints for a group (same type). - Neutron client support for new API. - Horizon support for new API. - API documentation update. For the multiple local subnets, stories can cover: - create IPsec connection with one local subnet, but using new API. - create IPSec connection with multiple local subnets. - Show IPSec connection to display endpoint group IDs (or endpoints?). - Ensure previous API still works, but uses new tables. - Validation to ensure old and new APIs are not mixed. - Modify CLI client. - Validate multiple local subnets on same router. - Validate local and peer endpoints are of same IP version. - Functional tests with multiple local subnets - API and How-To documentation update Note: The intent here is to have the initial stories take slices vertically through the process so that we can demonstrate the capability early. Note: Horizon work to support the changes is not expected to be part of this effort and would be handled by the Horizon team separately, if support is desired. neutron-vpnaas-12.0.1/doc/source/devref/devstack.rst0000666000175000017500000000327413370230615022463 0ustar zuulzuul00000000000000=============================== Configuring VPNaaS for DevStack =============================== ----------------------- Multinode vs All-In-One ----------------------- Devstack typically runs in single or "All-In-One" (AIO) mode. However, it can also be deployed to run on multiple nodes. For VPNaaS, running on an AIO setup is simple, as everything happens on the same node. However, to deploy to a multinode setup requires the following things to happen: #. Each controller node requires database migrations in support of running VPNaaS. #. Each network node that would run VPNaaS L3 agent extension. Therefore, the devstack plugin script needs some extra logic. ---------------- How to Configure ---------------- To configure VPNaaS, it is only necessary to enable the neutron-vpnaas devstack plugin by adding the following line to the [[local|localrc]] section of devstack's local.conf file:: enable_plugin neutron-vpnaas [BRANCH] is the URL of a neutron-vpnaas repository [BRANCH] is an optional git ref (branch/ref/tag). The default is master. For example:: enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas stable/kilo The default implementation for IPSEC package under DevStack is 'strongswan'. However, depending upon the Linux distribution, you may need to override this value. Select 'libreswan' for Fedora/RHEL/CentOS:: For example, install libreswan for CentOS/RHEL 7:: IPSEC_PACKAGE=libreswan This VPNaaS devstack plugin code will then #. Install the common VPNaaS configuration and code, #. Apply database migrations on nodes that are running the controller (as determined by enabling the q-svc service), neutron-vpnaas-12.0.1/doc/source/devref/index.rst0000666000175000017500000000655713370230615021775 0ustar zuulzuul00000000000000.. Copyright 2015 OpenStack Foundation 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. Developer Guide =============== In the Developer Guide, you will find information on the design, and architecture of the Neutron Virtual Private Network as a Service repo. This include things like, information on the reference implementation flavors, design details on VPNaaS internals, and testing. Developers will extend this, as needed, in the future to contain more information. If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad in the `neutron`__ project with ``vpnaas`` tag added. New features should be filed on Launchpad in the `neutron`__ project with ``rfe`` tag added in order to get decision from `neutron drivers`_ team. Before doing that, it is recommended to check `Request for Feature Enhancements`_ (RFE) process. .. __: https://bugs.launchpad.net/neutron/+bugs?field.tag=vpnaas .. __: https://bugs.launchpad.net/neutron/+bugs?field.tag=rfe .. _`neutron drivers`: https://review.openstack.org/#/admin/groups/464,members .. _`Request for Feature Enhancements`: https://docs.openstack.org/neutron/latest/contributor/policies/blueprints.html#neutron-request-for-feature-enhancements To get in touch with the neutron-vpnaas community, look at the following resource: - Join the ``#openstack-vpnaas`` IRC channel on Freenode. This is where the VPNaaS team is available for discussion. - We will hold for `VPN-as-a-Service (bi-)weekly IRC meeting` when needed in the near further. These are great places to get recommendations on where to start contributing to neutron-vpnaas. VPNaaS Team ----------- .. toctree:: :maxdepth: 3 team VPNaaS Flavors ----------------- .. toctree:: :maxdepth: 3 .. todo:: Info on the different Swan flavors, how they are different, and what Operating Systems support them. VPNaaS Internals ----------------- .. toctree:: :maxdepth: 3 multiple-local-subnets VPNaaS Tests ------------ .. toctree:: :maxdepth: 3 vpnaas-tempest-test vpnaas-rally-test Testing ------- .. toctree:: :maxdepth: 3 devstack testing-with-devstack .. todo:: Add notes about functional testing, with info on how different reference drivers are tested. Module Reference ---------------- .. toctree:: :maxdepth: 3 .. todo:: Add in all the big modules as automodule indexes. Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` neutron-vpnaas-12.0.1/doc/source/devref/vpnaas-rally-test.rst0000666000175000017500000000537213370230615024246 0ustar zuulzuul00000000000000=================== VPNaaS Rally Tests =================== This contains the rally test codes for the Neutron VPN as a Service (VPNaaS) service. The tests currently require rally to be installed via devstack or standalone. It is assumed that you also have Neutron with the Neutron VPNaaS service installed. These tests could also be run against a multinode openstack. Please see /neutron-vpnaas/devstack/README.md for the required devstack configuration settings for Neutron-VPNaaS. Structure: ========== 1. plugins - Directory where you can add rally plugins. Almost everything in Rally is a plugin. Contains base, common methods and actual scenario tests 2. rally-configs - Contains input configurations for the scenario tests How to test: ============ Included in the repo are rally tests. For information on rally, please see the rally README : https://github.com/openstack/rally/blob/master/README.rst * Create a rally deployment for your cloud and make sure it is active. rally deployment create --file=cloud_cred.json --name=MyCloud You can also create a rally deployment from the environment variables. rally deployment create --fromenv --name=MyCloud * Create a folder structure as below sudo mkdir /opt/rally * Create a symbolic link to the plugins directory cd /opt/rally sudo ln -s /opt/stack/neutron-vpnaas/rally-jobs/plugins * Run the tests. You can run the tests in various combinations. (a) Single Node with DVR with admin credentials (b) Single Node with DVR with non admin credentials (c) Multi Node with DVR with admin credentials (d) Multi Node with DVR with non admin credentials (e) Single Node, Non DVR with admin credentials (f) Multi Node, Non DVR with admin credentials -> Create a args.json file with the correct credentials depending on whether it is a single node or multinode cloud. A args_template.json file is available at /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/args_template.json for your reference. -> Update the rally_config_dvr.yaml or rally_config_non_dvr.yaml file to change the admin/non_admin credentials. -> Use the appropriate config files to run either dvr or non_dvr tests. With DVR: rally task start /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/rally_config_dvr.yaml --task-args-file /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/args.json Non DVR: rally task start /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/rally_config_non_dvr.yaml --task-args-file /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/args.json **Note:** Non DVR scenario can only be run as admin as you need admin credentials to create a non DVR router. External Resources: =================== For more information on the rally testing framework see: neutron-vpnaas-12.0.1/doc/source/devref/testing-with-devstack.rst0000666000175000017500000002753513370230615025115 0ustar zuulzuul00000000000000============================ Testing VPNaaS with devstack ============================ Installation ------------ In order to use Neutron-VPNaaS with `devstack `_ a single node setup, you'll need the following settings in your local.conf. .. literalinclude:: ../../../devstack/local.conf.sample You can find an example at `devstack/local.conf.sample `_ in the source tree. Quick Test Script ----------------- This quick test script creates two sites with a router, a network and a subnet connected with public network. Then, connect both sites via VPN. You can find an example at `tools/test_script.sh `_ in the source tree. Using Two DevStack Nodes for Testing ------------------------------------ You can use two DevStack nodes connected by a common "public" network to test VPNaaS. The second node can be set up with the same public network as the first node, except it will use a different gateway IP (and hence router IP). In this example, we'll assume we have two DevStack nodes (``East`` and ``West``), each running on hardware. .. note:: - You can do the same thing with multiple VM guests, if desired. - You can also create similar topology using two virtual routers with one devstack. Example Topology ^^^^^^^^^^^^^^^^ .. code-block:: none (10.1.0.0/24 - DevStack East) | | 10.1.0.1 [Neutron Router] | 172.24.4.226 | | 172.24.4.225 [Internet GW] | | [Internet GW] | 172.24.4.232 | | 172.24.4.233 [Neutron Router] | 10.2.0.1 | (10.2.0.0/24 DevStack West) DevStack Configuration ^^^^^^^^^^^^^^^^^^^^^^ For ``East`` you need to append the following lines to the local.conf, which will give you a private net of 10.1.0.0/24 and public network of 172.24.4.0/24 .. code-block:: none PUBLIC_SUBNET_NAME=yoursubnet PRIVATE_SUBNET_NAME=mysubnet FIXED_RANGE=10.1.0.0/24 NETWORK_GATEWAY=10.1.0.1 PUBLIC_NETWORK_GATEWAY=172.24.4.225 Q_FLOATING_ALLOCATION_POOL=start=172.24.4.226,end=172.24.4.231 For ``West`` you can add the following lines to local.conf to use a different local network, public GW (and implicitly router) IP. .. code-block:: none PUBLIC_SUBNET_NAME=yoursubnet PRIVATE_SUBNET_NAME=mysubnet FIXED_RANGE=10.2.0.0/24 NETWORK_GATEWAY=10.2.0.1 PUBLIC_NETWORK_GATEWAY=172.24.4.232 Q_FLOATING_ALLOCATION_POOL=start=172.24.4.233,end=172.24.4.238 VPNaaS Configuration ^^^^^^^^^^^^^^^^^^^^ With DevStack running on ``East`` and ``West`` and connectivity confirmed (make sure you can ping one router/GW from the other), you can perform these VPNaaS CLI commands. On ``East`` .. code-block:: none neutron vpn-ikepolicy-create ikepolicy1 neutron vpn-ipsecpolicy-create ipsecpolicy1 neutron vpn-service-create --name myvpn --description "My vpn service" router1 neutron vpn-endpoint-group-create --name my-locals --type subnet --value mysubnet neutron vpn-endpoint-group-create --name my-peers --type cidr --value 10.2.0.0/24 neutron ipsec-site-connection-create --name vpnconnection1 --vpnservice-id myvpn \ --ikepolicy-id ikepolicy1 --ipsecpolicy-id ipsecpolicy1 --peer-address 172.24.4.233 \ --peer-id 172.24.4.233 --local-ep-group my-locals --peer-ep-group my-peers --psk secret On ``West`` .. code-block:: none neutron vpn-ikepolicy-create ikepolicy1 neutron vpn-ipsecpolicy-create ipsecpolicy1 neutron vpn-service-create --name myvpn --description "My vpn service" router1 neutron vpn-endpoint-group-create --name my-locals --type subnet --value mysubnet neutron vpn-endpoint-group-create --name my-peers --type cidr --value 10.1.0.0/24 neutron ipsec-site-connection-create --name vpnconnection1 --vpnservice-id myvpn \ --ikepolicy-id ikepolicy1 --ipsecpolicy-id ipsecpolicy1 --peer-address 172.24.4.226 \ --peer-id 172.24.4.226 --local-ep-group my-locals --peer-ep-group my-peers --psk secret .. note:: Make sure setup security group (open icmp for vpn subnet etc) Verification ^^^^^^^^^^^^ You can spin up VMs on each node, and then from the VM ping to the other one. With tcpdump running on one of the nodes, you can see that pings appear as encrypted packets (ESP). Note that BOOTP, IGMP, and the keepalive packets between the two nodes are not encrypted (nor are pings between the two external IP addresses). Once stacked, VMs were created for testing, VPN IPsec commands used to establish connections between the nodes, and security group rules added to allow ICMP and SSH. Using single DevStack and two routers for testing ------------------------------------------------- Simple instructions on how to setup a test environment where a VPNaaS IPsec connection can be established using the reference implementation (StrongSwan). This example uses VirtualBox running on laptop to provide a VM for running DevStack. The idea here is to have a single OpenStack cloud created using DevStack, two routers (one created automatically), two private networks (one created automatically) 10.1.0.0/24 and 10.2.0.0/24, a VM in each private network, and establish a VPN connection between the two private nets, using the public network (172.24.4.0/24). Preparation ^^^^^^^^^^^ Create a VM (e.g. 4 GB RAM, 2 CPUs) running Ubuntu 16.04, with NAT I/F for access to the Internet. Clone a DevStack repo with latest. DevStack Configuration ^^^^^^^^^^^^^^^^^^^^^^ For single DevStack and two routers case, You can find an example at `devstack/local_AIO.conf.sample `_ in the source tree. Start up the cloud using ``./stack.sh`` and ensure it completes successfully. Once stacked, you can change ``RECLONE`` option in local.conf to No. Cloud Configuration ^^^^^^^^^^^^^^^^^^^ Once stacking is completed, you'll have a private network (10.1.0.0/24), and a router (router1). To prepare for establishing a VPN connection, a second network, subnet, and router needs to be created, and a VM spun up in each private network. .. code-block:: none # Create second net, subnet, router source ~/devstack/openrc admin demo neutron net-create privateB neutron subnet-create --name subB privateB 10.2.0.0/24 --gateway 10.2.0.1 neutron router-create routerB neutron router-interface-add routerB subB neutron router-gateway-set routerB public # Start up a VM in the privateA subnet. PRIVATE_NET=`neutron net-list | grep 'private ' | cut -f 2 -d' '` nova boot --flavor 1 --image cirros-0.3.5-x86_64-uec --nic net-id=$PRIVATE_NET peter # Start up a VM in the privateB subnet PRIVATE_NETB=`neutron net-list | grep privateB | cut -f 2 -d' '` nova boot --flavor 1 --image cirros-0.3.5-x86_64-uec --nic net-id=$PRIVATE_NETB paul At this point, you can verify that you have basic connectivity. .. note:: DevStack will create a static route that will allow you to ping the private interface IP of router1 from privateB network. You can remove the route, if desired. IPsec Site-to-site Connection Creation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following commands will create the IPsec connection: .. code-block:: none # Create VPN connections neutron vpn-ikepolicy-create ikepolicy neutron vpn-ipsecpolicy-create ipsecpolicy neutron vpn-service-create --name myvpn --description "My vpn service" router1 neutron vpn-endpoint-group-create --name my-localsA --type subnet --value privateA neutron vpn-endpoint-group-create --name my-peersA --type cidr --value 10.2.0.0/24 neutron ipsec-site-connection-create --name vpnconnection1 --vpnservice-id myvpn \ --ikepolicy-id ikepolicy --ipsecpolicy-id ipsecpolicy --peer-address 172.24.4.13 \ --peer-id 172.24.4.13 --local-ep-group my-localsA --peer-ep-group my-peersA --psk secret neutron vpn-service-create --name myvpnB --description "My vpn serviceB" routerB neutron vpn-endpoint-group-create --name my-localsB --type subnet --value subB neutron vpn-endpoint-group-create --name my-peersB --type cidr --value 10.1.0.0/24 neutron ipsec-site-connection-create --name vpnconnection2 --vpnservice-id myvpnB \ --ikepolicy-id ikepolicy --ipsecpolicy-id ipsecpolicy --peer-address 172.24.4.11 \ --peer-id 172.24.4.11 --local-ep-group my-localsB --peer-ep-group my-peersB --psk secret At this point (once the connections become active - which can take up to 30 seconds or so), you should be able to ping from the VM in the privateA network, to the VM in the privateB network. You'll see encrypted packets, if you tcpdump using the qg-# interface from one of the router namespaces. If you delete one of the connections, you'll see that the pings fail (if all works out correctly :)). .. note:: Because routerB is created manually, its public IP address may change (172.24.4.13 in this case). Multiple Local Subnets ^^^^^^^^^^^^^^^^^^^^^^ Early in Mitaka, IPsec site-to-site connections will support multiple local subnets, in addition to the current multiple peer CIDRs. The multiple local subnet feature is triggered by not specifying a local subnet, when creating a VPN service. Backwards compatibility is maintained with single local subnets, by providing the subnet in the VPN service creation. To support multiple local subnets, a new capability has been provided (since Liberty), called "Endpoint Groups". Each endpoint group will define one or more endpoints of a specific type, and can be used to specify both local and peer endpoints for IPsec connections. The Endpoint Groups separate the "what gets connected" from the "how to connect" for a VPN service, and can be used for different flavors of VPN, in the future. An example: .. code-block:: none # Create VPN connections neutron vpn-ikepolicy-create ikepolicy neutron vpn-ipsecpolicy-create ipsecpolicy neutron vpn-service-create --name myvpnC --description "My vpn service" router1 To prepare for an IPsec site-to-site, one would create an endpoint group for the local subnets, and an endpoint group for the peer CIDRs, like so: .. code-block:: none neutron vpn-endpoint-group-create --name my-locals --type subnet --value privateA --value privateA2 neutron vpn-endpoint-group-create --name my-peers --type cidr --value 10.2.0.0/24 --value 20.2.0.0/24 where privateA and privateA2 are two local (private) subnets, and 10.2.0.0/24 and 20.2.0.0/24 are two CIDRs representing peer (private) subnets that will be used by a connection. Then, when creating the IPsec site-to-site connection, these endpoint group IDs would be specified, instead of the peer-cidrs attribute: .. code-block:: none neutron ipsec-site-connection-create --name vpnconnection3 --vpnservice-id myvpnC \ --ikepolicy-id ikepolicy --ipsecpolicy-id ipsecpolicy --peer-address 172.24.4.11 \ --peer-id 172.24.4.11 --local-ep-group my-locals --peer-ep-group my-peers --psk secret .. note:: - The validation logic makes sure that endpoint groups and peer CIDRs are not intermixed. - Endpoint group types are subnet, cidr, network, router, and vlan. However, only subnet and cidr are implemented (for IPsec use). - The endpoints in a group must be of the same type, although It can mix IP versions. - For IPsec connections, validation currently enforces that the local and peer endpoints all use the same IP version. - IPsec connection validation requires that local endpoints are subnets, and peer endpoints are CIDRs. - Migration will convert information for any existing VPN services and connections to endpoint groups. - The original APIs will work for backward compatibility. neutron-vpnaas-12.0.1/doc/source/index.rst0000666000175000017500000000374613370230615020517 0ustar zuulzuul00000000000000.. Copyright 2015 OpenStack Foundation 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. Welcome to Neutron VPNaaS developer documentation! ================================================== This provides Virtual Private Network as a Service (VPNaaS) capabilities to Neutron. Maintained as a separate repo, this works in conjunction with the Neutron repo to provide VPN services for OpenStack. The `VPNaaS API`_ is implementation as an extension to Neutron's networking API: .. _`VPNaaS API`: https://developer.openstack.org/api-ref/networking/v2/index.html#vpnaas-2-0-unmaintained-vpn-vpnservices-ikepolicies-ipsecpolicies-endpoint-groups-ipsec-site-connections This documentation is generated by the Sphinx toolkit and lives in the source tree. Additional documentation on VPNaaS and other components of OpenStack can be found on the `OpenStack wiki`_ and the `Neutron section of the wiki`_ (see the VPN related pages). The `Neutron Development wiki`_ is also a good resource for new contributors. .. _`OpenStack wiki`: https://wiki.openstack.org/wiki/Main_Page .. _`Neutron section of the wiki`: https://wiki.openstack.org/wiki/Neutron .. _`Neutron Development wiki`: https://wiki.openstack.org/wiki/NeutronDevelopment Enjoy! Developer Docs ============== .. toctree:: :maxdepth: 1 devref/index API Extensions ============== Go to https://developer.openstack.org/api-ref/networking/ for information about OpenStack Network API extensions. neutron-vpnaas-12.0.1/README.rst0000666000175000017500000000117513370230615016272 0ustar zuulzuul00000000000000Welcome! ======== This package contains the code for the Neutron VPN as a Service (VPNaaS) service. This includes third-party drivers. This package requires Neutron to run. External Resources: =================== The homepage for Neutron is: https://launchpad.net/neutron. Use this site for asking for help, and filing bugs. We use a single Launchpad page for all Neutron projects. Code is available on git.openstack.org at: https://git.openstack.org/cgit/openstack/neutron-vpnaas. Please refer to Neutron documentation for more information: `Neutron README.rst `_ neutron-vpnaas-12.0.1/test-requirements.txt0000666000175000017500000000143313370230615021041 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 astroid<1.4.0 # LGPLv2.1 # breaks pylint 1.4.4 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.12 # LGPLv3 mock>=2.0.0 # BSD pylint==1.4.5 # GPLv2 requests-mock>=1.1.0 # Apache-2.0 sphinx!=1.6.6,>=1.6.2 # BSD openstackdocstheme>=1.18.1 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 testtools>=2.2.0 # MIT testresources>=2.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD WebOb>=1.7.1 # MIT WebTest>=2.0.27 # MIT oslotest>=3.2.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 neutron-vpnaas-12.0.1/tox.ini0000666000175000017500000000720013370230615016111 0ustar zuulzuul00000000000000[tox] envlist = py35,py27,pep8 minversion = 1.6 skipsdist = True [testenv] setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning usedevelop = True install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/queens} {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt whitelist_externals = sh commands = {toxinidir}/tools/ostestr_compat_shim.sh {posargs} # there is also secret magic in ostestr which lets you run in a fail only # mode. To do this define the TRACE_FAILONLY environmental variable. [testenv:functional] deps = {[testenv]deps} -r{toxinidir}/neutron_vpnaas/tests/functional/requirements.txt setenv = OS_SUDO_TESTING=1 OS_ROOTWRAP_CMD=sudo {envdir}/bin/neutron-rootwrap {envdir}/etc/neutron/rootwrap.conf OS_ROOTWRAP_DAEMON_CMD=sudo {envdir}/bin/neutron-rootwrap-daemon {envdir}/etc/neutron/rootwrap.conf OS_FAIL_ON_MISSING_DEPS=1 whitelist_externals = sh cp sudo [testenv:dsvm-functional] setenv = OS_TEST_PATH=./neutron_vpnaas/tests/functional/openswan {[testenv:functional]setenv} deps = {[testenv:functional]deps} sitepackages=True whitelist_externals = {[testenv:functional]whitelist_externals} commands = {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir} ostestr '{posargs}' [testenv:dsvm-functional-sswan] setenv = OS_TEST_PATH=./neutron_vpnaas/tests/functional/strongswan {[testenv:functional]setenv} deps = {[testenv:functional]deps} sitepackages=True whitelist_externals = {[testenv:functional]whitelist_externals} commands = {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir} ostestr '{posargs}' [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:pep8] commands = flake8 pylint --rcfile=.pylintrc --output-format=colorized {posargs:neutron_vpnaas} {toxinidir}/tools/check_unit_test_structure.sh neutron-db-manage --subproject neutron-vpnaas --database-connection sqlite:// check_migration {[testenv:genconfig]commands} whitelist_externals = sh [testenv:i18n] commands = python ./tools/check_i18n.py ./neutron-vpnaas ./tools/i18n_cfg.py [testenv:cover] commands = python setup.py test --coverage --coverage-package-name=neutron_vpnaas --testr-args='{posargs}' [testenv:venv] commands = {posargs} [testenv:docs] commands = sphinx-build -W -b html doc/source doc/build [flake8] # E125 continuation line does not distinguish itself from next logical line # E126 continuation line over-indented for hanging indent # E128 continuation line under-indented for visual indent # E129 visually indented line with same indent as next logical line # E265 block comment should start with ‘# ‘ # H404 multi line docstring should start with a summary # H405 multi line docstring summary not separated with an empty line # TODO(dougwig) -- uncomment this to test for remaining linkages # N530 direct neutron imports not allowed # N531 Log messages require translation hints # H106 Don't put vim configuration in source files # H203 Use assertIs(Not)None to check for None # H904 Delay string interpolations at logging calls ignore = E125,E126,E128,E129,E265,H404,H405,N530,N531 enable-extensions=H106,H203,H904 show-source = true exclude = .venv,.git,.tox,dist,doc,.tmp,*lib/python*,*egg,build,tools,.ropeproject,rally-scenarios import-order-style = pep8 [hacking] import_exceptions = neutron_vpnaas._i18n local-check-factory = neutron_lib.hacking.checks.factory [testenv:genconfig] commands = {toxinidir}/tools/generate_config_file_samples.sh neutron-vpnaas-12.0.1/LICENSE0000666000175000017500000002363713370230606015617 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. neutron-vpnaas-12.0.1/HACKING.rst0000666000175000017500000000036413370230606016400 0ustar zuulzuul00000000000000Neutron VPNaaS Style Commandments ================================= Please see the Neutron HACKING.rst file for style commandments for neutron-vpnaas: `Neutron HACKING.rst `_ neutron-vpnaas-12.0.1/etc/0000775000175000017500000000000013370231105015343 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/etc/oslo-config-generator/0000775000175000017500000000000013370231105021546 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/etc/oslo-config-generator/vpn_agent.ini0000666000175000017500000000014313370230606024235 0ustar zuulzuul00000000000000[DEFAULT] output_file = etc/vpn_agent.ini.sample wrap_width = 79 namespace = neutron.vpnaas.agent neutron-vpnaas-12.0.1/etc/oslo-config-generator/neutron_vpnaas.conf0000666000175000017500000000014313370230606025464 0ustar zuulzuul00000000000000[DEFAULT] output_file = etc/neutron_vpnaas.conf.sample wrap_width = 79 namespace = neutron.vpnaas neutron-vpnaas-12.0.1/etc/neutron/0000775000175000017500000000000013370231105017035 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/etc/neutron/rootwrap.d/0000775000175000017500000000000013370231105021134 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/etc/neutron/rootwrap.d/vpnaas.filters0000666000175000017500000000141513370230615024026 0ustar zuulzuul00000000000000# neutron-rootwrap command filters for nodes on which neutron is # expected to control network # # This file should be owned by (and only-writable by) the root user # format seems to be # cmd-name: filter-name, raw-command, user, args [Filters] cp: RegExpFilter, cp, root, cp, -a, .*, .*/strongswan.d ip: IpFilter, ip, root ip_exec: IpNetnsExecFilter, ip, root ipsec: CommandFilter, ipsec, root rm: RegExpFilter, rm, root, rm, -rf, (.*/strongswan.d|.*/ipsec/[0-9a-z-]+) strongswan: CommandFilter, strongswan, root neutron_netns_wrapper: CommandFilter, neutron-vpn-netns-wrapper, root neutron_netns_wrapper_local: CommandFilter, /usr/local/bin/neutron-vpn-netns-wrapper, root chown: RegExpFilter, chown, root, chown, --from=.*, root.root, .*/(ipsec.secrets|ipsec/[0-9a-z-]+/log) neutron-vpnaas-12.0.1/etc/README.txt0000666000175000017500000000047713370230606017060 0ustar zuulzuul00000000000000To generate the sample neutron VPNaaS configuration files, run the following command from the top level of the neutron VPNaaS directory: tox -e genconfig If a 'tox' environment is unavailable, then you can run the following script instead to generate the configuration files: ./tools/generate_config_file_samples.sh neutron-vpnaas-12.0.1/tools/0000775000175000017500000000000013370231105015730 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/tools/install_venv_common.py0000666000175000017500000001350713370230606022373 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 IBM Corp. # # 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. """Provides methods needed by installation script for OpenStack development virtual environments. Since this script is used to bootstrap a virtualenv from the system's Python environment, it should be kept strictly compatible with Python 2.6. Synced in from openstack-common """ from __future__ import print_function import optparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...', end=' ') if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # setuptools and pbr self.pip_install('pip>=1.4') self.pip_install('setuptools') self.pip_install('pbr') self.pip_install('-r', self.requirements, '-r', self.test_requirements) def parse_args(self, argv): """Parses command-line arguments.""" parser = optparse.OptionParser() parser.add_option('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install.") return parser.parse_args(argv[1:])[0] class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...', end=' ') if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() neutron-vpnaas-12.0.1/tools/clean.sh0000777000175000017500000000030413370230606017355 0ustar zuulzuul00000000000000#!/usr/bin/env bash rm -rf ./*.deb ./*.tar.gz ./*.dsc ./*.changes rm -rf */*.deb rm -rf ./plugins/**/build/ ./plugins/**/dist rm -rf ./plugins/**/lib/neutron_*_plugin.egg-info ./plugins/neutron-* neutron-vpnaas-12.0.1/tools/check_i18n_test_case.txt0000666000175000017500000000264513370230606022455 0ustar zuulzuul00000000000000# test-case for check_i18n.py # python check_i18n.py check_i18n.txt -d # message format checking # capital checking msg = _("hello world, error") msg = _("hello world_var, error") msg = _('file_list xyz, pass') msg = _("Hello world, pass") # format specifier checking msg = _("Hello %s world %d, error") msg = _("Hello %s world, pass") msg = _("Hello %(var1)s world %(var2)s, pass") # message has been localized # is_localized msg = _("Hello world, pass") msg = _("Hello world, pass") % var LOG.debug(_('Hello world, pass')) LOG.info(_('Hello world, pass')) raise x.y.Exception(_('Hello world, pass')) raise Exception(_('Hello world, pass')) # message need be localized # is_log_callfunc LOG.debug('hello world, error') LOG.debug('hello world, error' % xyz) sys.append('hello world, warn') # is_log_i18n_msg_with_mod LOG.debug(_('Hello world, error') % xyz) # default warn msg = 'hello world, warn' msg = 'hello world, warn' % var # message needn't be localized # skip only one word msg = '' msg = "hello,pass" # skip dict msg = {'hello world, pass': 1} # skip list msg = ["hello world, pass"] # skip subscript msg['hello world, pass'] # skip xml marker msg = ", pass" # skip sql statement msg = "SELECT * FROM xyz WHERE hello=1, pass" msg = "select * from xyz, pass" # skip add statement msg = 'hello world' + e + 'world hello, pass' # skip doc string """ Hello world, pass """ class Msg: pass neutron-vpnaas-12.0.1/tools/test_script.sh0000777000175000017500000000430213370230606020640 0ustar zuulzuul00000000000000#!/usr/bin/env bash EXT_NW_ID=`neutron net-list | awk '/public/{print $2}'` WEST_SUBNET='192.168.1.0/24' EAST_SUBNET='192.168.2.0/24' function setup_site(){ local site_name=$1 local cidr=$2 neutron net-create net_$site_name neutron subnet-create --name subnet_$site_name net_$site_name $2 neutron router-create router_$site_name neutron router-interface-add router_$site_name subnet_$site_name neutron router-gateway-set router_$site_name $EXT_NW_ID neutron vpn-service-create --name vpn_$site_name router_$site_name subnet_$site_name } function get_external_ip(){ local router_id=`neutron router-show $1 | awk '/ id /{print $4}'` echo `neutron port-list -c fixed_ips -c device_id -c device_owner|grep router_gateway | awk '/'.$router_id.'/{print $5}' | sed 's/["}]//g'` } function clean_site(){ local site_name=$1 neutron ipsec-site-connection-delete conn_$site_name neutron vpn-service-list | awk '/vpn_'$site_name'/{print "neutron vpn-service-delete " $2}' | bash neutron router-gateway-clear router_$site_name neutron router-interface-delete router_$site_name subnet_$site_name neutron router-list | awk '/router_'$site_name'/{print "neutron router-delete " $2}' | bash neutron subnet-list | awk '/subnet_'$site_name'/{print "neutron subnet-delete " $2}' | bash neutron net-list | awk '/net_'$site_name'/{print "neutron net-delete " $2}' | bash } function setup(){ neutron vpn-ikepolicy-create ikepolicy1 neutron vpn-ipsecpolicy-create ipsecpolicy1 setup_site west $WEST_SUBNET WEST_IP=$(get_external_ip router_west) setup_site east $EAST_SUBNET EAST_IP=$(get_external_ip router_east) neutron ipsec-site-connection-create --name conn_east --vpnservice-id vpn_east --ikepolicy-id ikepolicy1 --ipsecpolicy-id ipsecpolicy1 --peer-address $WEST_IP --peer-id $WEST_IP --peer-cidr $WEST_SUBNET --psk secret neutron ipsec-site-connection-create --name conn_west --vpnservice-id vpn_west --ikepolicy-id ikepolicy1 --ipsecpolicy-id ipsecpolicy1 --peer-address $EAST_IP --peer-id $EAST_IP --peer-cidr $EAST_SUBNET --psk secret } function cleanup(){ clean_site west clean_site east neutron vpn-ikepolicy-delete ikepolicy1 neutron vpn-ipsecpolicy-delete ipsecpolicy1 } cleanup setup neutron-vpnaas-12.0.1/tools/check_unit_test_structure.sh0000777000175000017500000000307713370230606023600 0ustar zuulzuul00000000000000#!/usr/bin/env bash # This script identifies the unit test modules that do not correspond # directly with a module in the code tree. See TESTING.rst for the # intended structure. neutron_path=$(cd "$(dirname "$0")/.." && pwd) base_test_path=neutron_vpnaas/tests/unit test_path=$neutron_path/$base_test_path test_files=$(find ${test_path} -iname 'test_*.py') ignore_regexes=( "^plugins.*$" ) error_count=0 ignore_count=0 total_count=0 for test_file in ${test_files[@]}; do relative_path=${test_file#$test_path/} expected_path=$(dirname $neutron_path/neutron_vpnaas/$relative_path) test_filename=$(basename "$test_file") expected_filename=${test_filename#test_} # Module filename (e.g. foo/bar.py -> foo/test_bar.py) filename=$expected_path/$expected_filename # Package dir (e.g. foo/ -> test_foo.py) package_dir=${filename%.py} if [ ! -f "$filename" ] && [ ! -d "$package_dir" ]; then for ignore_regex in ${ignore_regexes[@]}; do if [[ "$relative_path" =~ $ignore_regex ]]; then ((ignore_count++)) continue 2 fi done echo "Unexpected test file: $base_test_path/$relative_path" ((error_count++)) fi ((total_count++)) done if [ "$ignore_count" -ne 0 ]; then echo "$ignore_count unmatched test modules were ignored" fi if [ "$error_count" -eq 0 ]; then echo 'Success! All test modules match targets in the code tree.' exit 0 else echo "Failure! $error_count of $total_count test modules do not match targets in the code tree." exit 1 fi neutron-vpnaas-12.0.1/tools/i18n_cfg.py0000666000175000017500000000664313370230606017720 0ustar zuulzuul00000000000000import compiler import re def is_log_callfunc(n): """LOG.xxx('hello %s' % xyz) and LOG('hello')""" if isinstance(n.parent, compiler.ast.Mod): n = n.parent if isinstance(n.parent, compiler.ast.CallFunc): if isinstance(n.parent.node, compiler.ast.Getattr): if isinstance(n.parent.node.getChildNodes()[0], compiler.ast.Name): if n.parent.node.getChildNodes()[0].name == 'LOG': return True return False def is_log_i18n_msg_with_mod(n): """LOG.xxx("Hello %s" % xyz) should be LOG.xxx("Hello %s", xyz)""" if not isinstance(n.parent.parent, compiler.ast.Mod): return False n = n.parent.parent if isinstance(n.parent, compiler.ast.CallFunc): if isinstance(n.parent.node, compiler.ast.Getattr): if isinstance(n.parent.node.getChildNodes()[0], compiler.ast.Name): if n.parent.node.getChildNodes()[0].name == 'LOG': return True return False def is_wrong_i18n_format(n): """Check _('hello %s' % xyz)""" if isinstance(n.parent, compiler.ast.Mod): n = n.parent if isinstance(n.parent, compiler.ast.CallFunc): if isinstance(n.parent.node, compiler.ast.Name): if n.parent.node.name == '_': return True return False """ Used for check message need be localized or not. (predicate_func, action, message) """ i18n_msg_predicates = [ # Skip ['hello world', 1] (lambda n: isinstance(n.parent, compiler.ast.List), 'skip', ''), # Skip {'hellow world', 1} (lambda n: isinstance(n.parent, compiler.ast.Dict), 'skip', ''), # Skip msg['hello world'] (lambda n: isinstance(n.parent, compiler.ast.Subscript), 'skip', ''), # Skip doc string (lambda n: isinstance(n.parent, compiler.ast.Discard), 'skip', ''), # Skip msg = "hello", in normal, message should more than one word (lambda n: len(n.value.strip().split(' ')) <= 1, 'skip', ''), # Skip msg = 'hello world' + vars + 'world hello' (lambda n: isinstance(n.parent, compiler.ast.Add), 'skip', ''), # Skip xml markers msg = "" (lambda n: len(re.compile("").findall(n.value)) > 0, 'skip', ''), # Skip sql statement (lambda n: len( re.compile("^SELECT.*FROM", flags=re.I).findall(n.value)) > 0, 'skip', ''), # LOG.xxx() (is_log_callfunc, 'error', 'Message must be localized'), # _('hello %s' % xyz) should be _('hello %s') % xyz (is_wrong_i18n_format, 'error', ("Message format was wrong, _('hello %s' % xyz) " "should be _('hello %s') % xyz")), # default (lambda n: True, 'warn', 'Message might need localized') ] """ Used for checking message format. (checker_func, message) """ msg_format_checkers = [ # If message contain more than on format specifier, it should use # mapping key (lambda n: len(re.compile("%[bcdeEfFgGnosxX]").findall(n.value)) > 1, "The message shouldn't contain more than one format specifier"), # Check capital (lambda n: n.value.split(' ')[0].count('_') == 0 and n.value[0].isalpha() and n.value[0].islower(), "First letter must be capital"), (is_log_i18n_msg_with_mod, 'LOG.xxx("Hello %s" % xyz) should be LOG.xxx("Hello %s", xyz)') ] file_black_list = ["./neutron/tests/unit", "./neutron/openstack", "./neutron/plugins/bigswitch/tests"] neutron-vpnaas-12.0.1/tools/configure_for_vpn_func_testing.sh0000777000175000017500000000325613370230606024566 0ustar zuulzuul00000000000000#!/usr/bin/env bash # 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. set -e IS_GATE=${IS_GATE:-False} USE_CONSTRAINT_ENV=${USE_CONSTRAINT_ENV:-False} PROJECT_NAME=${PROJECT_NAME:-neutron-vpnaas} REPO_BASE=${GATE_DEST:-$(cd $(dirname "$BASH_SOURCE")/../.. && pwd)} source $REPO_BASE/neutron/tools/configure_for_func_testing.sh source $REPO_BASE/neutron-vpnaas/devstack/settings source $NEUTRON_VPNAAS_DIR/devstack/plugin.sh function _install_vpn_package { case $VENV in dsvm-functional-sswan*) IPSEC_PACKAGE=strongswan ;; *) IPSEC_PACKAGE=openswan ;; esac echo_summary "Installing $IPSEC_PACKAGE for $VENV" neutron_agent_vpnaas_install_agent_packages } function configure_host_for_vpn_func_testing { echo_summary "Configuring for VPN functional testing" if [ "$IS_GATE" == "True" ]; then configure_host_for_func_testing fi # Note(pc_m): Need to ensure this is installed so we have # oslo-config-generator present (as this script runs before tox.ini). sudo pip install --force oslo.config _install_vpn_package } if [ "$IS_GATE" != "True" ]; then configure_host_for_vpn_func_testing fi neutron-vpnaas-12.0.1/tools/check_i18n.py0000666000175000017500000001243413370230606020231 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # 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 __future__ import print_function import compiler import imp import os.path import sys def is_localized(node): """Check message wrapped by _()""" if isinstance(node.parent, compiler.ast.CallFunc): if isinstance(node.parent.node, compiler.ast.Name): if node.parent.node.name == '_': return True return False class ASTWalker(compiler.visitor.ASTVisitor): def default(self, node, *args): for child in node.getChildNodes(): child.parent = node compiler.visitor.ASTVisitor.default(self, node, *args) class Visitor(object): def __init__(self, filename, i18n_msg_predicates, msg_format_checkers, debug): self.filename = filename self.debug = debug self.error = 0 self.i18n_msg_predicates = i18n_msg_predicates self.msg_format_checkers = msg_format_checkers with open(filename) as f: self.lines = f.readlines() def visitConst(self, node): if not isinstance(node.value, str): return if is_localized(node): for (checker, msg) in self.msg_format_checkers: if checker(node): print('%s:%d %s: %s Error: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], checker.__name__, msg), file=sys.stderr) self.error = 1 return if debug: print('%s:%d %s: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], "Pass")) else: for (predicate, action, msg) in self.i18n_msg_predicates: if predicate(node): if action == 'skip': if debug: print('%s:%d %s: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], "Pass")) return elif action == 'error': print('%s:%d %s: %s Error: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], predicate.__name__, msg), file=sys.stderr) self.error = 1 return elif action == 'warn': print('%s:%d %s: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], "Warn: %s" % msg)) return print('Predicate with wrong action!', file=sys.stderr) def is_file_in_black_list(black_list, f): for f in black_list: if os.path.abspath(input_file).startswith( os.path.abspath(f)): return True return False def check_i18n(input_file, i18n_msg_predicates, msg_format_checkers, debug): input_mod = compiler.parseFile(input_file) v = compiler.visitor.walk(input_mod, Visitor(input_file, i18n_msg_predicates, msg_format_checkers, debug), ASTWalker()) return v.error if __name__ == '__main__': input_path = sys.argv[1] cfg_path = sys.argv[2] try: cfg_mod = imp.load_source('', cfg_path) except Exception: print("Load cfg module failed", file=sys.stderr) sys.exit(1) i18n_msg_predicates = cfg_mod.i18n_msg_predicates msg_format_checkers = cfg_mod.msg_format_checkers black_list = cfg_mod.file_black_list debug = False if len(sys.argv) > 3: if sys.argv[3] == '-d': debug = True if os.path.isfile(input_path): sys.exit(check_i18n(input_path, i18n_msg_predicates, msg_format_checkers, debug)) error = 0 for dirpath, dirs, files in os.walk(input_path): for f in files: if not f.endswith('.py'): continue input_file = os.path.join(dirpath, f) if is_file_in_black_list(black_list, input_file): continue if check_i18n(input_file, i18n_msg_predicates, msg_format_checkers, debug): error = 1 sys.exit(error) neutron-vpnaas-12.0.1/tools/deploy_rootwrap.sh0000777000175000017500000000361013370230606021527 0ustar zuulzuul00000000000000#!/usr/bin/env bash # 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. set -eu if [ $# -ne 2 ]; then >&2 echo "Usage: $0 /path/to/repo /path/to/virtual-env Deploy rootwrap configuration and filters. Warning: Any existing rootwrap files at the specified etc path will be removed by this script. Optional: set OS_SUDO_TESTING=1 to deploy the filters required by Neutron's functional testing suite." exit 1 fi OS_SUDO_TESTING=${OS_SUDO_TESTING:-0} repo_path=$1 venv_path=$2 src_conf_path=${repo_path}/neutron_vpnaas/tests/contrib src_conf=${src_conf_path}/functional-test-rootwrap.conf src_rootwrap_path=${repo_path}/etc/neutron/rootwrap.d dst_conf_path=${venv_path}/etc/neutron dst_conf=${dst_conf_path}/rootwrap.conf dst_rootwrap_path=${dst_conf_path}/rootwrap.d # Clear any existing filters in virtual env if [[ -d "$dst_rootwrap_path" ]]; then rm -rf ${dst_rootwrap_path} fi mkdir -p -m 755 ${dst_rootwrap_path} # Get all needed filters cp -p ${src_rootwrap_path}/* ${dst_rootwrap_path}/ if [[ "$OS_SUDO_TESTING" = "1" ]]; then cp -p ${repo_path}/neutron_vpnaas/tests/contrib/functional-testing.filters \ ${dst_rootwrap_path}/ fi # Get config file and modify for this repo cp -p ${src_conf} ${dst_conf} sed -i "s:^filters_path=.*$:filters_path=${dst_rootwrap_path}:" ${dst_conf} sed -i "s:^\(exec_dirs=.*\)$:\1,${venv_path}/bin:" ${dst_conf} sudo mkdir -p /etc/neutron/ sudo cp ${dst_conf} /etc/neutron/ neutron-vpnaas-12.0.1/tools/ostestr_compat_shim.sh0000777000175000017500000000025113370230615022362 0ustar zuulzuul00000000000000#!/bin/sh # preserve old behavior of using an arg as a regex when '--' is not present case $@ in (*--*) ostestr $@;; ('') ostestr;; (*) ostestr --regex "$@" esac neutron-vpnaas-12.0.1/tools/install_venv.py0000666000175000017500000000437313370230606021024 0ustar zuulzuul00000000000000#!/usr/bin/env python # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2010 OpenStack Foundation. # Copyright 2013 IBM Corp. # # 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. """ Installation script for Neutron's development virtualenv """ from __future__ import print_function import os import sys import install_venv_common as install_venv def print_help(): help = """ Neutron development environment setup is complete. Neutron development uses virtualenv to track and manage Python dependencies while in development and testing. To activate the Neutron virtualenv for the extent of your current shell session you can run: $ . .venv/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ tools/with_venv.sh Also, make test will automatically use the virtualenv. """ print(help) def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) venv = os.path.join(root, '.venv') pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) project = 'Neutron' install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, py_version, project) options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() print_help() if __name__ == '__main__': main(sys.argv) neutron-vpnaas-12.0.1/tools/generate_config_file_samples.sh0000777000175000017500000000143313370230606024141 0ustar zuulzuul00000000000000#!/bin/sh # # 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. set -e GEN_CMD=oslo-config-generator if ! type "$GEN_CMD" > /dev/null; then echo "ERROR: $GEN_CMD not installed on the system." exit 1 fi for file in etc/oslo-config-generator/*; do $GEN_CMD --config-file=$file done set -x neutron-vpnaas-12.0.1/tools/tox_install.sh0000777000175000017500000000443513370230615020644 0ustar zuulzuul00000000000000#!/usr/bin/env bash # Many of neutron's repos suffer from the problem of depending on neutron, # but it not existing on pypi. # This wrapper for tox's package installer will use the existing package # if it exists, else use zuul-cloner if that program exists, else grab it # from neutron master via a hard-coded URL. That last case should only # happen with devs running unit tests locally. # From the tox.ini config page: # install_command=ARGV # default: # pip install {opts} {packages} ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner BRANCH_NAME=master neutron_installed=$(echo "import neutron" | python 2>/dev/null ; echo $?) NEUTRON_DIR=$HOME/neutron set -e set -x install_cmd="pip install -c$1" shift # The devstack based functional tests have neutron checked out in # $NEUTRON_DIR on the test systems - with the change to test in it. # Use this directory if it exists, so that this script installs the # neutron version to test here. # Note that the functional tests use sudo to run tox and thus # variables used for zuul-cloner to check out the correct version are # lost. if [ -d "$NEUTRON_DIR" ]; then echo "FOUND Neutron code at $NEUTRON_DIR - using" $install_cmd -U -e $NEUTRON_DIR elif [ $neutron_installed -eq 0 ]; then echo "ALREADY INSTALLED" > /tmp/tox_install.txt location=$(python -c "import neutron; print(neutron.__file__)") echo "ALREADY INSTALLED at $location" echo "Neutron already installed; using existing package" elif [ -x "$ZUUL_CLONER" ]; then echo "ZUUL CLONER" > /tmp/tox_install.txt # Make this relative to current working directory so that # git clean can remove it. We cannot remove the directory directly # since it is referenced after $install_cmd -e. mkdir -p .tmp NEUTRON_DIR=$(/bin/mktemp -d -p $(pwd)/.tmp) pushd $NEUTRON_DIR $ZUUL_CLONER --cache-dir \ /opt/git \ --branch $BRANCH_NAME \ git://git.openstack.org \ openstack/neutron cd openstack/neutron $install_cmd -e . popd else echo "PIP HARDCODE" > /tmp/tox_install.txt if [ -z "$NEUTRON_PIP_LOCATION" ]; then NEUTRON_PIP_LOCATION="git+https://git.openstack.org/openstack/neutron@$BRANCH_NAME#egg=neutron" fi $install_cmd -U -e ${NEUTRON_PIP_LOCATION} fi if [ -n "$*" ]; then $install_cmd -U $* fi exit $? neutron-vpnaas-12.0.1/tools/with_venv.sh0000777000175000017500000000133313370230606020307 0ustar zuulzuul00000000000000#!/usr/bin/env bash # Copyright 2011 OpenStack Foundation. # 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. TOOLS=`dirname $0` VENV=$TOOLS/../.venv source $VENV/bin/activate && "$@" neutron-vpnaas-12.0.1/AUTHORS0000664000175000017500000002375213370231104015650 0ustar zuulzuul00000000000000Aaron Rosen Abhishek Raut Adam Harwell Aishwarya Thangappa Akihiro MOTOKI Akihiro Motoki Akihiro Motoki Al Miller Aleks Chirko Alessandro Pilotti Alessio Ababilov Alessio Ababilov Amir Sadoughi Andre Pech Andreas Jaeger Andreas Jaeger Andrew Boik Andrey Pavlov Angus Lees Ann Kamyshnikova Armando Migliaccio Arundhati Surpur Arvind Somy Arvind Somya Assaf Muller Bertrand Lallau Bertrand Lallau Bharath M Bhuvan Arumugam Bo Chi Bob Kukura Bob Melander Boden R Bogdan Tabor Brad Hall Brandon Logan Brandon Logan Brant Knudson Brent Eagles Brian Haley Brian Waldon Cao Xuan Hoang Carl Baldwin Cedric Brandily Chandan Kumar Chang Bo Guo Christian Berendt Chuck Short Clark Boylan Clint Byrum Cyril Roelandt Dan Prince Dan Wendlandt Dariusz Smigiel Davanum Srinivas Davanum Srinivas Dave Lapsley Deepak N Dirk Mueller Dongcan Ye Doug Hellmann Doug Hellmann Doug Wiegley Doug Wiegley Duong Ha-Quang Edgar Magana Elena Ezhova Emilien Macchi Eugene Nikanorov Gary Kotton Gary Kotton German Eichberger Gordon Chung Guilherme Salgado Ha Van Tu Hareesh Puthalath He Jie Xu Hemanth Ravi Henry Gessau Henry Gessau Henry Gessau HenryVIII Hirofumi Ichihara Hoang Trung Hieu Hunt Xu Ignacio Scopetta Ihar Hrachyshka Ionuț Arțăriși Irena Berezovsky Irina Isaku Yamahata Isaku Yamahata JJ Asghar Jacek Swiderski Jakub Libosvar James Arendt James E. Blair James E. Blair James Page Jason Kölker Jay Pipes Jeremy Stanley Jiajun Liu Joe Gordon Joe Heck John Davidge John Dunning Jordan Tardif Juliano Martinez Julien Danjou Justin Lund Ken'ichi Ohmichi Keshava Bharadwaj Kevin Benton Kevin Benton Kevin L. Mitchell Kris Lindgren Kun Huang Kyle Mestery Kyle Mestery Lewis Zhang Li Ma LiuNanke Luke Gorrie Luong Anh Tuan Ly Loi Major Hayden Mark McClain Mark McClain Mark McLoughlin Martin Hickey Maru Newby Maru Newby Mate Lakat Mathieu Rohon Matt Riedemann Matthew Kassawara Matthew Treinish Michael Johnson Michael Smith Miguel Angel Ajo Mohammad Banikazemi Monty Taylor Morgan Fainberg Motohiro OTSUKA Nachi Ueno Nachi Ueno Nader Lahouti Nate Johnston Nguyen Phuong An Nick Numan Siddique Oleg Bondarev Ondřej Nový OpenStack Release Bot Paul Michali Paul Michali Peng Zhi Xiong Praneet Bachheti Rajaram Mallya Ralf Haferkamp Reedip Banerjee Rich Curran Roman Podoliaka Rui Zang Russell Bryant Ryan Moats Ryota MIBU Salvatore Orlando Salvatore Orlando Samer Deeb Santhosh Santhosh Kumar Sascha Peilicke Sascha Peilicke Sascha Peilicke Sean Dague Sean Dague Sean M. Collins Sergey Lukjanov Sergey Skripnick Sergey Vilgelm Shiv Haris Somik Behera Somik Behera Sridhar Ramaswamy Sridhar Ramaswamy Steven Gonzales Sukhdev Sumit Naiksatam Sun Zhengnan Sushil Kumar Swaminathan Vasudevan Sylvain Afchain Takashi NATSUME Terry Wilson Thierry Carrez Thomas Bechtold Thomas Bechtold Tim Miller Tomoko Inoue Tony Breeds Trevor McCasland Trinath Somanchi Tyler Smith Van Hung Pham Vladislav Belogrudov Vu Cong Tuan Wei Hu Weidong Shao Wu Wenxiang YAMAMOTO Takashi YAMAMOTO Takashi Yaguang Tang Yanping Qu Yatin Kumbhare Ying Liu Yong Sheng Gong Yong Sheng Gong Yoshihiro Kaneko Zang MingJie Zhang Hua Zhenguo Niu Zhenmei ZhiQiang Fan ZhiQiang Fan Zhongyue Luo ajmiller alexpilotti armando-migliaccio armando-migliaccio ashish-kumar-gupta berlin changzhi chen-li eeldill fujioka yuuichi fumihiko kakuma gengchc2 ghanshyam gongysh gongysh gordon chung hobo.kengo ji-xuepeng johndavidge justin Lund lawrancejing leejian0612 liu-sheng liuqing liuyamin llg8212 madhusudhan-kandadai mark mcclain mathieu-rohon melissaml nfedotov nick.zhuyj rabi rajat29 ricolin rohitagarwalla ronak rossella sanuptpm shihanzhang sridhargaddam sukhdev trinaths venkata anil vikas vinkesh banka xiaoli zhhuabj zhuyijing neutron-vpnaas-12.0.1/.testr.conf0000666000175000017500000000037313370230615016670 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_LOG_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./neutron_vpnaas/tests/unit} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list neutron-vpnaas-12.0.1/TESTING.rst0000666000175000017500000000051013370230606016442 0ustar zuulzuul00000000000000Testing Neutron VPNaaS ====================== Please see the TESTING.rst file for the Neutron project itself. This will have the latest up to date instructions for how to test Neutron, and will be applicable to neutron-vpnaas as well: `Neutron TESTING.rst `_ neutron-vpnaas-12.0.1/rally-jobs/0000775000175000017500000000000013370231105016646 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/rally-jobs/__init__.py0000666000175000017500000000000013370230606020754 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/rally-jobs/rally-configs/0000775000175000017500000000000013370231105021417 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/rally-jobs/rally-configs/args_template.json0000666000175000017500000000046213370230606025152 0ustar zuulzuul00000000000000{ "image_name": "^cirros.*-disk$", "private_key": "", "controller_creds": [ { "ip": "x.x.x.x", "username": "xxx" } ], "compute_creds": [ { "ip": "x.x.x.x", "username": "xxx", "port": 22 } ] } neutron-vpnaas-12.0.1/rally-jobs/rally-configs/rally_config_non_dvr.yaml0000666000175000017500000000477313370230606026522 0ustar zuulzuul00000000000000--- TestVpnBasicScenario.create_and_delete_vpn_connection: - args: flavor: name: "m1.tiny" image: name: {{image_name}} phase1_negotiation_mode: "main" auth_algorithm: "sha1" encryption_algorithm: "aes-128" pfs: "group5" value: 7200 ike_version: "v1" transform_protocol: "esp" encapsulation_mode: "tunnel" mtu: 1500 secret: "secret" nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: False use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnStatusScenario.check_vpn_status: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 400 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: False use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnTenantScenario.multitenants_vpn_test: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: False use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 neutron-vpnaas-12.0.1/rally-jobs/rally-configs/rally_config_dvr.yaml0000666000175000017500000000477213370230606025647 0ustar zuulzuul00000000000000--- TestVpnBasicScenario.create_and_delete_vpn_connection: - args: flavor: name: "m1.tiny" image: name: {{image_name}} phase1_negotiation_mode: "main" auth_algorithm: "sha1" encryption_algorithm: "aes-128" pfs: "group5" value: 7200 ike_version: "v1" transform_protocol: "esp" encapsulation_mode: "tunnel" mtu: 1500 secret: "secret" nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: True use_admin_client: False ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnStatusScenario.check_vpn_status: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 400 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: True use_admin_client: False ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnTenantScenario.multitenants_vpn_test: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: True use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0neutron-vpnaas-12.0.1/rally-jobs/plugins/0000775000175000017500000000000013370231105020327 5ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/rally-jobs/plugins/test_vpn_status.py0000666000175000017500000000564613370230606024170 0ustar zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 rally.common import logging from rally.task import scenario from rally.task import types import vpn_base LOG = logging.getLogger(__name__) class TestVpnStatusScenario(vpn_base.VpnBase): @types.convert(image={"type": "glance_image"}, flavor={"type": "nova_flavor"}) @scenario.configure() def check_vpn_status(self, **kwargs): """Test VPN's status correctly after bringing router's status to DOWN and back to ACTIVE state 1. Create 2 private networks, subnets and routers 2. Create public network, subnets and GW IPs on routers, if not present 3. Execute ip netns command and get the snat and qrouter namespaces (assuming we use DVR) 4. Verify that there is a route between the router gateways by pinging each other from their snat namespaces 5. Add security group rules for SSH and ICMP 6. Start a nova instance in each of the private networks 7. Create IKE and IPSEC policies 8. Create VPN service at each of the routers 9. Create IPSEC site connections at both endpoints 10. Bring both the private router's status to DOWN state 11. Verify that vpn-service and ipsec-site-connection is DOWN 12. Bring back the router's status to ACTIVE state 13. Verify the vpn-service and ipsec-site-connection is back to ACTIVE 14. Perform resource cleanup """ try: self.setup(**kwargs) self.create_networks(**kwargs) self.check_route() self.ike_policy = self._create_ike_policy(**kwargs) self.ipsec_policy = self._create_ipsec_policy(**kwargs) self.create_vpn_services() self.create_ipsec_site_connections(**kwargs) self.assert_statuses(final_status='ACTIVE', **kwargs) self.update_router(self.router_ids[0], admin_state_up=False) self.update_router(self.router_ids[1], admin_state_up=False) self.assert_statuses(final_status='DOWN', **kwargs) self.update_router(self.router_ids[0], admin_state_up=True) self.update_router(self.router_ids[1], admin_state_up=True) self.assert_statuses(final_status='ACTIVE', **kwargs) LOG.info("VPN STATUS TEST PASSED!") finally: self.cleanup() neutron-vpnaas-12.0.1/rally-jobs/plugins/__init__.py0000666000175000017500000000000013370230606022435 0ustar zuulzuul00000000000000neutron-vpnaas-12.0.1/rally-jobs/plugins/vpn_base.py0000666000175000017500000005223513370230606022514 0ustar zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 concurrent.futures import re import threading import time from oslo_utils import uuidutils from rally.common import logging from rally.plugins.openstack import scenario as rally_base from rally.task import atomic import vpn_utils LOG = logging.getLogger(__name__) LOCK = threading.RLock() MAX_RESOURCES = 2 class VpnBase(rally_base.OpenStackScenario): def setup(self, **kwargs): """Create and initialize data structures to hold various resources""" with LOCK: LOG.debug('SETUP RESOURCES') self.neutron_admin_client = self.admin_clients("neutron") if kwargs['use_admin_client']: self.neutron_client = self.neutron_admin_client self.keystone_client = self.admin_clients("keystone") self.nova_client = self.admin_clients("nova") else: self.neutron_client = self.clients("neutron") self.nova_client = self.clients("nova") self.suffixes = [uuidutils.generate_uuid(), uuidutils.generate_uuid()] self.remote_key_files = ['rally_keypair_' + x for x in self.suffixes] self.local_key_files = ['/tmp/' + x for x in self.remote_key_files] self.private_key_file = kwargs["private_key"] self.keypairs = [] self.tenant_ids = [] self.ns_controller_tuples = [] self.qrouterns_compute_tuples = [] self.router_ids = [] self.rally_router_gw_ips = [] self.rally_routers = [] self.rally_networks = [] self.rally_subnets = [] self.rally_cidrs = [] self.ike_policy = None self.ipsec_policy = None self.vpn_services = [] self.ipsec_site_connections = [] self.servers = [] self.server_private_ips = [] self.server_fips = [] def create_tenants(self): """Create tenants""" for x in range(MAX_RESOURCES): tenant_id = vpn_utils.create_tenant( self.keystone_client, self.suffixes[x]) with LOCK: self.tenant_ids.append(tenant_id) def create_networks(self, **kwargs): """Create networks to test vpn connectivity""" for x in range(MAX_RESOURCES): if self.tenant_ids: router, network, subnet, cidr = vpn_utils.create_network( self.neutron_client, self.neutron_admin_client, self.suffixes[x], tenant_id=self.tenant_ids[x], DVR_flag=kwargs["DVR_flag"], ext_net_name=kwargs["ext-net"]) else: router, network, subnet, cidr = vpn_utils.create_network( self.neutron_client, self.neutron_admin_client, self.suffixes[x], DVR_flag=kwargs["DVR_flag"], ext_net_name=kwargs["ext-net"]) with LOCK: self.rally_cidrs.append(cidr) self.rally_subnets.append(subnet) self.rally_networks.append(network) self.rally_routers.append(router) self.router_ids.append(router["router"]['id']) self.rally_router_gw_ips.append( router["router"]["external_gateway_info"] ["external_fixed_ips"][0]["ip_address"]) if(kwargs["DVR_flag"]): ns, controller = vpn_utils.wait_for_namespace_creation( "snat-", router["router"]['id'], kwargs['controller_creds'], self.private_key_file, kwargs['namespace_creation_timeout']) else: ns, controller = vpn_utils.wait_for_namespace_creation( "qrouter-", router["router"]['id'], kwargs['controller_creds'], self.private_key_file, kwargs['namespace_creation_timeout']) with LOCK: self.ns_controller_tuples.append((ns, controller)) def create_servers(self, **kwargs): """Create servers""" for x in range(MAX_RESOURCES): kwargs.update({ "nics": [{"net-id": self.rally_networks[x]["network"]["id"]}], "sec_group_suffix": self.suffixes[x], "server_suffix": self.suffixes[x] }) keypair = vpn_utils.create_keypair( self.nova_client, self.suffixes[x]) server = vpn_utils.create_server( self.nova_client, keypair, **kwargs) vpn_utils.assert_server_status(server, **kwargs) with LOCK: self.servers.append(server) self.keypairs.append(keypair) self.server_private_ips.append(vpn_utils.get_server_ip( self.nova_client, server.id, self.suffixes[x])) if(kwargs["DVR_flag"]): qrouter, compute = vpn_utils.wait_for_namespace_creation( "qrouter-", self.router_ids[x], kwargs['compute_creds'], self.private_key_file, kwargs['namespace_creation_timeout']) vpn_utils.write_key_to_compute_node( keypair, self.local_key_files[x], self.remote_key_files[x], compute, self.private_key_file) with LOCK: self.qrouterns_compute_tuples.append((qrouter, compute)) else: vpn_utils.write_key_to_local_path(self.keypairs[x], self.local_key_files[x]) fip = vpn_utils.add_floating_ip(self.nova_client, server) with LOCK: self.server_fips.append(fip) def check_route(self): """Verify route exists between the router gateways""" LOG.debug("VERIFY ROUTE EXISTS BETWEEN THE ROUTER GATEWAYS") for tuple in self.ns_controller_tuples: for ip in self.rally_router_gw_ips: assert(vpn_utils.ping_router_gateway( tuple, ip, self.private_key_file)), ( "PING TO IP " + ip + " FAILED") @atomic.action_timer("_create_ike_policy") def _create_ike_policy(self, **kwargs): """Create IKE policy :return: IKE policy """ LOG.debug('CREATING IKE_POLICY') ike_policy = self.neutron_client.create_ikepolicy({ "ikepolicy": { "phase1_negotiation_mode": kwargs.get("phase1_negotiation_mode", "main"), "auth_algorithm": kwargs.get("auth_algorithm", "sha1"), "encryption_algorithm": kwargs.get("encryption_algorithm", "aes-128"), "pfs": kwargs.get("pfs", "group5"), "lifetime": { "units": "seconds", "value": kwargs.get("value", 7200)}, "ike_version": kwargs.get("ike_version", "v1"), "name": "rally_ikepolicy" } }) return ike_policy @atomic.action_timer("_create_ipsec_policy") def _create_ipsec_policy(self, **kwargs): """Create IPSEC policy :return: IPSEC policy """ LOG.debug('CREATING IPSEC_POLICY') ipsec_policy = self.neutron_client.create_ipsecpolicy({ "ipsecpolicy": { "name": "rally_ipsecpolicy", "transform_protocol": kwargs.get("transform_protocol", "esp"), "auth_algorithm": kwargs.get("auth_algorithm", "sha1"), "encapsulation_mode": kwargs.get("encapsulation_mode", "tunnel"), "encryption_algorithm": kwargs.get("encryption_algorithm", "aes-128"), "pfs": kwargs.get("pfs", "group5"), "lifetime": { "units": "seconds", "value": kwargs.get("value", 7200) } } }) return ipsec_policy @atomic.action_timer("_create_vpn_service") def _create_vpn_service(self, rally_subnet, rally_router, vpn_suffix=None): """Create VPN service endpoints :param rally_subnet: local subnet :param rally_router: router endpoint :param vpn_suffix: suffix name for vpn service :return: VPN service """ LOG.debug('CREATING VPN_SERVICE') vpn_service = self.neutron_client.create_vpnservice({ "vpnservice": { "subnet_id": rally_subnet["subnet"]["id"], "router_id": rally_router["router"]["id"], "name": "rally_vpn_service_" + vpn_suffix, "admin_state_up": True } }) return vpn_service def create_vpn_services(self): """Create VPN services""" for x in range(MAX_RESOURCES): vpn_service = self._create_vpn_service( self.rally_subnets[x], self.rally_routers[x], self.suffixes[x]) with LOCK: self.vpn_services.append(vpn_service) @atomic.action_timer("_create_ipsec_site_connection") def _create_ipsec_site_connection(self, local_index, peer_index, **kwargs): """Create IPSEC site connection :param local_index: parameter to point to the local end-point :param peer_index: parameter to point to the peer end-point :return: IPSEC site connection """ LOG.debug('CREATING IPSEC_SITE_CONNECTION') ipsec_site_conn = self.neutron_client.create_ipsec_site_connection({ "ipsec_site_connection": { "psk": kwargs.get("secret", "secret"), "initiator": "bi-directional", "ipsecpolicy_id": self.ipsec_policy["ipsecpolicy"]["id"], "admin_state_up": True, "peer_cidrs": self.rally_cidrs[peer_index], "mtu": kwargs.get("mtu", "1500"), "ikepolicy_id": self.ike_policy["ikepolicy"]["id"], "dpd": { "action": "disabled", "interval": 60, "timeout": 240 }, "vpnservice_id": self.vpn_services[local_index]["vpnservice"]["id"], "peer_address": self.rally_router_gw_ips[peer_index], "peer_id": self.rally_router_gw_ips[peer_index], "name": "rally_ipsec_site_connection_" + self.suffixes[local_index] } }) return ipsec_site_conn def create_ipsec_site_connections(self, **kwargs): """Create IPSEC site connections""" a = self._create_ipsec_site_connection(0, 1, **kwargs) b = self._create_ipsec_site_connection(1, 0, **kwargs) with LOCK: self.ipsec_site_connections = [a, b] def _get_resource(self, resource_tag, resource_id): """Get the resource(vpn_service or ipsec_site_connection) :param resource_tag: "vpnservice" or "ipsec_site_connection" :param resource_id: id of the resource :return: resource (vpn_service or ipsec_site_connection) """ if resource_tag == "vpnservice": vpn_service = self.neutron_client.show_vpnservice(resource_id) if vpn_service: return vpn_service elif resource_tag == 'ipsec_site_connection': ipsec_site_conn = self.neutron_client.show_ipsec_site_connection( resource_id) if ipsec_site_conn: return ipsec_site_conn def _wait_for_status_change(self, resource, resource_tag, final_status, wait_timeout=60, check_interval=1): """Wait for resource's status change Wait till the status of the resource changes to final state or till the time exceeds the wait_timeout value. :param resource: resource whose status has to be checked :param final_status: desired final status of the resource :param resource_tag: to identify the resource as vpnservice or ipsec_site_connection :param wait_timeout: timeout value in seconds :param check_interval: time to sleep before each check for the status change :return: resource """ LOG.debug('WAIT_FOR_%s_STATUS_CHANGE ', resource[resource_tag]['id']) start_time = time.time() while True: resource = self._get_resource( resource_tag, resource[resource_tag]['id']) current_status = resource[resource_tag]['status'] if current_status == final_status: return resource time.sleep(check_interval) if time.time() - start_time > wait_timeout: raise Exception( "Timeout waiting for resource {} to change to {} status". format(resource[resource_tag]['name'], final_status)) @atomic.action_timer("wait_time_for_status_change") def _assert_statuses(self, ipsec_site_conn, vpn_service, final_status, **kwargs): """Assert statuses of vpn_service and ipsec_site_connection :param ipsec_site_conn: ipsec_site_connection object :param vpn_service: vpn_service object :param final_status: status of vpn and ipsec_site_connection object """ vpn_service = self._wait_for_status_change( vpn_service, resource_tag="vpnservice", final_status=final_status, wait_timeout=kwargs.get("vpn_service_creation_timeout"), check_interval=5) ipsec_site_conn = self._wait_for_status_change( ipsec_site_conn, resource_tag="ipsec_site_connection", final_status=final_status, wait_timeout=kwargs.get("ipsec_site_connection_creation_timeout"), check_interval=5) LOG.debug("VPN SERVICE STATUS %s", vpn_service['vpnservice']['status']) LOG.debug("IPSEC_SITE_CONNECTION STATUS %s", ipsec_site_conn['ipsec_site_connection']['status']) def assert_statuses(self, final_status, **kwargs): """Assert active statuses for VPN services and VPN connections :param final_status: the final status you expect the resource to be in """ LOG.debug("ASSERTING ACTIVE STATUSES FOR VPN-SERVICES AND " "IPSEC-SITE-CONNECTIONS") for x in range(MAX_RESOURCES): self._assert_statuses( self.ipsec_site_connections[x], self.vpn_services[x], final_status, **kwargs) def _get_qg_interface(self, peer_index): """Get the qg- interface :param peer_index: parameter to point to the local end-point :return: qg-interface """ qg = vpn_utils.get_interfaces( self.ns_controller_tuples[peer_index], self.private_key_file) p = re.compile(r"qg-\w+-\w+") for line in qg: m = p.search(line) if m: return m.group() return None @atomic.action_timer("_verify_vpn_connection") def _verify_vpn_connectivity(self, local_index, peer_index, **kwargs): """Verify the vpn connectivity between the endpoints Get the qg- interface from the snat namespace corresponding to the peer router and start a tcp dump. Concurrently, SSH into the nova instance on the local subnet from the qrouter namespace and try to ping the nova instance on the peer subnet. Inspect the captured packets to see if they are encrypted. :param local_index: parameter to point to the local end-point :param peer_index: parameter to point to the peer end-point :return: True if vpn connectivity test passes False if the test fails """ qg_interface = self._get_qg_interface(peer_index) if qg_interface: with concurrent.futures.ThreadPoolExecutor(max_workers=2) as e: tcpdump_future = e.submit(vpn_utils.start_tcpdump, self.ns_controller_tuples[peer_index], qg_interface, self.private_key_file) if(kwargs["DVR_flag"]): ssh_future = e.submit( vpn_utils.ssh_and_ping_server, self.server_private_ips[local_index], self.server_private_ips[peer_index], self.qrouterns_compute_tuples[local_index], self.remote_key_files[local_index], self.private_key_file) else: ssh_future = e.submit( vpn_utils.ssh_and_ping_server_with_fip, self.server_fips[local_index], self.server_private_ips[peer_index], self.local_key_files[local_index], self.private_key_file) assert(ssh_future.result()), "SSH/Ping failed" for line in tcpdump_future.result(): if 'ESP' in line: return True return False def verify_vpn_connectivity(self, **kwargs): """Verify VPN connectivity""" LOG.debug("VERIFY THE VPN CONNECTIVITY") with LOCK: assert(self._verify_vpn_connectivity( 0, 1, **kwargs)), "VPN CONNECTION FAILED" with LOCK: assert(self._verify_vpn_connectivity( 1, 0, **kwargs)), "VPN CONNECTION FAILED" def update_router(self, router_id, admin_state_up=False): """Update router's admin_state_up field :param router_id: uuid of the router :param admin_state_up: True or False """ LOG.debug('UPDATE ROUTER') router_args = {'router': {'admin_state_up': admin_state_up}} self.neutron_client.update_router(router_id, router_args) @atomic.action_timer("_delete_ipsec_site_connection") def _delete_ipsec_site_connections(self): """Delete IPSEC site connections""" for site_conn in self.ipsec_site_connections: LOG.debug("DELETING IPSEC_SITE_CONNECTION %s", site_conn['ipsec_site_connection']['id']) self.neutron_client.delete_ipsec_site_connection( site_conn['ipsec_site_connection']['id']) @atomic.action_timer("_delete_vpn_service") def _delete_vpn_services(self): """Delete VPN service endpoints""" for vpn_service in self.vpn_services: LOG.debug("DELETING VPN_SERVICE %s", vpn_service['vpnservice']['id']) self.neutron_client.delete_vpnservice( vpn_service['vpnservice']['id']) @atomic.action_timer("_delete_ipsec_policy") def _delete_ipsec_policy(self): """Delete IPSEC policy""" LOG.debug("DELETING IPSEC POLICY") if self.ipsec_policy: self.neutron_client.delete_ipsecpolicy( self.ipsec_policy['ipsecpolicy']['id']) @atomic.action_timer("_delete_ike_policy") def _delete_ike_policy(self): """Delete IKE policy""" LOG.debug('DELETING IKE POLICY') if self.ike_policy: self.neutron_client.delete_ikepolicy( self.ike_policy['ikepolicy']['id']) @atomic.action_timer("cleanup") def cleanup(self): """Clean the resources""" vpn_utils.delete_servers(self.nova_client, self.servers) if self.server_fips: vpn_utils.delete_floating_ips(self.nova_client, self.server_fips) vpn_utils.delete_keypairs(self.nova_client, self.keypairs) if self.qrouterns_compute_tuples: vpn_utils.delete_hosts_from_knownhosts_file( self.server_private_ips, self.qrouterns_compute_tuples, self.private_key_file) vpn_utils.delete_keyfiles( self.local_key_files, self.remote_key_files, self.qrouterns_compute_tuples, self.private_key_file) else: vpn_utils.delete_hosts_from_knownhosts_file( self.server_private_ips) vpn_utils.delete_keyfiles(self.local_key_files) self._delete_ipsec_site_connections() self._delete_vpn_services() self._delete_ipsec_policy() self._delete_ike_policy() vpn_utils.delete_networks( self.neutron_client, self.neutron_admin_client, self.rally_routers, self.rally_networks, self.rally_subnets) if self.tenant_ids: vpn_utils.delete_tenants(self.keystone_client, self.tenant_ids) neutron-vpnaas-12.0.1/rally-jobs/plugins/test_vpn_tenant_scenario.py0000666000175000017500000000461113370230606026010 0ustar zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 rally.common import logging from rally.task import scenario from rally.task import types import vpn_base LOG = logging.getLogger(__name__) class TestVpnTenantScenario(vpn_base.VpnBase): """Rally scenarios for VPNaaS""" @types.convert(image={"type": "glance_image"}, flavor={"type": "nova_flavor"}) @scenario.configure() def multitenants_vpn_test(self, **kwargs): """Test VPN connectivity under two different tenants. 1. Create 2 private networks with 2 different tenants, subnets, routers 2. Create public network, subnets and GW IPs on routers, if not present 3. Execute ip netns command and get the snat and qrouter namespaces (assuming we use DVR) 4. Verify that there is a route between the router gateways by pinging each other from their snat namespaces 5. Add security group rules for SSH and ICMP 6. Start a nova instance in each of the private networks 7. Create IKE and IPSEC policies 8. Create VPN service at each of the routers 9. Create IPSEC site connections at both endpoints 10. Verify that the vpn-service and ipsec-site-connection are ACTIVE 11. Cleanup the resources that are setup for this test """ try: self.setup(**kwargs) self.create_tenants() self.create_networks(**kwargs) self.check_route() self.ike_policy = self._create_ike_policy(**kwargs) self.ipsec_policy = self._create_ipsec_policy(**kwargs) self.create_vpn_services() self.create_ipsec_site_connections(**kwargs) self.assert_statuses(final_status='ACTIVE', **kwargs) LOG.info("VPN TENANT TEST PASSED!") finally: self.cleanup() neutron-vpnaas-12.0.1/rally-jobs/plugins/vpn_utils.py0000666000175000017500000006061613370230606022744 0ustar zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 os import socket import stat import time import paramiko from rally.common import logging from rally.plugins.openstack.wrappers import network as network_wrapper from rally.task import utils as task_utils LOG = logging.getLogger(__name__) SUBNET_IP_VERSION = 4 START_CIDR = "10.2.0.0/24" EXT_NET_CIDR = "172.16.1.0/24" def execute_cmd_over_ssh(host, cmd, private_key): """Run the given command over ssh Using paramiko package, it creates a connection to the given host; executes the required command on it and returns the output. :param host: Dictionary of ip, username and password :param cmd: Command to be run over ssh :param private_key: path to private key file :return: Output of the executed command """ LOG.debug('EXECUTE COMMAND <%s> OVER SSH', cmd) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) k = paramiko.RSAKey.from_private_key_file(private_key) try: client.connect(host["ip"], username=host["username"], pkey=k) except paramiko.BadHostKeyException as e: raise Exception( "BADHOSTKEY EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.AuthenticationException as e: raise Exception( "AUTHENTICATION EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.SSHException as e: raise Exception("SSH EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except socket.error as e: raise Exception("SOCKET ERROR WHEN CONNECTING TO %s", host["ip"], e) LOG.debug("CONNECTED TO HOST <%s>", host["ip"]) try: stdin, stdout, stderr = client.exec_command(cmd) return stdout.read().splitlines() except paramiko.SSHException as e: raise Exception("SSHEXCEPTION WHEN CONNECTING TO %s", host["ip"], e) finally: client.close() def create_tenant(keystone_client, tenant_suffix): """Creates keystone tenant with a random name. :param keystone_client: keystone client :param tenant_suffix: suffix name for the tenant :returns: uuid of the new tenant """ tenant_name = "rally_tenant_" + tenant_suffix LOG.debug("CREATING NEW TENANT %s", tenant_name) return keystone_client.tenants.create(tenant_name).id def create_network(neutron_client, neutron_admin_client, network_suffix, tenant_id=None, DVR_flag=True, ext_net_name=None): """Create neutron network, subnet, router :param neutron_client: neutron client :param neutron_admin_client: neutron client with admin credentials :param network_suffix: str, suffix name of the new network :param tenant_id: uuid of the tenant :param DVR_flag: True - creates a DVR router False - creates a non DVR router :param ext_net_name: external network that is to be used :return: router, subnet, network, subnet_cidr """ subnet_cidr = network_wrapper.generate_cidr(start_cidr=START_CIDR) def _create_network(neutron_client, network_suffix, is_external=False): """Creates neutron network""" network_name = "rally_network_" + network_suffix network_args = {"name": network_name, "router:external": is_external } if tenant_id: network_args["tenant_id"] = tenant_id LOG.debug("ADDING NEW NETWORK %s", network_name) return neutron_client.create_network({"network": network_args}) def _create_subnet(neutron_client, rally_network, network_suffix, cidr): """Create neutron subnet""" network_id = rally_network["network"]["id"] subnet_name = "rally_subnet_" + network_suffix subnet_args = {"name": subnet_name, "cidr": cidr, "network_id": network_id, "ip_version": SUBNET_IP_VERSION } if tenant_id: subnet_args["tenant_id"] = tenant_id LOG.debug("ADDING SUBNET %s", subnet_name) return neutron_client.create_subnet({"subnet": subnet_args}) def _create_router(neutron_client, ext_network_id, rally_subnet, dvr_flag): """Create router, set the external gateway and add router interface :param neutron_client: neutron_client :param ext_network_id: uuid of the external network :param rally_subnet: subnet to add router interface :param dvr_flag: True - creates a DVR router False - creates a non DVR router :return: router """ router_name = "rally_router_" + network_suffix gw_info = {"network_id": ext_network_id} router_args = {"name": router_name, "external_gateway_info": gw_info } if not dvr_flag: router_args["distributed"] = dvr_flag if tenant_id: router_args["tenant_id"] = 'tenant_id' LOG.debug("ADDING ROUTER %s", router_name) rally_router = neutron_client.create_router({"router": router_args}) LOG.debug("[%s]: ADDING ROUTER INTERFACE") neutron_client.add_interface_router( rally_router['router']["id"], {"subnet_id": rally_subnet["subnet"]["id"]}) return rally_router def _get_external_network_id(ext_net_name): """Fetch the network id for the given external network, if it exists. Else fetch the first external network present. """ ext_nets = neutron_client.list_networks( **{'router:external': True})['networks'] ext_nets_searched = [n for n in ext_nets if n['name'] == ext_net_name] if ext_nets_searched: return ext_nets_searched[0]['id'] elif ext_nets: return ext_nets[0]['id'] else: return None def _create_external_network(): """Creat external network and subnet""" ext_net = _create_network(neutron_admin_client, "public", True) _create_subnet(neutron_admin_client, ext_net, "public", EXT_NET_CIDR) return ext_net['network']['id'] ext_network_id = _get_external_network_id(ext_net_name) if not ext_network_id: ext_network_id = _create_external_network() rally_network = _create_network(neutron_client, network_suffix) rally_subnet = _create_subnet(neutron_client, rally_network, network_suffix, subnet_cidr) rally_router = _create_router(neutron_client, ext_network_id, rally_subnet, DVR_flag) return rally_router, rally_network, rally_subnet, subnet_cidr def create_keypair(nova_client, keypair_suffix): """Create keypair :param nova_client: nova_client :param keypair_suffix: sufix name for the keypair :return: keypair """ keypair_name = "rally_keypair_" + keypair_suffix LOG.debug("CREATING A KEYPAIR %s", keypair_name) keypair = nova_client.keypairs.create(keypair_name) return keypair def write_key_to_local_path(keypair, local_key_file): """Write the private key of the nova instance to a temp file :param keypair: nova keypair :param local_key_file: path to private key file :return: """ with open(local_key_file, 'w') as f: os.chmod(local_key_file, stat.S_IREAD | stat.S_IWRITE) f.write(keypair.private_key) def write_key_to_compute_node(keypair, local_path, remote_path, host, private_key): """Write the private key of the nova instance to the compute node First fetches the private key from the keypair and writes it to a temporary file in the local machine. It then sftp's the file to the compute host. :param keypair: nova keypair :param local_path: path to private key file of the nova instance in the local machine :param remote_path: path where the private key file has to be placed in the remote machine :param host: compute host credentials :param private_key: path to your private key file :return: """ LOG.debug("WRITING PRIVATE KEY TO COMPUTE NODE") k = paramiko.RSAKey.from_private_key_file(private_key) write_key_to_local_path(keypair, local_path) try: transport = paramiko.Transport(host['ip'], host['port']) except paramiko.SSHException as e: raise Exception( "PARAMIKO TRANSPORT FAILED. CHECK IF THE HOST IP %s AND PORT %s " "ARE CORRECT %s", host['ip'], host['port'], e) try: transport.connect( username=host['username'], pkey=k) except paramiko.BadHostKeyException as e: transport.close() raise Exception( "BADHOSTKEY EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.AuthenticationException as e: transport.close() raise Exception("AUTHENTICATION EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.SSHException as e: transport.close() raise Exception("SSH EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) LOG.debug("CONNECTED TO HOST <%s>", host["ip"]) try: sftp_client = paramiko.SFTPClient.from_transport(transport) sftp_client.put(local_path, remote_path) except IOError as e: raise Exception("FILE PATH DOESN'T EXIST", e) finally: transport.close() def create_server(nova_client, keypair, **kwargs): """Create nova instance :param nova_client: nova client :param keypair: key-pair to allow ssh :return: new nova instance """ # add sec-group sec_group_name = "rally_secgroup_" + kwargs["sec_group_suffix"] LOG.debug("ADDING NEW SECURITY GROUP %s", sec_group_name) secgroup = nova_client.security_groups.create(sec_group_name, sec_group_name) # add security rules for SSH and ICMP nova_client.security_group_rules.create(secgroup.id, from_port=22, to_port=22, ip_protocol="tcp", cidr="0.0.0.0/0") nova_client.security_group_rules.create(secgroup.id, from_port=-1, to_port=-1, ip_protocol="icmp", cidr="0.0.0.0/0") # boot new nova instance server_name = "rally_server_" + (kwargs["server_suffix"]) LOG.debug("BOOTING NEW INSTANCE: %s", server_name) LOG.debug("%s", kwargs["image"]) server = nova_client.servers.create(server_name, image=kwargs["image"], flavor=kwargs["flavor"], key_name=keypair.name, security_groups=[secgroup.id], nics=kwargs["nics"]) return server def assert_server_status(server, **kwargs): """Assert server status :param server: nova server """ LOG.debug('WAITING FOR SERVER TO GO ACTIVE') server = task_utils.wait_for( server, is_ready=task_utils.resource_is("ACTIVE"), update_resource=task_utils.get_from_manager(), timeout=kwargs["nova_server_boot_timeout"], check_interval=5) LOG.debug("SERVER STATUS: %s", server.status) assert('ACTIVE' == server.status), ("THE INSTANCE IS NOT IN ACTIVE STATE") def get_server_ip(nova_client, server_id, network_suffix): """Get the ip associated with the nova instance :param nova_client: nova client :param server_id: uuid of the nova instance whose ip is required :param network_suffix: suffix name of the network :return: ip address of the instance """ network_name = "rally_network_" + network_suffix server_details = nova_client.servers.get(server_id) server_ip = server_details.addresses[network_name][0]["addr"] return server_ip def add_floating_ip(nova_client, server): """Associates floating-ip to a server :param nova_client: nova client :param server: nova instance :return: associated floating ip """ fip_list = nova_client.floating_ips.list() for fip in fip_list: if fip.instance_id is None: floating_ip = fip break else: LOG.debug("CREATING NEW FLOATING IP") floating_ip = nova_client.floating_ips.create() LOG.debug("ASSOCIATING FLOATING IP %s", floating_ip.ip) nova_client.servers.add_floating_ip(server.id, floating_ip.ip) return floating_ip def get_namespace(host, private_key): """SSH into the host and get the namespaces :param host : dictionary of controller/compute node credentials {ip:x.x.x.x, username:xxx, password:xxx} :param private_key: path to private key file :return: namespaces """ LOG.debug("GET NAMESPACES") cmd = "sudo ip netns" namespaces = execute_cmd_over_ssh(host, cmd, private_key) LOG.debug("NAMESPACES %s", namespaces) return namespaces def wait_for_namespace_creation(namespace_tag, router_id, hosts, private_key, timeout=60): """Wait for the namespace creation Get into each of the controllers/compute nodes and check which one contains the snat/qrouter namespace corresponding to rally_router. Sleep for a sec and repeat until either the namespace is found or the namespace_creation_ time exceeded. :param namespace_tag: which namespace ("snat_" or "qrouter_") :param router_id: uuid of the rally_router :param hosts: controllers or compute hosts :param private_key: path to private key file :param timeout: namespace creation time :return: """ start_time = time.time() while True: for host in hosts: namespaces = get_namespace(host, private_key) for line in namespaces: if line == (namespace_tag + router_id): namespace_tag = line return namespace_tag, host time.sleep(1) if time.time() - start_time > timeout: raise Exception("TIMEOUT WHILE WAITING FOR" " NAMESPACES TO BE CREATED") def ping(host, cmd, private_key): """Execute ping command over ssh""" ping_result = execute_cmd_over_ssh(host, cmd, private_key) if ping_result: LOG.debug("PING RESULT %s", ping_result) return True else: return False def ping_router_gateway(namespace_controller_tuple, router_gw_ip, private_key): """Ping the ip address from network namespace Get into controller's snat-namespaces and ping the peer router gateway ip. :param namespace_controller_tuple: namespace, controller tuple. (It's the controller that contains the namespace ) :param router_gw_ip: ip address to be pinged :param private_key: path to private key file :return: True if ping succeeds False if ping fails """ namespace, controller = namespace_controller_tuple LOG.debug("PING %s FROM THE NAMESPACE %s", router_gw_ip, namespace) count = 4 cmd = "sudo ip netns exec {} ping -w {} -c {} {}".format( namespace, 2 * count, count, router_gw_ip) return ping(controller, cmd, private_key) def get_interfaces(namespace_controller_tuple, private_key): """Get the interfaces Get into the controller's snat namespace and list the interfaces. :param namespace_controller_tuple: namespace, controller tuple(the controller that contains the namespace). :param private_key: path to private key file :return: interfaces """ namespace, controller = namespace_controller_tuple LOG.debug("GET THE INTERFACES BY USING 'ip a' FROM THE NAMESPACE %s", namespace) cmd = "sudo ip netns exec {} ip a".format(namespace) interfaces = execute_cmd_over_ssh(controller, cmd, private_key) LOG.debug("INTERFACES %s", interfaces) return interfaces def start_tcpdump(namespace_controller_tuple, interface, private_key): """Start the tcpdump at the given interface Get into the controller's snat namespace and start a tcp dump at the qg-interface. :param namespace_controller_tuple: namespace, controller tuple. (It's the controller that contains the namespace ) :param interface: interface in which tcpdump has to be run :param private_key: path to private key file :return: tcpdump output """ namespace, controller = namespace_controller_tuple LOG.debug("START THE TCPDUMP USING 'tcpdump -i %s FROM THE NAMESPACE" " %s", interface, namespace) cmd = ("sudo ip netns exec {} timeout 15 tcpdump -n -i {}" .format(namespace, interface)) tcpdump = execute_cmd_over_ssh(controller, cmd, private_key) LOG.debug("TCPDUMP %s", tcpdump) return tcpdump def ssh_and_ping_server(local_server, peer_server, ns_compute_tuple, keyfile, private_key): """SSH and ping the nova instance from the namespace Get into the compute node's qrouter namespace and then ssh into the local nova instance & ping the peer nova instance. :param local_server: private ip of the server to ssh into :param peer_server: private ip of the server to ping to :param ns_compute_tuple: namespace, compute tuple. (It's the compute node that contains the namespace ) :param keyfile: path to private key file of the nova instance :param private_key: path to private key file :return: True if ping succeeds False if ping fails """ namespace, compute_host = ns_compute_tuple LOG.debug("SSH INTO SERVER %s AND PING THE PEER SERVER %s FROM THE" " NAMESPACE %s", local_server, peer_server, namespace) host = "cirros@" + local_server count = 20 cmd = ("sudo ip netns exec {} ssh -v -o StrictHostKeyChecking=no -o" "HashKnownHosts=no -i {} {} ping -w {} -c {} {}" .format(namespace, keyfile, host, 2 * count, count, peer_server)) return ping(compute_host, cmd, private_key) def ssh_and_ping_server_with_fip(local_server, peer_server, keyfile, private_key): """SSH into the local nova instance and ping the peer instance using fips :param local_server: fip of the server to ssh into :param peer_server: private ip of the server to ping to :param keyfile: path to private key file of the nova instance :param private_key: path to private key file :return: True if ping succeeds False if ping fails """ LOG.debug("SSH INTO LOCAL SERVER %s AND PING THE PEER SERVER %s", local_server.ip, peer_server) count = 20 local_host = {"ip": "127.0.0.1", "username": None} host = "cirros@" + local_server.ip cmd = ("ssh -v -o StrictHostKeyChecking=no -o" "HashKnownHosts=no -i {} {} ping -w {} -c {} {}" .format(keyfile, host, 2 * count, count, peer_server)) return ping(local_host, cmd, private_key) def delete_servers(nova_client, servers): """Delete nova servers It deletes the nova servers, associated security groups. :param nova_client: nova client :param servers: nova instances to be deleted :return: """ for server in servers: LOG.debug("DELETING NOVA INSTANCE: %s", server.id) sec_group_id = server.security_groups[0]['name'] nova_client.servers.delete(server.id) LOG.debug("WAITING FOR INSTANCE TO GET DELETED") task_utils.wait_for_delete( server, update_resource=task_utils.get_from_manager()) for secgroup in nova_client.security_groups.list(): if secgroup.id == sec_group_id: LOG.debug("DELETING SEC_GROUP: %s", sec_group_id) nova_client.security_groups.delete(secgroup.id) def delete_floating_ips(nova_client, fips): """Delete floating ips :param nova_client: nova client :param fips: list of floating ips :return: """ for fip in fips: nova_client.floating_ips.delete(fip.id) def delete_keypairs(nova_client, keypairs): """Delete key pairs :param nova_client: nova client :param keypairs: list of keypairs :return """ for key_pair in keypairs: LOG.debug("DELETING KEY_PAIR %s", key_pair.name) nova_client.keypairs.delete(key_pair.id) def delete_networks(neutron_client, neutron_admin_client, routers, networks, subnets): """Delete neutron network, subnets amd routers :param neutron_client: neutron client :param neutron_admin_client: neutron_admin_client :param routers: list of routers to be deleted :param networks: list of networks to be deleted :param subnets: list of subnets to be deleted :return """ LOG.debug("DELETING RALLY ROUTER INTERFACES & GATEWAYS") for router in routers: neutron_client.remove_gateway_router(router['router']['id']) router_name = router['router']['name'] subnet_name = ("rally_subnet_" + router_name[13:len(router_name)]) for subnet in subnets: if subnet_name == subnet['subnet']['name']: neutron_client.remove_interface_router( router['router']['id'], {"subnet_id": subnet['subnet']['id']}) LOG.debug("DELETING RALLY ROUTERS") for router in routers: neutron_client.delete_router(router['router']['id']) LOG.debug("DELETING RALLY NETWORKS") for network in networks: if (network['network']['router:external'] and network['network']['name'] == "rally_network_public"): external_network = network neutron_admin_client.delete_network( external_network['network']["id"]) elif network['network']['router:external']: pass else: neutron_client.delete_network(network['network']['id']) def delete_tenants(keystone_client, tenant_ids): """Delete keystone tenant :param keystone_client: keystone client :param tenant_ids: list of tenants' uuids :returns: delete keystone tenant instance """ LOG.debug('DELETE TENANTS') for id in tenant_ids: keystone_client.tenants.delete(id) def delete_keyfiles(local_key_files, remote_key_files=None, ns_compute_tuples=None, private_key=None): """Delete the SSH keyfiles from the compute and the local nodes :param local_key_files: paths to ssh key files in local node :param remote_key_files: paths to ssh key files in compute nodes :param ns_compute_tuples: namespace, compute tuple. (It's the compute node that contains the namespace ) :param private_key: path to private key file :return: """ LOG.debug("DELETING RALLY KEY FILES FROM LOCAL MACHINE") for key in local_key_files: if os.path.exists(key): os.remove(key) if ns_compute_tuples: LOG.debug("DELETING RALLY KEY FILES FROM COMPUTE HOSTS") for key, ns_comp in zip(remote_key_files, ns_compute_tuples): cmd = "sudo rm -f {}".format(key) host = ns_comp[1] execute_cmd_over_ssh(host, cmd, private_key) def delete_hosts_from_knownhosts_file(hosts, ns_compute_tuples=None, private_key=None): """Remove the hosts from the knownhosts file :param hosts: host ips to be removed from /root/.ssh/knownhosts :param ns_compute_tuples: namespace, compute tuple. (It's the compute node that contains the namespace ) :param private_key: path to private key file :return: """ if ns_compute_tuples: LOG.debug("DELETES HOSTS FROM THE KNOWNHOSTS FILE") for host, ns_comp in zip(hosts, ns_compute_tuples): compute_host = ns_comp[1] cmd = ("sudo ssh-keygen -f /root/.ssh/known_hosts -R" " {}".format(host)) execute_cmd_over_ssh(compute_host, cmd, private_key) else: for host in hosts: os.system("sudo ssh-keygen -f /root/.ssh/known_hosts -R" " {}".format(host)) neutron-vpnaas-12.0.1/rally-jobs/plugins/test_vpn_connectivity.py0000666000175000017500000000557113370230606025360 0ustar zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 rally.common import logging from rally.task import scenario from rally.task import types import vpn_base LOG = logging.getLogger(__name__) class TestVpnBasicScenario(vpn_base.VpnBase): """Rally scenarios for VPNaaS""" @types.convert(image={"type": "glance_image"}, flavor={"type": "nova_flavor"}) @scenario.configure() def create_and_delete_vpn_connection(self, **kwargs): """Basic VPN connectivity scenario. 1. Create 2 private networks, subnets and routers 2. Create public network, subnets and GW IPs on routers, if not present 3. Execute ip netns command and get the snat and qrouter namespaces (assuming we use DVR) 4. Verify that there is a route between the router gateways by pinging each other from their snat namespaces 5. Add security group rules for SSH and ICMP 6. Start a nova instance in each of the private networks 7. Create IKE and IPSEC policies 8. Create VPN service at each of the routers 9. Create IPSEC site connections at both endpoints 10. Verify that the ipsec-site-connection is ACTIVE (takes upto 30secs) 11. To verify the vpn connectivity, get into the peer router's snat namespace and start a tcpdump at the qg-xxxx interface 12. SSH into the nova instance from the local qrouter namespace and try to ping the nova instance on the peer network. 14. Verify that the captured packets are encapsulated and encrypted. 15. Verify the connectivity in the reverse direction following the steps 11 through 13 16. Submit a request to delete all the resources """ try: self.setup(**kwargs) self.create_networks(**kwargs) self.create_servers(**kwargs) self.check_route() self.ike_policy = self._create_ike_policy(**kwargs) self.ipsec_policy = self._create_ipsec_policy(**kwargs) self.create_vpn_services() self.create_ipsec_site_connections(**kwargs) self.assert_statuses(final_status='ACTIVE', **kwargs) self.verify_vpn_connectivity(**kwargs) LOG.info("VPN CONNECTIVITY TEST PASSED!") finally: self.cleanup() neutron-vpnaas-12.0.1/ChangeLog0000664000175000017500000014175113370231104016352 0ustar zuulzuul00000000000000CHANGES ======= 12.0.1 ------ * Match IPSEC SA established state * import zuul job settings from project-config * Make libreswan driver work with recent versions * Fix functional tests gate * Fix: sphinx-docs job for stable branch and pep8 issues * Add release note on migration from vpn-agent to L3 agent extension * Update UPPER\_CONSTRAINTS\_FILE for stable/queens * Update .gitreview for stable/queens 12.0.0 ------ * Zuul: Remove project name * Updated from global requirements * Switch to neutron-tempest-plugin for tempest tests * [doc] Add testing VPNaaS with devstack * devstack: adapt to lib/neutron * [doc] add more info to contributor guide * [doc] Add more info about vpnaas team * [doc] Update for devstack configuration * Move legacy jobs to project * Updated from global requirements * Updated from global requirements * Fix usage of method ensure\_dir * Remove setting of version/release from releasenotes * Updated from global requirements * use flavors api def from neutron-lib * Updated from global requirements * Redundant alias in import statement * Updated from global requirements * VPN as a Service (VPNaaS) Agent * Switch to tempest.common.utils.requires\_ext * Replace the usage of some aliases in tempest * Cleanup test-requirements * Update for os-testr 1.0.0 / stestr * DB migration milestone for Pike * Updated from global requirements * Remove vestigate HUDSON\_PUBLISH\_DOCS reference * devstack: Use entrypoint name for service\_plugin * Fix to use "." to source script files * Update reno for stable/pike 11.0.0.0rc1 ----------- * Updated from global requirements * Devref for VMWare NSX-v IPsec VPN driver * Enable some off-by-default checks * Updated from global requirements * Replace test.attr with decorators.attr * Fix doc rendering for more easy to read * Update URLs in documents according to document migration * VPNaaS integration with services flavor framework * Use flake8-import-order plugin and clean up exceptions * Updated from global requirements * Optimize the link address * Drop MANIFEST.in - it's not needed by pbr * Switch from oslosphinx to openstackdocstheme * use service type constants from neutron\_lib plugins * Stop using deprecated 'message' attribute in Exception * Add myself to list of driver maintainers * Delete the IPSec before the router is deleted * Updated comments in tox.ini [flake8] * Updated from global requirements * Updated from global requirements * Updated from global requirements * consume neutron-lib callbacks * Enable vpnaas extension * Tag the alembic migration revisions for Ocata * Migrate neutron.plugins.common to neutron-lib * Updated from global requirements * Remove subunit-trace fork * Add a simple tempest scenario * Remove log translations from neutron-vpnaas * Rehome L3 exceptions to neutron-lib * Use the new path of agent config * doc: Add a list of driver maintainers * Use neutron\_lib's get\_random\_mac * Updated from global requirements * Use neutron-lib's context module instead of neutron * devref/devstack: Switch the default to strongswan * [Fix gate]Update test requirement * Add router\_id to query when check subnet is used by vpnservice * Update a py35 environment to tox and classifier * Use replace\_file from neutron-lib * devstack: Adapt to lib/neutron * Adopt to new model classes' locations * devstack: Add neutron server config explicitly * doc: Fix a warning * Update reno for stable/ocata * Updated from global requirements * Updated from global requirements * strongswan: Use non-blocking version of ipsec up * ipsec device driver: Sprinkle log\_method\_call 10.0.0 ------ * gate\_hook: Add "tempest" case * Updated from global requirements * Updated from global requirements * tests: Add 'agent' argument for LegacyRouter * Use CORE from neutron-lib * Updated from global requirements * Restore RPC after tenant\_id -> project\_id DB column rename * devstack: Switch the default to strongswan * Replace six.iteritems() with .items() * Use DB field sizes instead of \_MAX\_LEN constants * Remove PLURALS * Use ExtensionDescriptor from neutron-lib * Switch to using plugins directory in lieu of neutron manager * Updated from global requirements * Add sha384 and sha512 auth algorithms for vendor drivers * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Fix releasenotes index * Updated from global requirements * Validate peer\_cidrs for ipsec\_site\_connections * Fix a typo in vpnaas.filters,filters.template,and etc * Fix the types.set error for rally job run * Updated from global requirements * Enable release notes translation * Remove FWaaS dependency in VPNaaS devstack plugin * Updated from global requirements * Fix import breakage in functional test * Updated from global requirements * Updated from global requirements * Add vpnaas conf to Q\_PLUGIN\_EXTRA\_CONF\_FILES * Update reno for stable/newton 9.0.0.0rc1 ---------- * Updated from global requirements * Tag the alembic migration revisions for Newton * Make tests resilient to new project\_id field in API * Updated from global requirements 9.0.0.0b3 --------- * Use model\_base from neutron-lib * Readd tox\_install NEUTRON\_DIR * Add debug output to tox\_install.sh * Add support for Guru Meditation Reports for VPNaaS agents * Use temporary directory for neutron install * Move service plugin aliases from neutron to neutron-vpnaas * Updated from global requirements * Fix DeprecationWarnings part II * Adding tests for endpoint-group api * TrivialFix: Add validation for tenant\_id * TrivialFix: Cleanup imports in code * Revert "Update disable\_ssl\_certificate\_validation reference" * Update disable\_ssl\_certificate\_validation reference * Strongswan: Fix incorrect strongswan auth algorithm sha256 symbol * Constrain remaining tox targets * Updated from global requirements * Remove temporary local HasProject * Fix DeprecationWarnings part I * Enable DeprecationWarning in test environments * Add connection API cases * Updated from global requirements * Update imports (common.config -> conf.common) * Updated from global requirements * Fix API Tests * Add Libreswan installation guide in devstack.rst * Drop Newton tag from DB migration * Rename DB columns: tenant -> project * Add migrations testing to VPNaaS functional jobs 9.0.0.0b2 --------- * remove unused LOG * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix VPNaaS functional tests * Use tempest.lib tenants\_client * Replace tempest-lib to tempest in test-requirements.txt * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use strongswan piddir as bind mount dir 9.0.0.0b1 --------- * Copy/remove the strongswan.d config as root * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix grenade plugin * Add grenade upgrade hooks to stop agent * Set path for neutron conf dir in devstack if missing * Support local\_id configuration * Switch to using hacking checks from neutron-lib * Strongswan: complete the ipsec.conf * Openswan/Libreswan: Check config changes before restart 8.1.0 ----- * Updated from global requirements * Updated from global requirements * Don't use zuul-cloner for venv env, for periodic jobs * Openswan/Libreswan: support sha256 for auth algorithm * Fix doc build if git is absent * Ensure that PK values don't default to NULL * Updated from global requirements * OpenSwan: handle disconnect properly for multiple subnets 8.0.0 ----- * Constraint requirements using mitaka upper-constraints.txt file * VPNaaS returns 500 INTERNAL error with long names, descriptions 8.0.0.0rc2 ---------- * Update devstack plugin for dependent packages * Update devstack plugin for dependent packages * Update reno for stable/mitaka * Update .gitreview for stable/mitaka 8.0.0.0rc1 ---------- * neutron-vpnaas fix for 'tox -e api' test * Tag the alembic migration revisions for Mitaka * VPNaaS Fix bandit Jinja issue * Move db migration added during Mitaka to proper directory * Fix tox.ini constraints for post jobs * Updated from global requirements * Put py34 first in the env order of tox * vyatta: added missing agent console script 8.0.0.0b3 --------- * Track alembic heads * Remove unused pngmath Sphinx extension * VPNaaS: Cleanup constraints in tox.ini * Updated from global requirements * Updated from global requirements * VPNaaS: make use of neutron\_lib exceptions * Updated from global requirements * Use constraints on all targets * Fix configure script for functional tests * Switch from testr to ostestr * Single/Multinode VPNaaS Scenario Tests using Rally * Remove Foreign Key constraint during ALTER * Add "nonstandard-exception" to .pylintrc * Add VPNaaS API tests in neutron-vpnaas tree * Add an explicit BRANCH\_NAME to tox\_install.sh * Don't need builtins defined in tox.ini * Update translation setup * Updated from global requirements * Consume \_ from local \_1i8n * Updated from global requirements 8.0.0.0b2 --------- * Clean up removed hacking rule from [flake8] ignore lists * Add multi-node devstack support to VPNaaS * Convert warnings to errors * ipsec site connection status is blocked on "DOWN" * Add constraints targets support for neutron-vpnaas * Updated from global requirements * Fix for the deprecated library function * Updated from global requirements * LOG.warn -> LOG.warning * Make VPN endpoint groups aware from API * Don't use constrained environment for functional tests * Fix some inconsistency in docstrings * Updated from global requirements * tox\_install.sh: don't hide output from tox logs * Avoid duplicating tenant check when creating resources * Fix a typo from UPPER\_CONTRAINTS\_FILE to UPPER\_CONSTRAINTS\_FILE * Setup for translation * Remove Neutron VPNaaS static example configuration files * Deprecated tox -downloadcache option removed * Automatically generate neutron VPNaaS configuration files * Updated from global requirements * Updated from global requirements * Added constraints tox targets * Fix db error when running python34 Unit tests * Add reno for release notes management * Remove pylint from the default list of tox targets * Fix pylint/astroid breakage * Remove version from setup.cfg 8.0.0.0b1 --------- * Switch to internal \_i18n pattern, as per oslo\_i18n guidelines * Updated from global requirements * Fix wrong file name in setup.cfg * Prepare neutron "use\_namespaces" option removal * Updated from global requirements * Fix options for pluto per-peer logging * Updated from global requirements * Updated from global requirements * Set ZUUL\_BRANCH using BRANCH if not available * Remove unused logging module import * Set IPSec site connection Down if peer doesn't respond * VPNaaS: Remove unneeded test * Don't assume the order of endpoints on get is the same as on create * Don't add disabled ipsec connections to pluto * Support testing of multiple local subnets * Updated from global requirements * VPNaaS: Multiple Local Subnets feature * Remove references to router\_delete\_namespaces option in tests * Update list of modules supporting py34 tests * Switch to using neutron.common.utils:replace\_file() * Updated from global requirements * Updated from global requirements * Cleanup .ctl/.pid files for both OpenSwan and LibreSwan * Updated from global requirements * Removed new=True argument from create\_connection * Include VPN scenario test for two different tenants * Include alembic versions directory to the package 7.0.0 ----- * Add testresources used by oslo.db fixture * Add testresources used by oslo.db fixture * Updated from global requirements * Updated from global requirements * Include scenario test for checking VPN status * Remove root owned ipsec.secrets for ensure\_configs * Enable configuring LibreSwan in VPNaaS * Fix argument order for assertEqual to (expected, observed) * Updated from global requirements * Include README.rst for rally tests * VPN Scenario tests using Rally 7.0.0.0rc2 ---------- * Tag the alembic migration revisions for Liberty * Tag the alembic migration revisions for Liberty * VPNaaS: Provide Endpoint groups capability * Fix minor comment typos in VPNaaS * Use stable/liberty branch for neutron dep * Kill HEADS file 7.0.0.0rc1 ---------- * Update defaultbranch in .gitreview to stable/liberty * Open Mitaka development * Change ignore-errors to ignore\_errors * Updated from global requirements * VPNaaS Scenario tests using Rally * Make chown rootwrap filter ipsec.secrets file specific * tox.ini: switch to --subproject for check-migration * Added +x permission to gate hook scripts * Exclude neutron\_vpnaas/tests from coverage report * [OpenSwan] Enable usage of the MTU value of an IPSec connection * Use ping assertions from net\_utils in test\_scenario * VPNaaS: Restore coverage operation * Set owner to root for ipsec.secrets for LibreSwan * Support VPNaaS with L3 HA * Updated from global requirements * Support for VPN functional tests on Neutron commits * Fix AH-ESP transform protocol in IPSec Policy * Manage cleanup of .ctl/.pid files for LibreSwan * Implement ModelMigrationSyncTest * Drop for 'tenant\_id' column for 'cisco\_csr\_identifier\_map' table 7.0.0.0b3 --------- * VPNaaS: DevRef for multiple local subnets * Remove fall-back logic to service provider registration * Adopt the migration chaining and branch names from neutron * Explictly set file mode on IPSec pre-shared key files * Removing unused dependency: discover * Allow enabling detailed logging for OpenSwan * VPNaaS: Use new service name for devstack plugin * Fix UT fallout * Switch to using os-testr's copy of subunit2html * VPNaaS: Splitting out models from database class * Register provider configuration with ServiceTypeManager * Fix stale module import * [DevStack] Fix StrongSwan setup on Fedora * Add cisco\_csr\_rest\_client.py module for py34 support * Add test\_cisco\_ipsec.py module for py34 support * Updated from global requirements * Revert "Remove default service provider from conf file" * Rename a test method in test\_ipsec.py * Remove default service provider from conf file * Killed existing downgrade rules in migration scripts * script.py.mako: added license header and missing branch\_labels * Add test\_netns\_wrapper.py module for py34 support * Use oslo.log library instead of system logging module * Updated from global requirements * VPNaaS: Store local side's tunnel IP for later retrieval * Don't include encryption algorithm in phase2alg for the AH protocol * py34: Enable initial python34 testing for VPNaaS * VPNaaS: Correcting method name for functional test 7.0.0.0b2 --------- * py34: Fix usage of gettext.install * Register alembic\_migrations at install time * Updated from global requirements * StrongSwanProcess: redefine DIALECT\_MAP at class level * migrations: rearrange the tree to support split migration phases * Remove quantum untracked files from .gitignore * adopt neutron.common.utils.ensure\_dir * Revert "VPNaaS: Temporarily disable check\_migration" * VPNaaS: Enable devstack plugin for tests * VPNaaS DevStack Plugin support * VPNaaS: Temporarily disable check\_migration * Scenario test for vpnaas: ipsec-site-connection * Remove dependency on config file for db check\_migration * Set vpn agent's agent\_state['binary'] attribute * VPNaaS: Fix migration head * VPNaaS: Don't clone neutron automatically for tests * COMMON\_PREFIXES cleanup - patch 4/5 * Updated from global requirements * VPNaaS: Fix another import due to Neutron change * Fix breakage due to recent movements of Neutron modules * Switch to oslo.service * Updated from global requirements 7.0.0.0b1 --------- * Use DvrEdgeRouter instead of decomposed DvrRouter in test\_ipsec * Switch to oslo\_utils.uuidutils * Trim some unused test requirements * Update version for Liberty 7.0.0a0 ------- * Updated from global requirements * Resize cisco\_csr\_identifier\_map.ipsec\_site\_conn\_id * Updated from global requirements * Updated from global requirements * VPNaaS: And devref doc infrastructure * VPNaaS: Enable pylint duplicate-key check * Updated from global requirements * Enable random hash seeds * Python 3: use six.iteritems instead of dict.items * Do not assume order of mounts in execute\_with\_mount * Set owner of Q\_VPN\_CONF\_FILE file to STACK\_USER user * VPNaaS: Cleanup functional hook scripts * gate-neutron-vpnaas-pep8 failing for test\_cisco\_ipsec.py * Assign external\_ip based on ip version of peer\_address * Switch from MySQL-python to PyMySQL * Updated from global requirements * VPNaaS: Fix breakage in status reporting * VPNaaS: Revise functional test hooks * Add neutron-vpnaas/tests/unit/extensions/\_\_init\_\_ * Remove contextlib.nested from tests * IPv6 support for OpenSwan, Libreswan and Strongswan * Updated from global requirements * Libreswan driver support in VPNaaS * Updated from global requirements * Provide Fedora support for StrongSwan * Fix failures for integration tests 2015.1.0 -------- * VPNaaS: Refactor functional tests to use discover * update .gitreview for stable/kilo * Add Kilo release milestone * Add Kilo release milestone * Pin neutron to stable/kilo * VPNService takes names of device drivers from self.conf * VPNaaS Remove dependency on Neutron for unit test 2015.1.0rc1 ----------- * VPNaaS: Remove check for bash usage * VPNaaS: Reorganize test tree * Open Liberty development * VPNaaS: Refactoring to use callback mechanism * VPNaaS Fix unit test breakage * Use BaseSudoTestCase instead of BaseLinuxTestCase * Set ipsec connection to Error if peer fqdn can't be resolved * Updated from global requirements * Introduce Vyatta VPN agent cmd in monkey patched eventlet module * Add some unit tests for strongswan driver 2015.1.0b3 ---------- * tests: stop overwriting neutron BaseTestCase configuration files * Functional tests of ipsec strongswan vpnaas driver * IPsec strongSwan driver implemention * VPNaaS breakage by refactoring commit * Fix functional test breakage from DevStack change * VPNaaS: Enable StrongSwan in gate hook * Remove the reference for non-existent cisco.l3.plugging\_drivers * VPNaaS: device driver and agent refactoring * Decouple L3 and VPN during DVR router migration * VPNaaS: Fixing UT breakage * Updating alembic HEAD file according to the current code * Updated from global requirements * VPNaaS: Fix unit test breakage * Fix up the import path for vyatta.common to use networking\_brocade * Move pylint checks to pep8 testenv * VPNaaS: Restructure test dir layout * Decouple L3 and VPN service plugins during router operations * Migrate to oslo.log * Add test case for the 'Peer ID gets additional "@"' fix * VPNaaS refactor service driver to reuse VpnDriver code * Change L3 agent AdvancedService class to be non-singleton * Fix the ipsec conn issue when peer addr is fqdn * Updated from global requirements * Remove remaining root\_helper references * Implementation of Brocade Vyatta VPNaaS Plugin * Explicitly monkey patch VPN agent * Add IPSec encap mode validation to Cisco VPNaas * VPNaaS Enable coverage testing for functional tests * Reorder Neutron import statements in file * Stop storing and passing root\_helper * Fix breakage caused by removing deprecated root\_helper config in neutron * Add index on tenant\_id * Provide service info for RouterInUse exception * VPNaaS: Remove duplication for exception - part 1 * VPNaaS: Enable coverage tests * Fixed tests to use neutron\_vpnaas extensions and neutrons * oslo: migrate to namespace-less import paths 2015.1.0b2 ---------- * Updated from global requirements * Move config and extensions to service repo * Provide hooks for VPNaaS repo functional gate * Pass root\_helper to ip\_lib by keyword argument to prep for removal * Handle common boilerplate arguments to RouterInfo * Fix the neutron-vpnaas unit test failures * Updated from global requirements * Updated from global requirements * vpn namespace wrapper * Updated from global requirements * Migrate to oslo.concurrency * Updated from global requirements * Update hacking to 0.10 * Updated from global requirements * Adapt VPN agent to use new main for L3 Agent * Updated from global requirements * VPNaaS: Remove unneeded metaclass decorator * Fix VPN Service for Distributed Routers * Backward compatibility for vpnaas * Moved vpnaas.filters from main neutron repo * Cleaned up requirements.txt * Bump from global requirements * Added \_\_init\_\_.py so migrations can work * Fix gitignore of egg files properly * Do not list neutron in requirements.txt * VPNaas: L3 Agent restructure - observer hierarchy * VPNaaS: Unit tests using policy.conf * Update documentation files for VPNaaS 2015.1.0b1 ---------- * Do not restart vpn processes for every router update * VPNaaS: Advanced Services split - unit tests * Kill oslo-incubator files * tests: initialize admin context after super().setUp call * Init separate alembic migration chain * Remove erroneously commited egg files * Move classes out of l3\_agent.py * Fix python neutron path for neutron\_vpnaas * After the services split, get neutron-vpnaas Jenkins jobs passing * Point gitreview at correct repo * Split vpnaas services code into neutron-vpnaas * Workflow documentation is now in infra-manual * tox.ini: Prevent casual addition of bash dependency * Updated from global requirements * Convert several uses of RpcCallback * Get rid of py26 references: OrderedDict, httplib, xml testing * Updated the README.rst * pretty\_tox.sh: Portablity improvement * test\_dhcp\_agent: Fix no-op tests * Enable undefined-loop-variable pylint check * Fix incorrect exception order in \_execute\_request * Migrate to oslo.i18n * Migrate to oslo.middleware * Migrate to oslo.utils * Remove Python 2.6 classifier * Remove ryu plugin * Updated from global requirements * Drop RpcProxy usage from VPNaaS code * Show progress output while running unit tests * enable H401 hacking check * enable H237 check * Updated from global requirements * Drop several uses of RpcCallback * Updated from global requirements * Update i18n translation for neutron.agents log msg's * enable F812 check for flake8 * enable F811 check for flake8 * Support pudb as a different post mortem debugger * switch to oslo.serialization * Add rootwrap filters for ofagent * Cisco VPNaaS and L3 router plugin integration * Remove openvswitch core plugin entry point * Updated from global requirements * Purge use of "PRED and A or B" poor-mans-ternary * Remove use\_namespaces from RouterInfo Property * Updated from global requirements * Remove XML support * enable F402 check for flake8 * enable E713 in pep8 tests * Hyper-V: Remove useless use of "else" clause on for loop * Enable no-name-in-module pylint check * Move disabling of metadata and ipv6\_ra to \_destroy\_router\_namespace * Updated from global requirements * Remove duplicate import of constants module * Switch run-time import to using importutils.import\_module * Enable assignment-from-no-return pylint check * tox.ini: Avoid using bash where unnecessary * Empty files should not contain copyright or license * Remove single occurrence of lost-exception warning * Updated fileutils and its dependencies * VPNaaS Cisco unit test clean-up * remove E251 exemption from pep8 check * Update VPN logging to use new i18n functions * mock.assert\_called\_once() is not a valid method * Check for VPN Objects when deleting interfaces * Add pylint tox environment and disable all existing warnings * Updated from global requirements * Ignore top-level hidden dirs/files by default * Avoid constructing a RouterInfo object to get namespace name * Drop sslutils and versionutils modules * Refactor \_process\_routers to handle a single router * Remove all\_routers argument from \_process\_routers * Removed kombu from requirements * Updated from global requirements * Updated from global requirements * Remove sslutils from openstack.common * Fix setup of Neutron core plugin in VPNaaS UT * remove linuxbridge plugin * Open Kilo development * Implement ModelsMigrationsSync test from oslo.db * Do not assume order of report list elements * Fix entrypoint of OneConvergencePlugin plugin * Rework and enable VPNaaS UT for Cisco CSR REST * Set dsvm-functional job to use system packages * Separate Configuration from Freescale SDN ML2 mechanism Driver * Remove @author(s) from copyright statements * Add HA support to the l3 agent * Updated from global requirements * Adds ipset support for Security Groups * UTs: Disable auto deletion of ports/subnets/nets * Add requests\_mock to test-requirements.txt * Removed kombu from requirements * Supply missing cisco\_cfg\_agent.ini file * Updated from global requirements * Work toward Python 3.4 support and testing * Revert "Cisco DFA ML2 Mechanism Driver" * Big Switch: Separate L3 functions into L3 service * Remove reference to cisco\_cfg\_agent.ini from setup.cfg again * Adds router service plugin for CSR1kv * Support for extensions in ML2 * Cisco DFA ML2 Mechanism Driver * Adding mechanism driver in ML2 plugin for Nuage Networks * Fix state\_path in tests * Remove ovs dependency in embrane plugin * Use lockutils module for tox functional env * Cisco VPN with in-band CSR (interim solution) * Inline "for val in [ref]" statements * Updated from global requirements * VPNaaS: Enable UT cases with newer oslo.messaging * Add specific docs build option to tox * Fix bigswitch setup.cfg lines * Remove auto-generation of db schema from models at startup * Updated from global requirements * Use jsonutils instead of stdlib json * VPNaaS: Cisco fix validation for GW IP * Opencontrail plug-in implementation for core resources * Do not assume order of new\_peers list elements * Remove redundant topic from rpc calls * Add a tox test environment for random hashseed testing * Updated from global requirements * Move Cisco VPN RESTapi URI strings to constants * Remove reference to cisco\_cfg\_agent.ini from setup.cfg * Exit Firewall Agent if config is invalid * Fix spelling mistakes * Removed configobj from test requirements * Updated from global requirements * Functional tests work fine with random PYTHONHASHSEED * Set python hash seed to 0 in tox.ini * Configuration agent for Cisco devices * Updated from global requirements * Define some abstract methods in VpnDriver class * ML2 mechanism driver for SR-IOV capable NIC based switching, Part 2 * Modify L3 Agent for Distributed Routers * This patch changes the name of directory from mech\_arista to arista * ML2 mechanism driver for SR-IOV capable NIC based switching, Part 1 * Allow to import \_LC, \_LE, \_LI and \_LW functions directly * Make readme reference git.openstack.org not github * VPNaaS: Separate validation for Cisco impl * VPNaaS: separate out validation logic for ref impl * VPNaaS Cisco REST client enhance CSR create * Bump hacking to version 0.9.2 * Use auth\_token from keystonemiddleware * Change all occurences of no\_delete to do\_delete * Revert "VPNaaS REST Client UT Broken" * Extract CommonDBMixin to a separate file * Remove reference to setuptools\_git * Add a gate-specific tox env for functional tests * Add CONTRIBUTING.rst * Updated from global requirements * VPNaaS REST Client UT Broken * Updated from global requirements * Updated from global requirements * Fix example for running individual tests * Switch to using of oslo.db * remove unsupported middleware * Add config for performance gate job * Synced log module and its dependencies from olso-incubator * don't ignore rules that are already enforced * Moved rpc\_compat.py code back into rpc.py * Updated from global requirements * Updated from global requirements * ofagent: move main module from ryu repository * Remove the useless vim modelines * Removed 'rpc' and 'notifier' incubator modules * Removed create\_rpc\_dispatcher methods * Use openstack.common.lockutils module for locks in tox functional tests * Renamed consume\_in\_thread -> consume\_in\_threads * Port to oslo.messaging * Pass 'top' to remove\_rule so that rule matching succeeds * Updated from global requirements * Ignore emacs checkpoint files * Added missing core\_plugins symbolic names * Introduced rpc\_compat.create\_connection() * Introduce RpcCallback class * remove pep8 E122 exemption and correct style * remove E112 hacking exemption and fix errors * Updated from global requirements * Added RpcProxy class * Freescale SDN Mechanism Driver for ML2 Plugin * Remove run-time version checking for openvswitch features * Added missing plugin .ini files to setup.cfg * Updated from global requirements * Synced jsonutils from oslo-incubator * Cisco APIC ML2 mechanism driver, part 2 * NSX: get rid of the last Nicira/NVP bits * Metaclass Python 3.x Compatibility * Add missing translation support * Add mailmap entry * Updated from global requirements * Remove explicit dependency on amqplib * Remove duplicate module-rgx line in .pylintrc * Fix H302 violations * Fix H302 violations in unit tests * Cisco VPN device driver - support IPSec connection updates * Updated from global requirements * Fix H302 violations in db package and services * Updated from global requirements * Support enhancements to Cisco CSR VPN REST APIs * Exclude .ropeproject from flake8 checks * Enable flake8 E711 and E712 checking * Updated from global requirements * Sync service and systemd modules from oslo-incubator * Move bash whitelisting to pep8 testenv * Fix Jenkins translation jobs * Set ns\_name in RouterInfo as attribute * ignore build directory for pep8 * Enable hacking H301 check * Updated from global requirements * Remove last parts of Quantum compatibility shim * UT: do not hide an original error in test resource ctxtmgr * Open Juno development * Start using oslosphinx theme for docs * Cisco VPN driver correct reporting for admin state chg * Updated from global requirements * VPNaaS support for VPN service admin state change and reporting * add HEAD sentinel file that contains migration revision * Fix usage of save\_and\_reraise\_exception * Cisco VPN device driver post-merge cleanup * Bugfix and refactoring for ovs\_lib flow methods * Removes calls to mock.patch.stopall in unit tests * VPNaaS Device Driver for Cisco CSR * Updated from global requirements * Updated from global requirements * Updated from global requirements * One Convergence Neutron Plugin l3 ext support * One Convergence Neutron Plugin Implementation * BigSwitch: Add SSL Certificate Validation * VPNaaS Service Driver for Cisco CSR * Updated from global requirements * Add OpenDaylight ML2 MechanismDriver * Implementaion of Mechanism driver for Brocade VDX cluster of switches * Implement Mellanox ML2 MechanismDriver * Support advanced NVP IPsec VPN Service * Implement OpenFlow Agent mechanism driver * Finish off rebranding of the Nicira NVP plugin * BigSwitch: Add agent to support neutron sec groups * Adds the new IBM SDN-VE plugin * Updated from global requirements * Update License Headers to replace Nicira with VMware * Developer documentation * tests/service: consolidate setUp/tearDown logic * options: consolidate options definitions * Rename Neutron core/service plugins for VMware NSX * Updated from global requirements * Fix VPN agent does not handle multiple connections per vpn service * Sync minimum requirements * Copy cache package from oslo-incubator * tests/unit: Initialize core plugin in TestL3GwModeMixin * Fix typo in service\_drivers.ipsec * Remove dependent module py3kcompat * Use save\_and\_reraise\_exception when reraise exception * Add migration support from agent to NSX dhcp/metadata services * Remove psutil dependency * LBaaS: move agent based driver files into a separate dir * mailmap: update .mailmap * Return request-id in API response * Prepare for multiple cisco ML2 mech drivers * Support building wheels (PEP-427) * Use oslo.rootwrap library instead of local copy * Enables BigSwitch/Restproxy ML2 VLAN driver * Add an explicit tox job for functional tests * Base ML2 bulk support on the loaded drivers * Enable hacking H233 rule * Update RPC code from oslo * Configure plugins by name * Update lockutils and fixture in openstack.common * Rename nicira configuration elements to match new naming structure * Remove unused imports * Rename check\_nvp\_config utility tool * Corrects broken format strings in check\_i18n.py * Updates tox.ini to use new features * Updated from global requirements * Sync global requirements to pin sphinx to sphinx>=1.1.2,<1.2 * validate if the router has external gateway interface set * Add fwaas\_driver.ini to setup.cfg * Add vpnaas and debug filters to setup.cfg * Fix misspells * update error msg for invalid state to update vpn resources * Updates .gitignore * Update Zhenguo Niu's mailmap * Replace stubout with fixtures * Ensure get\_pid\_to\_kill works with rootwrap script * Apply six for metaclass * Updated from global requirements * Cleanup HACKING.rst * Fix import log\_handler error with publish\_errors set * Updated from global requirements * Updated from global requirements * Fix incorrect indentations found by Pep 1.4.6+ * Cleanup and make HACKING.rst DRYer * Add support for managing async processes * Use L3 api from vpn ipsec driver via service plugin * Fix access to lifetime dict in update\_ipsecpolicy method * Remove obsolete redhat-eventlet.patch * Enable Quota DB driver by default * Open Icehouse development * Updated from global requirements * Require oslo.config 1.2.0 final * Use built-in print() instead of print statement * Increase size of peer\_address attribute in VPNaaS * Add router ownership check on vpnservice creation * Fix error code for deletion of router which is in use by vpnservice * Add l2 population base classes * Adds support for L3 routing/NAT as a service plugin * Fix message i18n error * Install metering\_agent.ini and vpn\_agent.ini * fix conversion type missing * Ensure unit tests do not let looping calls roam freely * Enclose command args in with\_venv.sh * ML2 Mechanism Driver for Cisco Nexus * Reference driver implementation (IPsec) for VPNaaS * Verify MTU is valid for ipsec\_site\_connection * Implement ML2 port binding * Arista ML2 Mechanism driver * ML2 Mechanism Driver for Tail-f Network Control System (NCS) * Default to not capturing log output in tests * Make ipsec\_site\_connection dpd\_timeout == dpd\_interval return 400 * Add Neutron l3 metering agent * Update mailmap * Fix wrong example in HACKING.rst * Bumps hacking to 0.7.0 * remove binaries under bin * Fixes Windows setup dependency bug * Restore Babel to requirements.txt * Remove DHCP lease logic * Remove last vestiges of nose * Updated from global requirements * Ignore pbr\*.egg directory * Fix H102, H103 Apache 2.0 license hacking check error * Remove openstack.common.exception usage * Adds Babel dependency missing from 555d27c * Fix the alphabetical order in requirement files * VPNaaS datamodel IKEPolicy lifetime unit typo * Remove comments from requirements.txt (workaround pbr bug) * VPN as a Service (VPNaaS) API and DataModel * remove netifaces dependency of ryu-agent * Add gre tunneling support for the ML2 plugin * Add VXLAN tunneling support for the ML2 plugin * xenapi - rename quantum to neutron * Fix issue with pip installing oslo.config-1.2.0 * Initial Modular L2 Mechanism Driver implementation * Add cover/ to .gitignore * fix some missing change from quantum to neutron * git remove old non-working packaging files * Rename Quantum to Neutron * Rename quantum to neutron in .gitreview * Sync install\_venv\_common from oslo * Update to use OSLO db * Require greenlet 0.3.2 (or later) * Remove single-version-externally-managed in setup.cfg * Fix single-version-externally-mananged typo in setup.cfg * Allow use of lowercase section names in conf files * Require pbr 0.5.16 or newer * Update to the latest stevedore * Rename agent\_loadbalancer directory to loadbalancer * Remove unit tests that are no longer run * Update with latest OSLO code * Remove explicit distribute depend * Fix and enable H90x tests * Remove generic Exception when using assertRaises * Add \*.swo/swp to .gitignore * python3: Introduce py33 to tox.ini * Rename README to README.rst * Rename requires files to standard names * Initial Modular L2 plugin implementation * Revert dependency on oslo.config 1.2.0 * Perform a sync with oslo-incubator * Require oslo.config 1.2.0a2 * update mailmap * Revert "Fix ./run\_tests.sh --pep8" * Move to pbr * Docstrings formatted according to pep257 * relax amqplib and kombu version requirements * Fix ./run\_tests.sh --pep8 * blueprint mellanox-quantum-plugin * Update flake8 pinned versions * Let the cover venv run individual tests * Copy the RHEL6 eventlet workaround from Oslo * Remove locals() from strings substitutions * Enable automatic validation of many HACKING rules * Shorten the path of the nicira nvp plugin * Allow pdb debugging in manually-invoked tests * Reformat openstack-common.conf * Switch to flake8 from pep8 * Parallelize quantum unit testing: * blueprint cisco-single-config * Add lbaas\_agent files to setup.py * Add VIRTUAL\_ENV key to enviroment passed to patch\_tox\_env * Pin SQLAlchemy to 0.7.x * Sync latest Oslo components for updated copyright * drop rfc.sh * Replace "OpenStack LLC" with "OpenStack Foundation" * First havana commit * remove references to netstack in setup.py * Switch to final 1.1.0 oslo.config release * Update to Quantum Client 2.2.0 * Update tox.ini to support RHEL 6.x * Switch to oslo.config * Add common test base class to hold common things * Pin pep8 to 1.3.3 * Add initial testr support * LBaaS Agent Reference Implementation * Bump python-quantumclient version to 2.1.2 * Add scheduling feature basing on agent management extension * Remove compat cfg wrapper * Unpin PasteDeploy dependency version * Use testtools instead of unittest or unittest2 * Add midonet to setup.py * Sync latest install\_venv\_common.py with olso * Add check-nvp-config utility * Add unit test for ryu-agent * Use oslo-config-2013.1b3 * Adds Brocade Plugin implementation * Synchronize code from oslo * PLUMgrid quantum plugin * Update .coveragerc * Allow tools/install\_venv\_common.py to be run from within the source directory * Updated to latest oslo-version code * Use install\_venv\_common.py from oslo * Cisco plugin cleanup * Use babel to generate translation file * Update WebOb version to >=1.2 * Update latest OSLO * Adding multi switch support to the Cisco Nexus plugin * Adds support for deploying Quantum on Windows * Latest OSLO updates * Port to argparse based cfg * Add migration support to Quantum * Undo change to require WebOb 1.2.3, instead, require only >=1.0.8 * .gitignore cleanup * Upgrade WebOb to 1.2.3 * Logging module cleanup * Add OVS cleanup utility * Add tox artifacts to .gitignore * Add restproxy.ini to config\_path in setup.py * Add script for checking i18n message * l3 agent rpc * Add metadata\_agent.ini to config\_path in setup.py * Remove \_\_init\_\_.py from bin/ and tools/ * add metadata proxy support for Quantum Networks * Use auth\_token middleware in keystoneclient * Add QUANTUM\_ prefix for env used by quantum-debug * Make tox.ini run pep8 checks on bin * Explicitly include versioninfo in tarball * Import lockutils and fileutils from openstack-common * Updated openstack-common setup and version code * Ensure that the anyjson version is correct * Add eventlet\_backdoor and threadgroup from openstack-common * Add loopingcall from openstack-common * Added service from openstack-common * Drop lxml dependency * Add uuidutils module * Import order clean-up * pin sqlalchemy to 0.7 * Correct Intended Audience * Add OpenStack trove classifier for PyPI * Improve unit test times * l3\_nat\_agent was renamed to l3\_agent and this was missed * Support for several HA RabbitMQ servers * add missing files from setup.py * Create .mailmap file * Lower webob dep from v1.2.0 to v1.0.8 * Implements agent for Quantum Networking testing * Create utility to clean-up netns * Update rootwrap; track changes in nova/cinder * Execute unit tests for Cisco plugin with Quantum tests * Add lease expiration script support for dnsmasq * Add nosehtmloutput as a test dependency * quantum l3 + floating IP support * Updates pip requirements * NEC OpenFlow plugin support * remove old gflags config code * RPC support for OVS Plugin and Agent * Initial implemention of MetaPlugin * RPC support for Linux Bridge Plugin and Agent * Exempt openstack-common from pep8 check * fix bug lp:1025526,update iniparser.py to accept empty value * Introduce files from openstack common * fix bug lp:1019230,update rpc from openstack-common * implement dhcp agent for quantum * Use setuptools git plugin for file inclusion * Remove paste configuration details to a seperate file. blueprint use-common-cfg * Implements the blueprint use-common-cfg for the quantum service. More specifically uses global CONF for the quantum.conf file * Add authZ through incorporation of policy checks * Bug #1013967 - Quantum is breaking on tests with pep 1.3 * Use openstack.common.exception * API v2: mprove validation of post/put, rename few attributes * Add API v2 support * Fix up test running to match jenkins expectation * Add build\_sphinx options * Quantum should use openstack.common.jsonutils * Remove hardcoded version for pep8 from tools/test-requires * Quantum should use openstack.common.importutils * PEP8 fixes * Bug #1002605 * Parse linuxbridge plugins using openstack.common.cfg * Add HACKING.rst to tarball generation bug 1001220 * Include AUTHORS in release package * Change Resource.\_\_call\_\_() to not leak internal errors * Removed simplejson from pip-requires * Remove dependency on python-quantumclient * Add sphinx to the test build deps * Add HACKING.rst coding style doc * bug 963152: add a few missing files to sdist tarball * Fix path to python-quantumclient * Split out pip requires and aligned tox file * Fix missing files in sdist package [bug 954906] * Downgraded required version of WebOb to 1.0.8 * more files missing in sdist tarball * make sure pip-requires is included in setup.py sdist * remove pep8 and strict lxml version from setup.py * plugin: introduce ryu plugin * bug 934459: pip no longer supports -E * blueprint quantum-ovs-tunnel-agent * Initial commit: nvp plugin * Cleanup the source distribution * blueprint quantum-linux-bridge-plugin * Remove quantum CLI console script * Bug 925372: remove deprecated webob attributes (and also specify stable webob version in pip-requires) * Make tox config work * Pin versions to standard versions * Split out quantum.client and quantum.common * Quantum was missing depend on lxml * moving batch config out of quantum-server repo * Getting ready for the client split * Removed erroneous print from setup.py * Base version.py on glance * Fix lp bug 897882 * Install a good version of pip in the venv * Rename .quantum-venv to .venv * Remove plugin pip-requires * Bug #890028 * Fix for bug 900316 * Second round of packaging changes * Changes to make pip-based tests work with jenkins * Fix for bug 888811 * Fix for Bug #888820 - pip-requires file support for plugins * blueprint quantum-packaging * Add .gitreview config file for gerrit * Add code-coverage support to run\_tests.sh (lp860160) 2011.3 ------ * Add rfc.sh to help with gerrit workflow * merge tyler's unit tests for cisco plugin changes lp845140 * merge salv's no-cheetah CLI branch lp 842190 * merge sumit's branch for lp837752 * Merging latest from lp:quantum * Merging lo:~salvatore-orlando/quantum/quantum-api-auth * Updating CLI for not using Cheetah anymore. Now using a mechanism based on Python built-in templates * Merging Sumit's changes including fixes for multinic support, and CLI module for working with extensions * Merging from Cisco branch * Merging from lp:quantum * merge cisco consolidated plugin changes * Merging lp:~salvatore-orlando/quantum/bug834449 * merge trunk * Merging from lp:quantum * merge salvatore's new cli code * Addressing comments from Dan * Merging from quantum * merge cisco extensions branch * Merging from Sumit's branch, changes to VIF-driver and Scheduler; extension action names have been changed in response to Salvatore's review comments in the extensions branch review * Syncing with Cisco extensions branch * Merging from Sumit's branch, import ordering related changes * Merging the Cisco branch * Finishing cli work Fixing bug with XML deserialization * Merging lp:~salvatore-orlando/quantum/quantum-api-alignment * merge latest quantum branch and resolve conflicts * Merging lp:~asomya/quantum/lp833163 Fix for Bug #833163: Pep8 violations in recent packaging changes that were merged into trunk (Critical) * PEP8 fixes for setup.py * Merging lp:~cisco-openstack/quantum/802dot1qbh-vifdriver-scheduler * Merging lp:~cisco-openstack/quantum/l2network-plugin-persistence * Merging lp:quantum * merging with lp:quantum * Making Keystone version configurable * Merging branch: lp:~danwent/quantum/test-refactor * Syncing with lp:quantum * Merging fixes and changes batch-config script. Thanks lp:danwent ! * Merging lp:~asomya/quantum/lp824145 Fix for Bug#824145 : Adding a setup script for quantum * merge trunk pep8 fixes adapting CLI to API v1.0 Fixing wsgi to avoid failure with extensions * merge trunk * Pulling in changes from lp:quantum * Merging Cisco's contribution to Quantum. Thanks to various folks at Cisco Systems, Quantum will have plugins to integrate with Cisco UCS blade servers using 802.1Qbh, Cisco Nexus family of switches and the ability for Quantum plugin to have multiple switches/devices within a single Quantum plugin * Merging from Sumit's branch pylint fixes and incorporating review comments * Mergin from cisco brach * Merging from lp:quantum * Introducting cheetah Updating list\_nets in CLI Writing unit tests for list\_nets Stubbing out with FakeConnection now * Merging quantum extenions framework into trunk. Thanks rajaram vinkesh, deepak & santhosh for the great work! * lp Bug#824145 : Adding a setup script for quantum * skeleton for cli unit tests * merge trunk * Merged quantum trunk * - Adding setup script * force batch\_config.py to use json, as XML has issues (see bug: 798262) * update batch\_config.py to use new client lib, hooray for deleting code * Merging changes addressing Bug # 802772. Thanks lp:danwent ! * Merging bugfix for Bug 822890 - Added License file for Quantum code distribution * L2 Network Plugin Framework merge * Adding Apache Version 2.0 license file. This is the official license agreement under which Quantum code is available to the Open Source community * merge * merge heckj's pip-requires fixes * updates to pip-requires for CI * Merged quantum trunk * Merging changes from lp:quantum * Completing API spec alignment Unit tests aligned with changes in the API spec * Merging the brand new Quantum-client-library feature * Merging lp:quantum updates * persistence of l2network & ucs plugins using mysql - db\_conn.ini - configuration details of making a connection to the database - db\_test\_plugin.py - contains abstraction methods for storing database values in a dict and unit test cases for DB testing - l2network\_db.py - db methods for l2network models - l2network\_models.py - class definitions for the l2 network tables - ucs\_db.py - db methods for ucs models - ucs\_models.py - class definition for the ucs tables dynamic loading of the 2nd layer plugin db's based on passed arguments Create, Delete, Get, Getall, Update database methods at - Quantum, L2Network and Ucs Unit test cases for create, delete, getall and update operations for L2Network and Ucs plugins pep8 checks done branch based off revision 34 plugin-framework * Merged from trunk * merged the latest changes from plugin-framework branch - revision 39 conforming to the new cisco plugin directory structure and moving all db related modules into cisco/db folder updated db\_test\_plugin.py - added import of cisco constants module - added LOG.getLogger for logging component name - updated import module paths for l2network\_models/db and ucs\_models/db to use the new directory structure - updated (rearranged) imports section to obey openstack alphabetical placement convention updated db\_conn.ini - updated database name from cisco\_naas to quantum\_l2network unit test cases ran successfully and pep8 checks done again * merge branch for to fix bug817826 * Merging the latest changes from lp:quantum * fix bug 817826 and similar error in batch\_config.py * merge Salvatore's api branch with fixes for tests. Tweaking branch to remove unwanted bin/quantum.py as part of merge * Santhosh/Rajaram|latest merge from quantum and made extensions use options to load plugin * Apply fix for bug #797419 merging lp:~salvatore-orlando/quantum/bug797419 * Merging branch lp:~netstack/quantum/quantum-unit-tests * Merged from quantum trunk * Adapated plugin infrastructure to allow API to pass options to plugins Now using in-memory sqlite db for tests on FakePlugin teardown() now 'resets' the in-memory db Adding unit tests for APIs * Adding Routes>=1.12.3 to tools/pip-requires * Merging dan wendlandt's bugfixes for Bug #800466 and improvements that enable Quantum to seamlessly run on KVM! * more pep8 goodness * refactor batch\_config, allow multiple attaches with the empty string * merge and pep8 cleanup * Merging latest changes from parent repo - lp:network-service , Parent repo had approved merge proposal for merging lp:~santhom/network-service/quantum\_testing\_framework , which has now been merged into lp:network-service * Merging pep8 and functional test related changes lp:~santhom/network-service/quantum\_testing\_framework branch * add example to usage string for batch\_config.py * Bug fixes and clean-up, including supporting libvirt * Santhosh/Vinkesh | Added the testing framework. Moved the smoketest to tests/functional * Pushing initial started code based on Glance project and infrstructure work done by the melange team * Merging in latest changes from lp:quantum neutron-vpnaas-12.0.1/requirements.txt0000666000175000017500000000203113370230615020057 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 # BSD License (3 clause) netaddr>=0.7.18 # BSD SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT alembic>=0.8.10 # MIT six>=1.10.0 # MIT neutron-lib>=1.13.0 # Apache-2.0 oslo.concurrency>=3.25.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0 oslo.db>=4.27.0 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.messaging>=5.29.0 # Apache-2.0 oslo.reports>=1.18.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.service!=1.28.1,>=1.24.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 # This project does depend on neutron as a library, but the # openstack tooling does not play nicely with projects that # are not publicly available in pypi. # -e git+https://git.openstack.org/openstack/neutron#egg=neutron neutron-vpnaas-12.0.1/.pylintrc0000666000175000017500000000713013370230606016445 0ustar zuulzuul00000000000000# The format of this file isn't really documented; just use --generate-rcfile [MASTER] # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. # # Note the 'openstack' below is intended to match only # neutron.openstack.common. If we ever have another 'openstack' # dirname, then we'll need to expand the ignore features in pylint :/ ignore=.git,tests,openstack [MESSAGES CONTROL] # NOTE(gus): This is a long list. A number of these are important and # should be re-enabled once the offending code is fixed (or marked # with a local disable) disable= # "F" Fatal errors that prevent further processing import-error, # "I" Informational noise locally-disabled, # "E" Error for important programming issues (likely bugs) access-member-before-definition, bad-super-call, maybe-no-member, no-member, no-method-argument, no-self-argument, not-callable, no-value-for-parameter, super-on-old-class, too-few-format-args, # "W" Warnings for stylistic problems or minor programming issues abstract-method, anomalous-backslash-in-string, anomalous-unicode-escape-in-string, arguments-differ, attribute-defined-outside-init, bad-builtin, bad-indentation, broad-except, dangerous-default-value, deprecated-lambda, expression-not-assigned, fixme, global-statement, global-variable-not-assigned, logging-not-lazy, no-init, non-parent-init-called, pointless-string-statement, protected-access, redefined-builtin, redefined-outer-name, redefine-in-handler, signature-differs, star-args, super-init-not-called, unnecessary-lambda, unnecessary-pass, unpacking-non-sequence, unreachable, unused-argument, unused-import, unused-variable, # TODO(dougwig) - disable nonstandard-exception while we have neutron_lib shims nonstandard-exception, # "C" Coding convention violations bad-continuation, invalid-name, missing-docstring, old-style-class, superfluous-parens, # "R" Refactor recommendations abstract-class-little-used, abstract-class-not-used, duplicate-code, interface-not-implemented, no-self-use, too-few-public-methods, too-many-ancestors, too-many-arguments, too-many-branches, too-many-instance-attributes, too-many-lines, too-many-locals, too-many-public-methods, too-many-return-statements, too-many-statements [BASIC] # Variable names can be 1 to 31 characters long, with lowercase and underscores variable-rgx=[a-z_][a-z0-9_]{0,30}$ # Argument names can be 2 to 31 characters long, with lowercase and underscores argument-rgx=[a-z_][a-z0-9_]{1,30}$ # Method names should be at least 3 characters long # and be lowecased with underscores method-rgx=([a-z_][a-z0-9_]{2,}|setUp|tearDown)$ # Module names matching neutron-* are ok (files in bin/) module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(neutron-[a-z0-9_-]+))$ # Don't require docstrings on tests. no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$ [FORMAT] # Maximum number of characters on a single line. max-line-length=79 [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. # _ is used by our localization additional-builtins=_ [CLASSES] # List of interface methods to ignore, separated by a comma. ignore-iface-methods= [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules= # should use openstack.common.jsonutils json [TYPECHECK] # List of module names for which member attributes should not be checked ignored-modules=six.moves,_MovedItems [REPORTS] # Tells whether to display a full report or only the messages reports=no neutron-vpnaas-12.0.1/setup.cfg0000666000175000017500000000473213370231105016421 0ustar zuulzuul00000000000000[metadata] name = neutron-vpnaas summary = OpenStack Networking VPN as a Service description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = neutron_vpnaas data_files = etc/neutron/rootwrap.d = etc/neutron/rootwrap.d/vpnaas.filters [global] setup-hooks = pbr.hooks.setup_hook [entry_points] console_scripts = neutron-vpn-netns-wrapper = neutron_vpnaas.services.vpn.common.netns_wrapper:main neutron-vyatta-agent = neutron_vpnaas.cmd.eventlet.vyatta_agent:main neutron.agent.l3.extensions = vpnaas = neutron_vpnaas.services.vpn.agent:L3WithVPNaaS device_drivers = neutron.services.vpn.device_drivers.ipsec.OpenSwanDriver = neutron_vpnaas.services.vpn.device_drivers.ipsec:OpenSwanDriver neutron.services.vpn.device_drivers.cisco_ipsec.CiscoCsrIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.cisco_ipsec:CiscoCsrIPsecDriver neutron.services.vpn.device_drivers.vyatta_ipsec.VyattaIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.vyatta_ipsec:VyattaIPsecDriver neutron.db.alembic_migrations = neutron-vpnaas = neutron_vpnaas.db.migration:alembic_migrations neutron.service_plugins = vpnaas = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin oslo.config.opts = neutron.vpnaas = neutron_vpnaas.opts:list_opts neutron.vpnaas.agent = neutron_vpnaas.opts:list_agent_opts tempest.test_plugins = neutron_vpnaas_tests = neutron_vpnaas.tests.tempest.plugin:VPNTempestPlugin [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = neutron_vpnaas/locale/neutron_vpnaas.pot [compile_catalog] directory = neutron_vpnaas/locale domain = neutron_vpnaas [update_catalog] domain = neutron_vpnaas output_dir = neutron_vpnaas/locale input_file = neutron_vpnaas/locale/neutron_vpnaas.pot [wheel] universal = 1 [pbr] warnerrors = true [egg_info] tag_build = tag_date = 0 neutron-vpnaas-12.0.1/.zuul.yaml0000666000175000017500000000363013370230615016542 0ustar zuulzuul00000000000000- project: templates: - check-requirements - periodic-stable-jobs-neutron - openstack-python-jobs-neutron - openstack-python35-jobs-neutron - publish-openstack-sphinx-docs - release-notes-jobs check: jobs: - neutron-vpnaas-dsvm-functional-sswan - neutron-vpnaas-dsvm-tempest - openstack-tox-cover: voting: false required-projects: - openstack/neutron gate: jobs: - neutron-vpnaas-dsvm-functional-sswan - neutron-vpnaas-dsvm-tempest experimental: jobs: - neutron-vpnaas-dsvm-rally - job: name: neutron-vpnaas-dsvm-tempest parent: legacy-dsvm-base run: playbooks/legacy/neutron-vpnaas-dsvm-tempest/run.yaml post-run: playbooks/legacy/neutron-vpnaas-dsvm-tempest/post.yaml timeout: 3900 required-projects: - openstack-infra/devstack-gate - openstack/neutron - openstack/tempest - openstack/neutron-tempest-plugin - job: name: neutron-vpnaas-dsvm-functional-sswan parent: legacy-dsvm-base run: playbooks/legacy/neutron-vpnaas-dsvm-functional-sswan/run.yaml post-run: playbooks/legacy/neutron-vpnaas-dsvm-functional-sswan/post.yaml timeout: 7800 required-projects: - openstack-infra/devstack-gate - openstack/neutron - openstack/neutron-vpnaas irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^neutron-vpnaas/tests/unit/.*$ - job: name: neutron-vpnaas-dsvm-rally parent: legacy-dsvm-base run: playbooks/legacy/neutron-vpnaas-dsvm-rally/run.yaml post-run: playbooks/legacy/neutron-vpnaas-dsvm-rally/post.yaml timeout: 7800 required-projects: - openstack-infra/devstack-gate - openstack/neutron - openstack/neutron-vpnaas - openstack/rally irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^neutron-vpnaas/tests/unit/.*$ neutron-vpnaas-12.0.1/.stestr.conf0000666000175000017500000000011413370230606017044 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./neutron_vpnaas/tests/unit} top_dir=./ neutron-vpnaas-12.0.1/babel.cfg0000666000175000017500000000002113370230606016316 0ustar zuulzuul00000000000000[python: **.py]