Merge "Remove unmaintained drivers"

This commit is contained in:
Zuul 2018-06-21 01:57:23 +00:00 committed by Gerrit Code Review
commit af6c6301b5
29 changed files with 29 additions and 6334 deletions

View File

@ -41,11 +41,7 @@ function neutron_vpnaas_configure_agent {
plugin_agent_add_l3_agent_extension vpnaas
configure_l3_agent
if [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then
if is_fedora; then
iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.fedora_strongswan_ipsec.FedoraStrongSwanDriver
else
iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver
fi
iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver
elif [[ "$IPSEC_PACKAGE" == "libreswan" ]]; then
iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.libreswan_ipsec.LibreSwanDriver
else

View File

@ -26,10 +26,6 @@ It includes both of in-tree and out-of-tree drivers.
+----------------------------+---------------------------+------------------+
| Driver | Contact person | IRC nick |
+============================+===========================+==================+
| CiscoCsrIPsecDriver | ??? | ??? |
+----------------------------+---------------------------+------------------+
| FedoraStrongSwanDriver | ??? | ??? |
+----------------------------+---------------------------+------------------+
| LibreSwanDriver | Hunt Xu | huntxu |
+----------------------------+---------------------------+------------------+
| MidonetIPsecVPNDriver [#]_ | YAMAMOTO Takashi | yamamoto |
@ -42,8 +38,6 @@ It includes both of in-tree and out-of-tree drivers.
| StrongSwanDriver +---------------------------+------------------+
| | Cao Xuan Hoang | hoangcx |
+----------------------------+---------------------------+------------------+
| VyattaIPsecDriver | ??? | ??? |
+----------------------------+---------------------------+------------------+
.. [#] networking-midonet: https://docs.openstack.org/networking-midonet/latest/install/installation.html#vpnaas
.. [#] vmware-nsx: Maintained under the vmware-nsx repository - https://github.com/openstack/vmware-nsx

View File

@ -1,26 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging as sys_logging
from oslo_reports import guru_meditation_report as gmr
from neutron_vpnaas import version
# During the call to gmr.TextGuruMeditation.setup_autorun(), Guru Meditation
# Report tries to start logging. Set a handler here to accommodate this.
logger = sys_logging.getLogger(None)
if not logger.handlers:
logger.addHandler(sys_logging.StreamHandler())
_version_string = version.version_info.release_string()
gmr.TextGuruMeditation.setup_autorun(version=_version_string)

View File

@ -1,14 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import eventlet
eventlet.monkey_patch()

View File

@ -1 +1 @@
b6a2519ab7dc
e50641731f1a

View File

@ -1,5 +1,4 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
# Copyright 2018, Fujitsu Vietnam Limited
#
# 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
@ -14,8 +13,20 @@
# under the License.
#
from neutron_vpnaas.services.vpn import vyatta_agent
"""drop cisco_csr_identifier_map table
Revision ID: e50641731f1a
Revises: b6a2519ab7dc
Create Date: 2018-02-28 10:28:59.846652
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = 'e50641731f1a'
down_revision = 'b6a2519ab7dc'
def main():
vyatta_agent.main()
def upgrade():
op.drop_table('cisco_csr_identifier_map')

View File

@ -24,7 +24,6 @@ Based on this comparison database can be healed with healing migration.
from neutron.db.migration.models import head
from neutron_vpnaas.db.vpn import vpn_db # noqa
from neutron_vpnaas.services.vpn.service_drivers import cisco_csr_db # noqa
def get_metadata():

View File

@ -32,14 +32,8 @@ vpn_agent_opts = [
sample_default=['neutron_vpnaas.services.vpn.device_drivers.ipsec.'
'OpenSwanDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'cisco_ipsec.CiscoCsrIPsecDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'vyatta_ipsec.VyattaIPSecDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'strongswan_ipsec.StrongSwanDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'fedora_strongswan_ipsec.FedoraStrongSwanDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'libreswan_ipsec.LibreSwanDriver'],
help=_("The vpn device drivers Neutron will use")),
]

View File

@ -16,7 +16,3 @@
IPSEC_DRIVER_TOPIC = 'ipsec_driver'
IPSEC_AGENT_TOPIC = 'ipsec_agent'
CISCO_IPSEC_DRIVER_TOPIC = 'cisco_csr_ipsec_driver'
CISCO_IPSEC_AGENT_TOPIC = 'cisco_csr_ipsec_agent'
BROCADE_IPSEC_DRIVER_TOPIC = 'brocade_vyatta_ipsec_driver'
BROCADE_IPSEC_AGENT_TOPIC = 'brocade_vyatta_ipsec_agent'

View File

@ -1,291 +0,0 @@
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
import netaddr
from oslo_log import log as logging
from oslo_serialization import jsonutils
import requests
from requests import exceptions as r_exc
TIMEOUT = 20.0
LOG = logging.getLogger(__name__)
HEADER_CONTENT_TYPE_JSON = {'content-type': 'application/json'}
URL_BASE = 'https://%(host)s/api/v1/%(resource)s'
# CSR RESTapi URIs
URI_VPN_IPSEC_POLICIES = 'vpn-svc/ipsec/policies'
URI_VPN_IPSEC_POLICIES_ID = URI_VPN_IPSEC_POLICIES + '/%s'
URI_VPN_IKE_POLICIES = 'vpn-svc/ike/policies'
URI_VPN_IKE_POLICIES_ID = URI_VPN_IKE_POLICIES + '/%s'
URI_VPN_IKE_KEYRINGS = 'vpn-svc/ike/keyrings'
URI_VPN_IKE_KEYRINGS_ID = URI_VPN_IKE_KEYRINGS + '/%s'
URI_VPN_IKE_KEEPALIVE = 'vpn-svc/ike/keepalive'
URI_VPN_SITE_TO_SITE = 'vpn-svc/site-to-site'
URI_VPN_SITE_TO_SITE_ID = URI_VPN_SITE_TO_SITE + '/%s'
URI_VPN_SITE_TO_SITE_STATE = URI_VPN_SITE_TO_SITE + '/%s/state'
URI_VPN_SITE_ACTIVE_SESSIONS = URI_VPN_SITE_TO_SITE + '/active/sessions'
URI_ROUTING_STATIC_ROUTES = 'routing-svc/static-routes'
URI_ROUTING_STATIC_ROUTES_ID = URI_ROUTING_STATIC_ROUTES + '/%s'
def make_route_id(cidr, interface):
"""Build ID that will be used to identify route for later deletion."""
net = netaddr.IPNetwork(cidr)
return '%(network)s_%(prefix)s_%(interface)s' % {
'network': net.network,
'prefix': net.prefixlen,
'interface': interface}
class CsrRestClient(object):
"""REST CsrRestClient for accessing the Cisco Cloud Services Router."""
def __init__(self, settings):
self.port = str(settings.get('protocol_port', 55443))
self.host = ':'.join([settings.get('rest_mgmt_ip', ''), self.port])
self.auth = (settings['username'], settings['password'])
self.inner_if_name = settings.get('inner_if_name', '')
self.outer_if_name = settings.get('outer_if_name', '')
self.token = None
self.vrf = settings.get('vrf', '')
self.vrf_prefix = 'vrf/%s/' % self.vrf if self.vrf else ""
self.status = requests.codes.OK
self.timeout = settings.get('timeout')
self.max_tries = 5
self.session = requests.Session()
def _response_info_for(self, response, method):
"""Return contents or location from response.
For a POST or GET with a 200 response, the response content
is returned.
For a POST with a 201 response, return the header's location,
which contains the identifier for the created resource.
If there is an error, return the response content, so that
it can be used in error processing ('error-code', 'error-message',
and 'detail' fields).
"""
if method in ('POST', 'GET') and self.status == requests.codes.OK:
LOG.debug('RESPONSE: %s', response.json())
return response.json()
if method == 'POST' and self.status == requests.codes.CREATED:
return response.headers.get('location', '')
if self.status >= requests.codes.BAD_REQUEST and response.content:
if b'error-code' in response.content:
content = jsonutils.loads(response.content)
LOG.debug("Error response content %s", content)
return content
def _request(self, method, url, **kwargs):
"""Perform REST request and save response info."""
try:
LOG.debug("%(method)s: Request for %(resource)s payload: "
"%(payload)s",
{'method': method.upper(), 'resource': url,
'payload': kwargs.get('data')})
start_time = time.time()
response = self.session.request(method, url, verify=False,
timeout=self.timeout, **kwargs)
LOG.debug("%(method)s Took %(time).2f seconds to process",
{'method': method.upper(),
'time': time.time() - start_time})
except (r_exc.Timeout, r_exc.SSLError) as te:
# Should never see SSLError, unless requests package is old (<2.0)
timeout_val = 0.0 if self.timeout is None else self.timeout
LOG.warning("%(method)s: Request timeout%(ssl)s "
"(%(timeout).3f sec) for CSR(%(host)s)",
{'method': method,
'timeout': timeout_val,
'ssl': '(SSLError)'
if isinstance(te, r_exc.SSLError) else '',
'host': self.host})
self.status = requests.codes.REQUEST_TIMEOUT
except r_exc.ConnectionError:
LOG.exception("%(method)s: Unable to connect to "
"CSR(%(host)s)",
{'method': method, 'host': self.host})
self.status = requests.codes.NOT_FOUND
except Exception as e:
LOG.error("%(method)s: Unexpected error for CSR (%(host)s): "
"%(error)s",
{'method': method, 'host': self.host, 'error': e})
self.status = requests.codes.INTERNAL_SERVER_ERROR
else:
self.status = response.status_code
LOG.debug("%(method)s: Completed [%(status)s]",
{'method': method, 'status': self.status})
return self._response_info_for(response, method)
def authenticate(self):
"""Obtain a token to use for subsequent CSR REST requests.
This is called when there is no token yet, or if the token has expired
and attempts to use it resulted in an UNAUTHORIZED REST response.
"""
url = URL_BASE % {'host': self.host, 'resource': 'auth/token-services'}
headers = {'Content-Length': '0',
'Accept': 'application/json'}
headers.update(HEADER_CONTENT_TYPE_JSON)
LOG.debug("%(auth)s with CSR %(host)s",
{'auth': 'Authenticating' if self.token is None
else 'Reauthenticating', 'host': self.host})
self.token = None
response = self._request("POST", url, headers=headers, auth=self.auth)
if response:
self.token = response['token-id']
LOG.debug("Successfully authenticated with CSR %s", self.host)
return True
LOG.error("Failed authentication with CSR %(host)s [%(status)s]",
{'host': self.host, 'status': self.status})
def _do_request(self, method, resource, payload=None, more_headers=None,
full_url=False):
"""Perform a REST request to a CSR resource.
If this is the first time interacting with the CSR, a token will
be obtained. If the request fails, due to an expired token, the
token will be obtained and the request will be retried once more.
"""
if self.token is None:
if not self.authenticate():
return
if full_url:
url = resource
else:
url = ('https://%(host)s/api/v1/%(resource)s' %
{'host': self.host, 'resource': resource})
headers = {'Accept': 'application/json', 'X-auth-token': self.token}
if more_headers:
headers.update(more_headers)
if payload:
payload = jsonutils.dumps(payload)
response = self._request(method, url, data=payload, headers=headers)
if self.status == requests.codes.UNAUTHORIZED:
if not self.authenticate():
return
headers['X-auth-token'] = self.token
response = self._request(method, url, data=payload,
headers=headers)
if self.status != requests.codes.REQUEST_TIMEOUT:
return response
LOG.error("%(method)s: Request timeout for CSR(%(host)s)",
{'method': method, 'host': self.host})
def get_request(self, resource, full_url=False):
"""Perform a REST GET requests for a CSR resource."""
return self._do_request('GET', resource, full_url=full_url)
def post_request(self, resource, payload=None):
"""Perform a POST request to a CSR resource."""
return self._do_request('POST', resource, payload=payload,
more_headers=HEADER_CONTENT_TYPE_JSON)
def put_request(self, resource, payload=None):
"""Perform a PUT request to a CSR resource."""
return self._do_request('PUT', resource, payload=payload,
more_headers=HEADER_CONTENT_TYPE_JSON)
def delete_request(self, resource):
"""Perform a DELETE request on a CSR resource."""
return self._do_request('DELETE', resource,
more_headers=HEADER_CONTENT_TYPE_JSON)
# VPN Specific APIs
def create_ike_policy(self, policy_info):
base_ike_policy_info = {u'version': u'v1',
u'local-auth-method': u'pre-share'}
base_ike_policy_info.update(policy_info)
return self.post_request(URI_VPN_IKE_POLICIES,
payload=base_ike_policy_info)
def create_ipsec_policy(self, policy_info):
base_ipsec_policy_info = {u'mode': u'tunnel'}
base_ipsec_policy_info.update(policy_info)
return self.post_request(URI_VPN_IPSEC_POLICIES,
payload=base_ipsec_policy_info)
def create_pre_shared_key(self, psk_info):
return self.post_request(self.vrf_prefix + URI_VPN_IKE_KEYRINGS,
payload=psk_info)
def create_ipsec_connection(self, connection_info):
base_conn_info = {
u'vpn-type': u'site-to-site',
u'ip-version': u'ipv4',
u'local-device': {
u'tunnel-ip-address': self.outer_if_name,
u'ip-address': self.inner_if_name
}
}
connection_info.update(base_conn_info)
if self.vrf:
connection_info[u'tunnel-vrf'] = self.vrf
return self.post_request(self.vrf_prefix + URI_VPN_SITE_TO_SITE,
payload=connection_info)
def configure_ike_keepalive(self, keepalive_info):
base_keepalive_info = {u'periodic': True}
keepalive_info.update(base_keepalive_info)
return self.put_request(URI_VPN_IKE_KEEPALIVE, keepalive_info)
def create_static_route(self, route_info):
return self.post_request(self.vrf_prefix + URI_ROUTING_STATIC_ROUTES,
payload=route_info)
def delete_static_route(self, route_id):
return self.delete_request(
self.vrf_prefix + URI_ROUTING_STATIC_ROUTES_ID % route_id)
def set_ipsec_connection_state(self, tunnel, admin_up=True):
"""Set the IPSec site-to-site connection (tunnel) admin state.
Note: When a tunnel is created, it will be admin up.
"""
info = {u'vpn-interface-name': tunnel, u'enabled': admin_up}
return self.put_request(
self.vrf_prefix + URI_VPN_SITE_TO_SITE_STATE % tunnel, info)
def delete_ipsec_connection(self, conn_id):
return self.delete_request(
self.vrf_prefix + URI_VPN_SITE_TO_SITE_ID % conn_id)
def delete_ipsec_policy(self, policy_id):
return self.delete_request(URI_VPN_IPSEC_POLICIES_ID % policy_id)
def delete_ike_policy(self, policy_id):
return self.delete_request(URI_VPN_IKE_POLICIES_ID % policy_id)
def delete_pre_shared_key(self, key_id):
return self.delete_request(
self.vrf_prefix + URI_VPN_IKE_KEYRINGS_ID % key_id)
def read_tunnel_statuses(self):
results = self.get_request(self.vrf_prefix +
URI_VPN_SITE_ACTIVE_SESSIONS)
if self.status != requests.codes.OK or not results:
return []
tunnels = [(t[u'vpn-interface-name'], t[u'status'])
for t in results['items']]
return tunnels

View File

@ -1,741 +0,0 @@
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
from neutron.common import rpc as n_rpc
from neutron_lib import constants
from neutron_lib import context as ctx
from neutron_lib import exceptions as nexception
from neutron_lib.plugins import utils as plugin_utils
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from oslo_service import loopingcall
import requests
from neutron_vpnaas._i18n import _
from neutron_vpnaas.services.vpn.common import topics
from neutron_vpnaas.services.vpn import device_drivers
from neutron_vpnaas.services.vpn.device_drivers import (
cisco_csr_rest_client as csr_client)
ipsec_opts = [
cfg.IntOpt('status_check_interval',
default=60,
help=_("Status check interval for Cisco CSR IPSec connections"))
]
cfg.CONF.register_opts(ipsec_opts, 'cisco_csr_ipsec')
LOG = logging.getLogger(__name__)
RollbackStep = collections.namedtuple('RollbackStep',
['action', 'resource_id', 'title'])
class CsrResourceCreateFailure(nexception.NeutronException):
message = _("Cisco CSR failed to create %(resource)s (%(which)s)")
class CsrAdminStateChangeFailure(nexception.NeutronException):
message = _("Cisco CSR failed to change %(tunnel)s admin state to "
"%(state)s")
class CsrDriverMismatchError(nexception.NeutronException):
message = _("Required %(resource)s attribute %(attr)s mapping for Cisco "
"CSR is missing in device driver")
class CsrUnknownMappingError(nexception.NeutronException):
message = _("Device driver does not have a mapping of '%(value)s for "
"attribute %(attr)s of %(resource)s")
class CiscoCsrIPsecVpnDriverApi(object):
"""RPC API for agent to plugin messaging."""
def __init__(self, topic):
target = oslo_messaging.Target(topic=topic, version='1.0')
self.client = n_rpc.get_client(target)
def get_vpn_services_on_host(self, context, host):
"""Get list of vpnservices on this host.
The vpnservices including related ipsec_site_connection,
ikepolicy, ipsecpolicy, and Cisco info on this host.
"""
cctxt = self.client.prepare()
return cctxt.call(context, 'get_vpn_services_on_host', host=host)
def update_status(self, context, status):
"""Update status for all VPN services and connections."""
cctxt = self.client.prepare()
return cctxt.call(context, 'update_status', status=status)
class CiscoCsrIPsecDriver(device_drivers.DeviceDriver):
"""Cisco CSR VPN Device Driver for IPSec.
This class is designed for use with L3-agent now.
However this driver will be used with another agent in future.
so the use of "Router" is kept minimal now.
Instead of router_id, we are using process_id in this code.
"""
# history
# 1.0 Initial version
target = oslo_messaging.Target(version='1.0')
def __init__(self, vpn_service, host):
# TODO(pc_m): Once all driver implementations no longer need
# vpn_service argument, replace with just config argument.
self.host = host
self.conn = n_rpc.Connection()
context = ctx.get_admin_context_without_session()
node_topic = '%s.%s' % (topics.CISCO_IPSEC_AGENT_TOPIC, self.host)
self.service_state = {}
self.endpoints = [self]
self.conn.create_consumer(node_topic, self.endpoints, fanout=False)
self.conn.consume_in_threads()
self.agent_rpc = (
CiscoCsrIPsecVpnDriverApi(topics.CISCO_IPSEC_DRIVER_TOPIC))
self.periodic_report = loopingcall.FixedIntervalLoopingCall(
self.report_status, context)
self.periodic_report.start(
interval=vpn_service.conf.cisco_csr_ipsec.status_check_interval)
LOG.debug("Device driver initialized for %s", node_topic)
def vpnservice_updated(self, context, **kwargs):
"""Handle VPNaaS service driver change notifications."""
LOG.debug("Handling VPN service update notification '%s'",
kwargs.get('reason', ''))
self.sync(context, [])
def create_vpn_service(self, service_data):
"""Create new entry to track VPN service and its connections."""
csr = csr_client.CsrRestClient(service_data['router_info'])
vpn_service_id = service_data['id']
self.service_state[vpn_service_id] = CiscoCsrVpnService(
service_data, csr)
return self.service_state[vpn_service_id]
def update_connection(self, context, vpn_service_id, conn_data):
"""Handle notification for a single IPSec connection."""
vpn_service = self.service_state[vpn_service_id]
conn_id = conn_data['id']
conn_is_admin_up = conn_data[u'admin_state_up']
if conn_id in vpn_service.conn_state: # Existing connection...
ipsec_conn = vpn_service.conn_state[conn_id]
config_changed = ipsec_conn.check_for_changes(conn_data)
if config_changed:
LOG.debug("Update: Existing connection %s changed", conn_id)
ipsec_conn.delete_ipsec_site_connection(context, conn_id)
ipsec_conn.create_ipsec_site_connection(context, conn_data)
ipsec_conn.conn_info = conn_data
if ipsec_conn.forced_down:
if vpn_service.is_admin_up and conn_is_admin_up:
LOG.debug("Update: Connection %s no longer admin down",
conn_id)
ipsec_conn.set_admin_state(is_up=True)
ipsec_conn.forced_down = False
else:
if not vpn_service.is_admin_up or not conn_is_admin_up:
LOG.debug("Update: Connection %s forced to admin down",
conn_id)
ipsec_conn.set_admin_state(is_up=False)
ipsec_conn.forced_down = True
else: # New connection...
ipsec_conn = vpn_service.create_connection(conn_data)
ipsec_conn.create_ipsec_site_connection(context, conn_data)
if not vpn_service.is_admin_up or not conn_is_admin_up:
LOG.debug("Update: Created new connection %s in admin down "
"state", conn_id)
ipsec_conn.set_admin_state(is_up=False)
ipsec_conn.forced_down = True
else:
LOG.debug("Update: Created new connection %s", conn_id)
ipsec_conn.is_dirty = False
ipsec_conn.last_status = conn_data['status']
ipsec_conn.is_admin_up = conn_is_admin_up
return ipsec_conn
def update_service(self, context, service_data):
"""Handle notification for a single VPN Service and its connections."""
vpn_service_id = service_data['id']
if vpn_service_id in self.service_state:
LOG.debug("Update: Existing VPN service %s detected",
vpn_service_id)
vpn_service = self.service_state[vpn_service_id]
else:
LOG.debug("Update: New VPN service %s detected", vpn_service_id)
vpn_service = self.create_vpn_service(service_data)
if not vpn_service:
return
vpn_service.is_dirty = False
vpn_service.connections_removed = False
vpn_service.last_status = service_data['status']
vpn_service.is_admin_up = service_data[u'admin_state_up']
for conn_data in service_data['ipsec_conns']:
self.update_connection(context, vpn_service_id, conn_data)
LOG.debug("Update: Completed update processing")
return vpn_service
def update_all_services_and_connections(self, context):
"""Update services and connections based on plugin info.
Perform any create and update operations and then update status.
Mark every visited connection as no longer "dirty" so they will
not be deleted at end of sync processing.
"""
services_data = self.agent_rpc.get_vpn_services_on_host(context,
self.host)
LOG.debug("Sync updating for %d VPN services", len(services_data))
vpn_services = []
for service_data in services_data:
vpn_service = self.update_service(context, service_data)
if vpn_service:
vpn_services.append(vpn_service)
return vpn_services
def mark_existing_connections_as_dirty(self):
"""Mark all existing connections as "dirty" for sync."""
service_count = 0
connection_count = 0
for service_state in self.service_state.values():
service_state.is_dirty = True
service_count += 1
for conn_id in service_state.conn_state:
service_state.conn_state[conn_id].is_dirty = True
connection_count += 1
LOG.debug("Mark: %(service)d VPN services and %(conn)d IPSec "
"connections marked dirty", {'service': service_count,
'conn': connection_count})
def remove_unknown_connections(self, context):
"""Remove connections that are not known by service driver."""
service_count = 0
connection_count = 0
for vpn_service_id, vpn_service in list(self.service_state.items()):
dirty = [c_id for c_id, c in vpn_service.conn_state.items()
if c.is_dirty]
vpn_service.connections_removed = len(dirty) > 0
for conn_id in dirty:
conn_state = vpn_service.conn_state[conn_id]
conn_state.delete_ipsec_site_connection(context, conn_id)
connection_count += 1
del vpn_service.conn_state[conn_id]
if vpn_service.is_dirty:
service_count += 1
del self.service_state[vpn_service_id]
elif dirty:
self.connections_removed = True
LOG.debug("Sweep: Removed %(service)d dirty VPN service%(splural)s "
"and %(conn)d dirty IPSec connection%(cplural)s",
{'service': service_count, 'conn': connection_count,
'splural': 's'[service_count == 1:],
'cplural': 's'[connection_count == 1:]})
def build_report_for_connections_on(self, vpn_service):
"""Create the report fragment for IPSec connections on a service.
Collect the current status from the Cisco CSR and use that to update
the status and generate report fragment for each connection on the
service. If there is no status information, or no change, then no
report info will be created for the connection. The combined report
data is returned.
"""
LOG.debug("Report: Collecting status for IPSec connections on VPN "
"service %s", vpn_service.service_id)
tunnels = vpn_service.get_ipsec_connections_status()
report = {}
for connection in vpn_service.conn_state.values():
if connection.forced_down:
LOG.debug("Connection %s forced down", connection.conn_id)
current_status = constants.DOWN
else:
current_status = connection.find_current_status_in(tunnels)
LOG.debug("Connection %(conn)s reported %(status)s",
{'conn': connection.conn_id,
'status': current_status})
frag = connection.update_status_and_build_report(current_status)
if frag:
LOG.debug("Report: Adding info for IPSec connection %s",
connection.conn_id)
report.update(frag)
return report
def build_report_for_service(self, vpn_service):
"""Create the report info for a VPN service and its IPSec connections.
Get the report info for the connections on the service, and include
it into the report info for the VPN service. If there is no report
info for the connection, then no change has occurred and no report
will be generated. If there is only one connection for the service,
we'll set the service state to match the connection (with ERROR seen
as DOWN).
"""
conn_report = self.build_report_for_connections_on(vpn_service)
if conn_report or vpn_service.connections_removed:
pending_handled = plugin_utils.in_pending_status(
vpn_service.last_status)
vpn_service.update_last_status()
LOG.debug("Report: Adding info for VPN service %s",
vpn_service.service_id)
return {u'id': vpn_service.service_id,
u'status': vpn_service.last_status,
u'updated_pending_status': pending_handled,
u'ipsec_site_connections': conn_report}
else:
return {}
@lockutils.synchronized('vpn-agent', 'neutron-')
def report_status(self, context):
"""Report status of all VPN services and IPSec connections to plugin.
This is called periodically by the agent, to push up changes in
status. Use a lock to serialize access to (and changing of)
running state.
"""
return self.report_status_internal(context)
def report_status_internal(self, context):
"""Generate report and send to plugin, if anything changed."""
service_report = []
LOG.debug("Report: Starting status report processing")
for vpn_service_id, vpn_service in self.service_state.items():
LOG.debug("Report: Collecting status for VPN service %s",
vpn_service_id)
report = self.build_report_for_service(vpn_service)
if report:
service_report.append(report)
if service_report:
LOG.info("Sending status report update to plugin")
self.agent_rpc.update_status(context, service_report)
LOG.debug("Report: Completed status report processing")
return service_report
@lockutils.synchronized('vpn-agent', 'neutron-')
def sync(self, context, routers):
"""Synchronize with plugin and report current status.
Mark all "known" services/connections as dirty, update them based on
information from the plugin, remove (sweep) any connections that are
not updated (dirty), and report updates, if any, back to plugin.
Called when update/delete a service or create/update/delete a
connection (vpnservice_updated message), or router change
(_process_routers).
Use lock to serialize access (and changes) to running state for VPN
service and IPsec connections.
"""
self.mark_existing_connections_as_dirty()
self.update_all_services_and_connections(context)
self.remove_unknown_connections(context)
self.report_status_internal(context)
def create_router(self, router):
"""Actions taken when router created."""
# Note: Since Cisco CSR is running out-of-band, nothing to do here
pass
def destroy_router(self, process_id):
"""Actions taken when router deleted."""
# Note: Since Cisco CSR is running out-of-band, nothing to do here
pass
class CiscoCsrVpnService(object):
"""Maintains state/status information for a service and its connections."""
def __init__(self, service_data, csr):
self.service_id = service_data['id']
self.conn_state = {}
self.csr = csr
self.is_admin_up = True
# TODO(pcm) FUTURE - handle sharing of policies
def create_connection(self, conn_data):
conn_id = conn_data['id']
self.conn_state[conn_id] = CiscoCsrIPSecConnection(conn_data, self.csr)
return self.conn_state[conn_id]
def get_connection(self, conn_id):
return self.conn_state.get(conn_id)
def conn_status(self, conn_id):
conn_state = self.get_connection(conn_id)
if conn_state:
return conn_state.last_status
def snapshot_conn_state(self, ipsec_conn):
"""Create/obtain connection state and save current status."""
conn_state = self.conn_state.setdefault(
ipsec_conn['id'], CiscoCsrIPSecConnection(ipsec_conn, self.csr))
conn_state.last_status = ipsec_conn['status']
conn_state.is_dirty = False
return conn_state
STATUS_MAP = {'ERROR': constants.ERROR,
'UP-ACTIVE': constants.ACTIVE,
'UP-IDLE': constants.ACTIVE,
'UP-NO-IKE': constants.ACTIVE,
'DOWN': constants.DOWN,
'DOWN-NEGOTIATING': constants.DOWN}
def get_ipsec_connections_status(self):
"""Obtain current status of all tunnels on a Cisco CSR.
Convert them to OpenStack status values.
"""
tunnels = self.csr.read_tunnel_statuses()
for tunnel in tunnels:
LOG.debug("CSR Reports %(tunnel)s status '%(status)s'",
{'tunnel': tunnel[0], 'status': tunnel[1]})
return dict(map(lambda x: (x[0], self.STATUS_MAP[x[1]]), tunnels))
def find_matching_connection(self, tunnel_id):
"""Find IPSec connection using Cisco CSR tunnel specified, if any."""
for connection in self.conn_state.values():
if connection.tunnel == tunnel_id:
return connection.conn_id
def no_connections_up(self):
return not any(c.last_status == 'ACTIVE'
for c in self.conn_state.values())
def update_last_status(self):
if not self.is_admin_up or self.no_connections_up():
self.last_status = constants.DOWN
else:
self.last_status = constants.ACTIVE
class CiscoCsrIPSecConnection(object):
"""State and actions for IPSec site-to-site connections."""
def __init__(self, conn_info, csr):
self.conn_info = conn_info
self.csr = csr
self.steps = []
self.forced_down = False
self.changed = False
@property
def conn_id(self):
return self.conn_info['id']
@property
def is_admin_up(self):
return self.conn_info['admin_state_up']
@is_admin_up.setter
def is_admin_up(self, is_up):
self.conn_info['admin_state_up'] = is_up
@property
def tunnel(self):
return self.conn_info['cisco']['site_conn_id']
def check_for_changes(self, curr_conn):
return not all([self.conn_info[attr] == curr_conn[attr]
for attr in ('mtu', 'psk', 'peer_address',
'peer_cidrs', 'ike_policy',
'ipsec_policy', 'cisco')])
def find_current_status_in(self, statuses):
if self.tunnel in statuses:
return statuses[self.tunnel]
else:
return constants.ERROR
def update_status_and_build_report(self, current_status):
if current_status != self.last_status:
pending_handled = plugin_utils.in_pending_status(self.last_status)
self.last_status = current_status
return {self.conn_id: {'status': current_status,
'updated_pending_status': pending_handled}}
else:
return {}
DIALECT_MAP = {'ike_policy': {'name': 'IKE Policy',
'v1': u'v1',
# auth_algorithm -> hash
'sha1': u'sha',
# encryption_algorithm -> encryption
'3des': u'3des',
'aes-128': u'aes',
'aes-192': u'aes192',
'aes-256': u'aes256',
# pfs -> dhGroup
'group2': 2,
'group5': 5,
'group14': 14},
'ipsec_policy': {'name': 'IPSec Policy',
# auth_algorithm -> esp-authentication
'sha1': u'esp-sha-hmac',
# transform_protocol -> ah
'esp': None,
'ah': u'ah-sha-hmac',
'ah-esp': u'ah-sha-hmac',
# encryption_algorithm -> esp-encryption
'3des': u'esp-3des',
'aes-128': u'esp-aes',
'aes-192': u'esp-192-aes',
'aes-256': u'esp-256-aes',
# pfs -> pfs
'group2': u'group2',
'group5': u'group5',
'group14': u'group14'}}
def translate_dialect(self, resource, attribute, info):
"""Map VPNaaS attributes values to CSR values for a resource."""
name = self.DIALECT_MAP[resource]['name']
if attribute not in info:
raise CsrDriverMismatchError(resource=name, attr=attribute)
value = info[attribute].lower()
if value in self.DIALECT_MAP[resource]:
return self.DIALECT_MAP[resource][value]
raise CsrUnknownMappingError(resource=name, attr=attribute,
value=value)
def create_psk_info(self, psk_id, conn_info):
"""Collect/create attributes needed for pre-shared key."""
return {u'keyring-name': psk_id,
u'pre-shared-key-list': [
{u'key': conn_info['psk'],
u'encrypted': False,
u'peer-address': conn_info['peer_address']}]}
def create_ike_policy_info(self, ike_policy_id, conn_info):
"""Collect/create/map attributes needed for IKE policy."""
for_ike = 'ike_policy'
policy_info = conn_info[for_ike]
version = self.translate_dialect(for_ike,
'ike_version',
policy_info)
encrypt_algorithm = self.translate_dialect(for_ike,
'encryption_algorithm',
policy_info)
auth_algorithm = self.translate_dialect(for_ike,
'auth_algorithm',
policy_info)
group = self.translate_dialect(for_ike,
'pfs',
policy_info)
lifetime = policy_info['lifetime_value']
return {u'version': version,
u'priority-id': ike_policy_id,
u'encryption': encrypt_algorithm,
u'hash': auth_algorithm,
u'dhGroup': group,
u'lifetime': lifetime}
def create_ipsec_policy_info(self, ipsec_policy_id, info):
"""Collect/create attributes needed for IPSec policy.
Note: OpenStack will provide a default encryption algorithm, if one is
not provided, so a authentication only configuration of (ah, sha1),
which maps to ah-sha-hmac transform protocol, cannot be selected.
As a result, we'll always configure the encryption algorithm, and
will select ah-sha-hmac for transform protocol.
"""
for_ipsec = 'ipsec_policy'
policy_info = info[for_ipsec]
transform_protocol = self.translate_dialect(for_ipsec,
'transform_protocol',
policy_info)
auth_algorithm = self.translate_dialect(for_ipsec,
'auth_algorithm',
policy_info)
encrypt_algorithm = self.translate_dialect(for_ipsec,
'encryption_algorithm',
policy_info)
group = self.translate_dialect(for_ipsec, 'pfs', policy_info)
lifetime = policy_info['lifetime_value']
settings = {u'policy-id': ipsec_policy_id,
u'protection-suite': {
u'esp-encryption': encrypt_algorithm,
u'esp-authentication': auth_algorithm},
u'lifetime-sec': lifetime,
u'pfs': group,
u'anti-replay-window-size': u'disable'}
if transform_protocol:
settings[u'protection-suite'][u'ah'] = transform_protocol
return settings
def create_site_connection_info(self, site_conn_id, ipsec_policy_id,
conn_info):
"""Collect/create attributes needed for the IPSec connection."""
mtu = conn_info['mtu']
return {
u'vpn-interface-name': site_conn_id,
u'ipsec-policy-id': ipsec_policy_id,
u'remote-device': {
u'tunnel-ip-address': conn_info['peer_address']
},
u'mtu': mtu
}
def create_routes_info(self, site_conn_id, conn_info):
"""Collect/create attributes for static routes."""
routes_info = []
for peer_cidr in conn_info.get('peer_cidrs', []):
route = {u'destination-network': peer_cidr,
u'outgoing-interface': site_conn_id}
route_id = csr_client.make_route_id(peer_cidr, site_conn_id)
routes_info.append((route_id, route))
return routes_info
def _check_create(self, resource, which):
"""Determine if REST create request was successful."""
if self.csr.status == requests.codes.CREATED:
LOG.debug("%(resource)s %(which)s is configured",
{'resource': resource, 'which': which})
return
LOG.error("Unable to create %(resource)s %(which)s: "
"%(status)d",
{'resource': resource, 'which': which,
'status': self.csr.status})
# ToDO(pcm): Set state to error
raise CsrResourceCreateFailure(resource=resource, which=which)
def do_create_action(self, action_suffix, info, resource_id, title):
"""Perform a single REST step for IPSec site connection create."""
create_action = 'create_%s' % action_suffix
try:
getattr(self.csr, create_action)(info)
except AttributeError:
LOG.exception("Internal error - '%s' is not defined",
create_action)
raise CsrResourceCreateFailure(resource=title,
which=resource_id)
self._check_create(title, resource_id)
self.steps.append(RollbackStep(action_suffix, resource_id, title))
def _verify_deleted(self, status, resource, which):
"""Determine if REST delete request was successful."""
if status in (requests.codes.NO_CONTENT, requests.codes.NOT_FOUND):
LOG.debug("%(resource)s configuration %(which)s was removed",
{'resource': resource, 'which': which})
else:
LOG.warning("Unable to delete %(resource)s %(which)s: "
"%(status)d", {'resource': resource,
'which': which,
'status': status})
def do_rollback(self):
"""Undo create steps that were completed successfully."""
for step in reversed(self.steps):
delete_action = 'delete_%s' % step.action
LOG.debug("Performing rollback action %(action)s for "
"resource %(resource)s", {'action': delete_action,
'resource': step.title})
try:
getattr(self.csr, delete_action)(step.resource_id)
except AttributeError:
LOG.exception("Internal error - '%s' is not defined",
delete_action)
raise CsrResourceCreateFailure(resource=step.title,
which=step.resource_id)
self._verify_deleted(self.csr.status, step.title, step.resource_id)
self.steps = []
def create_ipsec_site_connection(self, context, conn_info):
"""Creates an IPSec site-to-site connection on CSR.
Create the PSK, IKE policy, IPSec policy, connection, static route,
and (future) DPD.
"""
# Get all the IDs
conn_id = conn_info['id']
psk_id = conn_id
site_conn_id = conn_info['cisco']['site_conn_id']
ike_policy_id = conn_info['cisco']['ike_policy_id']
ipsec_policy_id = conn_info['cisco']['ipsec_policy_id']
LOG.debug('Creating IPSec connection %s', conn_id)
# Get all the attributes needed to create
try:
psk_info = self.create_psk_info(psk_id, conn_info)
ike_policy_info = self.create_ike_policy_info(ike_policy_id,
conn_info)
ipsec_policy_info = self.create_ipsec_policy_info(ipsec_policy_id,
conn_info)
connection_info = self.create_site_connection_info(site_conn_id,
ipsec_policy_id,
conn_info)
routes_info = self.create_routes_info(site_conn_id, conn_info)
except (CsrUnknownMappingError, CsrDriverMismatchError) as e:
LOG.exception(e)
return
try:
self.do_create_action('pre_shared_key', psk_info,
conn_id, 'Pre-Shared Key')
self.do_create_action('ike_policy', ike_policy_info,
ike_policy_id, 'IKE Policy')
self.do_create_action('ipsec_policy', ipsec_policy_info,
ipsec_policy_id, 'IPSec Policy')
self.do_create_action('ipsec_connection', connection_info,
site_conn_id, 'IPSec Connection')
# TODO(pcm): FUTURE - Do DPD for v1 and handle if >1 connection
# and different DPD settings
for route_id, route_info in routes_info:
self.do_create_action('static_route', route_info,
route_id, 'Static Route')
except CsrResourceCreateFailure:
self.do_rollback()
LOG.info("FAILED: Create of IPSec site-to-site connection %s",
conn_id)
else:
LOG.info("SUCCESS: Created IPSec site-to-site connection %s",
conn_id)
def delete_ipsec_site_connection(self, context, conn_id):
"""Delete the site-to-site IPSec connection.
This will be best effort and will continue, if there are any
failures.
"""
LOG.debug('Deleting IPSec connection %s', conn_id)
if not self.steps:
LOG.warning('Unable to find connection %s', conn_id)
else:
self.do_rollback()
LOG.info("SUCCESS: Deleted IPSec site-to-site connection %s",
conn_id)
def set_admin_state(self, is_up):
"""Change the admin state for the IPSec connection."""
self.csr.set_ipsec_connection_state(self.tunnel, admin_up=is_up)
if self.csr.status != requests.codes.NO_CONTENT:
state = "UP" if is_up else "DOWN"
LOG.error("Unable to change %(tunnel)s admin state to "
"%(state)s", {'tunnel': self.tunnel,
'state': state})
raise CsrAdminStateChangeFailure(tunnel=self.tunnel, state=state)

View File

@ -1,106 +0,0 @@
# Copyright (c) 2015 IBM, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_config import cfg
from neutron_vpnaas.services.vpn.device_drivers import ipsec
from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec
TEMPLATE_PATH = os.path.dirname(os.path.abspath(__file__))
cfg.CONF.set_default(name='default_config_area',
default=os.path.join(
TEMPLATE_PATH,
'/usr/share/strongswan/templates/'
'config/strongswan.d'),
group='strongswan')
class FedoraStrongSwanProcess(strongswan_ipsec.StrongSwanProcess):
binary = 'strongswan'
CONFIG_DIRS = [
'var/run',
'log',
'etc',
'etc/strongswan/ipsec.d/aacerts',
'etc/strongswan/ipsec.d/acerts',
'etc/strongswan/ipsec.d/cacerts',
'etc/strongswan/ipsec.d/certs',
'etc/strongswan/ipsec.d/crls',
'etc/strongswan/ipsec.d/ocspcerts',
'etc/strongswan/ipsec.d/policies',
'etc/strongswan/ipsec.d/private',
'etc/strongswan/ipsec.d/reqs',
'etc/pki/nssdb/'
]
STATUS_NOT_RUNNING_RE = ('Command:.*[ipsec|strongswan].*status.*'
'Exit code: [1|3] ')
def __init__(self, conf, process_id, vpnservice, namespace):
super(FedoraStrongSwanProcess, self).__init__(conf, process_id,
vpnservice, namespace)
def ensure_configs(self):
"""Generate config files which are needed for StrongSwan.
If there is no directory, this function will create
dirs.
"""
self.ensure_config_dir(self.vpnservice)
self.ensure_config_file(
'ipsec.conf',
cfg.CONF.strongswan.ipsec_config_template,
self.vpnservice)
self.ensure_config_file(
'strongswan.conf',
cfg.CONF.strongswan.strongswan_config_template,
self.vpnservice)
self.ensure_config_file(
'ipsec.secrets',
cfg.CONF.strongswan.ipsec_secret_template,
self.vpnservice,
0o600)
self.copy_and_overwrite(cfg.CONF.strongswan.default_config_area,
self._get_config_filename('strongswan.d'))
# Fedora uses /usr/share/strongswan/templates/config/ as strongswan
# template directory. But /usr/share/strongswan/templates/config/
# strongswan.d does not include charon. Those configuration files
# are in /usr/share/strongswan/templates/config/plugins directory.
charon_dir = os.path.join(
cfg.CONF.strongswan.default_config_area,
'charon')
if not os.path.exists(charon_dir):
plugins_dir = os.path.join(
cfg.CONF.strongswan.default_config_area, '../plugins')
self.copy_and_overwrite(
plugins_dir,
self._get_config_filename('strongswan.d/charon'))
def _get_config_filename(self, kind):
config_dir = '%s/strongswan' % self.etc_dir
return os.path.join(config_dir, kind)
class FedoraStrongSwanDriver(ipsec.IPsecDriver):
def create_process(self, process_id, vpnservice, namespace):
return FedoraStrongSwanProcess(
self.conf,
process_id,
vpnservice,
namespace)

View File

@ -1,308 +0,0 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import pprint
from networking_brocade.vyatta.common import exceptions as v_exc
from networking_brocade.vyatta.common import vrouter_config
from networking_brocade.vyatta.vpn import config as vyatta_vpn_config
from neutron.common import rpc as n_rpc
from neutron_lib import context as n_ctx
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_service import loopingcall
from oslo_service import periodic_task
from neutron_vpnaas._i18n import _
from neutron_vpnaas.services.vpn.common import topics
from neutron_vpnaas.services.vpn import device_drivers
LOG = logging.getLogger(__name__)
_KEY_CONNECTIONS = 'ipsec_site_connections'
_KEY_IKEPOLICY = 'ikepolicy'
_KEY_ESPPOLICY = 'ipsecpolicy'
class _DriverRPCEndpoint(object):
"""
VPN device driver RPC endpoint (server > agent)
history
1.0 Initial version
"""
target = messaging.Target(version='1.0')
def __init__(self, driver):
self.driver = driver
def vpnservice_updated(self, context, **kwargs):
self.driver.sync(context, [])
class NeutronServerAPI(object):
"""
VPN service driver RPC endpoint (agent > server)
"""
def __init__(self, topic):
target = messaging.Target(topic=topic, version='1.0')
self.client = n_rpc.get_client(target)
def get_vpn_services_on_host(self, context, host):
# make RPC call to neutron server
cctxt = self.client.prepare()
data = cctxt.call(context, 'get_vpn_services_on_host', host=host)
vpn_services = list()
for svc in data:
try:
for conn in svc[_KEY_CONNECTIONS]:
vyatta_vpn_config.validate_svc_connection(conn)
except v_exc.InvalidVPNServiceError:
LOG.error('Invalid or incomplete VPN service data: '
'id={id}'.format(id=svc.get('id')))
continue
vpn_services.append(svc)
# return transformed data to caller
return vpn_services
def update_status(self, context, status):
cctxt = self.client.prepare()
cctxt.cast(context, 'update_status', status=status)
class VyattaIPSecDriver(device_drivers.DeviceDriver):
"""
Vyatta VPN device driver
"""
rpc_endpoint_factory = _DriverRPCEndpoint
def __init__(self, vpn_service, host):
super(VyattaIPSecDriver, self).__init__(vpn_service, host)
self.vpn_service = vpn_service
self.host = host
# register RPC endpoint
conn = n_rpc.Connection()
node_topic = '%s.%s' % (topics.BROCADE_IPSEC_AGENT_TOPIC,
self.host)
endpoints = [self.rpc_endpoint_factory(self)]
conn.create_consumer(node_topic, endpoints, fanout=False)
conn.consume_in_threads()
# initialize agent to server RPC link
self.server_api = NeutronServerAPI(
topics.BROCADE_IPSEC_DRIVER_TOPIC)
# initialize VPN service cache (to keep service state)
self._svc_cache = list()
self._router_resources_cache = dict()
# setup periodic task. All periodic task require fully configured
# device driver. It will be called asynchronously, and soon, so it
# should be last, when all configuration is done.
self._periodic_tasks = periodic = _VyattaPeriodicTasks(self)
loop = loopingcall.DynamicLoopingCall(periodic)
loop.start(initial_delay=5)
def sync(self, context, processes):
"""
Called by _DriverRPCEndpoint instance.
"""
svc_update = self.server_api.get_vpn_services_on_host(
context, self.host)
to_del, to_change, to_add = self._svc_diff(
self._svc_cache, svc_update)
for svc in to_del:
resources = self.get_router_resources(svc['router_id'])
self._svc_delete(svc, resources)
for old, new in to_change:
resources = self.get_router_resources(old['router_id'])
self._svc_delete(old, resources)
self._svc_add(new, resources)
for svc in to_add:
resources = self.get_router_resources(svc['router_id'])
self._svc_add(svc, resources)
self._svc_cache = svc_update
def create_router(self, router):
router_id = router.router_id
vrouter = self.vpn_service.get_router_client(router_id)
config_raw = vrouter.get_vrouter_configuration()
resources = self.get_router_resources(router_id)
with resources.make_patch() as patch:
vrouter_svc = vyatta_vpn_config.parse_vrouter_config(
vrouter_config.parse_config(config_raw), patch)
for svc in vrouter_svc:
svc['router_id'] = router_id
self._svc_cache.extend(vrouter_svc)
def destroy_router(self, router_id):
to_del = list()
for idx, svc in enumerate(self._svc_cache):
if svc['router_id'] != router_id:
continue
resources = self.get_router_resources(svc['router_id'])
self._svc_delete(svc, resources)
to_del.insert(0, idx)
for idx in to_del:
del self._svc_cache[idx]
def _svc_add(self, svc, resources):
vrouter = self.vpn_service.get_router_client(svc['router_id'])
for conn in svc[_KEY_CONNECTIONS]:
with resources.make_patch() as patch:
iface = self._get_router_gw_iface(vrouter, svc['router_id'])
batch = vyatta_vpn_config.connect_setup_commands(
vrouter, iface, svc, conn, patch)
vrouter.exec_cmd_batch(batch)
def _svc_delete(self, svc, resources):
vrouter = self.vpn_service.get_router_client(svc['router_id'])
for conn in svc[_KEY_CONNECTIONS]:
with resources.make_patch() as patch:
iface = self._get_router_gw_iface(vrouter, svc['router_id'])
batch = vyatta_vpn_config.connect_remove_commands(
vrouter, iface, svc, conn, patch)
vrouter.exec_cmd_batch(batch)
def _svc_diff(self, svc_old, svc_new):
state_key = 'admin_state_up'
old_idnr = set(x['id'] for x in svc_old)
new_idnr = set(x['id'] for x in svc_new if x[state_key])
to_del = old_idnr - new_idnr
to_add = new_idnr - old_idnr
possible_change = old_idnr & new_idnr
svc_old = dict((x['id'], x) for x in svc_old)
svc_new = dict((x['id'], x) for x in svc_new)
to_del = [svc_old[x] for x in to_del]
to_add = [svc_new[x] for x in to_add]
to_change = list()
for idnr in possible_change:
old = svc_old[idnr]
new = svc_new[idnr]
assert old['router_id'] == new['router_id']
vrouter = self.vpn_service.get_router_client(old['router_id'])
gw_iface = self._get_router_gw_iface(vrouter, old['router_id'])
if vyatta_vpn_config.compare_vpn_services(
vrouter, gw_iface, old, new):
continue
to_change.append((old, new))
return to_del, to_change, to_add
def get_active_services(self):
return tuple(self._svc_cache)
def get_router_resources(self, router_id):
try:
res = self._router_resources_cache[router_id]
except KeyError:
res = vyatta_vpn_config.RouterResources(router_id)
self._router_resources_cache[router_id] = res
return res
def update_status(self, ctx, stat):
LOG.debug('STAT: %s', pprint.pformat(stat))
self.server_api.update_status(ctx, stat)
def _get_router_gw_iface(self, vrouter, router_id):
router = self.vpn_service.get_router(router_id)
try:
gw_interface = vrouter.get_ethernet_if_id(
router['gw_port']['mac_address'])
except KeyError:
raise v_exc.InvalidL3AgentStateError(description=_(
'Router id={0} have no external gateway.').format(
router['id']))
return gw_interface
class _VyattaPeriodicTasks(periodic_task.PeriodicTasks):
def __init__(self, driver):
super(_VyattaPeriodicTasks, self).__init__(cfg.CONF)
self.driver = driver
def __call__(self):
ctx_admin = n_ctx.get_admin_context()
return self.run_periodic_tasks(ctx_admin)
@periodic_task.periodic_task(spacing=5)
def grab_vpn_status(self, ctx):
LOG.debug('VPN device driver periodic task: grab_vpn_status.')
svc_by_vrouter = collections.defaultdict(list)
for svc in self.driver.get_active_services():
svc_by_vrouter[svc['router_id']].append(svc)
status = list()
for router_id, svc_set in svc_by_vrouter.items():
vrouter = self.driver.vpn_service.get_router_client(router_id)
resources = self.driver.get_router_resources(router_id)
try:
ipsec_sa = vrouter.get_vpn_ipsec_sa()
except v_exc.VRouterOperationError as e:
LOG.warning('Failed to fetch tunnel stats from router '
'{0}: {1}'.format(router_id, unicode(e)))
continue
conn_ok = vyatta_vpn_config.parse_vpn_connections(
ipsec_sa, resources)
for svc in svc_set:
svc_ok = True
conn_stat = dict()
for conn in svc[_KEY_CONNECTIONS]:
ok = conn['id'] in conn_ok
svc_ok = svc_ok and ok
conn_stat[conn['id']] = {
'status': 'ACTIVE' if ok else 'DOWN',
'updated_pending_status': True
}
status.append({
'id': svc['id'],
'status': 'ACTIVE' if svc_ok else 'DOWN',
'updated_pending_status': True,
'ipsec_site_connections': conn_stat
})
self.driver.update_status(ctx, status)

View File

@ -1,238 +0,0 @@
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.db import model_base
from neutron_lib import exceptions as nexception
from oslo_db import exception as db_exc
from oslo_log import log as logging
import sqlalchemy as sa
from sqlalchemy.orm import exc as sql_exc
from neutron_vpnaas._i18n import _
from neutron_vpnaas.db.vpn import vpn_models
LOG = logging.getLogger(__name__)
# Note: Artificially limit these to reduce mapping table size and performance
# Tunnel can be 0..7FFFFFFF, IKE policy can be 1..10000, IPSec policy can be
# 1..31 characters long.
MAX_CSR_TUNNELS = 10000
MAX_CSR_IKE_POLICIES = 2000
MAX_CSR_IPSEC_POLICIES = 2000
TUNNEL = 'Tunnel'
IKE_POLICY = 'IKE Policy'
IPSEC_POLICY = 'IPSec Policy'
MAPPING_LIMITS = {TUNNEL: (0, MAX_CSR_TUNNELS),
IKE_POLICY: (1, MAX_CSR_IKE_POLICIES),
IPSEC_POLICY: (1, MAX_CSR_IPSEC_POLICIES)}
class CsrInternalError(nexception.NeutronException):
message = _("Fatal - %(reason)s")
class IdentifierMap(model_base.BASEV2):
"""Maps OpenStack IDs to compatible numbers for Cisco CSR."""
__tablename__ = 'cisco_csr_identifier_map'
ipsec_site_conn_id = sa.Column(sa.String(36),
sa.ForeignKey('ipsec_site_connections.id',
ondelete="CASCADE"),
primary_key=True)
csr_tunnel_id = sa.Column(sa.Integer, nullable=False)
csr_ike_policy_id = sa.Column(sa.Integer, nullable=False)
csr_ipsec_policy_id = sa.Column(sa.Integer, nullable=False)
def get_next_available_id(session, table_field, id_type):
"""Find first unused id for the specified field in IdentifierMap table.
As entries are removed, find the first "hole" and return that as the
next available ID. To improve performance, artificially limit
the number of entries to a smaller range. Currently, these IDs are
globally unique. Could enhance in the future to be unique per router
(CSR).
"""
min_value = MAPPING_LIMITS[id_type][0]
max_value = MAPPING_LIMITS[id_type][1]
rows = session.query(table_field).order_by(table_field)
used_ids = set([row[0] for row in rows])
all_ids = set(range(min_value, max_value + min_value))
available_ids = all_ids - used_ids
if not available_ids:
msg = _("No available Cisco CSR %(type)s IDs from "
"%(min)d..%(max)d") % {'type': id_type,
'min': min_value,
'max': max_value}
LOG.error(msg)
raise IndexError(msg)
return available_ids.pop()
def get_next_available_tunnel_id(session):
"""Find first available tunnel ID from 0..MAX_CSR_TUNNELS-1."""
return get_next_available_id(session, IdentifierMap.csr_tunnel_id,
TUNNEL)
def get_next_available_ike_policy_id(session):
"""Find first available IKE Policy ID from 1..MAX_CSR_IKE_POLICIES."""
return get_next_available_id(session, IdentifierMap.csr_ike_policy_id,
IKE_POLICY)
def get_next_available_ipsec_policy_id(session):
"""Find first available IPSec Policy ID from 1..MAX_CSR_IKE_POLICIES."""
return get_next_available_id(session, IdentifierMap.csr_ipsec_policy_id,
IPSEC_POLICY)
def find_conn_with_policy(policy_field, policy_id, conn_id, session):
"""Return ID of another connection (if any) that uses same policy ID."""
qry = session.query(vpn_models.IPsecSiteConnection.id)
match = qry.filter_request(
policy_field == policy_id,
vpn_models.IPsecSiteConnection.id != conn_id).first()
if match:
return match[0]
def find_connection_using_ike_policy(ike_policy_id, conn_id, session):
"""Return ID of another connection that uses same IKE policy ID."""
return find_conn_with_policy(vpn_models.IPsecSiteConnection.ikepolicy_id,
ike_policy_id, conn_id, session)
def find_connection_using_ipsec_policy(ipsec_policy_id, conn_id, session):
"""Return ID of another connection that uses same IPSec policy ID."""
return find_conn_with_policy(vpn_models.IPsecSiteConnection.ipsecpolicy_id,
ipsec_policy_id, conn_id, session)
def lookup_policy(policy_type, policy_field, conn_id, session):
"""Obtain specified policy's mapping from other connection."""
try:
return session.query(policy_field).filter_by(
ipsec_site_conn_id=conn_id).one()[0]
except sql_exc.NoResultFound:
msg = _("Database inconsistency between IPSec connection and "
"Cisco CSR mapping table (%s)") % policy_type
raise CsrInternalError(reason=msg)
def lookup_ike_policy_id_for(conn_id, session):
"""Obtain existing Cisco CSR IKE policy ID from another connection."""
return lookup_policy(IKE_POLICY, IdentifierMap.csr_ike_policy_id,
conn_id, session)
def lookup_ipsec_policy_id_for(conn_id, session):
"""Obtain existing Cisco CSR IPSec policy ID from another connection."""
return lookup_policy(IPSEC_POLICY, IdentifierMap.csr_ipsec_policy_id,
conn_id, session)
def determine_csr_policy_id(policy_type, conn_policy_field, map_policy_field,
policy_id, conn_id, session):
"""Use existing or reserve a new policy ID for Cisco CSR use.
TODO(pcm) FUTURE: Once device driver adds support for IKE/IPSec policy
ID sharing, add call to find_conn_with_policy() to find used ID and
then call lookup_policy() to find the current mapping for that ID.
"""
csr_id = get_next_available_id(session, map_policy_field, policy_type)
LOG.debug("Reserved new CSR ID %(csr_id)d for %(policy)s "
"ID %(policy_id)s", {'csr_id': csr_id,
'policy': policy_type,
'policy_id': policy_id})
return csr_id
def determine_csr_ike_policy_id(ike_policy_id, conn_id, session):
"""Use existing, or reserve a new IKE policy ID for Cisco CSR."""
return determine_csr_policy_id(IKE_POLICY,
vpn_models.IPsecSiteConnection.ikepolicy_id,
IdentifierMap.csr_ike_policy_id,
ike_policy_id, conn_id, session)
def determine_csr_ipsec_policy_id(ipsec_policy_id, conn_id, session):
"""Use existing, or reserve a new IPSec policy ID for Cisco CSR."""
return determine_csr_policy_id(
IPSEC_POLICY,
vpn_models.IPsecSiteConnection.ipsecpolicy_id,
IdentifierMap.csr_ipsec_policy_id,
ipsec_policy_id, conn_id, session)
def get_tunnel_mapping_for(conn_id, session):
try:
entry = session.query(IdentifierMap).filter_by(
ipsec_site_conn_id=conn_id).one()
LOG.debug("Mappings for IPSec connection %(conn)s - "
"tunnel=%(tunnel)s ike_policy=%(csr_ike)d "
"ipsec_policy=%(csr_ipsec)d",
{'conn': conn_id, 'tunnel': entry.csr_tunnel_id,
'csr_ike': entry.csr_ike_policy_id,
'csr_ipsec': entry.csr_ipsec_policy_id})
return (entry.csr_tunnel_id, entry.csr_ike_policy_id,
entry.csr_ipsec_policy_id)
except sql_exc.NoResultFound:
msg = _("Existing entry for IPSec connection %s not found in Cisco "
"CSR mapping table") % conn_id
raise CsrInternalError(reason=msg)
def create_tunnel_mapping(context, conn_info):
"""Create Cisco CSR IDs, using mapping table and OpenStack UUIDs."""
conn_id = conn_info['id']
ike_policy_id = conn_info['ikepolicy_id']
ipsec_policy_id = conn_info['ipsecpolicy_id']
tenant_id = conn_info['tenant_id']
with context.session.begin():
csr_tunnel_id = get_next_available_tunnel_id(context.session)
csr_ike_id = determine_csr_ike_policy_id(ike_policy_id, conn_id,
context.session)
csr_ipsec_id = determine_csr_ipsec_policy_id(ipsec_policy_id, conn_id,
context.session)
map_entry = IdentifierMap(tenant_id=tenant_id,
ipsec_site_conn_id=conn_id,
csr_tunnel_id=csr_tunnel_id,
csr_ike_policy_id=csr_ike_id,
csr_ipsec_policy_id=csr_ipsec_id)
try:
context.session.add(map_entry)
# Force committing to database
context.session.flush()
except db_exc.DBDuplicateEntry:
msg = _("Attempt to create duplicate entry in Cisco CSR "
"mapping table for connection %s") % conn_id
raise CsrInternalError(reason=msg)
LOG.info("Mapped connection %(conn_id)s to Tunnel%(tunnel_id)d "
"using IKE policy ID %(ike_id)d and IPSec policy "
"ID %(ipsec_id)d",
{'conn_id': conn_id, 'tunnel_id': csr_tunnel_id,
'ike_id': csr_ike_id, 'ipsec_id': csr_ipsec_id})
def delete_tunnel_mapping(context, conn_info):
conn_id = conn_info['id']
with context.session.begin():
sess_qry = context.session.query(IdentifierMap)
sess_qry.filter_by(ipsec_site_conn_id=conn_id).delete()
LOG.info("Removed mapping for connection %s", conn_id)

View File

@ -1,222 +0,0 @@
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.common import rpc as n_rpc
from oslo_log import log as logging
import oslo_messaging
from neutron.db.models import servicetype
from neutron_vpnaas.db.vpn import vpn_models
from neutron_vpnaas.services.vpn.common import topics
from neutron_vpnaas.services.vpn import service_drivers
from neutron_vpnaas.services.vpn.service_drivers import base_ipsec
from neutron_vpnaas.services.vpn.service_drivers \
import cisco_csr_db as csr_id_map
from neutron_vpnaas.services.vpn.service_drivers import cisco_validator
LOG = logging.getLogger(__name__)
IPSEC = 'ipsec'
BASE_IPSEC_VERSION = '1.0'
LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400},
'IPSec Policy': {'min': 120, 'max': 2592000}}
MIN_CSR_MTU = 1500
MAX_CSR_MTU = 9192
VRF_SUFFIX_LEN = 6
T2_PORT_NAME = 't2_p:'
class CiscoCsrIPsecVpnDriverCallBack(object):
"""Handler for agent to plugin RPC messaging."""
# history
# 1.0 Initial version
target = oslo_messaging.Target(version=BASE_IPSEC_VERSION)
def __init__(self, driver):
super(CiscoCsrIPsecVpnDriverCallBack, self).__init__()
self.driver = driver
def create_rpc_dispatcher(self):
return n_rpc.PluginRpcDispatcher([self])
def get_vpn_services_using(self, context, router_id):
query = context.session.query(vpn_models.VPNService)
query = query.join(
servicetype.ProviderResourceAssociation,
servicetype.ProviderResourceAssociation.resource_id ==
vpn_models.VPNService.id)
query = query.join(vpn_models.IPsecSiteConnection)
query = query.join(vpn_models.IKEPolicy)
query = query.join(vpn_models.IPsecPolicy)
query = query.join(vpn_models.IPsecPeerCidr)
query = query.filter(vpn_models.VPNService.router_id == router_id)
query = query.filter(
servicetype.ProviderResourceAssociation.provider_name ==
self.driver.name)
return query.all()
def get_vpn_services_on_host(self, context, host=None):
"""Returns info on the VPN services on the host."""
routers = self.driver.l3_plugin.get_active_routers_for_host(context,
host)
host_vpn_services = []
for router in routers:
vpn_services = self.get_vpn_services_using(context, router['id'])
for vpn_service in vpn_services:
host_vpn_services.append(
self.driver.make_vpnservice_dict(context, vpn_service,
router))
return host_vpn_services
def update_status(self, context, status):
"""Update status of all vpnservices."""
plugin = self.driver.service_plugin
plugin.update_status_by_agent(context, status)
class CiscoCsrIPsecVpnAgentApi(service_drivers.BaseIPsecVpnAgentApi):
"""API and handler for Cisco IPSec plugin to agent RPC messaging."""
target = oslo_messaging.Target(version=BASE_IPSEC_VERSION)
def __init__(self, topic, default_version, driver):
super(CiscoCsrIPsecVpnAgentApi, self).__init__(
topic, default_version, driver)
def _agent_notification(self, context, method, router_id,
version=None, **kwargs):
"""Notify update for the agent.
Find the host for the router being notified and then
dispatches a notification for the VPN device driver.
"""
admin_context = context if context.is_admin else context.elevated()
if not version:
version = self.target.version
host = self.driver.l3_plugin.get_host_for_router(admin_context,
router_id)
LOG.debug('Notify agent at %(topic)s.%(host)s the message '
'%(method)s %(args)s for router %(router)s',
{'topic': self.topic,
'host': host,
'method': method,
'args': kwargs,
'router': router_id})
cctxt = self.client.prepare(server=host, version=version)
cctxt.cast(context, method, **kwargs)
class CiscoCsrIPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver):
"""Cisco CSR VPN Service Driver class for IPsec."""
def __init__(self, service_plugin):
super(CiscoCsrIPsecVPNDriver, self).__init__(
service_plugin,
cisco_validator.CiscoCsrVpnValidator(self))
def create_rpc_conn(self):
self.endpoints = [CiscoCsrIPsecVpnDriverCallBack(self)]
self.conn = n_rpc.Connection()
self.conn.create_consumer(
topics.CISCO_IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False)
self.conn.consume_in_threads()
self.agent_rpc = CiscoCsrIPsecVpnAgentApi(
topics.CISCO_IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self)
def create_ipsec_site_connection(self, context, ipsec_site_connection):
vpnservice = self.service_plugin._get_vpnservice(
context, ipsec_site_connection['vpnservice_id'])
csr_id_map.create_tunnel_mapping(context, ipsec_site_connection)
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
reason='ipsec-conn-create')
def update_ipsec_site_connection(
self, context, old_ipsec_site_connection, ipsec_site_connection):
vpnservice = self.service_plugin._get_vpnservice(
context, ipsec_site_connection['vpnservice_id'])
self.agent_rpc.vpnservice_updated(
context, vpnservice['router_id'],
reason='ipsec-conn-update')
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
vpnservice = self.service_plugin._get_vpnservice(
context, ipsec_site_connection['vpnservice_id'])
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
reason='ipsec-conn-delete')
def update_vpnservice(self, context, old_vpnservice, vpnservice):
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
reason='vpn-service-update')
def delete_vpnservice(self, context, vpnservice):
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'],
reason='vpn-service-delete')
def get_cisco_connection_mappings(self, conn_id, context):
"""Obtain persisted mappings for IDs related to connection."""
tunnel_id, ike_id, ipsec_id = csr_id_map.get_tunnel_mapping_for(
conn_id, context.session)
return {'site_conn_id': u'Tunnel%d' % tunnel_id,
'ike_policy_id': u'%d' % ike_id,
'ipsec_policy_id': u'%s' % ipsec_id}
def _create_interface(self, interface_info):
hosting_info = interface_info['hosting_info']
vlan = hosting_info['segmentation_id']
# Port name "currently" is t{1,2}_p:1, as only one router per CSR,
# but will keep a semi-generic algorithm
port_name = hosting_info['hosting_port_name']
name, sep, num = port_name.partition(':')
offset = 1 if name in T2_PORT_NAME else 0
if_num = int(num) * 2 + offset
return 'GigabitEthernet%d.%d' % (if_num, vlan)
def _get_router_info(self, router_info):
hosting_device = router_info['hosting_device']
return {'rest_mgmt_ip': hosting_device['management_ip_address'],
'username': hosting_device['credentials']['username'],
'password': hosting_device['credentials']['password'],
'inner_if_name': self._create_interface(
router_info['_interfaces'][0]),
'outer_if_name': self._create_interface(
router_info['gw_port']),
'vrf': 'nrouter-' + router_info['id'][:VRF_SUFFIX_LEN],
'timeout': 30} # Hard-coded for now
def make_vpnservice_dict(self, context, vpnservice, router_info):
"""Collect all service info, including Cisco info for IPSec conn."""
vpnservice_dict = dict(vpnservice)
# Populate tenant_id for RPC compat
vpnservice_dict['tenant_id'] = vpnservice_dict['project_id']
vpnservice_dict['ipsec_conns'] = []
vpnservice_dict['subnet'] = dict(vpnservice.subnet)
vpnservice_dict['router_info'] = self._get_router_info(router_info)
for ipsec_conn in vpnservice.ipsec_site_connections:
ipsec_conn_dict = dict(ipsec_conn)
ipsec_conn_dict['ike_policy'] = dict(ipsec_conn.ikepolicy)
ipsec_conn_dict['ipsec_policy'] = dict(ipsec_conn.ipsecpolicy)
ipsec_conn_dict['peer_cidrs'] = [
peer_cidr.cidr for peer_cidr in ipsec_conn.peer_cidrs]
ipsec_conn_dict['cisco'] = self.get_cisco_connection_mappings(
ipsec_conn['id'], context)
vpnservice_dict['ipsec_conns'].append(ipsec_conn_dict)
return vpnservice_dict

