VPNaaS: Multiple Local Subnets feature
Implements support for multiple local subnets for IPSec site to site connections, using the new Endpoint Group API. The implementation supports backwards compatibility as follows. If a VPN service is created with a subnet, then the older API is assumed and the user must specify the peer CIDRs for any IPSec connections and cannot specify multiple local subnets. If a subnet is not provided for the VPN service, then the user must use the newer API and provide a local and peer endpoint group IDs for the IPSec connection (and cannot specify the peer CIDRs in the IPSec connection API). Implication here is that the subnet will be an optional argument for the VPN service API. With this feature, when an endpoint group is deleted, a check is made to ensure that there are no IPSec connections using the group. Migration will move the subnet from VPN service API to a new endpoint group, and specify the group in any connections using the service. The peer CIDR(s) will be moved from each connection to an endpoint group, with the group ID specified in the connection. Note: As part of testing the database methods for this feature, several more tests were created to test database access only, instead of doing a round trip test. In a separate commit, these tests can be expanded, and the existing round trip tests removed. Note: Tests for building the dict used in sync requests was enhanced, as part of supporting this feature. The previous tests didn't check that the peer CIDR information was correct, so were enhanced as well. Note: The service driver passes the local CIDR(s) in a new field, called local_cidrs. This field is used for the older API, as well, passing the subnet's CIDR, and allowing consistent consumption by the device driver. The IP version is also passed, rather than obtaining it from the subnet info (so both new and old API use the same fields). Note: to support rolling upgrades, where an agent may be using the older release, after the server has been updated, the subnet CIDR field passed from the service driver to device driver, will be populated from the first local endpoint from the first connection (there has to be at least one connection, when sending data to the agent). Note: In the device driver, I noticed that the local CIDR's IP version can change the config file output, so I added test cases for IPv6, as part of enhancing the tests for multiple local CIDRs. Change-Id: I7a011e3170d7db463a6561e550b2ead3e3311125 Partial-Bug: 1459423
This commit is contained in:
parent
efde2a2dcc
commit
7ba17a3155
@ -0,0 +1,167 @@
|
||||
# (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
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2cb4ee992b41'
|
||||
down_revision = '2c82e782d734'
|
||||
depends_on = ('28ee739a7e4b',)
|
||||
|
||||
from alembic import op
|
||||
from oslo_utils import uuidutils
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.sql import expression as sa_expr
|
||||
|
||||
from neutron.api.v2 import attributes as attr
|
||||
|
||||
from neutron_vpnaas.services.vpn.common import constants as v_constants
|
||||
|
||||
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(attr.NAME_MAX_LEN)),
|
||||
sa.Column('description', sa.String(attr.DESCRIPTION_MAX_LEN)),
|
||||
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(attr.NAME_MAX_LEN)),
|
||||
sa.Column('description', sa.String(attr.DESCRIPTION_MAX_LEN)),
|
||||
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()
|
@ -0,0 +1,52 @@
|
||||
# (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
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '28ee739a7e4b'
|
||||
down_revision = '41b509d10b5e'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
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)
|
@ -1,4 +1,5 @@
|
||||
# (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
|
||||
@ -13,27 +14,28 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import netaddr
|
||||
|
||||
from neutron.callbacks import events
|
||||
from neutron.callbacks import registry
|
||||
from neutron.callbacks import resources
|
||||
from neutron.common import constants as n_constants
|
||||
from neutron.db import common_db_mixin as base_db
|
||||
from neutron.db import l3_agentschedulers_db as l3_agent_db
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import l3 as l3_exception
|
||||
from neutron.i18n import _LW
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.plugins.common import constants as p_constants
|
||||
from neutron.plugins.common import utils
|
||||
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 vpnaas
|
||||
from neutron_vpnaas.services.vpn.common import constants as v_constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -108,16 +110,39 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
'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']]
|
||||
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_subnet_ip_version(self, context, vpnservice_id):
|
||||
vpn_service_db = self._get_vpnservice(context, vpnservice_id)
|
||||
subnet = vpn_service_db.subnet['cidr']
|
||||
ip_version = netaddr.IPNetwork(subnet).version
|
||||
return ip_version
|
||||
def 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']
|
||||
@ -126,19 +151,19 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
tenant_id = self._get_tenant_id_for_create(context, ipsec_sitecon)
|
||||
with context.session.begin(subtransactions=True):
|
||||
#Check permissions
|
||||
self._get_resource(context, vpn_models.VPNService,
|
||||
ipsec_sitecon['vpnservice_id'])
|
||||
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_id = ipsec_sitecon['vpnservice_id']
|
||||
ip_version = self._get_subnet_ip_version(context, vpnservice_id)
|
||||
validator.validate_ipsec_site_connection(context,
|
||||
ipsec_sitecon,
|
||||
ip_version)
|
||||
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=tenant_id,
|
||||
@ -155,10 +180,12 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
dpd_interval=ipsec_sitecon['dpd_interval'],
|
||||
dpd_timeout=ipsec_sitecon['dpd_timeout'],
|
||||
admin_state_up=ipsec_sitecon['admin_state_up'],
|
||||
status=constants.PENDING_CREATE,
|
||||
status=p_constants.PENDING_CREATE,
|
||||
vpnservice_id=vpnservice_id,
|
||||
ikepolicy_id=ipsec_sitecon['ikepolicy_id'],
|
||||
ipsecpolicy_id=ipsec_sitecon['ipsecpolicy_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']:
|
||||
@ -179,15 +206,15 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
ipsec_site_conn_db = self._get_resource(
|
||||
context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id)
|
||||
vpnservice_id = ipsec_site_conn_db['vpnservice_id']
|
||||
ip_version = self._get_subnet_ip_version(context, vpnservice_id)
|
||||
vpnservice = self._get_vpnservice(context, vpnservice_id)
|
||||
|
||||
validator.assign_sensible_ipsec_sitecon_defaults(
|
||||
ipsec_sitecon, ipsec_site_conn_db)
|
||||
validator.validate_ipsec_site_connection(
|
||||
context,
|
||||
ipsec_sitecon,
|
||||
ip_version)
|
||||
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:
|
||||
vpnservice = self._get_vpnservice(context, vpnservice_id)
|
||||
validator.resolve_peer_address(ipsec_sitecon,
|
||||
vpnservice.router)
|
||||
self.assert_update_allowed(ipsec_site_conn_db)
|
||||
@ -209,7 +236,9 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
cidr=peer_cidr,
|
||||
ipsec_site_connection_id=ipsec_site_conn_id)
|
||||
context.session.add(pcidr)
|
||||
del ipsec_sitecon["peer_cidrs"]
|
||||
# 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)
|
||||
@ -280,7 +309,7 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
def create_ikepolicy(self, context, ikepolicy):
|
||||
ike = ikepolicy['ikepolicy']
|
||||
tenant_id = self._get_tenant_id_for_create(context, ike)
|
||||
lifetime_info = ike.get('lifetime', [])
|
||||
lifetime_info = ike['lifetime']
|
||||
lifetime_units = lifetime_info.get('units', 'seconds')
|
||||
lifetime_value = lifetime_info.get('value', 3600)
|
||||
|
||||
@ -449,7 +478,7 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
subnet_id=vpns['subnet_id'],
|
||||
router_id=vpns['router_id'],
|
||||
admin_state_up=vpns['admin_state_up'],
|
||||
status=constants.PENDING_CREATE)
|
||||
status=p_constants.PENDING_CREATE)
|
||||
context.session.add(vpnservice_db)
|
||||
return self._make_vpnservice_dict(vpnservice_db)
|
||||
|
||||
@ -518,6 +547,21 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
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'],
|
||||
@ -562,6 +606,7 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
|
||||
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)
|
||||
@ -576,6 +621,16 @@ class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
|
||||
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 _get_agent_hosting_vpn_services(self, context, host):
|
||||
@ -593,9 +648,6 @@ class VPNPluginRpcDbMixin(object):
|
||||
return []
|
||||
query = context.session.query(vpn_models.VPNService)
|
||||
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.join(l3_agent_db.RouterL3AgentBinding,
|
||||
l3_agent_db.RouterL3AgentBinding.router_id ==
|
||||
vpn_models.VPNService.router_id)
|
||||
@ -603,6 +655,21 @@ class VPNPluginRpcDbMixin(object):
|
||||
l3_agent_db.RouterL3AgentBinding.l3_agent_id == agent.id)
|
||||
return query
|
||||
|
||||
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.
|
||||
|
||||
@ -642,15 +709,15 @@ class VPNPluginRpcDbMixin(object):
|
||||
|
||||
|
||||
def vpn_callback(resource, event, trigger, **kwargs):
|
||||
vpnservice = manager.NeutronManager.get_service_plugins().get(
|
||||
constants.VPN)
|
||||
if vpnservice:
|
||||
vpn_plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
p_constants.VPN)
|
||||
if vpn_plugin:
|
||||
context = kwargs.get('context')
|
||||
if resource == resources.ROUTER_GATEWAY:
|
||||
check_func = vpnservice.check_router_in_use
|
||||
check_func = vpn_plugin.check_router_in_use
|
||||
resource_id = kwargs.get('router_id')
|
||||
elif resource == resources.ROUTER_INTERFACE:
|
||||
check_func = vpnservice.check_subnet_in_use
|
||||
check_func = vpn_plugin.check_subnet_in_use
|
||||
resource_id = kwargs.get('subnet_id')
|
||||
check_func(context, resource_id)
|
||||
|
||||
@ -658,13 +725,23 @@ def vpn_callback(resource, event, trigger, **kwargs):
|
||||
def migration_callback(resource, event, trigger, **kwargs):
|
||||
context = kwargs['context']
|
||||
router = kwargs['router']
|
||||
vpnservice = manager.NeutronManager.get_service_plugins().get(
|
||||
constants.VPN)
|
||||
if vpnservice:
|
||||
vpnservice.check_router_in_use(context, router['id'])
|
||||
vpn_plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
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 = manager.NeutronManager.get_service_plugins().get(
|
||||
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)
|
||||
@ -672,6 +749,9 @@ def 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
|
||||
|
@ -124,6 +124,14 @@ class IPsecSiteConnection(model_base.BASEV2,
|
||||
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, models_v2.HasId, models_v2.HasTenant):
|
||||
@ -134,8 +142,7 @@ class VPNService(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
||||
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'),
|
||||
nullable=False)
|
||||
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)
|
||||
|
@ -18,6 +18,7 @@ import socket
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import exceptions as nexception
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import models_v2
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants as nconstants
|
||||
from oslo_log import log as logging
|
||||
@ -62,7 +63,7 @@ class VpnReferenceValidator(object):
|
||||
raise vpnaas.IPsecSiteConnectionMtuError(mtu=mtu,
|
||||
version=ip_version)
|
||||
|
||||
def validate_peer_address(self, ip_version, router):
|
||||
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.
|
||||
@ -90,17 +91,128 @@ class VpnReferenceValidator(object):
|
||||
raise vpnaas.VPNPeerAddressNotResolved(peer_address=address)
|
||||
|
||||
ip_version = netaddr.IPAddress(ipsec_sitecon['peer_address']).version
|
||||
self.validate_peer_address(ip_version, router)
|
||||
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_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 not prev_conn:
|
||||
|
||||
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}
|
||||
@ -113,12 +225,38 @@ class VpnReferenceValidator(object):
|
||||
prev_conn['dpd_timeout'])
|
||||
|
||||
def validate_ipsec_site_connection(self, context, ipsec_sitecon,
|
||||
ip_version):
|
||||
"""Reference implementation of validation for IPSec connection."""
|
||||
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 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:
|
||||
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, ip_version)
|
||||
self._check_mtu(context, mtu, local_ip_version)
|
||||
|
||||
def _check_router(self, context, router_id):
|
||||
router = self.l3_plugin.get_router(context, router_id)
|
||||
@ -138,8 +276,9 @@ class VpnReferenceValidator(object):
|
||||
|
||||
def validate_vpnservice(self, context, vpnservice):
|
||||
self._check_router(context, vpnservice['router_id'])
|
||||
self._check_subnet_id(context, vpnservice['router_id'],
|
||||
vpnservice['subnet_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.
|
||||
|
@ -66,6 +66,10 @@ 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")
|
||||
@ -115,6 +119,55 @@ 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:
|
||||
attr._validate_subnet_list(data, key_specs)
|
||||
|
||||
attr.validators['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']
|
||||
@ -152,8 +205,8 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'subnet_id': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True},
|
||||
'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},
|
||||
@ -192,8 +245,15 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||
'is_visible': True},
|
||||
'peer_cidrs': {'allow_post': True, 'allow_put': True,
|
||||
'convert_to': attr.convert_to_list,
|
||||
'validate': {'type:subnet_list': None},
|
||||
'is_visible': True},
|
||||
'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},
|
||||
|
@ -494,8 +494,9 @@ class OpenSwanProcess(BaseSwanProcess):
|
||||
that are allowed as subnet for the remote client.
|
||||
"""
|
||||
virtual_privates = []
|
||||
nets = [self.vpnservice['subnet']['cidr']]
|
||||
nets = []
|
||||
for ipsec_site_conn in self.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
|
||||
@ -748,21 +749,20 @@ class IPsecDriver(device_drivers.DeviceDriver):
|
||||
:param vpnservice: vpnservices
|
||||
:param func: self.add_nat_rule or self.remove_nat_rule
|
||||
"""
|
||||
local_cidr = vpnservice['subnet']['cidr']
|
||||
# This ipsec rule is not needed for ipv6.
|
||||
if netaddr.IPNetwork(local_cidr).version == 6:
|
||||
return
|
||||
|
||||
router_id = vpnservice['router_id']
|
||||
for ipsec_site_connection in vpnservice['ipsec_site_connections']:
|
||||
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)
|
||||
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)
|
||||
|
||||
def vpnservice_updated(self, context, **kwargs):
|
||||
|
@ -8,7 +8,7 @@ conn %default
|
||||
{% for ipsec_site_connection in vpnservice.ipsec_site_connections if ipsec_site_connection.admin_state_up
|
||||
-%}
|
||||
conn {{ipsec_site_connection.id}}
|
||||
{% if vpnservice.subnet.ip_version == 6 -%}
|
||||
{% 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
|
||||
@ -26,8 +26,11 @@ conn {{ipsec_site_connection.id}}
|
||||
auto={{ipsec_site_connection.initiator}}
|
||||
# NOTE:REQUIRED
|
||||
# [subnet]
|
||||
leftsubnet={{vpnservice.subnet.cidr}}
|
||||
# leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only)
|
||||
{% 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").
|
||||
|
@ -12,7 +12,7 @@ conn %default
|
||||
conn {{ipsec_site_connection.id}}
|
||||
keyexchange={{ipsec_site_connection.ikepolicy.ike_version}}
|
||||
left={{ipsec_site_connection.external_ip}}
|
||||
leftsubnet={{vpnservice.subnet.cidr}}
|
||||
leftsubnet={{ipsec_site_connection['local_cidrs']|join(',')}}
|
||||
leftid={{ipsec_site_connection.external_ip}}
|
||||
leftfirewall=yes
|
||||
right={{ipsec_site_connection.peer_address}}
|
||||
|
@ -45,7 +45,8 @@ class IPsecVpnDriverCallBack(object):
|
||||
plugin = self.driver.service_plugin
|
||||
vpnservices = plugin._get_agent_hosting_vpn_services(
|
||||
context, host)
|
||||
return [self.driver.make_vpnservice_dict(vpnservice)
|
||||
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):
|
||||
@ -165,15 +166,22 @@ class BaseIPsecVPNDriver(service_drivers.VpnDriver):
|
||||
# don't need a check here.
|
||||
return ip_to_use
|
||||
|
||||
def make_vpnservice_dict(self, vpnservice):
|
||||
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)
|
||||
vpnservice_dict['ipsec_site_connections'] = []
|
||||
vpnservice_dict['subnet'] = dict(
|
||||
vpnservice.subnet)
|
||||
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.
|
||||
@ -192,11 +200,28 @@ class BaseIPsecVPNDriver(service_drivers.VpnDriver):
|
||||
ipsec_site_connection.ipsecpolicy)
|
||||
vpnservice_dict['ipsec_site_connections'].append(
|
||||
ipsec_site_connection_dict)
|
||||
peer_cidrs = [
|
||||
peer_cidr.cidr
|
||||
for peer_cidr in ipsec_site_connection.peer_cidrs]
|
||||
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
|
||||
|
@ -320,7 +320,9 @@ class TestIPSecBase(base.BaseSudoTestCase):
|
||||
'external_ip': vpn_service['external_ip'],
|
||||
'peer_cidrs': [peer_vpn_service['subnet']['cidr']],
|
||||
'peer_address': peer_vpn_service['external_ip'],
|
||||
'peer_id': peer_vpn_service['external_ip']
|
||||
'peer_id': peer_vpn_service['external_ip'],
|
||||
'local_cidrs': [vpn_service['subnet']['cidr']],
|
||||
'local_ip_vers': 4
|
||||
})
|
||||
vpn_service['ipsec_site_connections'] = [ipsec_conn]
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
# (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
|
||||
@ -14,6 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
import os
|
||||
|
||||
import mock
|
||||
@ -54,6 +56,8 @@ ROOTDIR = os.path.normpath(os.path.join(
|
||||
|
||||
extensions_path = ':'.join(extensions.__path__ + nextensions.__path__)
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
|
||||
class TestVpnCorePlugin(test_l3_plugin.TestL3NatIntPlugin,
|
||||
l3_agentschedulers_db.L3AgentSchedulerDbMixin,
|
||||
@ -295,6 +299,8 @@ class VPNTestMixin(object):
|
||||
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,
|
||||
@ -313,7 +319,9 @@ class VPNTestMixin(object):
|
||||
'ikepolicy_id': ikepolicy_id,
|
||||
'ipsecpolicy_id': ipsecpolicy_id,
|
||||
'admin_state_up': admin_state_up,
|
||||
'tenant_id': self._tenant_id}
|
||||
'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'][
|
||||
@ -347,6 +355,8 @@ class VPNTestMixin(object):
|
||||
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
|
||||
@ -359,6 +369,9 @@ class VPNTestMixin(object):
|
||||
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,
|
||||
@ -374,6 +387,8 @@ class VPNTestMixin(object):
|
||||
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)
|
||||
@ -447,7 +462,7 @@ class VPNPluginDbTestCase(VPNTestMixin,
|
||||
plugin_str,
|
||||
service_plugins=service_plugins
|
||||
)
|
||||
self._subnet_id = uuidutils.generate_uuid()
|
||||
self._subnet_id = _uuid()
|
||||
self.core_plugin = TestVpnCorePlugin()
|
||||
self.plugin = vpn_plugin.VPNPlugin()
|
||||
ext_mgr = api_extensions.PluginAwareExtensionManager(
|
||||
@ -1732,13 +1747,14 @@ class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin):
|
||||
|
||||
# Create VPN database instance
|
||||
self.plugin = vpn_db.VPNPluginDb()
|
||||
self.tenant_id = uuidutils.generate_uuid()
|
||||
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': {'name': 'my-service',
|
||||
'description': 'new service',
|
||||
'subnet_id': private_subnet['id'],
|
||||
'subnet_id': subnet_id,
|
||||
'router_id': router['id'],
|
||||
'admin_state_up': True}}
|
||||
|
||||
@ -1753,6 +1769,19 @@ class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin):
|
||||
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()
|
||||
@ -1782,7 +1811,7 @@ class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin):
|
||||
'type': group_type,
|
||||
'endpoints': endpoints}}
|
||||
|
||||
def test_endpoint_group_create_and_with_cidrs(self):
|
||||
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'])
|
||||
@ -1822,7 +1851,7 @@ class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin):
|
||||
self.assertDictSupersetOf(expected, new_endpoint_group)
|
||||
|
||||
def helper_create_endpoint_group(self, info):
|
||||
"""Helper to create endpoint group database entry."""
|
||||
"""Create endpoint group database entry and verify OK."""
|
||||
try:
|
||||
actual = self.plugin.create_endpoint_group(self.context, info)
|
||||
except db_exc.DBError as e:
|
||||
@ -1877,7 +1906,7 @@ class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin):
|
||||
self.assertDictSupersetOf(expected, actual)
|
||||
|
||||
def test_fail_showing_non_existent_endpoint_group(self):
|
||||
"""Test failure to show non-existent endpoint gourp."""
|
||||
"""Test failure to show non-existent endpoint group."""
|
||||
self.assertRaises(vpnaas.VPNEndpointGroupNotFound,
|
||||
self.plugin.get_endpoint_group,
|
||||
self.context, uuidutils.generate_uuid())
|
||||
@ -1937,4 +1966,207 @@ class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin):
|
||||
self.assertRaises(
|
||||
vpnaas.VPNEndpointGroupNotFound,
|
||||
self.plugin.update_endpoint_group,
|
||||
self.context, uuidutils.generate_uuid(), group_updates)
|
||||
self.context, _uuid(), group_updates)
|
||||
|
||||
def prepare_ike_policy_info(self):
|
||||
return {'ikepolicy': {'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': {'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):
|
||||
"""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': [],
|
||||
'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'])
|
||||
|
||||
def test_fail_subnet_change_when_in_use_by_endpoint_group(self):
|
||||
"""Prevent subnet changes, when used by endpoint group.
|
||||
|
||||
An IPSec connection *may* be using an endpoint group that includes
|
||||
the subnet being changed.
|
||||
|
||||
TODO(pcm): Either get notify before subnet change, and block if in
|
||||
use, or get notify after change and force a sync operation. Either
|
||||
way, this requires a Neutron change first.
|
||||
"""
|
||||
pass
|
||||
|
556
neutron_vpnaas/tests/unit/db/vpn/test_vpn_validator.py
Normal file
556
neutron_vpnaas/tests/unit/db/vpn/test_vpn_validator.py
Normal file
@ -0,0 +1,556 @@
|
||||
# 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 mock
|
||||
import socket
|
||||
|
||||
from neutron.common import exceptions as nexception
|
||||
from neutron import context as n_ctx
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron.plugins.common import constants as nconstants
|
||||
from oslo_utils import uuidutils
|
||||
from sqlalchemy.orm import query
|
||||
|
||||
from neutron_vpnaas.extensions import vpnaas
|
||||
from neutron_vpnaas.services.vpn.common import constants as v_constants
|
||||
from neutron_vpnaas.services.vpn import plugin as vpn_plugin
|
||||
from neutron_vpnaas.services.vpn.service_drivers \
|
||||
import ipsec_validator as vpn_validator
|
||||
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
|
||||
|
||||
IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.'
|
||||
'ipsec.IPsecVPNDriver')
|
||||
|
||||
|
||||
class TestValidatorSelection(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestValidatorSelection, self).setUp()
|
||||
vpnaas_provider = [{
|
||||
'service_type': nconstants.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()
|
||||
mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance',
|
||||
return_value=stm).start()
|
||||
self.vpn_plugin = vpn_plugin.VPNDriverPlugin()
|
||||
|
||||
def test_reference_driver_used(self):
|
||||
self.assertIsInstance(self.vpn_plugin._get_validator(),
|
||||
vpn_validator.IpsecVpnValidator)
|
||||
|
||||
|
||||
class TestIPsecDriverValidation(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestIPsecDriverValidation, self).setUp()
|
||||
self.l3_plugin = mock.Mock()
|
||||
mock.patch(
|
||||
'neutron.manager.NeutronManager.get_service_plugins',
|
||||
return_value={nconstants.L3_ROUTER_NAT: self.l3_plugin}).start()
|
||||
self.core_plugin = mock.Mock()
|
||||
mock.patch('neutron.manager.NeutronManager.get_plugin',
|
||||
return_value=self.core_plugin).start()
|
||||
self.context = n_ctx.Context('some_user', 'some_tenant')
|
||||
self.service_plugin = mock.Mock()
|
||||
self.validator = vpn_validator.IpsecVpnValidator(self.service_plugin)
|
||||
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_validate_ipsec_policy(self):
|
||||
ipsec_policy = {'transform_protocol': 'ah-esp'}
|
||||
self.assertRaises(vpn_validator.IpsecValidationFailure,
|
||||
self.validator.validate_ipsec_policy,
|
||||
self.context, ipsec_policy)
|
||||
|
||||
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 = vpn_validator.IpsecVpnValidator.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_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)
|
@ -272,30 +272,51 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
|
||||
"""Test case to delete an ipsecpolicy."""
|
||||
self._test_entity_delete('ipsecpolicy')
|
||||
|
||||
def test_vpnservice_create(self):
|
||||
"""Test case to create a vpnservice."""
|
||||
vpnservice_id = _uuid()
|
||||
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',
|
||||
'subnet_id': _uuid(),
|
||||
'router_id': _uuid(),
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['vpnservice'])
|
||||
return_value.update({'status': "ACTIVE", 'id': vpnservice_id})
|
||||
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)
|
||||
instance.create_vpnservice.assert_called_with(mock.ANY,
|
||||
vpnservice=data)
|
||||
vpnservice=actual_args)
|
||||
self.assertEqual(exc.HTTPCreated.code, res.status_int)
|
||||
res = self.deserialize(res)
|
||||
self.assertIn('vpnservice', res)
|
||||
self.assertEqual(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()
|
||||
@ -372,8 +393,8 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
|
||||
"""Test case to delete a vpnservice."""
|
||||
self._test_entity_delete('vpnservice')
|
||||
|
||||
def test_ipsec_site_connection_create(self):
|
||||
"""Test case to create a ipsec_site_connection."""
|
||||
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()
|
||||
@ -382,8 +403,6 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
|
||||
'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',
|
||||
@ -397,23 +416,45 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
|
||||
'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)
|
||||
instance.create_ipsec_site_connection.assert_called_with(
|
||||
mock.ANY, ipsec_site_connection=data
|
||||
)
|
||||
mock.ANY, ipsec_site_connection=actual_args)
|
||||
self.assertEqual(exc.HTTPCreated.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_create(self):
|
||||
"""Create an IPSec connection with peer CIDRs (old API)."""
|
||||
peer_cidrs = {'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24']}
|
||||
no_endpoint_groups = {'local_ep_group_id': None,
|
||||
'peer_ep_group_id': None}
|
||||
self._test_ipsec_site_connection_create(
|
||||
more_args=peer_cidrs, defaulted_args=no_endpoint_groups)
|
||||
|
||||
def test_ipsec_site_connection_create_with_endpoints(self):
|
||||
"""Create an IPSec connection with endpoint groups (new API)."""
|
||||
endpoint_groups = {'local_ep_group_id': _uuid(),
|
||||
'peer_ep_group_id': _uuid()}
|
||||
no_peer_cidrs = {'peer_cidrs': []}
|
||||
self._test_ipsec_site_connection_create(more_args=endpoint_groups,
|
||||
defaulted_args=no_peer_cidrs)
|
||||
|
||||
def test_ipsec_site_connection_list(self):
|
||||
"""Test case to list all ipsec_site_connections."""
|
||||
ipsecsite_con_id = _uuid()
|
||||
@ -422,6 +463,8 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
|
||||
'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}]
|
||||
@ -457,6 +500,8 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
|
||||
'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}
|
||||
@ -499,6 +544,8 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
|
||||
'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}
|
||||
|
||||
|
@ -49,7 +49,8 @@ FAKE_IKE_POLICY = {
|
||||
FAKE_IPSEC_POLICY = {
|
||||
'encryption_algorithm': 'aes-128',
|
||||
'auth_algorithm': 'sha1',
|
||||
'pfs': 'group5'
|
||||
'pfs': 'group5',
|
||||
'transform_protocol': 'esp'
|
||||
}
|
||||
|
||||
FAKE_VPN_SERVICE = {
|
||||
@ -58,17 +59,18 @@ FAKE_VPN_SERVICE = {
|
||||
'name': 'myvpn',
|
||||
'admin_state_up': True,
|
||||
'status': constants.PENDING_CREATE,
|
||||
'external_ip': '50.0.0.4',
|
||||
'subnet': {'cidr': '10.0.0.0/24'},
|
||||
'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': '50.0.0.4',
|
||||
'peer_address': '30.0.0.5',
|
||||
'external_ip': '60.0.0.4',
|
||||
'peer_address': '60.0.0.5',
|
||||
'mtu': 1500,
|
||||
'peer_id': '30.0.0.5',
|
||||
'peer_id': '60.0.0.5',
|
||||
'psk': 'password',
|
||||
'initiator': 'bi-directional',
|
||||
'ikepolicy': FAKE_IKE_POLICY,
|
||||
@ -76,10 +78,12 @@ FAKE_VPN_SERVICE = {
|
||||
'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': '50.0.0.4',
|
||||
'peer_address': '50.0.0.5',
|
||||
'peer_id': '50.0.0.5',
|
||||
'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,
|
||||
@ -89,7 +93,6 @@ FAKE_VPN_SERVICE = {
|
||||
'status': constants.PENDING_CREATE}]
|
||||
}
|
||||
|
||||
|
||||
AUTH_ESP = '''esp
|
||||
# [encryption_algorithm]-[auth_algorithm]-[pfs]
|
||||
phase2alg=aes128-sha1;modp1536'''
|
||||
@ -133,26 +136,34 @@ OPENSWAN_CONNECTION_DETAILS = '''# rightsubnet=networkA/netmaskA, networkB/netma
|
||||
# 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
|
||||
conn %(default_id)s
|
||||
conn %%default
|
||||
ikelifetime=480m
|
||||
keylife=60m
|
||||
keyingtries=%%forever
|
||||
conn %(conn1_id)s
|
||||
# NOTE: a default route is required for %%defaultroute to work...
|
||||
leftnexthop=%%defaultroute
|
||||
rightnexthop=%%defaultroute
|
||||
left=50.0.0.4
|
||||
leftid=50.0.0.4
|
||||
%(next_hop)s
|
||||
left=%(left)s
|
||||
leftid=%(left)s
|
||||
auto=start
|
||||
# NOTE:REQUIRED
|
||||
# [subnet]
|
||||
leftsubnet=10.0.0.0/24
|
||||
# leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only)
|
||||
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").
|
||||
@ -162,22 +173,19 @@ conn %(conn1_id)s
|
||||
# ipsec_site_connections
|
||||
######################
|
||||
# [peer_address]
|
||||
right=30.0.0.5
|
||||
right=%(right1)s
|
||||
# [peer_id]
|
||||
rightid=30.0.0.5
|
||||
rightid=%(right1)s
|
||||
# [peer_cidrs]
|
||||
rightsubnets={ 20.0.0.0/24 30.0.0.0/24 }
|
||||
rightsubnets={ %(peer_cidrs1)s }
|
||||
%(conn_details)sconn %(conn2_id)s
|
||||
# NOTE: a default route is required for %%defaultroute to work...
|
||||
leftnexthop=%%defaultroute
|
||||
rightnexthop=%%defaultroute
|
||||
left=50.0.0.4
|
||||
leftid=50.0.0.4
|
||||
%(next_hop)s
|
||||
left=%(left)s
|
||||
leftid=%(left)s
|
||||
auto=start
|
||||
# NOTE:REQUIRED
|
||||
# [subnet]
|
||||
leftsubnet=10.0.0.0/24
|
||||
# leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only)
|
||||
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").
|
||||
@ -187,24 +195,24 @@ conn %(conn1_id)s
|
||||
# ipsec_site_connections
|
||||
######################
|
||||
# [peer_address]
|
||||
right=50.0.0.5
|
||||
right=%(right2)s
|
||||
# [peer_id]
|
||||
rightid=50.0.0.5
|
||||
rightid=%(right2)s
|
||||
# [peer_cidrs]
|
||||
rightsubnets={ 40.0.0.0/24 50.0.0.0/24 }
|
||||
rightsubnets={ %(peer_cidrs2)s }
|
||||
%(conn_details)s
|
||||
"""
|
||||
|
||||
EXPECTED_IPSEC_OPENSWAN_SECRET_CONF = '''
|
||||
# Configuration for myvpn
|
||||
50.0.0.4 30.0.0.5 : PSK "password"
|
||||
50.0.0.4 50.0.0.5 : PSK "password"'''
|
||||
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_id)s
|
||||
conn %%default
|
||||
ikelifetime=60m
|
||||
keylife=20m
|
||||
rekeymargin=3m
|
||||
@ -214,28 +222,26 @@ conn %(default_id)s
|
||||
|
||||
conn %(conn1_id)s
|
||||
keyexchange=ikev1
|
||||
left=50.0.0.4
|
||||
leftsubnet=10.0.0.0/24
|
||||
leftid=50.0.0.4
|
||||
left=%(left)s
|
||||
leftsubnet=%(local_cidrs1)s
|
||||
leftid=%(left)s
|
||||
leftfirewall=yes
|
||||
right=30.0.0.5
|
||||
rightsubnet=20.0.0.0/24,30.0.0.0/24
|
||||
rightid=30.0.0.5
|
||||
right=%(right1)s
|
||||
rightsubnet=%(peer_cidrs1)s
|
||||
rightid=%(right1)s
|
||||
auto=route
|
||||
|
||||
conn %(conn2_id)s
|
||||
keyexchange=ikev1
|
||||
left=50.0.0.4
|
||||
leftsubnet=10.0.0.0/24
|
||||
leftid=50.0.0.4
|
||||
left=%(left)s
|
||||
leftsubnet=%(local_cidrs2)s
|
||||
leftid=%(left)s
|
||||
leftfirewall=yes
|
||||
right=50.0.0.5
|
||||
rightsubnet=40.0.0.0/24,50.0.0.0/24
|
||||
rightid=50.0.0.5
|
||||
right=%(right2)s
|
||||
rightsubnet=%(peer_cidrs2)s
|
||||
rightid=%(right2)s
|
||||
auto=route
|
||||
''' % {'default_id': '%default',
|
||||
'conn1_id': FAKE_IPSEC_SITE_CONNECTION1_ID,
|
||||
'conn2_id': FAKE_IPSEC_SITE_CONNECTION2_ID}
|
||||
'''
|
||||
|
||||
EXPECTED_STRONGSWAN_DEFAULT_CONF = '''
|
||||
charon {
|
||||
@ -250,9 +256,9 @@ include strongswan.d/*.conf
|
||||
|
||||
EXPECTED_IPSEC_STRONGSWAN_SECRET_CONF = '''
|
||||
# Configuration for myvpn
|
||||
50.0.0.4 30.0.0.5 : PSK "password"
|
||||
60.0.0.4 60.0.0.5 : PSK "password"
|
||||
|
||||
50.0.0.4 50.0.0.5 : PSK "password"
|
||||
60.0.0.4 60.0.0.6 : PSK "password"
|
||||
'''
|
||||
|
||||
ACTIVE_STATUS = "%(conn_id)s{1}: INSTALLED, TUNNEL" % {'conn_id':
|
||||
@ -264,7 +270,8 @@ 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):
|
||||
ipsec_process=openswan_ipsec.OpenSwanProcess,
|
||||
vpnservice=FAKE_VPN_SERVICE):
|
||||
super(BaseIPsecDeviceDriver, self).setUp()
|
||||
for klass in [
|
||||
'neutron.common.rpc.create_connection',
|
||||
@ -285,7 +292,7 @@ class BaseIPsecDeviceDriver(base.BaseTestCase):
|
||||
'interface_driver': mock.sentinel.interface_driver}
|
||||
self.iptables = mock.Mock()
|
||||
self.apply_mock = mock.Mock()
|
||||
self.vpnservice = copy.deepcopy(FAKE_VPN_SERVICE)
|
||||
self.vpnservice = copy.deepcopy(vpnservice)
|
||||
|
||||
@staticmethod
|
||||
def generate_diff(a, b):
|
||||
@ -296,6 +303,65 @@ class BaseIPsecDeviceDriver(base.BaseTestCase):
|
||||
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
|
||||
|
||||
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):
|
||||
|
||||
@ -332,7 +398,7 @@ class IPSecDeviceLegacy(BaseIPsecDeviceDriver):
|
||||
self.driver.processes = {
|
||||
FAKE_ROUTER_ID: process}
|
||||
self.driver.create_router(self.router)
|
||||
self._test_add_nat_rule_helper()
|
||||
self._test_add_nat_rule()
|
||||
process.enable.assert_called_once_with()
|
||||
|
||||
def test_destroy_router(self):
|
||||
@ -345,7 +411,7 @@ class IPSecDeviceLegacy(BaseIPsecDeviceDriver):
|
||||
process.disable.assert_called_once_with()
|
||||
self.assertNotIn(process_id, self.driver.processes)
|
||||
|
||||
def _test_add_nat_rule_helper(self):
|
||||
def _test_add_nat_rule(self):
|
||||
self.router.iptables_manager.ipv4['nat'].assert_has_calls([
|
||||
mock.call.add_rule(
|
||||
'POSTROUTING',
|
||||
@ -359,12 +425,57 @@ class IPSecDeviceLegacy(BaseIPsecDeviceDriver):
|
||||
top=True),
|
||||
mock.call.add_rule(
|
||||
'POSTROUTING',
|
||||
'-s 10.0.0.0/24 -d 40.0.0.0/24 -m policy '
|
||||
'-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 10.0.0.0/24 -d 50.0.0.0/24 -m policy '
|
||||
'-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)
|
||||
])
|
||||
@ -395,9 +506,17 @@ class IPSecDeviceLegacy(BaseIPsecDeviceDriver):
|
||||
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_helper()
|
||||
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.
|
||||
|
||||
@ -439,7 +558,7 @@ class IPSecDeviceLegacy(BaseIPsecDeviceDriver):
|
||||
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_helper()
|
||||
self._test_add_nat_rule()
|
||||
self.driver.processes[router_id].update.assert_called_once_with()
|
||||
|
||||
def test_delete_vpn_processes(self):
|
||||
@ -497,9 +616,8 @@ class IPSecDeviceLegacy(BaseIPsecDeviceDriver):
|
||||
ensure_process.side_effect = self.fake_ensure_process
|
||||
new_vpn_service = FAKE_VPN_SERVICE
|
||||
updated_vpn_service = copy.deepcopy(new_vpn_service)
|
||||
updated_vpn_service['ipsec_site_connections'].append(
|
||||
{'peer_cidrs': ['60.0.0.0/24',
|
||||
'70.0.0.0/24']})
|
||||
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 = [
|
||||
@ -689,6 +807,166 @@ class IPSecDeviceDVR(BaseIPsecDeviceDriver):
|
||||
'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}
|
||||
# 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])
|
||||
# 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']])
|
||||
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'])
|
||||
return EXPECTED_OPENSWAN_CONF % {
|
||||
'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::']}
|
||||
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'])
|
||||
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],
|
||||
'conn1_id': FAKE_IPSEC_SITE_CONNECTION1_ID,
|
||||
'conn2_id': FAKE_IPSEC_SITE_CONNECTION2_ID}
|
||||
|
||||
def test_ipsec_config_file(self):
|
||||
self._test_ipsec_connection_config({})
|
||||
|
||||
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::']}
|
||||
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(BaseIPsecDeviceDriver):
|
||||
|
||||
_test_timeout = 1
|
||||
@ -698,7 +976,6 @@ class TestOpenSwanProcess(BaseIPsecDeviceDriver):
|
||||
def setUp(self, driver=openswan_ipsec.OpenSwanDriver,
|
||||
ipsec_process=openswan_ipsec.OpenSwanProcess):
|
||||
super(TestOpenSwanProcess, self).setUp(driver, ipsec_process)
|
||||
# Insulate tests against changes to configuration defaults.
|
||||
self.conf.register_opts(openswan_ipsec.openswan_opts,
|
||||
'openswan')
|
||||
self.conf.set_override('state_path', '/tmp')
|
||||
@ -719,41 +996,6 @@ class TestOpenSwanProcess(BaseIPsecDeviceDriver):
|
||||
self.vpnservice,
|
||||
mock.ANY)
|
||||
|
||||
def _test_config_files_on_create(self, proto, auth_mode):
|
||||
"""Verify that the content of config files are correct on create."""
|
||||
auth_proto = {'transform_protocol': proto}
|
||||
for conn in self.vpnservice['ipsec_site_connections']:
|
||||
conn['ipsecpolicy'].update(auth_proto)
|
||||
content = self.process._gen_config_content(
|
||||
self.conf.openswan.ipsec_config_template,
|
||||
self.vpnservice)
|
||||
conn_details = OPENSWAN_CONNECTION_DETAILS % {'auth_mode': auth_mode}
|
||||
expected_openswan_conf = EXPECTED_OPENSWAN_CONF % {
|
||||
'default_id': '%default',
|
||||
'conn1_id': FAKE_IPSEC_SITE_CONNECTION1_ID,
|
||||
'conn2_id': FAKE_IPSEC_SITE_CONNECTION2_ID,
|
||||
'conn_details': conn_details}
|
||||
|
||||
res_diff = self.generate_diff(expected_openswan_conf.strip(),
|
||||
content.strip())
|
||||
|
||||
self.assertEqual(expected_openswan_conf.strip(),
|
||||
str(content.strip()), message=''.join(res_diff))
|
||||
content = self.process._gen_config_content(
|
||||
self.conf.openswan.ipsec_secret_template,
|
||||
self.vpnservice)
|
||||
res_diff = self.generate_diff(
|
||||
EXPECTED_IPSEC_OPENSWAN_SECRET_CONF.strip(),
|
||||
content.strip())
|
||||
self.assertEqual(EXPECTED_IPSEC_OPENSWAN_SECRET_CONF.strip(),
|
||||
str(content.strip()), message=''.join(res_diff))
|
||||
|
||||
def test_config_files_on_create_esp_transform_protocol(self):
|
||||
self._test_config_files_on_create('esp', AUTH_ESP)
|
||||
|
||||
def test_config_files_on_create_ah_transform_protocol(self):
|
||||
self._test_config_files_on_create('ah', AUTH_AH)
|
||||
|
||||
def test__resolve_fqdn(self):
|
||||
with mock.patch.object(socket, 'getaddrinfo') as mock_getaddr_info:
|
||||
mock_getaddr_info.return_value = [(2, 1, 6, '',
|
||||
@ -1065,26 +1307,6 @@ class IPsecStrongswanDeviceDriverLegacy(IPSecDeviceLegacy):
|
||||
self.driver.agent_rpc.get_vpn_services_on_host.return_value = [
|
||||
self.vpnservice]
|
||||
|
||||
def test_config_files_on_create(self):
|
||||
"""Verify that the content of config files are correct on create."""
|
||||
process = self.driver.ensure_process(self.router.router_id,
|
||||
self.vpnservice)
|
||||
content = process._gen_config_content(
|
||||
self.conf.strongswan.ipsec_config_template,
|
||||
self.vpnservice)
|
||||
self.assertEqual(EXPECTED_IPSEC_STRONGSWAN_CONF.strip(),
|
||||
str(content.strip()))
|
||||
content = process._gen_config_content(
|
||||
self.conf.strongswan.strongswan_config_template,
|
||||
self.vpnservice)
|
||||
self.assertEqual(EXPECTED_STRONGSWAN_DEFAULT_CONF.strip(),
|
||||
str(content.strip()))
|
||||
content = process._gen_config_content(
|
||||
self.conf.strongswan.ipsec_secret_template,
|
||||
self.vpnservice)
|
||||
self.assertEqual(EXPECTED_IPSEC_STRONGSWAN_SECRET_CONF.strip(),
|
||||
str(content.strip()))
|
||||
|
||||
def test_status_handling_for_downed_connection(self):
|
||||
"""Test status handling for downed connection."""
|
||||
router_id = self.router.router_id
|
||||
|
@ -14,24 +14,15 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import socket
|
||||
|
||||
from neutron.common import exceptions as nexception
|
||||
from neutron import context as n_ctx
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron.plugins.common import constants as nconstants
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron_vpnaas.extensions import vpnaas
|
||||
from neutron_vpnaas.services.vpn.common import constants
|
||||
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 as vpn_validator
|
||||
from neutron_vpnaas.tests import base
|
||||
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
FAKE_SERVICE_ID = _uuid()
|
||||
@ -43,333 +34,12 @@ FAKE_VPN_SERVICE = {
|
||||
'router_id': FAKE_ROUTER_ID
|
||||
}
|
||||
FAKE_HOST = 'fake_host'
|
||||
FAKE_ROUTER = {l3_db.EXTERNAL_GW_INFO: FAKE_ROUTER_ID}
|
||||
FAKE_SUBNET_ID = _uuid()
|
||||
IPV4 = 4
|
||||
IPV6 = 6
|
||||
FAKE_CONN_ID = _uuid()
|
||||
|
||||
IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.'
|
||||
'ipsec.IPsecVPNDriver')
|
||||
|
||||
|
||||
class TestValidatorSelection(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestValidatorSelection, 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 = (nconstants.VPN + ':vpnaas:' +
|
||||
IPSEC_SERVICE_DRIVER + ':default')
|
||||
cfg.CONF.set_override(
|
||||
'service_provider', [vpnaas_provider], 'service_providers')
|
||||
else:
|
||||
vpnaas_provider = [{
|
||||
'service_type': nconstants.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()
|
||||
mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance',
|
||||
return_value=stm).start()
|
||||
self.vpn_plugin = vpn_plugin.VPNDriverPlugin()
|
||||
|
||||
def test_reference_driver_used(self):
|
||||
self.assertIsInstance(self.vpn_plugin._get_validator(),
|
||||
vpn_validator.IpsecVpnValidator)
|
||||
|
||||
|
||||
class TestIPsecDriverValidation(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestIPsecDriverValidation, self).setUp()
|
||||
self.l3_plugin = mock.Mock()
|
||||
mock.patch(
|
||||
'neutron.manager.NeutronManager.get_service_plugins',
|
||||
return_value={nconstants.L3_ROUTER_NAT: self.l3_plugin}).start()
|
||||
self.core_plugin = mock.Mock()
|
||||
mock.patch('neutron.manager.NeutronManager.get_plugin',
|
||||
return_value=self.core_plugin).start()
|
||||
self.context = n_ctx.Context('some_user', 'some_tenant')
|
||||
self.service_plugin = mock.Mock()
|
||||
self.validator = vpn_validator.IpsecVpnValidator(self.service_plugin)
|
||||
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 _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._validate_peer_address(fixed_ips, IPV4)
|
||||
|
||||
# validate ipv6 peer_address with ipv6 gateway
|
||||
fixed_ips = [{'ip_address': '2001::1'}]
|
||||
self._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._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._validate_peer_address(fixed_ips, IPV4)
|
||||
|
||||
# validate ipv4 peer_address with ipv6 gateway
|
||||
fixed_ips = [{'ip_address': '2001::1'}]
|
||||
self._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._validate_peer_address(fixed_ips, IPV6, expected_exception=True)
|
||||
|
||||
def test_validate_ipsec_policy(self):
|
||||
ipsec_policy = {'transform_protocol': 'ah-esp'}
|
||||
self.assertRaises(vpn_validator.IpsecValidationFailure,
|
||||
self.validator.validate_ipsec_policy,
|
||||
self.context, ipsec_policy)
|
||||
|
||||
def test_defaults_for_ipsec_site_connections_on_update(self):
|
||||
"""Check that defaults are used for any values not specified."""
|
||||
ipsec_sitecon = {}
|
||||
prev_connection = {'dpd_action': 'clear',
|
||||
'dpd_timeout': 500,
|
||||
'dpd_interval': 250}
|
||||
self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon,
|
||||
prev_connection)
|
||||
expected = {
|
||||
'dpd_action': 'clear',
|
||||
'dpd_timeout': 500,
|
||||
'dpd_interval': 250
|
||||
}
|
||||
self.assertEqual(expected, ipsec_sitecon)
|
||||
|
||||
ipsec_sitecon = {'dpd': {'timeout': 200}}
|
||||
prev_connection = {'dpd_action': 'clear',
|
||||
'dpd_timeout': 500,
|
||||
'dpd_interval': 100}
|
||||
self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon,
|
||||
prev_connection)
|
||||
expected = {
|
||||
'dpd': {'timeout': 200},
|
||||
'dpd_action': 'clear',
|
||||
'dpd_timeout': 200,
|
||||
'dpd_interval': 100
|
||||
}
|
||||
self.assertEqual(expected, ipsec_sitecon)
|
||||
|
||||
def test_bad_dpd_settings_on_create(self):
|
||||
"""Failure tests of DPD settings for IPSec conn during create."""
|
||||
ipsec_sitecon = {'mtu': 1500, 'dpd_action': 'hold',
|
||||
'dpd_interval': 100, 'dpd_timeout': 100}
|
||||
self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError,
|
||||
self.validator.validate_ipsec_site_connection,
|
||||
self.context, ipsec_sitecon, IPV4)
|
||||
ipsec_sitecon = {'mtu': 1500, 'dpd_action': 'hold',
|
||||
'dpd_interval': 100, 'dpd_timeout': 99}
|
||||
self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError,
|
||||
self.validator.validate_ipsec_site_connection,
|
||||
self.context, ipsec_sitecon, IPV4)
|
||||
|
||||
def test_bad_dpd_settings_on_update(self):
|
||||
"""Failure tests of DPD settings for IPSec conn. during update.
|
||||
|
||||
Note: On an update, the user may specify only some of the DPD settings.
|
||||
Previous values will be assigned for any missing items, so by the
|
||||
time the validation occurs, all items will be available for checking.
|
||||
The MTU may not be provided, during validation and will be ignored,
|
||||
if that is the case.
|
||||
"""
|
||||
prev_connection = {'mtu': 2000,
|
||||
'dpd_action': 'hold',
|
||||
'dpd_interval': 100,
|
||||
'dpd_timeout': 120}
|
||||
ipsec_sitecon = {'dpd': {'interval': 120}}
|
||||
self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon,
|
||||
prev_connection)
|
||||
self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError,
|
||||
self.validator.validate_ipsec_site_connection,
|
||||
self.context, ipsec_sitecon, IPV4)
|
||||
|
||||
prev_connection = {'mtu': 2000,
|
||||
'dpd_action': 'hold',
|
||||
'dpd_interval': 100,
|
||||
'dpd_timeout': 120}
|
||||
ipsec_sitecon = {'dpd': {'timeout': 99}}
|
||||
self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon,
|
||||
prev_connection)
|
||||
self.assertRaises(vpnaas.IPsecSiteConnectionDpdIntervalValueError,
|
||||
self.validator.validate_ipsec_site_connection,
|
||||
self.context, ipsec_sitecon, IPV4)
|
||||
|
||||
def test_bad_mtu_for_ipsec_connection(self):
|
||||
"""Failure test of invalid MTU values for IPSec conn create/update."""
|
||||
ip_version_limits = vpn_validator.IpsecVpnValidator.IP_MIN_MTU
|
||||
for version, limit in ip_version_limits.items():
|
||||
ipsec_sitecon = {'mtu': limit - 1,
|
||||
'dpd_action': 'hold',
|
||||
'dpd_interval': 100,
|
||||
'dpd_timeout': 120}
|
||||
self.assertRaises(
|
||||
vpnaas.IPsecSiteConnectionMtuError,
|
||||
self.validator.validate_ipsec_site_connection,
|
||||
self.context, ipsec_sitecon, version)
|
||||
|
||||
def test_endpoints_all_cidrs_in_endpoint_group(self):
|
||||
"""All endpoints in the endpoint group are valid CIDRs."""
|
||||
endpoint_group = {'type': 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': 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': 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': 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': 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': 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': constants.SUBNET_ENDPOINT,
|
||||
'endpoints': [subnet_id]}
|
||||
self.assertRaises(vpnaas.NonExistingSubnetInEndpointGroup,
|
||||
self.validator.validate_endpoint_group,
|
||||
self.context, endpoint_group)
|
||||
|
||||
|
||||
class FakeSqlQueryObject(dict):
|
||||
"""To fake SqlAlchemy query object and access keys as attributes."""
|
||||
|
||||
@ -442,72 +112,244 @@ class TestIPsecDriver(base.BaseTestCase):
|
||||
[FAKE_VPN_SERVICE],
|
||||
{'router': {'id': FAKE_VPN_SERVICE['router_id']}})
|
||||
|
||||
def _test_make_vpnservice_dict_helper(self, peer_id, expected_peer_id):
|
||||
fake_subnet = FakeSqlQueryObject(id='foo-subnet-id',
|
||||
name='foo-subnet',
|
||||
network_id='foo-net-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')
|
||||
|
||||
fake_ikepolicy = FakeSqlQueryObject(id='foo-ike', name='ike-name')
|
||||
fake_ipsecpolicy = FakeSqlQueryObject(id='foo-ipsec')
|
||||
fake_peer_address = '10.0.0.2'
|
||||
|
||||
fake_ipsec_conn = FakeSqlQueryObject(peer_id=peer_id,
|
||||
peer_address=fake_peer_address,
|
||||
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,
|
||||
ikepolicy=fake_ikepolicy,
|
||||
ipsecpolicy=fake_ipsecpolicy,
|
||||
peer_cidrs=[])
|
||||
fake_external_ip = '10.0.0.99'
|
||||
fake_gw_port = {'fixed_ips': [{'ip_address': fake_external_ip}]}
|
||||
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=fake_external_ip,
|
||||
external_v4_ip=external_ip,
|
||||
external_v6_ip=None,
|
||||
subnet_id='foo-subnet-id',
|
||||
subnet_id=subnet_id,
|
||||
router_id='foo-router-id')
|
||||
fake_vpnservice.subnet = fake_subnet
|
||||
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
|
||||
|
||||
expected_vpnservice_dict = {'name': 'foo-vpn',
|
||||
'id': 'foo-vpn-id',
|
||||
'description': 'foo-vpn-service',
|
||||
'admin_state_up': True,
|
||||
'status': 'active',
|
||||
'external_v4_ip': fake_external_ip,
|
||||
'external_v6_ip': None,
|
||||
'subnet_id': 'foo-subnet-id',
|
||||
'router_id': 'foo-router-id',
|
||||
'subnet': {'id': 'foo-subnet-id',
|
||||
'name': 'foo-subnet',
|
||||
'network_id': 'foo-net-id'},
|
||||
'external_ip': fake_external_ip,
|
||||
'ipsec_site_connections': [
|
||||
{'peer_id': expected_peer_id,
|
||||
'external_ip': fake_external_ip,
|
||||
'peer_address': fake_peer_address,
|
||||
'ikepolicy': {'id': 'foo-ike',
|
||||
'name': 'ike-name'},
|
||||
'ipsecpolicy': {'id': 'foo-ipsec'},
|
||||
'peer_cidrs': []}]}
|
||||
def build_expected_dict(self, info):
|
||||
"""Create the expected dict used in sync operations.
|
||||
|
||||
actual_vpnservice_dict = self.driver.make_vpnservice_dict(
|
||||
fake_vpnservice)
|
||||
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.
|
||||
"""
|
||||
|
||||
self.assertEqual(expected_vpnservice_dict, actual_vpnservice_dict)
|
||||
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'])
|
||||
|
||||
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,
|
||||
'ipsec_site_connections': [
|
||||
{'id': 'conn-id',
|
||||
'peer_id': peer_id,
|
||||
'external_ip': external_ip,
|
||||
'peer_address': '10.0.0.2',
|
||||
'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'
|
||||
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,
|
||||
'ipsec_site_connections': [
|
||||
{'id': 'conn-id',
|
||||
'peer_id': peer_id,
|
||||
'external_ip': external_ip,
|
||||
'peer_address': '10.0.0.2',
|
||||
'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,
|
||||
fake_vpnservice.ipsec_site_connections[0].peer_id)
|
||||
|
||||
def test_make_vpnservice_dict_peer_id_is_ipaddr(self):
|
||||
self._test_make_vpnservice_dict_helper('10.0.0.2', '10.0.0.2')
|
||||
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):
|
||||
self._test_make_vpnservice_dict_helper('foo.peer.id', '@foo.peer.id')
|
||||
"""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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user