Merge "Remove unmaintained drivers"
This commit is contained in:
commit
af6c6301b5
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
|
@ -1 +1 @@
|
|||
b6a2519ab7dc
|
||||
e50641731f1a
|
||||
|
|
|
@ -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')
|
|
@ -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():
|
||||
|
|
|
@ -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")),
|
||||
]
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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")
|
|
@ -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)
|
|
@ -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')
|
|
@ -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)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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))
|
|
@ -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']}})
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
|
@ -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 =
|
||||
|
|
Loading…
Reference in New Issue