View File

@ -1,147 +0,0 @@
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import netaddr
from netaddr import core as net_exc
from neutron_lib import exceptions as nexception
from oslo_log import log as logging
from neutron_vpnaas._i18n import _
from neutron_vpnaas.services.vpn.service_drivers import driver_validator
LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400},
'IPSec Policy': {'min': 120, 'max': 2592000}}
MIN_CSR_MTU = 1500
MAX_CSR_MTU = 9192
LOG = logging.getLogger(__name__)
class CsrValidationFailure(nexception.BadRequest):
message = _("Cisco CSR does not support %(resource)s attribute %(key)s "
"with value '%(value)s'")
class CiscoCsrVpnValidator(driver_validator.VpnDriverValidator):
"""Driver-specific validator methods for the Cisco CSR."""
def validate_lifetime(self, for_policy, policy_info):
"""Ensure lifetime in secs and value is supported, based on policy."""
units = policy_info['lifetime']['units']
if units != 'seconds':
raise CsrValidationFailure(resource=for_policy,
key='lifetime:units',
value=units)
value = policy_info['lifetime']['value']
if (value < LIFETIME_LIMITS[for_policy]['min'] or
value > LIFETIME_LIMITS[for_policy]['max']):
raise CsrValidationFailure(resource=for_policy,
key='lifetime:value',
value=value)
def validate_ike_version(self, policy_info):
"""Ensure IKE policy is v1 for current REST API."""
version = policy_info['ike_version']
if version != 'v1':
raise CsrValidationFailure(resource='IKE Policy',
key='ike_version',
value=version)
def validate_mtu(self, conn_info):
"""Ensure the MTU value is supported."""
mtu = conn_info['mtu']
if mtu < MIN_CSR_MTU or mtu > MAX_CSR_MTU:
raise CsrValidationFailure(resource='IPSec Connection',
key='mtu',
value=mtu)
def validate_public_ip_present(self, router):
"""Ensure there is one gateway IP specified for the router used."""
gw_port = router.gw_port
if not gw_port or len(gw_port.fixed_ips) != 1:
raise CsrValidationFailure(resource='IPSec Connection',
key='router:gw_port:ip_address',
value='missing')
def validate_peer_id(self, ipsec_conn):
"""Ensure that an IP address is specified for peer ID."""
# TODO(pcm) Should we check peer_address too?
peer_id = ipsec_conn['peer_id']
try:
netaddr.IPAddress(peer_id)
except net_exc.AddrFormatError:
raise CsrValidationFailure(resource='IPSec Connection',
key='peer_id', value=peer_id)
def validate_ipsec_encap_mode(self, ipsec_policy):
"""Ensure IPSec policy encap mode is tunnel for current REST API."""
mode = ipsec_policy['encapsulation_mode']
if mode != 'tunnel':
raise CsrValidationFailure(resource='IPsec Policy',
key='encapsulation_mode',
value=mode)
def validate_ike_auth_algorithm(self, ike_policy):
"""Ensure IKE Policy auth algorithm is supported."""
auth_algorithm = ike_policy.get('auth_algorithm')
if auth_algorithm in ["sha384", "sha512"]:
raise CsrValidationFailure(resource='IKE Policy',
key='auth_algorithm',
value=auth_algorithm)
def validate_ipsec_auth_algorithm(self, ipsec_policy):
"""Ensure IPSec Policy auth algorithm is supported."""
auth_algorithm = ipsec_policy.get('auth_algorithm')
if auth_algorithm in ["sha384", "sha512"]:
raise CsrValidationFailure(resource='IPsec Policy',
key='auth_algorithm',
value=auth_algorithm)
def validate_ipsec_site_connection(self, context, ipsec_sitecon):
"""Validate IPSec site connection for Cisco CSR.
Do additional checks that relate to the Cisco CSR.
"""
service_plugin = self.driver.service_plugin
if 'ikepolicy_id' in ipsec_sitecon:
ike_policy = service_plugin.get_ikepolicy(
context, ipsec_sitecon['ikepolicy_id'])
self.validate_lifetime('IKE Policy', ike_policy)
self.validate_ike_version(ike_policy)
self.validate_ike_auth_algorithm(ike_policy)
if 'ipsecpolicy_id' in ipsec_sitecon:
ipsec_policy = service_plugin.get_ipsecpolicy(
context, ipsec_sitecon['ipsecpolicy_id'])
self.validate_lifetime('IPSec Policy', ipsec_policy)
self.validate_ipsec_auth_algorithm(ipsec_policy)
self.validate_ipsec_encap_mode(ipsec_policy)
if 'vpnservice_id' in ipsec_sitecon:
vpn_service = service_plugin.get_vpnservice(
context, ipsec_sitecon['vpnservice_id'])
router = self.l3_plugin._get_router(
context, vpn_service['router_id'])
self.validate_public_ip_present(router)
if 'mtu' in ipsec_sitecon:
self.validate_mtu(ipsec_sitecon)
if 'peer_id' in ipsec_sitecon:
self.validate_peer_id(ipsec_sitecon)
LOG.debug("IPSec connection validated for Cisco CSR")

View File

@ -1,38 +0,0 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from neutron.common import rpc as n_rpc
from neutron_vpnaas.services.vpn.common import topics
from neutron_vpnaas.services.vpn.service_drivers import base_ipsec
IPSEC = 'ipsec'
BASE_IPSEC_VERSION = '1.0'
class VyattaIPsecDriver(base_ipsec.BaseIPsecVPNDriver):
def __init__(self, service_plugin):
super(VyattaIPsecDriver, self).__init__(service_plugin)
def create_rpc_conn(self):
self.endpoints = [base_ipsec.IPsecVpnDriverCallBack(self)]
self.conn = n_rpc.Connection()
self.conn.create_consumer(
topics.BROCADE_IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False)
self.conn.consume_in_threads()
self.agent_rpc = base_ipsec.IPsecVpnAgentApi(
topics.BROCADE_IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self)

View File

@ -1,44 +0,0 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from networking_brocade.vyatta.common import l3_agent as vyatta_l3
from neutron.agent import l3_agent as entry
from oslo_config import cfg
from neutron_vpnaas._i18n import _
from neutron_vpnaas.services.vpn import vyatta_vpn_service
vpn_agent_opts = [
cfg.MultiStrOpt(
'vpn_device_driver',
default=['neutron_vpnaas.services.vpn.device_drivers.'
'vyatta_ipsec.VyattaIPSecDriver'],
help=_("The vpn device drivers Neutron will use")),
]
cfg.CONF.register_opts(vpn_agent_opts, 'vpnagent')
class VyattaVPNAgent(vyatta_l3.L3AgentMiddleware):
def __init__(self, host, conf=None):
super(VyattaVPNAgent, self).__init__(host, conf)
self.service = vyatta_vpn_service.VyattaVPNService(self)
self.device_drivers = self.service.load_device_drivers(host)
def main():
entry.main(
manager='neutron_vpnaas.services.vpn.vyatta_agent.VyattaVPNAgent')

View File

@ -1,43 +0,0 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron_vpnaas.services.vpn import vpn_service
class VyattaVPNService(vpn_service.VPNService):
"""Vyatta VPN Service handler."""
def __init__(self, l3_agent):
"""Creates a Vyatta VPN Service instance.
NOTE: Directly accessing l3_agent here is an interim solution
until we move to have a router object given down to device drivers
to access router related methods
"""
super(VyattaVPNService, self).__init__(l3_agent)
self.l3_agent = l3_agent
def get_router_client(self, router_id):
"""
Get Router RESTapi client
"""
return self.l3_agent.get_router_client(router_id)
def get_router(self, router_id):
"""
Get Router Object
"""
return self.l3_agent.get_router(router_id)

View File

@ -29,7 +29,6 @@ from oslo_config import cfg
from oslo_utils import uuidutils
from neutron_vpnaas.extensions import vpnaas
from neutron_vpnaas.services.vpn.device_drivers import fedora_strongswan_ipsec
from neutron_vpnaas.services.vpn.device_drivers import ipsec as openswan_ipsec
from neutron_vpnaas.services.vpn.device_drivers import libreswan_ipsec
from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec
@ -1497,20 +1496,3 @@ class IPsecStrongswanDeviceDriverDVR(IPSecDeviceDVR):
ipsec_process=strongswan_ipsec.StrongSwanProcess):
super(IPsecStrongswanDeviceDriverDVR, self).setUp(driver,
ipsec_process)
class IPsecFedoraStrongswanDeviceDriverLegacy(
IPsecStrongswanDeviceDriverLegacy):
def setUp(self, driver=fedora_strongswan_ipsec.FedoraStrongSwanDriver,
ipsec_process=fedora_strongswan_ipsec.FedoraStrongSwanProcess):
super(IPsecFedoraStrongswanDeviceDriverLegacy,
self).setUp(driver, ipsec_process)
class IPsecFedoraStrongswanDeviceDriverDVR(IPSecDeviceDVR):
def setUp(self, driver=fedora_strongswan_ipsec.FedoraStrongSwanDriver,
ipsec_process=fedora_strongswan_ipsec.FedoraStrongSwanProcess):
super(IPsecFedoraStrongswanDeviceDriverDVR, self).setUp(driver,
ipsec_process)

View File

@ -1,218 +0,0 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import sys
import mock
from neutron.agent.l3 import legacy_router
from oslo_utils import uuidutils
from neutron_vpnaas.tests import base
with mock.patch.dict(sys.modules, {
'networking_brocade': mock.Mock(),
'networking_brocade.vyatta': mock.Mock(),
'networking_brocade.vyatta.common': mock.Mock(),
'networking_brocade.vyatta.vrouter': mock.Mock(),
'networking_brocade.vyatta.vpn': mock.Mock(),
}):
from networking_brocade.vyatta.common import vrouter_config
from networking_brocade.vyatta.vpn import config as vyatta_vpn_config
from neutron_vpnaas.services.vpn.device_drivers import vyatta_ipsec
_uuid = uuidutils.generate_uuid
FAKE_HOST = 'fake_host'
class TestNeutronServerAPI(base.BaseTestCase):
def setUp(self):
super(TestNeutronServerAPI, self).setUp()
get_client_mock = mock.patch(
'neutron.common.rpc.get_client').start()
self.client = get_client_mock.return_value
self.api = vyatta_ipsec.NeutronServerAPI('fake-topic')
def test_get_vpn_services_on_host(self):
fake_context = mock.Mock()
svc_connections = [
self._make_svc_connection(),
self._make_svc_connection()
]
vpn_services_on_host = [{
vyatta_ipsec._KEY_CONNECTIONS: svc_connections
}]
cctxt = self.client.prepare.return_value
cctxt.call.return_value = vpn_services_on_host
vpn_services = self.api.get_vpn_services_on_host(
fake_context, FAKE_HOST)
cctxt.call.assert_called_with(
fake_context, 'get_vpn_services_on_host', host=FAKE_HOST)
validate_func = vyatta_vpn_config.validate_svc_connection
for connection in svc_connections:
validate_func.assert_any_call(connection)
self.assertEqual(len(vpn_services_on_host), len(vpn_services))
def test_update_status(self):
context = mock.Mock()
fake_status = 'fake-status'
cctxt = self.client.prepare.return_value
self.api.update_status(context, 'fake-status')
cctxt.cast.assert_called_once_with(
context, 'update_status', status=fake_status)
@staticmethod
def _make_svc_connection():
return {
vyatta_ipsec._KEY_IKEPOLICY: {
'encryption_algorithm': 'aes-256',
'lifetime_units': 'seconds',
},
vyatta_ipsec._KEY_ESPPOLICY: {
'encryption_algorithm': 'aes-256',
'lifetime_units': 'seconds',
'transform_protocol': 'esp',
'pfs': 'dh-group2',
'encapsulation_mode': 'tunnel'
},
'dpd_action': 'hold',
}
class TestVyattaDeviceDriver(base.BaseTestCase):
def setUp(self):
super(TestVyattaDeviceDriver, self).setUp()
mock.patch('oslo_service.loopingcall.DynamicLoopingCall').start()
self.server_api = mock.patch(
'neutron_vpnaas.services.vpn.device_drivers'
'.vyatta_ipsec.NeutronServerAPI').start()
self.agent = mock.Mock()
self.driver = vyatta_ipsec.VyattaIPSecDriver(self.agent, FAKE_HOST)
def test_create_router(self):
router_id = _uuid()
router = mock.Mock(legacy_router.LegacyRouter)
router.router_id = router_id
vrouter_svc_list = [self._make_vrouter_svc()]
parse_vrouter_config = mock.Mock()
parse_vrouter_config.return_value = vrouter_svc_list
with mock.patch.object(vrouter_config, 'parse_config'), \
mock.patch.object(vyatta_vpn_config, 'parse_vrouter_config',
parse_vrouter_config), \
mock.patch.object(self.driver, 'get_router_resources',
mock.MagicMock()):
self.driver.create_router(router)
svc_cache = self.driver._svc_cache
self.assertEqual(1, len(svc_cache))
self.assertEqual(router_id, svc_cache[0]['router_id'])
ipsec_connections = svc_cache[0]['ipsec_site_connections']
self.assertEqual(
'172.24.4.234',
ipsec_connections[0]['peer_address'])
def test_destroy_router(self):
router_id = _uuid()
get_router_resources = mock.Mock()
vrouter_svc = self._make_vrouter_svc()
vrouter_svc['router_id'] = router_id
svc_cache = [vrouter_svc]
svc_delete = mock.Mock()
with mock.patch.object(self.driver, 'get_router_resources',
get_router_resources), \
mock.patch.object(self.driver, '_svc_delete', svc_delete), \
mock.patch.object(self.driver, '_svc_cache', svc_cache):
self.driver.destroy_router(router_id)
self.assertNotIn(vrouter_svc, svc_cache)
svc_delete.assert_called_with(vrouter_svc, mock.ANY)
def test_sync(self):
router_id = _uuid()
self.agent.router_info = {
router_id: mock.Mock()
}
to_del = [self._make_svc()]
to_change = [
(self._make_svc(), self._make_svc()),
]
to_add = [self._make_svc()]
svc_diff = mock.Mock()
svc_diff.return_value = (
to_del,
to_change,
to_add,
)
svc_delete = mock.Mock()
svc_add = mock.Mock()
with mock.patch.object(self.driver, '_svc_diff', svc_diff), \
mock.patch.object(self.driver, '_svc_delete', svc_delete), \
mock.patch.object(self.driver, '_svc_add', svc_add):
self.driver.sync(mock.Mock(), None)
for svc in to_add:
svc_add.assert_any_call(svc, mock.ANY)
for svc in to_del:
svc_delete.assert_any_call(svc, mock.ANY)
for old, new in to_change:
svc_delete.assert_any_call(old, mock.ANY)
svc_add.assert_any_call(new, mock.ANY)
@staticmethod
def _make_vrouter_svc():
return {
'id': _uuid(),
vyatta_ipsec._KEY_CONNECTIONS: [{
'peer_address': '172.24.4.234',
}]
}
@staticmethod
def _make_svc():
return {
'router_id': _uuid()
}

View File

@ -1,508 +0,0 @@
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from six import moves
from neutron.db import servicetype_db as st_db
from neutron.tests.unit import testlib_api
from neutron_lib import context as n_ctx
from neutron_lib.plugins import constants
from neutron_lib.plugins import directory
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron_vpnaas.db.vpn import vpn_validator
from neutron_vpnaas.services.vpn import plugin as vpn_plugin
from neutron_vpnaas.services.vpn.service_drivers import cisco_csr_db as csr_db
from neutron_vpnaas.services.vpn.service_drivers \
import cisco_ipsec as ipsec_driver
from neutron_vpnaas.services.vpn.service_drivers \
import cisco_validator as validator
from neutron_vpnaas.tests import base
_uuid = uuidutils.generate_uuid
FAKE_VPN_CONN_ID = _uuid()
FAKE_SERVICE_ID = _uuid()
FAKE_VPN_CONNECTION = {
'vpnservice_id': FAKE_SERVICE_ID,
'id': FAKE_VPN_CONN_ID,
'ikepolicy_id': _uuid(),
'ipsecpolicy_id': _uuid(),
'tenant_id': _uuid()
}
FAKE_ROUTER_ID = _uuid()
FAKE_VPN_SERVICE = {
'router_id': FAKE_ROUTER_ID
}
FAKE_HOST = 'fake_host'
IPV4 = 4
CISCO_IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.'
'cisco_ipsec.CiscoCsrIPsecVPNDriver')
class TestCiscoValidatorSelection(base.BaseTestCase):
def setUp(self):
super(TestCiscoValidatorSelection, self).setUp()
# TODO(armax): remove this if branch as soon as the ServiceTypeManager
# API for adding provider configurations becomes available
if not hasattr(st_db.ServiceTypeManager, 'add_provider_configuration'):
vpnaas_provider = (constants.VPN +
':vpnaas:' +
CISCO_IPSEC_SERVICE_DRIVER + ':default')
cfg.CONF.set_override(
'service_provider', [vpnaas_provider], 'service_providers')
else:
vpnaas_provider = [{
'service_type': constants.VPN,
'name': 'vpnaas',
'driver': CISCO_IPSEC_SERVICE_DRIVER,
'default': True
}]
# override the default service provider
self.service_providers = (
mock.patch.object(st_db.ServiceTypeManager,
'get_service_providers').start())
self.service_providers.return_value = vpnaas_provider
mock.patch('neutron.common.rpc.Connection').start()
stm = st_db.ServiceTypeManager()
stm.get_provider_names_by_resource_ids = mock.Mock(
return_value={})
mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance',
return_value=stm).start()
mock.patch('neutron_vpnaas.db.vpn.vpn_db.VPNPluginDb.get_vpnservices',
return_value=[]).start()
self.vpn_plugin = vpn_plugin.VPNDriverPlugin()
def test_reference_driver_used(self):
default_provider = self.vpn_plugin.default_provider
default_driver = self.vpn_plugin.drivers[default_provider]
self.assertIsInstance(default_driver.validator,
validator.CiscoCsrVpnValidator)
class TestCiscoIPsecDriverValidation(base.BaseTestCase):
def setUp(self):
super(TestCiscoIPsecDriverValidation, self).setUp()
self.context = n_ctx.Context('some_user', 'some_tenant')
self.vpn_service = {'router_id': '123'}
self.router = mock.Mock()
driver = mock.Mock()
self.service_plugin = mock.Mock()
driver.service_plugin = self.service_plugin
self.validator = validator.CiscoCsrVpnValidator(driver)
def test_ike_version_unsupported(self):
"""Failure test that Cisco CSR REST API does not support IKE v2."""
policy_info = {'ike_version': 'v2',
'lifetime': {'units': 'seconds', 'value': 60}}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_ike_version,
policy_info)
def test_ike_lifetime_not_in_seconds(self):
"""Failure test of unsupported lifetime units for IKE policy."""
policy_info = {'lifetime': {'units': 'kilobytes', 'value': 1000}}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_lifetime,
"IKE Policy", policy_info)
def test_ipsec_lifetime_not_in_seconds(self):
"""Failure test of unsupported lifetime units for IPSec policy."""
policy_info = {'lifetime': {'units': 'kilobytes', 'value': 1000}}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_lifetime,
"IPSec Policy", policy_info)
def test_ike_lifetime_seconds_values_at_limits(self):
"""Test valid lifetime values for IKE policy."""
policy_info = {'lifetime': {'units': 'seconds', 'value': 60}}
self.validator.validate_lifetime('IKE Policy', policy_info)
policy_info = {'lifetime': {'units': 'seconds', 'value': 86400}}
self.validator.validate_lifetime('IKE Policy', policy_info)
def test_ipsec_lifetime_seconds_values_at_limits(self):
"""Test valid lifetime values for IPSec policy."""
policy_info = {'lifetime': {'units': 'seconds', 'value': 120}}
self.validator.validate_lifetime('IPSec Policy', policy_info)
policy_info = {'lifetime': {'units': 'seconds', 'value': 2592000}}
self.validator.validate_lifetime('IPSec Policy', policy_info)
def test_ike_lifetime_values_invalid(self):
"""Failure test of unsupported lifetime values for IKE policy."""
which = "IKE Policy"
policy_info = {'lifetime': {'units': 'seconds', 'value': 59}}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_lifetime,
which, policy_info)
policy_info = {'lifetime': {'units': 'seconds', 'value': 86401}}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_lifetime,
which, policy_info)
def test_ipsec_lifetime_values_invalid(self):
"""Failure test of unsupported lifetime values for IPSec policy."""
which = "IPSec Policy"
policy_info = {'lifetime': {'units': 'seconds', 'value': 119}}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_lifetime,
which, policy_info)
policy_info = {'lifetime': {'units': 'seconds', 'value': 2592001}}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_lifetime,
which, policy_info)
def test_ipsec_connection_with_mtu_at_limits(self):
"""Test IPSec site-to-site connection with MTU at limits."""
conn_info = {'mtu': 1500}
self.validator.validate_mtu(conn_info)
conn_info = {'mtu': 9192}
self.validator.validate_mtu(conn_info)
def test_ipsec_connection_with_invalid_mtu(self):
"""Failure test of IPSec site connection with unsupported MTUs."""
conn_info = {'mtu': 1499}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_mtu, conn_info)
conn_info = {'mtu': 9193}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_mtu, conn_info)
def simulate_gw_ip_available(self):
"""Helper function indicating that tunnel has a gateway IP."""
def have_one():
return 1
self.router.gw_port.fixed_ips.__len__ = have_one
ip_addr_mock = mock.Mock()
self.router.gw_port.fixed_ips = [ip_addr_mock]
def test_have_public_ip_for_router(self):
"""Ensure that router for IPSec connection has gateway IP."""
self.simulate_gw_ip_available()
try:
self.validator.validate_public_ip_present(self.router)
except Exception:
self.fail("Unexpected exception on validation")
def test_router_with_missing_gateway_ip(self):
"""Failure test of IPSec connection with missing gateway IP."""
self.simulate_gw_ip_available()
self.router.gw_port = None
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_public_ip_present,
self.router)
def test_peer_id_is_an_ip_address(self):
"""Ensure peer ID is an IP address for IPsec connection create."""
ipsec_sitecon = {'peer_id': '10.10.10.10'}
self.validator.validate_peer_id(ipsec_sitecon)
def test_peer_id_is_not_ip_address(self):
"""Failure test of peer_id that is not an IP address."""
ipsec_sitecon = {'peer_id': 'some-site.com'}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_peer_id, ipsec_sitecon)
def test_validation_for_create_ipsec_connection(self):
"""Ensure all validation passes for IPSec site connection create."""
self.simulate_gw_ip_available()
self.service_plugin.get_ikepolicy = mock.Mock(
return_value={'ike_version': 'v1',
'lifetime': {'units': 'seconds', 'value': 60}})
self.service_plugin.get_ipsecpolicy = mock.Mock(
return_value={'lifetime': {'units': 'seconds', 'value': 120},
'encapsulation_mode': 'tunnel'})
self.service_plugin.get_vpnservice = mock.Mock(
return_value=self.vpn_service)
self.validator.driver.l3_plugin._get_router = mock.Mock(
return_value=self.router)
# Provide the minimum needed items to validate
ipsec_sitecon = {'id': '1',
'vpnservice_id': FAKE_SERVICE_ID,
'ikepolicy_id': '123',
'ipsecpolicy_id': '2',
'mtu': 1500,
'peer_id': '10.10.10.10'}
# Using defaults for DPD info
expected = {'dpd_action': 'hold',
'dpd_interval': 30,
'dpd_timeout': 120}
expected.update(ipsec_sitecon)
plugin_validator = vpn_validator.VpnReferenceValidator()
plugin_validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon)
self.validator.validate_ipsec_site_connection(self.context,
ipsec_sitecon)
self.assertEqual(expected, ipsec_sitecon)
def test_ipsec_encap_mode_unsupported(self):
"""Failure test for unsupported encap mode for IPsec policy."""
policy_info = {'encapsulation_mode': 'transport'}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_ipsec_encap_mode,
policy_info)
def test_ipsec_auth_algorithm_unsupported(self):
"""Failure test for unsupported auth algorithm for IPSec Policy."""
auth_algorithm = {'auth_algorithm': 'sha384'}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_ipsec_auth_algorithm,
auth_algorithm)
auth_algorithm = {'auth_algorithm': 'sha512'}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_ipsec_auth_algorithm,
auth_algorithm)
def test_ike_auth_algorithm_unsupported(self):
"""Failure test for unsupported auth algorithm for IKE Policy."""
auth_algorithm = {'auth_algorithm': 'sha384'}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_ike_auth_algorithm,
auth_algorithm)
auth_algorithm = {'auth_algorithm': 'sha512'}
self.assertRaises(validator.CsrValidationFailure,
self.validator.validate_ike_auth_algorithm,
auth_algorithm)
class TestCiscoIPsecDriverMapping(base.BaseTestCase):
def setUp(self):
super(TestCiscoIPsecDriverMapping, self).setUp()
self.context = mock.patch.object(n_ctx, 'Context').start()
self.session = self.context.session
self.query_mock = self.session.query.return_value.order_by
def test_identifying_first_mapping_id(self):
"""Make sure first available ID is obtained for each ID type."""
# Simulate mapping table is empty - get first one
self.query_mock.return_value = []
next_id = csr_db.get_next_available_tunnel_id(self.session)
self.assertEqual(0, next_id)
next_id = csr_db.get_next_available_ike_policy_id(self.session)
self.assertEqual(1, next_id)
next_id = csr_db.get_next_available_ipsec_policy_id(self.session)
self.assertEqual(1, next_id)
def test_last_mapping_id_available(self):
"""Make sure can get the last ID for each of the table types."""
# Simulate query indicates table is full
self.query_mock.return_value = [
(x, ) for x in moves.range(csr_db.MAX_CSR_TUNNELS - 1)]
next_id = csr_db.get_next_available_tunnel_id(self.session)
self.assertEqual(csr_db.MAX_CSR_TUNNELS - 1, next_id)
self.query_mock.return_value = [
(x, ) for x in moves.range(1, csr_db.MAX_CSR_IKE_POLICIES)]
next_id = csr_db.get_next_available_ike_policy_id(self.session)
self.assertEqual(csr_db.MAX_CSR_IKE_POLICIES, next_id)
self.query_mock.return_value = [
(x, ) for x in moves.range(1, csr_db.MAX_CSR_IPSEC_POLICIES)]
next_id = csr_db.get_next_available_ipsec_policy_id(self.session)
self.assertEqual(csr_db.MAX_CSR_IPSEC_POLICIES, next_id)
def test_reusing_first_available_mapping_id(self):
"""Ensure that we reuse the first available ID.
Make sure that the next lowest ID is obtained from the mapping
table when there are "holes" from deletions. Database query sorts
the entries, so will return them in order. Using tunnel ID, as the
logic is the same for each ID type.
"""
self.query_mock.return_value = [(0, ), (1, ), (2, ), (5, ), (6, )]
next_id = csr_db.get_next_available_tunnel_id(self.session)
self.assertEqual(3, next_id)
def test_no_more_mapping_ids_available(self):
"""Failure test of trying to reserve ID, when none available."""
self.query_mock.return_value = [
(x, ) for x in moves.range(csr_db.MAX_CSR_TUNNELS)]
self.assertRaises(IndexError, csr_db.get_next_available_tunnel_id,
self.session)
self.query_mock.return_value = [
(x, ) for x in moves.range(1, csr_db.MAX_CSR_IKE_POLICIES + 1)]
self.assertRaises(IndexError, csr_db.get_next_available_ike_policy_id,
self.session)
self.query_mock.return_value = [
(x, ) for x in moves.range(1, csr_db.MAX_CSR_IPSEC_POLICIES + 1)]
self.assertRaises(IndexError,
csr_db.get_next_available_ipsec_policy_id,
self.session)
def test_create_tunnel_mappings(self):
"""Ensure successfully create new tunnel mappings."""
# Simulate that first IDs are obtained
self.query_mock.return_value = []
map_db_mock = mock.patch.object(csr_db, 'IdentifierMap').start()
conn_info = {'ikepolicy_id': '10',
'ipsecpolicy_id': '50',
'id': '100',
'tenant_id': '1000'}
csr_db.create_tunnel_mapping(self.context, conn_info)
map_db_mock.assert_called_once_with(csr_tunnel_id=0,
csr_ike_policy_id=1,
csr_ipsec_policy_id=1,
ipsec_site_conn_id='100',
tenant_id='1000')
# Create another, with next ID of 2 for all IDs (not mocking each
# ID separately, so will not have different IDs).
self.query_mock.return_value = [(0, ), (1, )]
map_db_mock.reset_mock()
conn_info = {'ikepolicy_id': '20',
'ipsecpolicy_id': '60',
'id': '101',
'tenant_id': '1000'}
csr_db.create_tunnel_mapping(self.context, conn_info)
map_db_mock.assert_called_once_with(csr_tunnel_id=2,
csr_ike_policy_id=2,
csr_ipsec_policy_id=2,
ipsec_site_conn_id='101',
tenant_id='1000')
class TestCiscoIPsecDriver(testlib_api.SqlTestCase):
"""Test that various incoming requests are sent to device driver."""
def setUp(self):
super(TestCiscoIPsecDriver, self).setUp()
mock.patch('neutron.common.rpc.Connection').start()
self._fake_vpn_router_id = _uuid()
service_plugin = mock.Mock()
service_plugin._get_vpnservice.return_value = {
'router_id': self._fake_vpn_router_id
}
l3_plugin = mock.Mock()
directory.add_plugin(constants.L3, l3_plugin)
l3_plugin.get_host_for_router.return_value = FAKE_HOST
l3_agent = mock.Mock()
l3_agent.host = 'some-host'
l3_plugin.get_l3_agents_hosting_routers.return_value = [l3_agent]
self.driver = ipsec_driver.CiscoCsrIPsecVPNDriver(service_plugin)
mock.patch.object(csr_db, 'create_tunnel_mapping').start()
self.context = n_ctx.Context('some_user', 'some_tenant')
def _test_update(self, func, args, additional_info=None):
with mock.patch.object(self.driver.agent_rpc.client,
'cast') as rpc_mock, \
mock.patch.object(self.driver.agent_rpc.client,
'prepare') as prepare_mock:
prepare_mock.return_value = self.driver.agent_rpc.client
func(self.context, *args)
prepare_args = {'server': 'fake_host', 'version': '1.0'}
prepare_mock.assert_called_once_with(**prepare_args)
rpc_mock.assert_called_once_with(self.context, 'vpnservice_updated',
**additional_info)
def test_create_ipsec_site_connection(self):
self._test_update(self.driver.create_ipsec_site_connection,
[FAKE_VPN_CONNECTION],
{'reason': 'ipsec-conn-create',
'router': {'id': self._fake_vpn_router_id}})
def test_update_ipsec_site_connection(self):
self._test_update(self.driver.update_ipsec_site_connection,
[FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION],
{'reason': 'ipsec-conn-update',
'router': {'id': self._fake_vpn_router_id}})
def test_delete_ipsec_site_connection(self):
self._test_update(self.driver.delete_ipsec_site_connection,
[FAKE_VPN_CONNECTION],
{'reason': 'ipsec-conn-delete',
'router': {'id': self._fake_vpn_router_id}})
def test_update_vpnservice(self):
self._test_update(self.driver.update_vpnservice,
[FAKE_VPN_SERVICE, FAKE_VPN_SERVICE],
{'reason': 'vpn-service-update',
'router': {'id': FAKE_VPN_SERVICE['router_id']}})
def test_delete_vpnservice(self):
self._test_update(self.driver.delete_vpnservice,
[FAKE_VPN_SERVICE],
{'reason': 'vpn-service-delete',
'router': {'id': FAKE_VPN_SERVICE['router_id']}})
class TestCiscoIPsecDriverRequests(base.BaseTestCase):
"""Test handling device driver requests for service info."""
def setUp(self):
super(TestCiscoIPsecDriverRequests, self).setUp()
mock.patch('neutron.common.rpc.Connection').start()
service_plugin = mock.Mock()
self.driver = ipsec_driver.CiscoCsrIPsecVPNDriver(service_plugin)
def test_build_router_tunnel_interface_name(self):
"""Check formation of inner/outer interface name for CSR router."""
router_info = {
'_interfaces': [
{'hosting_info': {'segmentation_id': 100,
'hosting_port_name': 't1_p:1'}}
],
'gw_port':
{'hosting_info': {'segmentation_id': 200,
'hosting_port_name': 't2_p:1'}}
}
self.assertEqual(
'GigabitEthernet2.100',
self.driver._create_interface(router_info['_interfaces'][0]))
self.assertEqual(
'GigabitEthernet3.200',
self.driver._create_interface(router_info['gw_port']))
def test_build_router_info(self):
"""Check creation of CSR info to send to device driver."""
router_info = {
'hosting_device': {
'management_ip_address': '1.1.1.1',
'credentials': {'username': 'me', 'password': 'secret'}
},
'gw_port':
{'hosting_info': {'segmentation_id': 101,
'hosting_port_name': 't2_p:1'}},
'id': u'c607b58e-f150-4289-b83f-45623578d122',
'_interfaces': [
{'hosting_info': {'segmentation_id': 100,
'hosting_port_name': 't1_p:1'}}
]
}
expected = {'rest_mgmt_ip': '1.1.1.1',
'username': 'me',
'password': 'secret',
'inner_if_name': 'GigabitEthernet2.100',
'outer_if_name': 'GigabitEthernet3.101',
'vrf': 'nrouter-c607b5',
'timeout': 30}
self.assertEqual(expected, self.driver._get_router_info(router_info))

View File

@ -1,97 +0,0 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import mock
from neutron_lib import context as n_ctx
from neutron_lib.plugins import constants
from neutron_lib.plugins import directory
from oslo_utils import uuidutils
from neutron_vpnaas.services.vpn.service_drivers import vyatta_ipsec
from neutron_vpnaas.tests import base
_uuid = uuidutils.generate_uuid
FAKE_HOST = 'fake_host'
FAKE_SERVICE_ID = _uuid()
FAKE_VPN_CONNECTION = {
'vpnservice_id': FAKE_SERVICE_ID
}
FAKE_ROUTER_ID = _uuid()
FAKE_VPN_SERVICE = {
'router_id': FAKE_ROUTER_ID
}
class TestVyattaDriver(base.BaseTestCase):
def setUp(self):
super(TestVyattaDriver, self).setUp()
mock.patch('neutron.common.rpc.Connection').start()
l3_agent = mock.Mock()
l3_agent.host = FAKE_HOST
plugin = mock.Mock()
plugin.get_l3_agents_hosting_routers.return_value = [l3_agent]
directory.add_plugin(constants.CORE, plugin)
directory.add_plugin(constants.L3, plugin)
service_plugin = mock.Mock()
service_plugin.get_l3_agents_hosting_routers.return_value = [l3_agent]
self._fake_vpn_router_id = _uuid()
service_plugin._get_vpnservice.return_value = {
'router_id': self._fake_vpn_router_id
}
self.driver = vyatta_ipsec.VyattaIPsecDriver(service_plugin)
def _test_update(self, func, args, additional_info=None):
ctxt = n_ctx.Context('', 'somebody')
with mock.patch.object(self.driver.agent_rpc.client, 'cast'
) as rpc_mock, \
mock.patch.object(self.driver.agent_rpc.client, 'prepare'
) as prepare_mock:
prepare_mock.return_value = self.driver.agent_rpc.client
func(ctxt, *args)
prepare_args = {'server': 'fake_host', 'version': '1.0'}
prepare_mock.assert_called_once_with(**prepare_args)
rpc_mock.assert_called_once_with(ctxt, 'vpnservice_updated',
**additional_info)
def test_create_ipsec_site_connection(self):
self._test_update(self.driver.create_ipsec_site_connection,
[FAKE_VPN_CONNECTION],
{'router': {'id': self._fake_vpn_router_id}})
def test_update_ipsec_site_connection(self):
self._test_update(self.driver.update_ipsec_site_connection,
[FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION],
{'router': {'id': self._fake_vpn_router_id}})
def test_delete_ipsec_site_connection(self):
self._test_update(self.driver.delete_ipsec_site_connection,
[FAKE_VPN_CONNECTION],
{'router': {'id': self._fake_vpn_router_id}})
def test_update_vpnservice(self):
self._test_update(self.driver.update_vpnservice,
[FAKE_VPN_SERVICE, FAKE_VPN_SERVICE],
{'router': {'id': FAKE_VPN_SERVICE['router_id']}})
def test_delete_vpnservice(self):
self._test_update(self.driver.delete_vpnservice,
[FAKE_VPN_SERVICE],
{'router': {'id': FAKE_VPN_SERVICE['router_id']}})

View File

@ -27,6 +27,7 @@ from neutron_lib.exceptions import flavors as flav_exc
from neutron_lib.plugins import constants as p_constants
from neutron_lib.plugins import directory
from oslo_utils import uuidutils
import testtools
from neutron_vpnaas.extensions import vpn_flavors
from neutron_vpnaas.services.vpn import plugin as vpn_plugin
@ -178,9 +179,12 @@ class TestVPNDriverPlugin(test_db_vpnaas.TestVpnaas,
self.assertEqual(lib_constants.ACTIVE, vpnservice['status'])
@testtools.skip('We have only one driver in our codebase,'
'so we cannot run the test successfully now')
class TestVPNDriverPluginMultipleDrivers(base.BaseTestCase):
def setUp(self):
# TODO(hoangcx): Set up a dummy driver in order to run test suite
super(TestVPNDriverPluginMultipleDrivers, self).setUp()
vpnaas_providers = [
{'service_type': p_constants.VPN,

View File

@ -1,52 +0,0 @@
# Copyright 2015 Brocade Communications System, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron.conf.agent import common as agent_config
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron_vpnaas.services.vpn import vyatta_vpn_service
from neutron_vpnaas.tests import base
_uuid = uuidutils.generate_uuid
FAKE_ROUTER_ID = _uuid()
class TestVyattaVPNService(base.BaseTestCase):
def setUp(self):
super(TestVyattaVPNService, self).setUp()
self.conf = cfg.CONF
agent_config.register_root_helper(self.conf)
self.ri_kwargs = {'root_helper': self.conf.AGENT.root_helper,
'agent_conf': self.conf,
'interface_driver': mock.sentinel.interface_driver}
self.agent = mock.Mock()
self.vyatta_service = vyatta_vpn_service.VyattaVPNService(
self.agent)
self.l3_agent = self.vyatta_service.l3_agent
def test_get_router_client(self):
self.vyatta_service.get_router_client(FAKE_ROUTER_ID)
self.l3_agent.get_router_client.assert_called_once_with(FAKE_ROUTER_ID)
def test_get_router(self):
self.vyatta_service.get_router(FAKE_ROUTER_ID)
self.l3_agent.get_router.assert_called_once_with(FAKE_ROUTER_ID)

View File

@ -0,0 +1,7 @@
---
upgrade:
- The following drivers are removed due to the lack of maintainers
of the drivers ``CiscoCsrIPsecDriver``, ``FedoraStrongSwanDriver``,
``VyattaIPsecDriver``. Please refer the following `mailing list post
<http://lists.openstack.org/pipermail/openstack-dev/2018-February/127793.html>`_
for more detail.

View File

@ -32,13 +32,10 @@ setup-hooks =
[entry_points]
console_scripts =
neutron-vpn-netns-wrapper = neutron_vpnaas.services.vpn.common.netns_wrapper:main
neutron-vyatta-agent = neutron_vpnaas.cmd.eventlet.vyatta_agent:main
neutron.agent.l3.extensions =
vpnaas = neutron_vpnaas.services.vpn.agent:L3WithVPNaaS
device_drivers =
neutron.services.vpn.device_drivers.ipsec.OpenSwanDriver = neutron_vpnaas.services.vpn.device_drivers.ipsec:OpenSwanDriver
neutron.services.vpn.device_drivers.cisco_ipsec.CiscoCsrIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.cisco_ipsec:CiscoCsrIPsecDriver
neutron.services.vpn.device_drivers.vyatta_ipsec.VyattaIPsecDriver = neutron_vpnaas.services.vpn.device_drivers.vyatta_ipsec:VyattaIPsecDriver
neutron.db.alembic_migrations =
neutron-vpnaas = neutron_vpnaas.db.migration:alembic_migrations
neutron.service_plugins =