Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: I43bc45605f9389a6941d71e0cb8399d31fc75c76
This commit is contained in:
Tony Breeds 2017-09-12 15:44:34 -06:00
parent 797c046669
commit 608063640d
205 changed files with 14 additions and 25935 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = neutron_vpnaas
omit = neutron_vpnaas/tests/*
[report]
ignore_errors = True

33
.gitignore vendored
View File

@ -1,33 +0,0 @@
AUTHORS
build/*
build-stamp
ChangeLog
cover/
covhtml/
dist/
doc/build
etc/*.sample
*.DS_Store
*.pyc
neutron.egg-info/
neutron_vpnaas.egg-info/
neutron/vcsversion.py
neutron/versioninfo
pbr*.egg/
run_tests.err.log
run_tests.log
setuptools*.egg/
subunit.log
*.mo
*.sw?
*~
/.*
!/.coveragerc
!/.gitignore
!/.gitreview
!/.mailmap
!/.pylintrc
!/.testr.conf
# Files created by releasenotes build
releasenotes/build

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/neutron-vpnaas.git

View File

@ -1,11 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>
lawrancejing <lawrancejing@gmail.com> <liuqing@windawn.com>
Jiajun Liu <jiajun@unitedstack.com> <iamljj@gmail.com>
Zhongyue Luo <zhongyue.nah@intel.com> <lzyeval@gmail.com>
Kun Huang <gareth@unitedstack.com> <academicgareth@gmail.com>
Zhenguo Niu <zhenguo@unitedstack.com> <Niu.ZGlinux@gmail.com>
Isaku Yamahata <isaku.yamahata@intel.com> <isaku.yamahata@gmail.com>
Isaku Yamahata <isaku.yamahata@intel.com> <yamahata@private.email.ne.jp>
Morgan Fainberg <morgan.fainberg@gmail.com> <m@metacloud.com>

132
.pylintrc
View File

@ -1,132 +0,0 @@
# The format of this file isn't really documented; just use --generate-rcfile
[MASTER]
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
#
# Note the 'openstack' below is intended to match only
# neutron.openstack.common. If we ever have another 'openstack'
# dirname, then we'll need to expand the ignore features in pylint :/
ignore=.git,tests,openstack
[MESSAGES CONTROL]
# NOTE(gus): This is a long list. A number of these are important and
# should be re-enabled once the offending code is fixed (or marked
# with a local disable)
disable=
# "F" Fatal errors that prevent further processing
import-error,
# "I" Informational noise
locally-disabled,
# "E" Error for important programming issues (likely bugs)
access-member-before-definition,
bad-super-call,
maybe-no-member,
no-member,
no-method-argument,
no-self-argument,
not-callable,
no-value-for-parameter,
super-on-old-class,
too-few-format-args,
# "W" Warnings for stylistic problems or minor programming issues
abstract-method,
anomalous-backslash-in-string,
anomalous-unicode-escape-in-string,
arguments-differ,
attribute-defined-outside-init,
bad-builtin,
bad-indentation,
broad-except,
dangerous-default-value,
deprecated-lambda,
expression-not-assigned,
fixme,
global-statement,
global-variable-not-assigned,
logging-not-lazy,
no-init,
non-parent-init-called,
pointless-string-statement,
protected-access,
redefined-builtin,
redefined-outer-name,
redefine-in-handler,
signature-differs,
star-args,
super-init-not-called,
unnecessary-lambda,
unnecessary-pass,
unpacking-non-sequence,
unreachable,
unused-argument,
unused-import,
unused-variable,
# TODO(dougwig) - disable nonstandard-exception while we have neutron_lib shims
nonstandard-exception,
# "C" Coding convention violations
bad-continuation,
invalid-name,
missing-docstring,
old-style-class,
superfluous-parens,
# "R" Refactor recommendations
abstract-class-little-used,
abstract-class-not-used,
duplicate-code,
interface-not-implemented,
no-self-use,
too-few-public-methods,
too-many-ancestors,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-lines,
too-many-locals,
too-many-public-methods,
too-many-return-statements,
too-many-statements
[BASIC]
# Variable names can be 1 to 31 characters long, with lowercase and underscores
variable-rgx=[a-z_][a-z0-9_]{0,30}$
# Argument names can be 2 to 31 characters long, with lowercase and underscores
argument-rgx=[a-z_][a-z0-9_]{1,30}$
# Method names should be at least 3 characters long
# and be lowecased with underscores
method-rgx=([a-z_][a-z0-9_]{2,}|setUp|tearDown)$
# Module names matching neutron-* are ok (files in bin/)
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(neutron-[a-z0-9_-]+))$
# Don't require docstrings on tests.
no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=79
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
# _ is used by our localization
additional-builtins=_
[CLASSES]
# List of interface methods to ignore, separated by a comma.
ignore-iface-methods=
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=
# should use openstack.common.jsonutils
json
[TYPECHECK]
# List of module names for which member attributes should not be checked
ignored-modules=six.moves,_MovedItems
[REPORTS]
# Tells whether to display a full report or only the messages
reports=no

View File

@ -1,4 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_LOG_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./neutron_vpnaas/tests/unit} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,4 +0,0 @@
Please see the Neutron CONTRIBUTING.rst file for how to contribute to
neutron-vpnaas:
`Neutron CONTRIBUTING.rst <https://git.openstack.org/cgit/openstack/neutron/tree/CONTRIBUTING.rst>`_

View File

@ -1,7 +0,0 @@
Neutron VPNaaS Style Commandments
=================================
Please see the Neutron HACKING.rst file for style commandments for
neutron-vpnaas:
`Neutron HACKING.rst <https://git.openstack.org/cgit/openstack/neutron/tree/HACKING.rst>`_

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

14
README Normal file
View File

@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,19 +0,0 @@
Welcome!
========
This package contains the code for the Neutron VPN as a Service
(VPNaaS) service. This includes third-party drivers. This package
requires Neutron to run.
External Resources:
===================
The homepage for Neutron is: https://launchpad.net/neutron. Use this
site for asking for help, and filing bugs. We use a single Launchpad
page for all Neutron projects.
Code is available on git.openstack.org at:
https://git.openstack.org/cgit/openstack/neutron-vpnaas.
Please refer to Neutron documentation for more information:
`Neutron README.rst <https://git.openstack.org/cgit/openstack/neutron/tree/README.rst>`_

View File

@ -1,8 +0,0 @@
Testing Neutron VPNaaS
======================
Please see the TESTING.rst file for the Neutron project itself. This will have
the latest up to date instructions for how to test Neutron, and will
be applicable to neutron-vpnaas as well:
`Neutron TESTING.rst <https://git.openstack.org/cgit/openstack/neutron/tree/TESTING.rst>`_

View File

@ -1,2 +0,0 @@
[python: **.py]

View File

@ -1,3 +0,0 @@
This directory contains the neutron-vpnaas devstack plugin. Please
see the devref for how to set up VPNaaS with devstack.

View File

@ -1,116 +0,0 @@
# plugin.sh - DevStack plugin.sh dispatch script template
VPNAAS_XTRACE=$(set +o | grep xtrace)
set -o xtrace
function neutron_vpnaas_install {
setup_develop $NEUTRON_VPNAAS_DIR
if is_service_enabled q-l3; then
neutron_agent_vpnaas_install_agent_packages
fi
}
function neutron_agent_vpnaas_install_agent_packages {
install_package $IPSEC_PACKAGE
if is_ubuntu && [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then
install_package apparmor
sudo ln -sf /etc/apparmor.d/usr.lib.ipsec.charon /etc/apparmor.d/disable/
sudo ln -sf /etc/apparmor.d/usr.lib.ipsec.stroke /etc/apparmor.d/disable/
# NOTE: Due to https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1387220
# one must use 'sudo start apparmor ACTION=reload' for Ubuntu 14.10
restart_service apparmor
fi
}
function neutron_vpnaas_configure_common {
cp $NEUTRON_VPNAAS_DIR/etc/neutron_vpnaas.conf.sample $NEUTRON_VPNAAS_CONF
neutron_server_config_add $NEUTRON_VPNAAS_CONF
_neutron_service_plugin_class_add $VPN_PLUGIN
_neutron_deploy_rootwrap_filters $NEUTRON_VPNAAS_DIR
inicomment $NEUTRON_VPNAAS_CONF service_providers service_provider
iniadd $NEUTRON_VPNAAS_CONF service_providers service_provider $NEUTRON_VPNAAS_SERVICE_PROVIDER
iniset $NEUTRON_CONF DEFAULT service_plugins $Q_SERVICE_PLUGIN_CLASSES
}
function neutron_vpnaas_configure_db {
$NEUTRON_BIN_DIR/neutron-db-manage --subproject neutron-vpnaas --config-file $NEUTRON_CONF --config-file /$Q_PLUGIN_CONF_FILE upgrade head
}
function neutron_vpnaas_configure_agent {
local conf_file=${1:-$Q_VPN_CONF_FILE}
cp $NEUTRON_VPNAAS_DIR/etc/vpn_agent.ini.sample $conf_file
if [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then
if is_fedora; then
iniset_multiline $conf_file vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.fedora_strongswan_ipsec.FedoraStrongSwanDriver
else
iniset_multiline $conf_file vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver
fi
elif [[ "$IPSEC_PACKAGE" == "libreswan" ]]; then
iniset_multiline $Q_VPN_CONF_FILE vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.libreswan_ipsec.LibreSwanDriver
else
iniset_multiline $conf_file vpnagent vpn_device_driver $NEUTRON_VPNAAS_DEVICE_DRIVER
fi
}
function neutron_vpnaas_start {
local cfg_file
local opts="--config-file $NEUTRON_CONF --config-file=$Q_L3_CONF_FILE --config-file=$Q_VPN_CONF_FILE"
for cfg_file in ${Q_VPN_EXTRA_CONF_FILES[@]}; do
opts+=" --config-file $cfg_file"
done
run_process neutron-vpnaas "$AGENT_VPN_BINARY $opts"
}
function neutron_vpnaas_stop {
local ipsec_data_dir=$DATA_DIR/neutron/ipsec
local pids
if [ -d $ipsec_data_dir ]; then
pids=$(find $ipsec_data_dir -name 'pluto.pid' -exec cat {} \;)
fi
if [ -n "$pids" ]; then
sudo kill $pids
fi
stop_process neutron-vpnaas
}
function neutron_vpnaas_generate_config_files {
# Uses oslo config generator to generate VPNaaS sample configuration files
(cd $NEUTRON_VPNAAS_DIR && exec sudo ./tools/generate_config_file_samples.sh)
}
# Main plugin processing
# NOP for pre-install step
if [[ "$1" == "stack" && "$2" == "install" ]]; then
echo_summary "Installing neutron-vpnaas"
neutron_vpnaas_install
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
neutron_vpnaas_generate_config_files
neutron_vpnaas_configure_common
if is_service_enabled q-svc; then
echo_summary "Configuring neutron-vpnaas on controller"
neutron_vpnaas_configure_db
fi
if is_service_enabled q-l3; then
echo_summary "Configuring neutron-vpnaas agent"
neutron_vpnaas_configure_agent
fi
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
if is_service_enabled q-l3; then
echo_summary "Initializing neutron-vpnaas"
neutron_vpnaas_start
fi
elif [[ "$1" == "unstack" ]]; then
if is_service_enabled q-l3; then
neutron_vpnaas_stop
fi
# NOP for clean step
fi
$VPNAAS_XTRACE

View File

@ -1,25 +0,0 @@
# Settings for the VPNaaS devstack plugin
enable_service neutron-vpnaas
AGENT_VPN_BINARY="$NEUTRON_BIN_DIR/neutron-vpn-agent"
# Plugin
VPN_PLUGIN=${VPN_PLUGIN:-"neutron_vpnaas.services.vpn.plugin.VPNDriverPlugin"}
# Service Driver
NEUTRON_VPNAAS_SERVICE_PROVIDER=${NEUTRON_VPNAAS_SERVICE_PROVIDER:-"VPN:strongswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default"}
# Device driver
IPSEC_PACKAGE=${IPSEC_PACKAGE:-"strongswan"}
NEUTRON_VPNAAS_DEVICE_DRIVER=${NEUTRON_VPNAAS_DEVICE_DRIVER:-"neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec:StrongSwanDriver"}
# Config files
NEUTRON_CONF_DIR=${NEUTRON_CONF_DIR:-"/etc/neutron"}
NEUTRON_VPNAAS_DIR=$DEST/neutron-vpnaas
Q_VPN_CONF_FILE=$NEUTRON_CONF_DIR/vpn_agent.ini
NEUTRON_VPNAAS_CONF_FILE=neutron_vpnaas.conf
NEUTRON_VPNAAS_CONF=$NEUTRON_CONF_DIR/$NEUTRON_VPNAAS_CONF_FILE
declare -a Q_VPN_EXTRA_CONF_FILES

View File

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

View File

@ -1 +0,0 @@
register_project_for_upgrade neutron-vpnaas

View File

@ -1,36 +0,0 @@
#!/bin/bash
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# ``upgrade-neutron-vpnaas``
set -o errexit
source $GRENADE_DIR/grenaderc
source $GRENADE_DIR/functions
source $BASE_DEVSTACK_DIR/functions
source $BASE_DEVSTACK_DIR/stackrc # needed for status directory
# TODO(kevinbenton): figure out best way to source this from devstack plugin
function neutron_vpnaas_stop {
local ipsec_data_dir=$DATA_DIR/neutron/ipsec
local pids
if [ -d $ipsec_data_dir ]; then
pids=$(find $ipsec_data_dir -name 'pluto.pid' -exec cat {} \;)
fi
if [ -n "$pids" ]; then
sudo kill $pids
fi
stop_process neutron-vpnaas
}
ENABLED_SERVICES+=,neutron-vpnaas
set -o xtrace
neutron_vpnaas_stop

View File

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

View File

@ -1,246 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2010 OpenStack Foundation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Keystone documentation build configuration file, created by
# sphinx-quickstart on Tue May 18 13:50:15 2010.
#
# This file is execfile()'d with the current directory set to it's containing
# dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import subprocess
import sys
import warnings
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
sys.path.insert(0, ROOT_DIR)
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.coverage',
'sphinx.ext.ifconfig',
'sphinx.ext.graphviz',
'sphinx.ext.todo',
'openstackdocstheme',]
todo_include_todos = True
# Add any paths that contain templates here, relative to this directory.
templates_path = []
if os.getenv('HUDSON_PUBLISH_DOCS'):
templates_path = ['_ga', '_templates']
else:
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master doctree document.
master_doc = 'index'
# General information about the project.
project = u'Neutron VPNaaS'
copyright = u'2011-present, OpenStack Foundation.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# Version info
from neutron_vpnaas.version import version_info as neutron_vpnaas_version
release = neutron_vpnaas_version.release_string()
# The short X.Y version.
version = neutron_vpnaas_version.version_string()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
# unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = []
# The reST default role (for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['neutron_vpnaas.']
# -- Options for man page output --------------------------------------------
# Grouping the document tree for man pages.
# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual'
#man_pages = [
# ('man/neutron-server', 'neutron-server', u'Neutron Server',
# [u'OpenStack'], 1)
#]
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = ['_theme']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
#htmlhelp_basename = 'neutrondoc'
# -- Options for LaTeX output ------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author,
# documentclass [howto/manual]).
#latex_documents = [
# ('index', 'Neutron.tex', u'Neutron Documentation',
# u'Neutron development team', 'manual'),
#]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
# -- Options for openstackdocstheme -------------------------------------------
repository_name = 'openstack/neutron-vpnaas'
bug_project = 'neutron'
bug_tag = 'doc'

View File

@ -1,54 +0,0 @@
===============================
Configuring VPNaaS for DevStack
===============================
-----------------------
Multinode vs All-In-One
-----------------------
Devstack typically runs in single or "All-In-One" (AIO) mode. However, it
can also be deployed to run on multiple nodes. For VPNaaS, running on an
AIO setup is simple, as everything happens on the same node. However, to
deploy to a multinode setup requires the following things to happen:
#. Each controller node requires database migrations in support of running
VPNaaS.
#. Each network node that would run the L3 agent needs to run the Neutron
VPNaaS agent in its place.
Therefore, the devstack plugin script needs some extra logic.
----------------
How to Configure
----------------
To configure VPNaaS, it is only necessary to enable the neutron-vpnaas
devstack plugin by adding the following line to the [[local|localrc]]
section of devstack's local.conf file::
enable_plugin neutron-vpnaas <GITURL> [BRANCH]
<GITURL> is the URL of a neutron-vpnaas repository
[BRANCH] is an optional git ref (branch/ref/tag). The default is master.
For example::
enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas stable/kilo
The default implementation for IPSEC package under DevStack is 'strongswan'.
However, depending upon the Linux distribution, you may need to override
this value. Select 'libreswan' for Fedora/RHEL/CentOS::
For example, install libreswan for CentOS/RHEL 7::
IPSEC_PACKAGE=libreswan
This VPNaaS devstack plugin code will then
#. Install the common VPNaaS configuration and code,
#. Apply database migrations on nodes that are running the controller (as
determined by enabling the q-svc service),
#. Run the VPNaaS agent on nodes that would normally be running the L3 agent
(as determined by enabling the q-l3 service).

View File

@ -1,86 +0,0 @@
..
Copyright 2015 OpenStack Foundation
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Developer Guide
===============
In the Developer Guide, you will find information on the design, and
architecture of the Neutron Virtual Private Network as a Service repo.
This include things like, information on the reference implementation
flavors, design details on VPNaaS internals, and testing. Developers
will extend this, as needed, in the future to contain more information.
VPNaaS Team
-----------
.. toctree::
:maxdepth: 3
team
VPNaaS Flavors
-----------------
.. toctree::
:maxdepth: 3
.. todo::
Info on the different Swan flavors, how they are different, and what
Operating Systems support them.
VPNaaS Internals
-----------------
.. toctree::
:maxdepth: 3
multiple-local-subnets
VPNaaS Tests
------------
.. toctree::
:maxdepth: 3
vpnaas-tempest-test
vpnaas-rally-test
Testing
-------
.. toctree::
:maxdepth: 3
devstack
.. todo::
Add notes about functional testing, with info on how
different reference drivers are tested.
Module Reference
----------------
.. toctree::
:maxdepth: 3
.. todo::
Add in all the big modules as automodule indexes.
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,296 +0,0 @@
=================================
Multiple Local Subnets for VPNaaS
=================================
As originally implemented, an VPN IPSec connection could have one or more
peer subnets specified, but only **one** local subnet. To support multiple
local subnets, multiple IPSec connections would be needed.
With the multiple local subnet support, three goals are addressed. First,
there can be multiple local and peer endpoints for a single IPSec connection.
Second, validation enforces that the same IP version is used for all
endpoints (to reduce complexity and ease testing).
Third, the "what is connected" is separated from the "how to connect",
so that other flavors of VPN (as they are developed) can use some of this
mechanism.
Design Notes
------------
There were three proposals considered, to support multiple local subnets.
Proposal A was to just add the local subnets to the IPSec connection API.
That would be the quickest way, and addresses the first two goals, but
not the third.
Proposal B was to create a new API that specifies of the local subnets
and peer CIDRs, and reference those in the connection API. This would
separate the "what is connected" from the "how to connect", and again
addresses the first two goals (only).
Proposal C, which was the *selected proposal*, creates a new API
that represents the "endpoint groups" for VPN connections, in the same
manner as proposal B. The added flexibility here, though, which meets
goal three, is to also include the endpoint group "type", thus allowing
subnets (local) and CIDRs (peer) to be used for IPSec, but routers,
networks, and VLANs to be used for other VPN types (BGP, L2, direct
connection). Additional types can be added in the future as needed.
Client CLI API
--------------
The originally implemented client CLI APIs (which are still available
for backward compatibility) for an IPsec connection are::
neutron vpn-service-create ROUTER SUBNET
neutron ipsec-site-connection-create
--vpnservice-id VPNSERVICE
--ikepolicy-id IKEPOLICY
--ipsecpolicy-id IPSECPOLICY
--peer-address PEER_ADDRESS
--peer-id PEER_ID
--peer-cidr PEER_CIDRS
--dpd action=ACTION,interval=INTERVAL,timeout=TIMEOUT
--initiator {bi-directional | response-only}
--mtu MTU
--psk PSK
Changes to the API, to support multiple local subnets, are shown in
**bold** text::
neutron vpn-service-create ROUTER
**neutron vpn-endpoint-groups-create**
**--name OPTIONAL-NAME**
**--description OPTIONAL-DESCRIPTION**
**--ep-type={subnet,cidr,network,vlan,router}**
**--ep-value=[list-of-endpoints-of-type]**
neutron ipsec-site-connection-create
--vpnservice-id VPNSERVICE
--ikepolicy-id IKEPOLICY
--ipsecpolicy-id IPSECPOLICY
--peer-address PEER_ADDRESS
--peer-id PEER_ID
--dpd action=ACTION,interval=INTERVAL,timeout=TIMEOUT
--initiator {bi-directional | response-only}
--mtu MTU
--psk PSK
**--local-endpoints ENDPOINT-GROUPS-UUID**
**--peer-endpoints ENDPOINT-GROUPS-UUID**
The SUBNET in the original service API is optional, and will be used as an
indicator of whether or not the multiple local subnets feature is active.
See the 'Backward Compatibility' section, below, for details.
For the endpoint groups, the --ep-type value is a string, so that other
types can be supported in the future.
The endpoint groups API would enforce that the endpoint values are all of
the same type, and match the endpoint type specified.
The connection APIs, would then provide additional validation. For example,
with IPSec, the endpoint type must be 'subnet' for local, and 'cidr' for
peer, all the endpoints should be of the same IP version, and for the local
endpoint, all subnets would be on the same router.
For BGP VPN with dynamic routing, only a local endpoint group would be
specified, and the type would be 'network'.
The ROUTER may also be able to be removed, in the future, and can be
determined, when the connections are created.
Note: Using --ep-type, as --endpoint-type is already used elsewhere, and
--type is too generic. Using --ep-value, as --endpoint is already in use,
--end-point could be easily mistyped as --endpoint, and --value is too
generic.
Examples
--------
The original APIs to create one side of an IPSec connection with
only one local and peer subnet::
neutron vpn-ikepolicy-create ikepolicy
neutron vpn-ipsecpolicy-create ipsecpolicy
neutron vpn-service-create --name myvpn router1 privateA
neutron ipsec-site-connection-create
--name vpnconnection1
--vpnservice-id myvpn
--ikepolicy-id ikepolicy
--ipsecpolicy-id ipsecpolicy
--peer-address 172.24.4.13
--peer-id 172.24.4.13
--peer-cidr 10.3.0.0/24
--psk secret
The local CIDR is obtained from the subnet, privateA. In this example,
that would be 10.1.0.0/24 (because that's how privateA was created).
Using the multiple local subnet feature, the APIs (with changes shown
in **bold** below::
neutron vpn-ikepolicy-create ikepolicy
neutron vpn-ipsecpolicy-create ipsecpolicy
neutron vpn-service-create --name myvpn router1
**neutron vpn-endpoint-group-create**
**--name local-eps**
**--ep-type=subnet**
**--ep-value=privateA**
**--ep-value=privateB**
**neutron vpn-endpoint-group-create**
**--name peer-eps**
**--ep-type=cidr**
**--ep-vallue=10.3.0.0/24**
neutron ipsec-site-connection-create
--name vpnconnection1
--vpnservice-id myvpn
--ikepolicy-id ikepolicy
--ipsecpolicy-id ipsecpolicy
--peer-address 172.24.4.13
--psk secret
**--local-endpoints local-eps**
**--peer-endpoints peer-eps**
The subnets privateA and privateB are used for local endpoints and the
10.3.0.0/24 CIDR is used for the peer endpoint.
Database
--------
The vpn_endpoints table contains single endpoint entries and a reference
to the containing endpoint group. The vpn_endpoint_groups table defines
the group, specifying the endpoint type.
Database Migration
------------------
For an older database, the first subnet, in the subnet entry of the
service table can be placed in an endpoint group that will be used
for the local endpoints of the connection. The CIDRs from the connection
can be placed into another endpoint group for the peer endpoints.
Backwards Compatibility
-----------------------
Operators would like to see this new capability provided, with backward
compatibility support. The implication, as I see it, is to provide the
ability for end users to be able to switch to the new API at any time,
versus being forced to use the new API immediately, upon upgrade to the
new release containing this feature. This would apply to both manual
API use, and client apps/scripting-tools that would be used to configure
VPNaaS.
There are several attributes that are involve here. One is the subnet ID
attribute in the VPN service API. The other is the peer CIDR attribute in
the IPSec connection API. Both would be specified by endpoint groups in
the new API, and these groups would be called out in the IPSec connection
API.
A plan to meet the backward compatibility goal of allowing both APIs to
be used at once involves taking the following steps.
For VPN service:
- Make the subnet ID attribute optional.
- If subnet ID is specified for create, consider old API mode.
- If subnet ID specified for create, create endpoint group and store ID.
- For delete, if subnet ID exists, delete corresponding endpoint group.
- For show/list, if subnet ID exists, show the ID in output.
- Subnet ID is not mutable, so no change for update API.
For IPSec site to site connection:
- For create, if old API mode, only allow peer-cidr attribute.
- For create, if not old API mode, require local/peer endpoint group IDs attributes.
- For create, if peer-cidr specified, create endpoint group and store ID.
- For create, reject endpoint group ID attributes, if old API mode.
- For create, reject peer-cidr attribute, if not old API mode.
- For create, if old API mode, lookup subnet in service, find containing endpoint group ID and store.
- For delete, if old API mode, delete endpoint group for peer.
- For update of CIDRs (old mode), will delete endpoint group and create new one. (note 1)
- For update of endpoint-group IDs (new mode), will allow different groups to be specified. (note 1,2)
- For show/list, if old API mode, only display the peer CIDR values from peer endpoint group.
- For show/list, if not old API mode, also show local subnets from local endpoint group.
Note 1: Implication is that connection is torn down and re-created (as is
done currently).
Note 2: Users would create a new endpoint group, and then select that group,
when modifying the IPSec connection.
For endpoint groups:
- For delete, if subnet, and (sole) subnet ID is used in a VPN service (old mode), reject request.
- Updates are not supported, so no action required. (note 2)
Note 2: Allowing updates would require deletion/recreation of connection
using endpoint group. Avoiding that complexity.
The thought here is to use endpoint groups under the hood, but if the old
API was being used, treat the endpoint groups as if they never existed.
Deleting connections and services would remove any endpoint groups, unlike
with the new API, where they are independent.
Migration can be used to move any VPNaaS configurations using the old
schema to the new schema. This would look at VPN services and for any
with a subnet ID, an endpoint group would be created and the group ID
stored in any existing IPSec connections for that service. Likewise,
any peer CIDRs in a connection would be copied into a new endpoint group
and the group ID stored in the connection.
The subnet ID field would then be removed from the VPN service table,
and the peer CIDRs table would be removed.
This migration could be done at the time of the new API release, in which
case all tenants with existing VPNaaS configurations would use the new
API to manage them (but could use old for new configurations).
Alternatively, the migration could be deferred until the old API is
removed, to ensure all existing configurations conform to the new schema.
Migration tools can then be created to manually migrate individual
tenants, as desired.
Stories
-------
For the endpoint groups, stories can cover:
- CRUD API for the endpoint groups.
- Database support for new tables.
- Migration creation of new tables.
- Validation of endpoints for a group (same type).
- Neutron client support for new API.
- Horizon support for new API.
- API documentation update.
For the multiple local subnets, stories can cover:
- create IPsec connection with one local subnet, but using new API.
- create IPSec connection with multiple local subnets.
- Show IPSec connection to display endpoint group IDs (or endpoints?).
- Ensure previous API still works, but uses new tables.
- Validation to ensure old and new APIs are not mixed.
- Modify CLI client.
- Validate multiple local subnets on same router.
- Validate local and peer endpoints are of same IP version.
- Functional tests with multiple local subnets
- API and How-To documentation update
Note: The intent here is to have the initial stories take slices
vertically through the process so that we can demonstrate the
capability early.
Note: Horizon work to support the changes is not expected to be part
of this effort and would be handled by the Horizon team separately,
if support is desired.

View File

@ -1,39 +0,0 @@
Driver maintainers
==================
The driver maintainers are supposed to try:
- Test the driver
- Fix bugs in the driver
- Keep the driver up-to-date for Neutron
- Keep the driver up-to-date for its backend
- Review relevant patches
The following is a list of drivers and their maintainers.
It includes both of in-tree and out-of-tree drivers.
(alphabetical order)
+----------------------------+---------------------------+------------------+
| Driver | Contact person | IRC nick |
+============================+===========================+==================+
| CiscoCsrIPsecDriver | ??? | ??? |
+----------------------------+---------------------------+------------------+
| FedoraStrongSwanDriver | ??? | ??? |
+----------------------------+---------------------------+------------------+
| LibreSwanDriver | ??? | ??? |
+----------------------------+---------------------------+------------------+
| MidonetIPsecVPNDriver [#]_ | YAMAMOTO Takashi | yamamoto |
+----------------------------+---------------------------+------------------+
| NSXvIPsecVpnDriver [#]_ | Roey Chen | roeyc |
+----------------------------+---------------------------+------------------+
| OpenSwanDriver | Lingxian Kong | kong |
+----------------------------+---------------------------+------------------+
| | Lingxian Kong | kong |
| StrongSwanDriver +---------------------------+------------------+
| | Cao Xuan Hoang | hoangcx |
+----------------------------+---------------------------+------------------+
| VyattaIPsecDriver | ??? | ??? |
+----------------------------+---------------------------+------------------+
.. [#] networking-midonet: https://docs.openstack.org/networking-midonet/latest/install/installation.html#vpnaas
.. [#] vmware-nsx: Maintained under the vmware-nsx repository - https://github.com/openstack/vmware-nsx

View File

@ -1,67 +0,0 @@
===================
VPNaaS Rally Tests
===================
This contains the rally test codes for the Neutron VPN as a Service (VPNaaS) service. The tests
currently require rally to be installed via devstack or standalone. It is assumed that you
also have Neutron with the Neutron VPNaaS service installed.
These tests could also be run against a multinode openstack.
Please see /neutron-vpnaas/devstack/README.md for the required devstack configuration settings
for Neutron-VPNaaS.
Structure:
==========
1. plugins - Directory where you can add rally plugins. Almost everything in Rally is a plugin.
Contains base, common methods and actual scenario tests
2. rally-configs - Contains input configurations for the scenario tests
How to test:
============
Included in the repo are rally tests. For information on rally, please see the rally README :
https://github.com/openstack/rally/blob/master/README.rst
* Create a rally deployment for your cloud and make sure it is active.
rally deployment create --file=cloud_cred.json --name=MyCloud
You can also create a rally deployment from the environment variables.
rally deployment create --fromenv --name=MyCloud
* Create a folder structure as below
sudo mkdir /opt/rally
* Create a symbolic link to the plugins directory
cd /opt/rally
sudo ln -s /opt/stack/neutron-vpnaas/rally-jobs/plugins
* Run the tests. You can run the tests in various combinations.
(a) Single Node with DVR with admin credentials
(b) Single Node with DVR with non admin credentials
(c) Multi Node with DVR with admin credentials
(d) Multi Node with DVR with non admin credentials
(e) Single Node, Non DVR with admin credentials
(f) Multi Node, Non DVR with admin credentials
-> Create a args.json file with the correct credentials depending on whether it is a
single node or multinode cloud. A args_template.json file is available at
/opt/stack/neutron-vpnaas/rally-jobs/rally-configs/args_template.json for your reference.
-> Update the rally_config_dvr.yaml or rally_config_non_dvr.yaml file to change the
admin/non_admin credentials.
-> Use the appropriate config files to run either dvr or non_dvr tests.
With DVR:
rally task start /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/rally_config_dvr.yaml
--task-args-file /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/args.json
Non DVR:
rally task start /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/rally_config_non_dvr.yaml
--task-args-file /opt/stack/neutron-vpnaas/rally-jobs/rally-configs/args.json
**Note:**
Non DVR scenario can only be run as admin as you need admin credentials to create
a non DVR router.
External Resources:
===================
For more information on the rally testing framework see: <https://github.com/openstack/rally>

View File

@ -1,68 +0,0 @@
====================
VPNaaS Tempest Tests
====================
This contains the tempest test codes for the Neutron VPN as a Service (VPNaaS) service. The tests
currently require tempest to be installed via devstack or standalone. It is assumed that you
also have Neutron with the Neutron VPNaaS service installed. These tests could also be run against
a multinode openstack.
Please see /neutron-vpnaas/devstack/README.md for the required devstack configuration settings
for Neutron-VPNaaS.
How to test:
============
As a tempest plugin, the steps to run tests by hands are:
1. Setup a local working environment for running tempest
::
tempest init ${your_tempest_dir}
2. Enter ${your_tempest_dir}
::
cd ${your_tempest_dir}
3. Check neutron_vpnaas_tests exist in tempest plugins:
::
tempest list-plugins
+----------------------+--------------------------------------------------------+
| Name | EntryPoint |
+----------------------+--------------------------------------------------------+
| neutron_tests | neutron.tests.tempest.plugin:NeutronTempestPlugin |
| neutron_vpnaas_tests | neutron_vpnaas.tests.tempest.plugin:VPNTempestPlugin |
+----------------------+--------------------------------------------------------+
4. Run neutron_vpnaas tests:
::
tempest run --regex "^neutron_vpnaas.tests.tempest.api\."
Usage in gate:
==============
In the jenkins gate, devstack-gate/devstack-vm-gate-wrap.sh will invoke tempest with proper
configurations, such as:
::
DEVSTACK_GATE_TEMPEST=1
DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
DEVSTACK_GATE_TEMPEST_REGEX="^neutron_vpnaas.tests.tempest.api\."
The actual raw command in gate running under the tempest code directory is:
::
tox -eall-plugin -- "^neutron_vpnaas.tests.tempest.api\."
External Resources:
===================
For more information on the tempest, see: <https://docs.openstack.org/tempest/latest/>

View File

@ -1,50 +0,0 @@
..
Copyright 2015 OpenStack Foundation
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Welcome to Neutron VPNaaS developer documentation!
==================================================
This provides Virtual Private Network as a Service (VPNaaS) capabilities to Neutron.
Maintained as a separate repo, this works in conjunction with the Neutron repo to
provide VPN services for OpenStack. The `VPNaaS API`_ is implementation as an
extension to Neutron's networking API:
.. _`VPNaaS API`: https://developer.openstack.org/api-ref/networking/v2/index.html#vpnaas-2-0-unmaintained-vpn-vpnservices-ikepolicies-ipsecpolicies-endpoint-groups-ipsec-site-connections
This documentation is generated by the Sphinx toolkit and lives in the source
tree. Additional documentation on VPNaaS and other components of OpenStack
can be found on the `OpenStack wiki`_ and the `Neutron section of the wiki`_ (see
the VPN related pages).
The `Neutron Development wiki`_ is also a good resource for new contributors.
.. _`OpenStack wiki`: https://wiki.openstack.org/wiki/Main_Page
.. _`Neutron section of the wiki`: https://wiki.openstack.org/wiki/Neutron
.. _`Neutron Development wiki`: https://wiki.openstack.org/wiki/NeutronDevelopment
Enjoy!
Developer Docs
==============
.. toctree::
:maxdepth: 1
devref/index
API Extensions
==============
Go to https://developer.openstack.org/api-ref/networking/ for information about OpenStack Network API extensions.

View File

@ -1,9 +0,0 @@
To generate the sample neutron VPNaaS configuration files, run the following
command from the top level of the neutron VPNaaS directory:
tox -e genconfig
If a 'tox' environment is unavailable, then you can run the following script
instead to generate the configuration files:
./tools/generate_config_file_samples.sh

View File

@ -1,19 +0,0 @@
# neutron-rootwrap command filters for nodes on which neutron is
# expected to control network
#
# This file should be owned by (and only-writable by) the root user
# format seems to be
# cmd-name: filter-name, raw-command, user, args
[Filters]
cp: RegExpFilter, cp, root, cp, -a, .*, .*/strongswan.d
ip: IpFilter, ip, root
ip_exec: IpNetnsExecFilter, ip, root
ipsec: CommandFilter, ipsec, root
rm: RegExpFilter, rm, root, rm, -rf, (.*/strongswan.d|.*/ipsec/[0-9a-z-]+)
strongswan: CommandFilter, strongswan, root
neutron_netns_wrapper: CommandFilter, neutron-vpn-netns-wrapper, root
neutron_netns_wrapper_local: CommandFilter, /usr/local/bin/neutron-vpn-netns-wrapper, root
chown: RegExpFilter, chown, root, chown, --from=.*, root.root, .*/ipsec.secrets

View File

@ -1,5 +0,0 @@
[DEFAULT]
output_file = etc/neutron_vpnaas.conf.sample
wrap_width = 79
namespace = neutron.vpnaas

View File

@ -1,5 +0,0 @@
[DEFAULT]
output_file = etc/vpn_agent.ini.sample
wrap_width = 79
namespace = neutron.vpnaas.agent

View File

@ -1,24 +0,0 @@
# Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import gettext
import six
if six.PY2:
gettext.install('neutron', unicode=1)
else:
gettext.install('neutron')

View File

@ -1,32 +0,0 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import oslo_i18n
DOMAIN = "neutron_vpnaas"
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
# The primary translation function using the well-known name "_"
_ = _translators.primary
# The contextual translation function using the name "_C"
_C = _translators.contextual_form
# The plural translation function using the name "_P"
_P = _translators.plural_form
def get_available_languages():
return oslo_i18n.get_available_languages(DOMAIN)

View File

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

View File

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

View File

@ -1,17 +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.
from neutron_vpnaas.services.vpn import agent
def main():
agent.main()

View File

@ -1,21 +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 vyatta_agent
def main():
vyatta_agent.main()

View File

@ -1 +0,0 @@
Generic single-database configuration.

View File

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

View File

@ -1,88 +0,0 @@
# Copyright 2014 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from logging import config as logging_config
from alembic import context
from neutron_lib.db import model_base
from oslo_config import cfg
from oslo_db.sqlalchemy import session
import sqlalchemy as sa
from sqlalchemy import event
from neutron_vpnaas.db.migration import alembic_migrations
MYSQL_ENGINE = None
config = context.config
neutron_config = config.neutron_config
logging_config.fileConfig(config.config_file_name)
target_metadata = model_base.BASEV2.metadata
def set_mysql_engine():
try:
mysql_engine = neutron_config.command.mysql_engine
except cfg.NoSuchOptError:
mysql_engine = None
global MYSQL_ENGINE
MYSQL_ENGINE = (mysql_engine or
model_base.BASEV2.__table_args__['mysql_engine'])
def run_migrations_offline():
set_mysql_engine()
kwargs = dict()
if neutron_config.database.connection:
kwargs['url'] = neutron_config.database.connection
else:
kwargs['dialect_name'] = neutron_config.database.engine
kwargs['version_table'] = alembic_migrations.VPNAAS_VERSION_TABLE
context.configure(**kwargs)
with context.begin_transaction():
context.run_migrations()
@event.listens_for(sa.Table, 'after_parent_attach')
def set_storage_engine(target, parent):
if MYSQL_ENGINE:
target.kwargs['mysql_engine'] = MYSQL_ENGINE
def run_migrations_online():
set_mysql_engine()
engine = session.create_engine(neutron_config.database.connection)
connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata,
version_table=alembic_migrations.VPNAAS_VERSION_TABLE
)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
engine.dispose()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@ -1,36 +0,0 @@
# Copyright ${create_date.year} <PUT YOUR NAME/COMPANY HERE>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}
"""
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
% if branch_labels:
branch_labels = ${repr(branch_labels)}
%endif
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
def upgrade():
${upgrades if upgrades else "pass"}

View File

@ -1,37 +0,0 @@
# Copyright 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""add_index_tenant_id
Revision ID: 3ea02b2a773e
Revises: start_neutron_vpnaas
Create Date: 2015-02-10 17:51:10.752504
"""
# revision identifiers, used by Alembic.
revision = '3ea02b2a773e'
down_revision = 'start_neutron_vpnaas'
from alembic import op
TABLES = ['ipsecpolicies', 'ikepolicies', 'ipsec_site_connections',
'vpnservices']
def upgrade():
for table in TABLES:
op.create_index(op.f('ix_%s_tenant_id' % table),
table, ['tenant_id'], unique=False)

View File

@ -1,29 +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.
#
"""kilo
Revision ID: kilo
Revises: 3ea02b2a773e
Create Date: 2015-04-16 00:00:00.000000
"""
# revision identifiers, used by Alembic.
revision = 'kilo'
down_revision = '3ea02b2a773e'
def upgrade():
"""A no-op migration for marking the Kilo release."""
pass

View File

@ -1,38 +0,0 @@
# Copyright 2015 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""drop_tenant_id_in_cisco_csr_identifier_map
Revision ID: 2c82e782d734
Revises: 333dfd6afaa2
Create Date: 2015-08-20 15:17:09.897944
"""
from alembic import op
from neutron.db import migration
# revision identifiers, used by Alembic.
revision = '2c82e782d734'
down_revision = '333dfd6afaa2'
# milestone identifier, used by neutron-db-manage
neutron_milestone = [migration.LIBERTY]
def upgrade():
op.drop_column('cisco_csr_identifier_map', 'tenant_id')

View File

@ -1,85 +0,0 @@
# Copyright 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Populate VPN service table fields
Revision ID: 333dfd6afaa2
Revises: 56893333aa52
Create Date: 2015-07-27 16:43:59.123456
"""
# revision identifiers, used by Alembic.
revision = '333dfd6afaa2'
down_revision = '56893333aa52'
depends_on = '24f28869838b'
from alembic import op
import netaddr
import sqlalchemy as sa
VPNService = sa.Table('vpnservices', sa.MetaData(),
sa.Column('router_id', sa.String(36), nullable=False),
sa.Column('external_v4_ip', sa.String(16)),
sa.Column('external_v6_ip', sa.String(64)),
sa.Column('id', sa.String(36), nullable=False,
primary_key=True))
Router = sa.Table('routers', sa.MetaData(),
sa.Column('gw_port_id', sa.String(36)),
sa.Column('id', sa.String(36), nullable=False,
primary_key=True))
Port = sa.Table('ports', sa.MetaData(),
sa.Column('id', sa.String(36), nullable=False,
primary_key=True))
IPAllocation = sa.Table('ipallocations', sa.MetaData(),
sa.Column('ip_address', sa.String(64),
nullable=False, primary_key=True),
sa.Column('port_id', sa.String(36)))
def _migrate_external_ips(engine):
"""Use router external IPs to populate external_v*_ip entries.
For each service, look through the associated router's
gw_port['fixed_ips'] list and store any IPv4 and/or IPv6
addresses into the new fields. If there are multiple
addresses for an IP version, then only the first one will
be stored (the same as the reference driver does).
"""
session = sa.orm.Session(bind=engine.connect())
services = session.query(VPNService).all()
for service in services:
addresses = session.query(IPAllocation.c.ip_address).filter(
service.router_id == Router.c.id,
Router.c.gw_port_id == Port.c.id,
Port.c.id == IPAllocation.c.port_id).all()
have_version = []
for address in addresses:
version = netaddr.IPAddress(address[0]).version
if version in have_version:
continue
have_version.append(version)
update = {'external_v%s_ip' % version: address[0]}
op.execute(VPNService.update().where(
VPNService.c.id == service.id).values(update))
session.commit()
def upgrade():
# Use the router to populate the fields
for_engine = op.get_bind()
_migrate_external_ips(for_engine)

View File

@ -1,57 +0,0 @@
# Copyright(c) 2015, Oracle and/or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""fix identifier map fk
Revision ID: 56893333aa52
Revises: kilo
Create Date: 2015-06-11 12:09:01.263253
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import column
from sqlalchemy.sql import expression as expr
from sqlalchemy.sql import func
from sqlalchemy.sql import table
from neutron.db import migration
from neutron.db.migration import cli
# revision identifiers, used by Alembic.
revision = '56893333aa52'
down_revision = 'kilo'
branch_labels = (cli.CONTRACT_BRANCH,)
def upgrade():
# re-size existing data if necessary
identifier_map = table('cisco_csr_identifier_map',
column('ipsec_site_conn_id', sa.String(36)))
ipsec_site_conn_id = identifier_map.columns['ipsec_site_conn_id']
op.execute(identifier_map.update(values={
ipsec_site_conn_id: expr.case([(func.length(ipsec_site_conn_id) > 36,
func.substr(ipsec_site_conn_id, 1, 36))],
else_=ipsec_site_conn_id)}))
# Need to drop foreign key constraint before mysql will allow changes
with migration.remove_fks_from_table('cisco_csr_identifier_map'):
op.alter_column(table_name='cisco_csr_identifier_map',
column_name='ipsec_site_conn_id',
type_=sa.String(36),
existing_nullable=False)

View File

@ -1,42 +0,0 @@
# Copyright 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Add fields to VPN service table
Revision ID: 24f28869838b
Revises: 30018084ed99
Create Date: 2015-07-06 14:52:24.339246
"""
from alembic import op
import sqlalchemy as sa
from neutron.db import migration
# revision identifiers, used by Alembic.
revision = '24f28869838b'
down_revision = '30018084ed99'
# milestone identifier, used by neutron-db-manage
neutron_milestone = [migration.LIBERTY]
def upgrade():
op.add_column('vpnservices',
sa.Column('external_v4_ip', sa.String(16), nullable=True))
op.add_column('vpnservices',
sa.Column('external_v6_ip', sa.String(64), nullable=True))

View File

@ -1,32 +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.
#
"""Initial no-op Liberty expand rule.
Revision ID: 30018084ed99
Revises: kilo
Create Date: 2015-07-16 00:00:00.000000
"""
from neutron.db.migration import cli
# revision identifiers, used by Alembic.
revision = '30018084ed99'
down_revision = 'kilo'
branch_labels = (cli.EXPAND_BRANCH,)
def upgrade():
pass

View File

@ -1,172 +0,0 @@
# (c) Copyright 2015 Cisco Systems Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Multiple local subnets
Revision ID: 2cb4ee992b41
Revises: 2c82e782d734
Create Date: 2015-09-09 20:32:54.254267
"""
from alembic import op
from oslo_utils import uuidutils
import sqlalchemy as sa
from sqlalchemy.sql import expression as sa_expr
from neutron.db import migration
from neutron_vpnaas.services.vpn.common import constants as v_constants
# revision identifiers, used by Alembic.
revision = '2cb4ee992b41'
down_revision = '2c82e782d734'
depends_on = ('28ee739a7e4b',)
# milestone identifier, used by neutron-db-manage
neutron_milestone = [migration.MITAKA]
vpnservices = sa.Table(
'vpnservices', sa.MetaData(),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=255), nullable=False),
sa.Column('name', sa.String(255)),
sa.Column('description', sa.String(255)),
sa.Column('status', sa.String(16), nullable=False),
sa.Column('admin_state_up', sa.Boolean(), nullable=False),
sa.Column('external_v4_ip', sa.String(16)),
sa.Column('external_v6_ip', sa.String(64)),
sa.Column('subnet_id', sa.String(36)),
sa.Column('router_id', sa.String(36), nullable=False))
ipsec_site_conns = sa.Table(
'ipsec_site_connections', sa.MetaData(),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=255), nullable=False),
sa.Column('name', sa.String(255)),
sa.Column('description', sa.String(255)),
sa.Column('peer_address', sa.String(255), nullable=False),
sa.Column('peer_id', sa.String(255), nullable=False),
sa.Column('route_mode', sa.String(8), nullable=False),
sa.Column('mtu', sa.Integer, nullable=False),
sa.Column('initiator', sa.Enum("bi-directional", "response-only",
name="vpn_initiators"), nullable=False),
sa.Column('auth_mode', sa.String(16), nullable=False),
sa.Column('psk', sa.String(255), nullable=False),
sa.Column('dpd_action', sa.Enum("hold", "clear", "restart", "disabled",
"restart-by-peer", name="vpn_dpd_actions"),
nullable=False),
sa.Column('dpd_interval', sa.Integer, nullable=False),
sa.Column('dpd_timeout', sa.Integer, nullable=False),
sa.Column('status', sa.String(16), nullable=False),
sa.Column('admin_state_up', sa.Boolean(), nullable=False),
sa.Column('vpnservice_id', sa.String(36), nullable=False),
sa.Column('ipsecpolicy_id', sa.String(36), nullable=False),
sa.Column('ikepolicy_id', sa.String(36), nullable=False),
sa.Column('local_ep_group_id', sa.String(36)),
sa.Column('peer_ep_group_id', sa.String(36)))
ipsecpeercidrs = sa.Table(
'ipsecpeercidrs', sa.MetaData(),
sa.Column('cidr', sa.String(32), nullable=False, primary_key=True),
sa.Column('ipsec_site_connection_id', sa.String(36), primary_key=True))
def _make_endpoint_groups(new_groups, new_endpoints):
"""Create endpoint groups and their corresponding endpoints."""
md = sa.MetaData()
engine = op.get_bind()
sa.Table('vpn_endpoint_groups', md, autoload=True, autoload_with=engine)
op.bulk_insert(md.tables['vpn_endpoint_groups'], new_groups)
sa.Table('vpn_endpoints', md, autoload=True, autoload_with=engine)
op.bulk_insert(md.tables['vpn_endpoints'], new_endpoints)
def _update_connections(connection_map):
"""Store the endpoint group IDs in the connections."""
for conn_id, mapping in connection_map.items():
stmt = ipsec_site_conns.update().where(
ipsec_site_conns.c.id == conn_id).values(
local_ep_group_id=mapping['local'],
peer_ep_group_id=mapping['peer'])
op.execute(stmt)
def upgrade():
new_groups = []
new_endpoints = []
service_map = {}
session = sa.orm.Session(bind=op.get_bind())
vpn_services = session.query(vpnservices).filter(
vpnservices.c.subnet_id is not None).all()
for vpn_service in vpn_services:
subnet_id = vpn_service.subnet_id
if subnet_id is None:
continue # Skip new service entries
# Define the subnet group
group_id = uuidutils.generate_uuid()
group = {'id': group_id,
'name': '',
'description': '',
'tenant_id': vpn_service.tenant_id,
'endpoint_type': v_constants.SUBNET_ENDPOINT}
new_groups.append(group)
# Define the (sole) endpoint
endpoint = {'endpoint_group_id': group_id,
'endpoint': subnet_id}
new_endpoints.append(endpoint)
# Save info to use for connections
service_map[vpn_service.id] = group_id
connection_map = {}
ipsec_conns = session.query(ipsec_site_conns).all()
for connection in ipsec_conns:
peer_cidrs = session.query(ipsecpeercidrs.c.cidr).filter(
ipsecpeercidrs.c.ipsec_site_connection_id == connection.id).all()
if not peer_cidrs:
continue # Skip new style connections
# Define the CIDR group
group_id = uuidutils.generate_uuid()
group = {'id': group_id,
'name': '',
'description': '',
'tenant_id': connection.tenant_id,
'endpoint_type': v_constants.CIDR_ENDPOINT}
new_groups.append(group)
# Define the endpoint(s)
for peer_cidr in peer_cidrs:
endpoint = {'endpoint_group_id': group_id,
'endpoint': peer_cidr[0]}
new_endpoints.append(endpoint)
# Save the endpoint group ID info for the connection
vpn_service = connection.vpnservice_id
connection_map[connection.id] = {'local': service_map[vpn_service],
'peer': group_id}
# Create all the defined endpoint groups and their endpoints
_make_endpoint_groups(new_groups, new_endpoints)
# Refer to new groups, in the IPSec connections
_update_connections(connection_map)
# Remove the peer_cidrs from IPSec connections
op.execute(sa_expr.table('ipsecpeercidrs').delete())
# Remove the subnets from VPN services
stmt = vpnservices.update().where(
vpnservices.c.subnet_id is not None).values(
subnet_id=None)
op.execute(stmt)
session.commit()

View File

@ -1,58 +0,0 @@
# (c) Copyright 2015 Cisco Systems Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Multiple local subnets
Revision ID: 28ee739a7e4b
Revises: 41b509d10b5e
Create Date: 2015-09-09 20:32:54.231765
"""
from alembic import op
import sqlalchemy as sa
from neutron.db import migration
# revision identifiers, used by Alembic.
revision = '28ee739a7e4b'
down_revision = '41b509d10b5e'
# milestone identifier, used by neutron-db-manage
neutron_milestone = [migration.MITAKA]
def upgrade():
op.add_column('ipsec_site_connections',
sa.Column('local_ep_group_id',
sa.String(length=36),
nullable=True))
op.add_column('ipsec_site_connections',
sa.Column('peer_ep_group_id',
sa.String(length=36),
nullable=True))
op.create_foreign_key(constraint_name=None,
source_table='ipsec_site_connections',
referent_table='vpn_endpoint_groups',
local_cols=['local_ep_group_id'],
remote_cols=['id'])
op.create_foreign_key(constraint_name=None,
source_table='ipsec_site_connections',
referent_table='vpn_endpoint_groups',
local_cols=['peer_ep_group_id'],
remote_cols=['id'])
op.alter_column('vpnservices', 'subnet_id',
existing_type=sa.String(length=36), nullable=True)

View File

@ -1,57 +0,0 @@
# (c) Copyright 2015 Cisco Systems Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""VPNaaS endpoint groups
Revision ID: 41b509d10b5e
Revises: 24f28869838b
Create Date: 2015-08-06 18:21:03.241664
"""
# revision identifiers, used by Alembic.
revision = '41b509d10b5e'
down_revision = '24f28869838b'
from alembic import op
import sqlalchemy as sa
from neutron_vpnaas.services.vpn.common import constants
def upgrade():
op.create_table(
'vpn_endpoint_groups',
sa.Column('id', sa.String(length=36), nullable=False,
primary_key=True),
sa.Column('tenant_id', sa.String(length=255),
index=True),
sa.Column('name', sa.String(length=255)),
sa.Column('description', sa.String(length=255)),
sa.Column('endpoint_type',
sa.Enum(constants.SUBNET_ENDPOINT, constants.CIDR_ENDPOINT,
constants.VLAN_ENDPOINT, constants.NETWORK_ENDPOINT,
constants.ROUTER_ENDPOINT,
name='endpoint_type'),
nullable=False),
)
op.create_table(
'vpn_endpoints',
sa.Column('endpoint', sa.String(length=255), nullable=False),
sa.Column('endpoint_group_id', sa.String(36), nullable=False),
sa.ForeignKeyConstraint(['endpoint_group_id'],
['vpn_endpoint_groups.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('endpoint', 'endpoint_group_id'),
)

View File

@ -1,143 +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.
#
"""rename tenant to project
Revision ID: b6a2519ab7dc
Revises: 2cb4ee992b41
Create Date: 2016-07-13 02:40:51.683659
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.engine import reflection
from neutron.db import migration
# revision identifiers, used by Alembic.
revision = 'b6a2519ab7dc'
down_revision = '2cb4ee992b41'
# milestone identifier, used by neutron-db-manage
neutron_milestone = [migration.NEWTON, migration.OCATA]
_INSPECTOR = None
def get_inspector():
"""Reuse inspector"""
global _INSPECTOR
if _INSPECTOR:
return _INSPECTOR
bind = op.get_bind()
_INSPECTOR = reflection.Inspector.from_engine(bind)
return _INSPECTOR
def get_tables():
"""
Returns hardcoded list of tables which have ``tenant_id`` column.
The list is hard-coded to match the state of the schema when this upgrade
script is run.
"""
tables = [
'ipsecpolicies',
'ikepolicies',
'ipsec_site_connections',
'vpnservices',
'vpn_endpoint_groups',
]
return tables
def get_columns(table):
"""Returns list of columns for given table."""
inspector = get_inspector()
return inspector.get_columns(table)
def get_data():
"""Returns combined list of tuples: [(table, column)].
The list is built from tables with a tenant_id column.
"""
output = []
tables = get_tables()
for table in tables:
columns = get_columns(table)
for column in columns:
if column['name'] == 'tenant_id':
output.append((table, column))
return output
def alter_column(table, column):
old_name = 'tenant_id'
new_name = 'project_id'
op.alter_column(
table_name=table,
column_name=old_name,
new_column_name=new_name,
existing_type=column['type'],
existing_nullable=column['nullable']
)
def recreate_index(index, table_name):
old_name = index['name']
new_name = old_name.replace('tenant', 'project')
op.drop_index(op.f(old_name), table_name)
op.create_index(new_name, table_name, ['project_id'])
def upgrade():
"""Code reused from
Change-Id: I87a8ef342ccea004731ba0192b23a8e79bc382dc
"""
inspector = get_inspector()
data = get_data()
for table, column in data:
alter_column(table, column)
indexes = inspector.get_indexes(table)
for index in indexes:
if 'tenant_id' in index['name']:
recreate_index(index, table)
def contract_creation_exceptions():
"""Special migration for the blueprint to support Keystone V3.
We drop all tenant_id columns and create project_id columns instead.
"""
return {
sa.Column: ['.'.join([table, 'project_id']) for table in get_tables()],
sa.Index: get_tables()
}

View File

@ -1,42 +0,0 @@
# Copyright 2016 <Yi Jing Zhu/IBM>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""support local id
Revision ID: 52783a36bd67
Revises: fe637dc3f042
Create Date: 2016-04-26 21:40:40.244196
"""
from alembic import op
import sqlalchemy as sa
from neutron.db import migration
# revision identifiers, used by Alembic.
revision = '52783a36bd67'
down_revision = 'fe637dc3f042'
# milestone identifier, used by neutron-db-manage
neutron_milestone = [migration.NEWTON]
def upgrade():
op.add_column('ipsec_site_connections',
sa.Column('local_id',
sa.String(length=255),
nullable=True))

View File

@ -1,39 +0,0 @@
# Copyright 2016 <Yi Jing Zhu/IBM>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""support sha256
Revision ID: fe637dc3f042
Revises: 28ee739a7e4b
Create Date: 2016-04-08 22:33:53.286083
"""
from neutron.db import migration
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'fe637dc3f042'
down_revision = '28ee739a7e4b'
new_auth = sa.Enum('sha1', 'sha256', name='vpn_auth_algorithms')
def upgrade():
migration.alter_enum('ikepolicies', 'auth_algorithm', new_auth,
nullable=False, do_drop=False)
migration.alter_enum('ipsecpolicies', 'auth_algorithm', new_auth,
nullable=False, do_rename=False, do_create=False)

View File

@ -1,43 +0,0 @@
# Copyright 2016 <Dongcan Ye/Awcloud>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""add_auth_algorithm_sha384_and_sha512
Revision ID: 38893903cbde
Revises: 52783a36bd67
Create Date: 2016-11-04 18:00:49.219140
"""
from neutron.db import migration
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '38893903cbde'
down_revision = '52783a36bd67'
# milestone identifier, used by neutron-db-manage
neutron_milestone = [migration.OCATA]
new_auth = sa.Enum('sha1', 'sha256', 'sha384', 'sha512',
name='vpn_auth_algorithms')
def upgrade():
migration.alter_enum('ikepolicies', 'auth_algorithm', new_auth,
nullable=False, do_drop=False)
migration.alter_enum('ipsecpolicies', 'auth_algorithm', new_auth,
nullable=False, do_rename=False, do_create=False)

View File

@ -1,37 +0,0 @@
# Copyright 2017 Eayun, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""add flavor id to vpnservices
Revision ID: 95601446dbcc
Revises: 38893903cbde
Create Date: 2017-04-10 10:14:41.724811
"""
# revision identifiers, used by Alembic.
revision = '95601446dbcc'
down_revision = '38893903cbde'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('vpnservices',
sa.Column('flavor_id', sa.String(length=36), nullable=True))
op.create_foreign_key('fk_vpnservices_flavors_id',
'vpnservices', 'flavors',
['flavor_id'], ['id'])

View File

@ -1,30 +0,0 @@
# Copyright 2014 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""start neutron-vpnaas chain
Revision ID: start_neutron_vpnaas
Revises: None
Create Date: 2014-12-09 18:50:01.946832
"""
# revision identifiers, used by Alembic.
revision = 'start_neutron_vpnaas'
down_revision = None
def upgrade():
pass

View File

@ -1,31 +0,0 @@
# Copyright (c) 2014 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
The module provides all database models at current HEAD.
Its purpose is to create comparable metadata with current database schema.
Based on this comparison database can be healed with healing migration.
"""
from neutron.db.migration.models import head
from neutron_vpnaas.db.vpn import vpn_db # noqa
from neutron_vpnaas.services.vpn.service_drivers import cisco_csr_db # noqa
def get_metadata():
return head.model_base.BASEV2.metadata

View File

@ -1,745 +0,0 @@
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
# (c) Copyright 2015 Cisco Systems Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.db import common_db_mixin as base_db
from neutron.db import models_v2
from neutron.plugins.common import utils
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants as lib_constants
from neutron_lib.exceptions import l3 as l3_exception
from neutron_lib.plugins import constants as p_constants
from neutron_lib.plugins import directory
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import uuidutils
import sqlalchemy as sa
from sqlalchemy.orm import exc
from neutron_vpnaas.db.vpn import vpn_models
from neutron_vpnaas.db.vpn import vpn_validator
from neutron_vpnaas.extensions import vpn_endpoint_groups
from neutron_vpnaas.extensions import vpnaas
from neutron_vpnaas.services.vpn.common import constants as v_constants
LOG = logging.getLogger(__name__)
class VPNPluginDb(vpnaas.VPNPluginBase,
vpn_endpoint_groups.VPNEndpointGroupsPluginBase,
base_db.CommonDbMixin):
"""VPN plugin database class using SQLAlchemy models."""
def _get_validator(self):
"""Obtain validator to use for attribute validation.
Subclasses may override this with a different validator, as needed.
Note: some UTs will directly create a VPNPluginDb object and then
call its methods, instead of creating a VPNDriverPlugin, which
will have a service driver associated that will provide a
validator object. As a result, we use the reference validator here.
"""
return vpn_validator.VpnReferenceValidator()
def update_status(self, context, model, v_id, status):
with context.session.begin(subtransactions=True):
v_db = self._get_resource(context, model, v_id)
v_db.update({'status': status})
def _get_resource(self, context, model, v_id):
try:
r = self._get_by_id(context, model, v_id)
except exc.NoResultFound:
with excutils.save_and_reraise_exception(reraise=False) as ctx:
if issubclass(model, vpn_models.IPsecSiteConnection):
raise vpnaas.IPsecSiteConnectionNotFound(
ipsec_site_conn_id=v_id
)
elif issubclass(model, vpn_models.IKEPolicy):
raise vpnaas.IKEPolicyNotFound(ikepolicy_id=v_id)
elif issubclass(model, vpn_models.IPsecPolicy):
raise vpnaas.IPsecPolicyNotFound(ipsecpolicy_id=v_id)
elif issubclass(model, vpn_models.VPNService):
raise vpnaas.VPNServiceNotFound(vpnservice_id=v_id)
elif issubclass(model, vpn_models.VPNEndpointGroup):
raise vpnaas.VPNEndpointGroupNotFound(
endpoint_group_id=v_id)
ctx.reraise = True
return r
def assert_update_allowed(self, obj):
status = getattr(obj, 'status', None)
_id = getattr(obj, 'id', None)
if utils.in_pending_status(status):
raise vpnaas.VPNStateInvalidToUpdate(id=_id, state=status)
def _make_ipsec_site_connection_dict(self, ipsec_site_conn, fields=None):
res = {'id': ipsec_site_conn['id'],
'tenant_id': ipsec_site_conn['tenant_id'],
'name': ipsec_site_conn['name'],
'description': ipsec_site_conn['description'],
'peer_address': ipsec_site_conn['peer_address'],
'peer_id': ipsec_site_conn['peer_id'],
'local_id': ipsec_site_conn['local_id'],
'route_mode': ipsec_site_conn['route_mode'],
'mtu': ipsec_site_conn['mtu'],
'auth_mode': ipsec_site_conn['auth_mode'],
'psk': ipsec_site_conn['psk'],
'initiator': ipsec_site_conn['initiator'],
'dpd': {
'action': ipsec_site_conn['dpd_action'],
'interval': ipsec_site_conn['dpd_interval'],
'timeout': ipsec_site_conn['dpd_timeout']
},
'admin_state_up': ipsec_site_conn['admin_state_up'],
'status': ipsec_site_conn['status'],
'vpnservice_id': ipsec_site_conn['vpnservice_id'],
'ikepolicy_id': ipsec_site_conn['ikepolicy_id'],
'ipsecpolicy_id': ipsec_site_conn['ipsecpolicy_id'],
'peer_cidrs': [pcidr['cidr']
for pcidr in ipsec_site_conn['peer_cidrs']],
'local_ep_group_id': ipsec_site_conn['local_ep_group_id'],
'peer_ep_group_id': ipsec_site_conn['peer_ep_group_id'],
}
return self._fields(res, fields)
def get_endpoint_info(self, context, ipsec_sitecon):
"""Obtain all endpoint info, and store in connection for validation."""
ipsec_sitecon['local_epg_subnets'] = self.get_endpoint_group(
context, ipsec_sitecon['local_ep_group_id'])
ipsec_sitecon['peer_epg_cidrs'] = self.get_endpoint_group(
context, ipsec_sitecon['peer_ep_group_id'])
def validate_connection_info(self, context, validator, ipsec_sitecon,
vpnservice):
"""Collect info and validate connection.
If endpoint groups used (default), collect the group info and
do not specify the IP version (as it will come from endpoints).
Otherwise, get the IP version from the (legacy) subnet for
validation purposes.
NOTE: Once the deprecated subnet is removed, the caller can just
call get_endpoint_info() and validate_ipsec_site_connection().
"""
if ipsec_sitecon['local_ep_group_id']:
self.get_endpoint_info(context, ipsec_sitecon)
ip_version = None
else:
ip_version = vpnservice.subnet.ip_version
validator.validate_ipsec_site_connection(context, ipsec_sitecon,
ip_version, vpnservice)
def create_ipsec_site_connection(self, context, ipsec_site_connection):
ipsec_sitecon = ipsec_site_connection['ipsec_site_connection']
validator = self._get_validator()
validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon)
with context.session.begin(subtransactions=True):
#Check permissions
vpnservice_id = ipsec_sitecon['vpnservice_id']
self._get_resource(context, vpn_models.VPNService, vpnservice_id)
self._get_resource(context, vpn_models.IKEPolicy,
ipsec_sitecon['ikepolicy_id'])
self._get_resource(context, vpn_models.IPsecPolicy,
ipsec_sitecon['ipsecpolicy_id'])
vpnservice = self._get_vpnservice(context, vpnservice_id)
validator.validate_ipsec_conn_optional_args(ipsec_sitecon,
vpnservice.subnet)
self.validate_connection_info(context, validator, ipsec_sitecon,
vpnservice)
validator.resolve_peer_address(ipsec_sitecon, vpnservice.router)
ipsec_site_conn_db = vpn_models.IPsecSiteConnection(
id=uuidutils.generate_uuid(),
tenant_id=ipsec_sitecon['tenant_id'],
name=ipsec_sitecon['name'],
description=ipsec_sitecon['description'],
peer_address=ipsec_sitecon['peer_address'],
peer_id=ipsec_sitecon['peer_id'],
local_id=ipsec_sitecon['local_id'],
route_mode='static',
mtu=ipsec_sitecon['mtu'],
auth_mode='psk',
psk=ipsec_sitecon['psk'],
initiator=ipsec_sitecon['initiator'],
dpd_action=ipsec_sitecon['dpd_action'],
dpd_interval=ipsec_sitecon['dpd_interval'],
dpd_timeout=ipsec_sitecon['dpd_timeout'],
admin_state_up=ipsec_sitecon['admin_state_up'],
status=lib_constants.PENDING_CREATE,
vpnservice_id=vpnservice_id,
ikepolicy_id=ipsec_sitecon['ikepolicy_id'],
ipsecpolicy_id=ipsec_sitecon['ipsecpolicy_id'],
local_ep_group_id=ipsec_sitecon['local_ep_group_id'],
peer_ep_group_id=ipsec_sitecon['peer_ep_group_id']
)
context.session.add(ipsec_site_conn_db)
for cidr in ipsec_sitecon['peer_cidrs']:
peer_cidr_db = vpn_models.IPsecPeerCidr(
cidr=cidr,
ipsec_site_connection_id=ipsec_site_conn_db['id']
)
context.session.add(peer_cidr_db)
return self._make_ipsec_site_connection_dict(ipsec_site_conn_db)
def update_ipsec_site_connection(
self, context,
ipsec_site_conn_id, ipsec_site_connection):
ipsec_sitecon = ipsec_site_connection['ipsec_site_connection']
changed_peer_cidrs = False
validator = self._get_validator()
with context.session.begin(subtransactions=True):
ipsec_site_conn_db = self._get_resource(
context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id)
vpnservice_id = ipsec_site_conn_db['vpnservice_id']
vpnservice = self._get_vpnservice(context, vpnservice_id)
validator.assign_sensible_ipsec_sitecon_defaults(
ipsec_sitecon, ipsec_site_conn_db)
validator.validate_ipsec_conn_optional_args(ipsec_sitecon,
vpnservice.subnet)
self.validate_connection_info(context, validator, ipsec_sitecon,
vpnservice)
if 'peer_address' in ipsec_sitecon:
validator.resolve_peer_address(ipsec_sitecon,
vpnservice.router)
self.assert_update_allowed(ipsec_site_conn_db)
if "peer_cidrs" in ipsec_sitecon:
changed_peer_cidrs = True
old_peer_cidr_list = ipsec_site_conn_db['peer_cidrs']
old_peer_cidr_dict = dict(
(peer_cidr['cidr'], peer_cidr)
for peer_cidr in old_peer_cidr_list)
new_peer_cidr_set = set(ipsec_sitecon["peer_cidrs"])
old_peer_cidr_set = set(old_peer_cidr_dict)
new_peer_cidrs = list(new_peer_cidr_set)
for peer_cidr in old_peer_cidr_set - new_peer_cidr_set:
context.session.delete(old_peer_cidr_dict[peer_cidr])
for peer_cidr in new_peer_cidr_set - old_peer_cidr_set:
pcidr = vpn_models.IPsecPeerCidr(
cidr=peer_cidr,
ipsec_site_connection_id=ipsec_site_conn_id)
context.session.add(pcidr)
# Note: Unconditionally remove peer_cidrs, as they will be set to
# previous, if unchanged (to be able to validate above).
del ipsec_sitecon["peer_cidrs"]
if ipsec_sitecon:
ipsec_site_conn_db.update(ipsec_sitecon)
result = self._make_ipsec_site_connection_dict(ipsec_site_conn_db)
if changed_peer_cidrs:
result['peer_cidrs'] = new_peer_cidrs
return result
def delete_ipsec_site_connection(self, context, ipsec_site_conn_id):
with context.session.begin(subtransactions=True):
ipsec_site_conn_db = self._get_resource(
context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id)
context.session.delete(ipsec_site_conn_db)
def _get_ipsec_site_connection(
self, context, ipsec_site_conn_id):
return self._get_resource(
context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id)
def get_ipsec_site_connection(self, context,
ipsec_site_conn_id, fields=None):
ipsec_site_conn_db = self._get_ipsec_site_connection(
context, ipsec_site_conn_id)
return self._make_ipsec_site_connection_dict(
ipsec_site_conn_db, fields)
def get_ipsec_site_connections(self, context, filters=None, fields=None):
return self._get_collection(context, vpn_models.IPsecSiteConnection,
self._make_ipsec_site_connection_dict,
filters=filters, fields=fields)
def update_ipsec_site_conn_status(self, context, conn_id, new_status):
with context.session.begin():
self._update_connection_status(context, conn_id, new_status, True)
def _update_connection_status(self, context, conn_id, new_status,
updated_pending):
"""Update the connection status, if changed.
If the connection is not in a pending state, unconditionally update
the status. Likewise, if in a pending state, and have an indication
that the status has changed, then update the database.
"""
try:
conn_db = self._get_ipsec_site_connection(context, conn_id)
except vpnaas.IPsecSiteConnectionNotFound:
return
if not utils.in_pending_status(conn_db.status) or updated_pending:
conn_db.status = new_status
def _make_ikepolicy_dict(self, ikepolicy, fields=None):
res = {'id': ikepolicy['id'],
'tenant_id': ikepolicy['tenant_id'],
'name': ikepolicy['name'],
'description': ikepolicy['description'],
'auth_algorithm': ikepolicy['auth_algorithm'],
'encryption_algorithm': ikepolicy['encryption_algorithm'],
'phase1_negotiation_mode': ikepolicy['phase1_negotiation_mode'],
'lifetime': {
'units': ikepolicy['lifetime_units'],
'value': ikepolicy['lifetime_value'],
},
'ike_version': ikepolicy['ike_version'],
'pfs': ikepolicy['pfs']
}
return self._fields(res, fields)
def create_ikepolicy(self, context, ikepolicy):
ike = ikepolicy['ikepolicy']
validator = self._get_validator()
lifetime_info = ike['lifetime']
lifetime_units = lifetime_info.get('units', 'seconds')
lifetime_value = lifetime_info.get('value', 3600)
with context.session.begin(subtransactions=True):
validator.validate_ike_policy(context, ike)
ike_db = vpn_models.IKEPolicy(
id=uuidutils.generate_uuid(),
tenant_id=ike['tenant_id'],
name=ike['name'],
description=ike['description'],
auth_algorithm=ike['auth_algorithm'],
encryption_algorithm=ike['encryption_algorithm'],
phase1_negotiation_mode=ike['phase1_negotiation_mode'],
lifetime_units=lifetime_units,
lifetime_value=lifetime_value,
ike_version=ike['ike_version'],
pfs=ike['pfs']
)
context.session.add(ike_db)
return self._make_ikepolicy_dict(ike_db)
def update_ikepolicy(self, context, ikepolicy_id, ikepolicy):
ike = ikepolicy['ikepolicy']
validator = self._get_validator()
with context.session.begin(subtransactions=True):
validator.validate_ike_policy(context, ike)
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
ikepolicy_id=ikepolicy_id).first():
raise vpnaas.IKEPolicyInUse(ikepolicy_id=ikepolicy_id)
ike_db = self._get_resource(
context, vpn_models.IKEPolicy, ikepolicy_id)
if ike:
lifetime_info = ike.get('lifetime')
if lifetime_info:
if lifetime_info.get('units'):
ike['lifetime_units'] = lifetime_info['units']
if lifetime_info.get('value'):
ike['lifetime_value'] = lifetime_info['value']
ike_db.update(ike)
return self._make_ikepolicy_dict(ike_db)
def delete_ikepolicy(self, context, ikepolicy_id):
with context.session.begin(subtransactions=True):
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
ikepolicy_id=ikepolicy_id).first():
raise vpnaas.IKEPolicyInUse(ikepolicy_id=ikepolicy_id)
ike_db = self._get_resource(
context, vpn_models.IKEPolicy, ikepolicy_id)
context.session.delete(ike_db)
def get_ikepolicy(self, context, ikepolicy_id, fields=None):
ike_db = self._get_resource(
context, vpn_models.IKEPolicy, ikepolicy_id)
return self._make_ikepolicy_dict(ike_db, fields)
def get_ikepolicies(self, context, filters=None, fields=None):
return self._get_collection(context, vpn_models.IKEPolicy,
self._make_ikepolicy_dict,
filters=filters, fields=fields)
def _make_ipsecpolicy_dict(self, ipsecpolicy, fields=None):
res = {'id': ipsecpolicy['id'],
'tenant_id': ipsecpolicy['tenant_id'],
'name': ipsecpolicy['name'],
'description': ipsecpolicy['description'],
'transform_protocol': ipsecpolicy['transform_protocol'],
'auth_algorithm': ipsecpolicy['auth_algorithm'],
'encryption_algorithm': ipsecpolicy['encryption_algorithm'],
'encapsulation_mode': ipsecpolicy['encapsulation_mode'],
'lifetime': {
'units': ipsecpolicy['lifetime_units'],
'value': ipsecpolicy['lifetime_value'],
},
'pfs': ipsecpolicy['pfs']
}
return self._fields(res, fields)
def create_ipsecpolicy(self, context, ipsecpolicy):
ipsecp = ipsecpolicy['ipsecpolicy']
validator = self._get_validator()
lifetime_info = ipsecp['lifetime']
lifetime_units = lifetime_info.get('units', 'seconds')
lifetime_value = lifetime_info.get('value', 3600)
with context.session.begin(subtransactions=True):
validator.validate_ipsec_policy(context, ipsecp)
ipsecp_db = vpn_models.IPsecPolicy(
id=uuidutils.generate_uuid(),
tenant_id=ipsecp['tenant_id'],
name=ipsecp['name'],
description=ipsecp['description'],
transform_protocol=ipsecp['transform_protocol'],
auth_algorithm=ipsecp['auth_algorithm'],
encryption_algorithm=ipsecp['encryption_algorithm'],
encapsulation_mode=ipsecp['encapsulation_mode'],
lifetime_units=lifetime_units,
lifetime_value=lifetime_value,
pfs=ipsecp['pfs'])
context.session.add(ipsecp_db)
return self._make_ipsecpolicy_dict(ipsecp_db)
def update_ipsecpolicy(self, context, ipsecpolicy_id, ipsecpolicy):
ipsecp = ipsecpolicy['ipsecpolicy']
validator = self._get_validator()
with context.session.begin(subtransactions=True):
validator.validate_ipsec_policy(context, ipsecp)
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
ipsecpolicy_id=ipsecpolicy_id).first():
raise vpnaas.IPsecPolicyInUse(ipsecpolicy_id=ipsecpolicy_id)
ipsecp_db = self._get_resource(
context, vpn_models.IPsecPolicy, ipsecpolicy_id)
if ipsecp:
lifetime_info = ipsecp.get('lifetime')
if lifetime_info:
if lifetime_info.get('units'):
ipsecp['lifetime_units'] = lifetime_info['units']
if lifetime_info.get('value'):
ipsecp['lifetime_value'] = lifetime_info['value']
ipsecp_db.update(ipsecp)
return self._make_ipsecpolicy_dict(ipsecp_db)
def delete_ipsecpolicy(self, context, ipsecpolicy_id):
with context.session.begin(subtransactions=True):
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
ipsecpolicy_id=ipsecpolicy_id).first():
raise vpnaas.IPsecPolicyInUse(ipsecpolicy_id=ipsecpolicy_id)
ipsec_db = self._get_resource(
context, vpn_models.IPsecPolicy, ipsecpolicy_id)
context.session.delete(ipsec_db)
def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None):
ipsec_db = self._get_resource(
context, vpn_models.IPsecPolicy, ipsecpolicy_id)
return self._make_ipsecpolicy_dict(ipsec_db, fields)
def get_ipsecpolicies(self, context, filters=None, fields=None):
return self._get_collection(context, vpn_models.IPsecPolicy,
self._make_ipsecpolicy_dict,
filters=filters, fields=fields)
def _make_vpnservice_dict(self, vpnservice, fields=None):
res = {'id': vpnservice['id'],
'name': vpnservice['name'],
'description': vpnservice['description'],
'tenant_id': vpnservice['tenant_id'],
'subnet_id': vpnservice['subnet_id'],
'router_id': vpnservice['router_id'],
'flavor_id': vpnservice['flavor_id'],
'admin_state_up': vpnservice['admin_state_up'],
'external_v4_ip': vpnservice['external_v4_ip'],
'external_v6_ip': vpnservice['external_v6_ip'],
'status': vpnservice['status']}
return self._fields(res, fields)
def create_vpnservice(self, context, vpnservice):
vpns = vpnservice['vpnservice']
flavor_id = vpns.get('flavor_id', None)
validator = self._get_validator()
with context.session.begin(subtransactions=True):
validator.validate_vpnservice(context, vpns)
vpnservice_db = vpn_models.VPNService(
id=uuidutils.generate_uuid(),
tenant_id=vpns['tenant_id'],
name=vpns['name'],
description=vpns['description'],
subnet_id=vpns['subnet_id'],
router_id=vpns['router_id'],
flavor_id=flavor_id,
admin_state_up=vpns['admin_state_up'],
status=lib_constants.PENDING_CREATE)
context.session.add(vpnservice_db)
return self._make_vpnservice_dict(vpnservice_db)
def set_external_tunnel_ips(self, context, vpnservice_id, v4_ip=None,
v6_ip=None):
"""Update the external tunnel IP(s) for service."""
vpns = {'external_v4_ip': v4_ip, 'external_v6_ip': v6_ip}
with context.session.begin(subtransactions=True):
vpns_db = self._get_resource(context, vpn_models.VPNService,
vpnservice_id)
vpns_db.update(vpns)
return self._make_vpnservice_dict(vpns_db)
def update_vpnservice(self, context, vpnservice_id, vpnservice):
vpns = vpnservice['vpnservice']
with context.session.begin(subtransactions=True):
vpns_db = self._get_resource(context, vpn_models.VPNService,
vpnservice_id)
self.assert_update_allowed(vpns_db)
if vpns:
vpns_db.update(vpns)
return self._make_vpnservice_dict(vpns_db)
def delete_vpnservice(self, context, vpnservice_id):
with context.session.begin(subtransactions=True):
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
vpnservice_id=vpnservice_id
).first():
raise vpnaas.VPNServiceInUse(vpnservice_id=vpnservice_id)
vpns_db = self._get_resource(context, vpn_models.VPNService,
vpnservice_id)
context.session.delete(vpns_db)
def _get_vpnservice(self, context, vpnservice_id):
return self._get_resource(context, vpn_models.VPNService,
vpnservice_id)
def get_vpnservice(self, context, vpnservice_id, fields=None):
vpns_db = self._get_resource(context, vpn_models.VPNService,
vpnservice_id)
return self._make_vpnservice_dict(vpns_db, fields)
def get_vpnservices(self, context, filters=None, fields=None):
return self._get_collection(context, vpn_models.VPNService,
self._make_vpnservice_dict,
filters=filters, fields=fields)
def check_router_in_use(self, context, router_id):
vpnservices = self.get_vpnservices(
context, filters={'router_id': [router_id]})
if vpnservices:
plural = "s" if len(vpnservices) > 1 else ""
services = ",".join([v['id'] for v in vpnservices])
raise l3_exception.RouterInUse(
router_id=router_id,
reason="is currently used by VPN service%(plural)s "
"(%(services)s)" % {'plural': plural,
'services': services})
def check_subnet_in_use(self, context, subnet_id, router_id):
with context.session.begin(subtransactions=True):
vpnservices = context.session.query(
vpn_models.VPNService).filter_by(
subnet_id=subnet_id, router_id=router_id).first()
if vpnservices:
raise vpnaas.SubnetInUseByVPNService(
subnet_id=subnet_id,
vpnservice_id=vpnservices['id'])
def check_subnet_in_use_by_endpoint_group(self, context, subnet_id):
with context.session.begin(subtransactions=True):
query = context.session.query(vpn_models.VPNEndpointGroup)
query = query.filter(vpn_models.VPNEndpointGroup.endpoint_type ==
v_constants.SUBNET_ENDPOINT)
query = query.join(
vpn_models.VPNEndpoint,
sa.and_(vpn_models.VPNEndpoint.endpoint_group_id ==
vpn_models.VPNEndpointGroup.id,
vpn_models.VPNEndpoint.endpoint == subnet_id))
group = query.first()
if group:
raise vpnaas.SubnetInUseByEndpointGroup(
subnet_id=subnet_id, group_id=group['id'])
def _make_endpoint_group_dict(self, endpoint_group, fields=None):
res = {'id': endpoint_group['id'],
'tenant_id': endpoint_group['tenant_id'],
'name': endpoint_group['name'],
'description': endpoint_group['description'],
'type': endpoint_group['endpoint_type'],
'endpoints': [ep['endpoint']
for ep in endpoint_group['endpoints']]}
return self._fields(res, fields)
def create_endpoint_group(self, context, endpoint_group):
group = endpoint_group['endpoint_group']
validator = self._get_validator()
with context.session.begin(subtransactions=True):
validator.validate_endpoint_group(context, group)
endpoint_group_db = vpn_models.VPNEndpointGroup(
id=uuidutils.generate_uuid(),
tenant_id=group['tenant_id'],
name=group['name'],
description=group['description'],
endpoint_type=group['type'])
context.session.add(endpoint_group_db)
for endpoint in group['endpoints']:
endpoint_db = vpn_models.VPNEndpoint(
endpoint=endpoint,
endpoint_group_id=endpoint_group_db['id']
)
context.session.add(endpoint_db)
return self._make_endpoint_group_dict(endpoint_group_db)
def update_endpoint_group(self, context, endpoint_group_id,
endpoint_group):
group_changes = endpoint_group['endpoint_group']
# Note: Endpoints cannot be changed, so will not do validation
with context.session.begin(subtransactions=True):
endpoint_group_db = self._get_resource(context,
vpn_models.VPNEndpointGroup,
endpoint_group_id)
endpoint_group_db.update(group_changes)
return self._make_endpoint_group_dict(endpoint_group_db)
def delete_endpoint_group(self, context, endpoint_group_id):
with context.session.begin(subtransactions=True):
self.check_endpoint_group_not_in_use(context, endpoint_group_id)
endpoint_group_db = self._get_resource(
context, vpn_models.VPNEndpointGroup, endpoint_group_id)
context.session.delete(endpoint_group_db)
def get_endpoint_group(self, context, endpoint_group_id, fields=None):
endpoint_group_db = self._get_resource(
context, vpn_models.VPNEndpointGroup, endpoint_group_id)
return self._make_endpoint_group_dict(endpoint_group_db, fields)
def get_endpoint_groups(self, context, filters=None, fields=None):
return self._get_collection(context, vpn_models.VPNEndpointGroup,
self._make_endpoint_group_dict,
filters=filters, fields=fields)
def check_endpoint_group_not_in_use(self, context, group_id):
query = context.session.query(vpn_models.IPsecSiteConnection)
query = query.filter(
sa.or_(
vpn_models.IPsecSiteConnection.local_ep_group_id == group_id,
vpn_models.IPsecSiteConnection.peer_ep_group_id == group_id)
)
if query.first():
raise vpnaas.EndpointGroupInUse(group_id=group_id)
class VPNPluginRpcDbMixin(object):
def _build_local_subnet_cidr_map(self, context):
"""Build a dict of all local endpoint subnets, with list of CIDRs."""
query = context.session.query(models_v2.Subnet.id,
models_v2.Subnet.cidr)
query = query.join(vpn_models.VPNEndpoint,
vpn_models.VPNEndpoint.endpoint ==
models_v2.Subnet.id)
query = query.join(vpn_models.VPNEndpointGroup,
vpn_models.VPNEndpointGroup.id ==
vpn_models.VPNEndpoint.endpoint_group_id)
query = query.join(vpn_models.IPsecSiteConnection,
vpn_models.IPsecSiteConnection.local_ep_group_id ==
vpn_models.VPNEndpointGroup.id)
return {sn.id: sn.cidr for sn in query.all()}
def update_status_by_agent(self, context, service_status_info_list):
"""Updating vpnservice and vpnconnection status.
:param context: context variable
:param service_status_info_list: list of status
The structure is
[{id: vpnservice_id,
status: ACTIVE|DOWN|ERROR,
updated_pending_status: True|False
ipsec_site_connections: {
ipsec_site_connection_id: {
status: ACTIVE|DOWN|ERROR,
updated_pending_status: True|False
}
}]
The agent will set updated_pending_status as True,
when agent updates any pending status.
"""
with context.session.begin(subtransactions=True):
for vpnservice in service_status_info_list:
try:
vpnservice_db = self._get_vpnservice(
context, vpnservice['id'])
except vpnaas.VPNServiceNotFound:
LOG.warning('vpnservice %s in db is already deleted',
vpnservice['id'])
continue
if (not utils.in_pending_status(vpnservice_db.status)
or vpnservice['updated_pending_status']):
vpnservice_db.status = vpnservice['status']
for conn_id, conn in vpnservice[
'ipsec_site_connections'].items():
self._update_connection_status(
context, conn_id, conn['status'],
conn['updated_pending_status'])
def vpn_callback(resource, event, trigger, **kwargs):
vpn_plugin = directory.get_plugin(p_constants.VPN)
if vpn_plugin:
context = kwargs.get('context')
router_id = kwargs.get('router_id')
if resource == resources.ROUTER_GATEWAY:
vpn_plugin.check_router_in_use(context, router_id)
elif resource == resources.ROUTER_INTERFACE:
subnet_id = kwargs.get('subnet_id')
vpn_plugin.check_subnet_in_use(context, subnet_id, router_id)
def migration_callback(resource, event, trigger, **kwargs):
context = kwargs['context']
router = kwargs['router']
vpn_plugin = directory.get_plugin(p_constants.VPN)
if vpn_plugin:
vpn_plugin.check_router_in_use(context, router['id'])
return True
def subnet_callback(resource, event, trigger, **kwargs):
"""Respond to subnet based notifications - see if subnet in use."""
context = kwargs['context']
subnet_id = kwargs['subnet_id']
vpn_plugin = directory.get_plugin(p_constants.VPN)
if vpn_plugin:
vpn_plugin.check_subnet_in_use_by_endpoint_group(context, subnet_id)
def subscribe():
registry.subscribe(
vpn_callback, resources.ROUTER_GATEWAY, events.BEFORE_DELETE)
registry.subscribe(
vpn_callback, resources.ROUTER_INTERFACE, events.BEFORE_DELETE)
registry.subscribe(
migration_callback, resources.ROUTER, events.BEFORE_UPDATE)
registry.subscribe(
subnet_callback, resources.SUBNET, events.BEFORE_DELETE)
# NOTE(armax): multiple VPN service plugins (potentially out of tree) may
# inherit from vpn_db and may need the callbacks to be processed. Having an
# implicit subscription (through the module import) preserves the existing
# behavior, and at the same time it avoids fixing it manually in each and
# every vpn plugin outta there. That said, The subscription is also made
# explicitly in the reference vpn plugin. The subscription operation is
# idempotent so there is no harm in registering the same callback multiple
# times.
subscribe()

View File

@ -1,188 +0,0 @@
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.db import constants as db_const
from neutron_lib.db import model_base
import sqlalchemy as sa
from sqlalchemy import orm
from neutron.db.models import l3
from neutron.db import models_v2
from neutron_vpnaas.services.vpn.common import constants
class IPsecPeerCidr(model_base.BASEV2):
"""Internal representation of a IPsec Peer Cidrs."""
cidr = sa.Column(sa.String(32), nullable=False, primary_key=True)
ipsec_site_connection_id = sa.Column(
sa.String(36),
sa.ForeignKey('ipsec_site_connections.id',
ondelete="CASCADE"),
primary_key=True)
class IPsecPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject):
"""Represents a v2 IPsecPolicy Object."""
__tablename__ = 'ipsecpolicies'
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
transform_protocol = sa.Column(sa.Enum("esp", "ah", "ah-esp",
name="ipsec_transform_protocols"),
nullable=False)
auth_algorithm = sa.Column(sa.Enum("sha1", "sha256",
"sha384", "sha512",
name="vpn_auth_algorithms"),
nullable=False)
encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128",
"aes-256", "aes-192",
name="vpn_encrypt_algorithms"),
nullable=False)
encapsulation_mode = sa.Column(sa.Enum("tunnel", "transport",
name="ipsec_encapsulations"),
nullable=False)
lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes",
name="vpn_lifetime_units"),
nullable=False)
lifetime_value = sa.Column(sa.Integer, nullable=False)
pfs = sa.Column(sa.Enum("group2", "group5", "group14",
name="vpn_pfs"), nullable=False)
class IKEPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject):
"""Represents a v2 IKEPolicy Object."""
__tablename__ = 'ikepolicies'
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
auth_algorithm = sa.Column(sa.Enum("sha1", "sha256",
"sha384", "sha512",
name="vpn_auth_algorithms"),
nullable=False)
encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128",
"aes-256", "aes-192",
name="vpn_encrypt_algorithms"),
nullable=False)
phase1_negotiation_mode = sa.Column(sa.Enum("main",
name="ike_phase1_mode"),
nullable=False)
lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes",
name="vpn_lifetime_units"),
nullable=False)
lifetime_value = sa.Column(sa.Integer, nullable=False)
ike_version = sa.Column(sa.Enum("v1", "v2", name="ike_versions"),
nullable=False)
pfs = sa.Column(sa.Enum("group2", "group5", "group14",
name="vpn_pfs"), nullable=False)
class IPsecSiteConnection(model_base.BASEV2, model_base.HasId,
model_base.HasProject):
"""Represents a IPsecSiteConnection Object."""
__tablename__ = 'ipsec_site_connections'
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
peer_address = sa.Column(sa.String(255), nullable=False)
peer_id = sa.Column(sa.String(255), nullable=False)
local_id = sa.Column(sa.String(255), nullable=True)
route_mode = sa.Column(sa.String(8), nullable=False)
mtu = sa.Column(sa.Integer, nullable=False)
initiator = sa.Column(sa.Enum("bi-directional", "response-only",
name="vpn_initiators"), nullable=False)
auth_mode = sa.Column(sa.String(16), nullable=False)
psk = sa.Column(sa.String(255), nullable=False)
dpd_action = sa.Column(sa.Enum("hold", "clear",
"restart", "disabled",
"restart-by-peer", name="vpn_dpd_actions"),
nullable=False)
dpd_interval = sa.Column(sa.Integer, nullable=False)
dpd_timeout = sa.Column(sa.Integer, nullable=False)
status = sa.Column(sa.String(16), nullable=False)
admin_state_up = sa.Column(sa.Boolean(), nullable=False)
vpnservice_id = sa.Column(sa.String(36),
sa.ForeignKey('vpnservices.id'),
nullable=False)
ipsecpolicy_id = sa.Column(sa.String(36),
sa.ForeignKey('ipsecpolicies.id'),
nullable=False)
ikepolicy_id = sa.Column(sa.String(36),
sa.ForeignKey('ikepolicies.id'),
nullable=False)
ipsecpolicy = orm.relationship(
IPsecPolicy, backref='ipsec_site_connection')
ikepolicy = orm.relationship(IKEPolicy, backref='ipsec_site_connection')
peer_cidrs = orm.relationship(IPsecPeerCidr,
backref='ipsec_site_connection',
lazy='joined',
cascade='all, delete, delete-orphan')
local_ep_group_id = sa.Column(sa.String(36),
sa.ForeignKey('vpn_endpoint_groups.id'))
peer_ep_group_id = sa.Column(sa.String(36),
sa.ForeignKey('vpn_endpoint_groups.id'))
local_ep_group = orm.relationship("VPNEndpointGroup",
foreign_keys=local_ep_group_id)
peer_ep_group = orm.relationship("VPNEndpointGroup",
foreign_keys=peer_ep_group_id)
class VPNService(model_base.BASEV2, model_base.HasId, model_base.HasProject):
"""Represents a v2 VPNService Object."""
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
status = sa.Column(sa.String(16), nullable=False)
admin_state_up = sa.Column(sa.Boolean(), nullable=False)
external_v4_ip = sa.Column(sa.String(16))
external_v6_ip = sa.Column(sa.String(64))
subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id'))
router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'),
nullable=False)
subnet = orm.relationship(models_v2.Subnet)
router = orm.relationship(l3.Router)
ipsec_site_connections = orm.relationship(
IPsecSiteConnection,
backref='vpnservice',
cascade="all, delete-orphan")
flavor_id = sa.Column(sa.String(36), sa.ForeignKey(
'flavors.id', name='fk_vpnservices_flavors_id'))
class VPNEndpoint(model_base.BASEV2):
"""Endpoints used in VPN connections.
All endpoints in a group must be of the same type. Note: the endpoint
is an 'opaque' field used to hold different endpoint types, and be
flexible enough to use for future types.
"""
__tablename__ = 'vpn_endpoints'
endpoint = sa.Column(sa.String(255), nullable=False, primary_key=True)
endpoint_group_id = sa.Column(sa.String(36),
sa.ForeignKey('vpn_endpoint_groups.id',
ondelete="CASCADE"),
primary_key=True)
class VPNEndpointGroup(model_base.BASEV2, model_base.HasId,
model_base.HasProject):
"""Collection of endpoints of a specific type, for VPN connections."""
__tablename__ = 'vpn_endpoint_groups'
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
endpoint_type = sa.Column(sa.Enum(*constants.VPN_SUPPORTED_ENDPOINT_TYPES,
name="vpn_endpoint_type"),
nullable=False)
endpoints = orm.relationship(VPNEndpoint,
backref='endpoint_group',
lazy='joined',
cascade='all, delete, delete-orphan')

View File

@ -1,346 +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 socket
import netaddr
from neutron.db import l3_db
from neutron.db import models_v2
from neutron_lib.api import validators
from neutron_lib import constants as const
from neutron_lib import exceptions as nexception
from neutron_lib.plugins import directory
from neutron_vpnaas._i18n import _
from neutron_vpnaas.extensions import vpnaas
from neutron_vpnaas.services.vpn.common import constants
class VpnReferenceValidator(object):
"""
Baseline validation routines for VPN resources.
The validations here should be common to all VPN service providers and
only raise exceptions from neutron_vpnaas.extensions.vpnaas.
"""
IP_MIN_MTU = {4: 68, 6: 1280}
@property
def l3_plugin(self):
try:
return self._l3_plugin
except AttributeError:
self._l3_plugin = directory.get_plugin(const.L3)
return self._l3_plugin
@property
def core_plugin(self):
try:
return self._core_plugin
except AttributeError:
self._core_plugin = directory.get_plugin()
return self._core_plugin
def _check_dpd(self, ipsec_sitecon):
"""Ensure that DPD timeout is greater than DPD interval."""
if ipsec_sitecon['dpd_timeout'] <= ipsec_sitecon['dpd_interval']:
raise vpnaas.IPsecSiteConnectionDpdIntervalValueError(
attr='dpd_timeout')
def _check_mtu(self, context, mtu, ip_version):
if mtu < VpnReferenceValidator.IP_MIN_MTU[ip_version]:
raise vpnaas.IPsecSiteConnectionMtuError(mtu=mtu,
version=ip_version)
def _validate_peer_address(self, ip_version, router):
# NOTE: peer_address ip version should match with
# at least one external gateway address ip version.
# ipsec won't work with IPv6 LLA and neutron unaware GUA.
# So to support vpnaas with ipv6, external network must
# have ipv6 subnet
for fixed_ip in router.gw_port['fixed_ips']:
addr = fixed_ip['ip_address']
if ip_version == netaddr.IPAddress(addr).version:
return
raise vpnaas.ExternalNetworkHasNoSubnet(
router_id=router.id,
ip_version="IPv6" if ip_version == 6 else "IPv4")
def resolve_peer_address(self, ipsec_sitecon, router):
address = ipsec_sitecon['peer_address']
# check if address is an ip address or fqdn
invalid_ip_address = validators.validate_ip_address(address)
if invalid_ip_address:
# resolve fqdn
try:
addrinfo = socket.getaddrinfo(address, None)[0]
ipsec_sitecon['peer_address'] = addrinfo[-1][0]
except socket.gaierror:
raise vpnaas.VPNPeerAddressNotResolved(peer_address=address)
ip_version = netaddr.IPAddress(ipsec_sitecon['peer_address']).version
self._validate_peer_address(ip_version, router)
def _get_local_subnets(self, context, endpoint_group):
if endpoint_group['type'] != constants.SUBNET_ENDPOINT:
raise vpnaas.WrongEndpointGroupType(
group_type=endpoint_group['type'], which=endpoint_group['id'],
expected=constants.SUBNET_ENDPOINT)
subnet_ids = endpoint_group['endpoints']
return context.session.query(models_v2.Subnet).filter(
models_v2.Subnet.id.in_(subnet_ids)).all()
def _get_peer_cidrs(self, endpoint_group):
if endpoint_group['type'] != constants.CIDR_ENDPOINT:
raise vpnaas.WrongEndpointGroupType(
group_type=endpoint_group['type'], which=endpoint_group['id'],
expected=constants.CIDR_ENDPOINT)
return endpoint_group['endpoints']
def _check_local_endpoint_ip_versions(self, group_id, local_subnets):
"""Ensure all subnets in endpoint group have the same IP version.
Will return the IP version, so it can be used for inter-group testing.
"""
if len(local_subnets) == 1:
return local_subnets[0]['ip_version']
ip_versions = set([subnet['ip_version'] for subnet in local_subnets])
if len(ip_versions) > 1:
raise vpnaas.MixedIPVersionsForIPSecEndpoints(group=group_id)
return ip_versions.pop()
def _check_peer_endpoint_ip_versions(self, group_id, peer_cidrs):
"""Ensure all CIDRs in endpoint group have the same IP version.
Will return the IP version, so it can be used for inter-group testing.
"""
if len(peer_cidrs) == 1:
return netaddr.IPNetwork(peer_cidrs[0]).version
ip_versions = set([netaddr.IPNetwork(pc).version for pc in peer_cidrs])
if len(ip_versions) > 1:
raise vpnaas.MixedIPVersionsForIPSecEndpoints(group=group_id)
return ip_versions.pop()
def _check_peer_cidrs(self, peer_cidrs):
"""Ensure all CIDRs have the valid format."""
for peer_cidr in peer_cidrs:
msg = validators.validate_subnet(peer_cidr)
if msg:
raise vpnaas.IPsecSiteConnectionPeerCidrError(
peer_cidr=peer_cidr)
def _check_peer_cidrs_ip_versions(self, peer_cidrs):
"""Ensure all CIDRs have the same IP version."""
if len(peer_cidrs) == 1:
return netaddr.IPNetwork(peer_cidrs[0]).version
ip_versions = set([netaddr.IPNetwork(pc).version for pc in peer_cidrs])
if len(ip_versions) > 1:
raise vpnaas.MixedIPVersionsForPeerCidrs()
return ip_versions.pop()
def _check_local_subnets_on_router(self, context, router, local_subnets):
for subnet in local_subnets:
self._check_subnet_id(context, router, subnet['id'])
def _validate_compatible_ip_versions(self, local_ip_version,
peer_ip_version):
if local_ip_version != peer_ip_version:
raise vpnaas.MixedIPVersionsForIPSecConnection()
def validate_ipsec_conn_optional_args(self, ipsec_sitecon, subnet):
"""Ensure that proper combinations of optional args are used.
When VPN service has a subnet, then we must have peer_cidrs, and
cannot have any endpoint groups. If no subnet for the service, then
we must have both endpoint groups, and no peer_cidrs. Method will
form a string indicating which endpoints are incorrect, for any
exception raised.
"""
local_epg_id = ipsec_sitecon.get('local_ep_group_id')
peer_epg_id = ipsec_sitecon.get('peer_ep_group_id')
peer_cidrs = ipsec_sitecon.get('peer_cidrs')
if subnet:
if not peer_cidrs:
raise vpnaas.MissingPeerCidrs()
epgs = []
if local_epg_id:
epgs.append('local')
if peer_epg_id:
epgs.append('peer')
if epgs:
which = ' and '.join(epgs)
suffix = 's' if len(epgs) > 1 else ''
raise vpnaas.InvalidEndpointGroup(which=which, suffix=suffix)
else:
if peer_cidrs:
raise vpnaas.PeerCidrsInvalid()
epgs = []
if not local_epg_id:
epgs.append('local')
if not peer_epg_id:
epgs.append('peer')
if epgs:
which = ' and '.join(epgs)
suffix = 's' if len(epgs) > 1 else ''
raise vpnaas.MissingRequiredEndpointGroup(which=which,
suffix=suffix)
def assign_sensible_ipsec_sitecon_defaults(self, ipsec_sitecon,
prev_conn=None):
"""Provide defaults for optional items, if missing.
With endpoint groups capabilities, the peer_cidr (legacy mode)
and endpoint group IDs (new mode), are optional. For updating,
we need to provide the previous values for any missing values,
so that we can detect if the update request is attempting to
mix modes.
Flatten the nested DPD information, and set default values for
any missing information. For connection updates, the previous
values will be used as defaults for any missing items.
"""
if prev_conn:
ipsec_sitecon.setdefault(
'peer_cidrs', [pc['cidr'] for pc in prev_conn['peer_cidrs']])
ipsec_sitecon.setdefault('local_ep_group_id',
prev_conn['local_ep_group_id'])
ipsec_sitecon.setdefault('peer_ep_group_id',
prev_conn['peer_ep_group_id'])
else:
prev_conn = {'dpd_action': 'hold',
'dpd_interval': 30,
'dpd_timeout': 120}
dpd = ipsec_sitecon.get('dpd', {})
ipsec_sitecon['dpd_action'] = dpd.get('action',
prev_conn['dpd_action'])
ipsec_sitecon['dpd_interval'] = dpd.get('interval',
prev_conn['dpd_interval'])
ipsec_sitecon['dpd_timeout'] = dpd.get('timeout',
prev_conn['dpd_timeout'])
def validate_ipsec_site_connection(self, context, ipsec_sitecon,
local_ip_version, vpnservice=None):
"""Reference implementation of validation for IPSec connection.
This makes sure that IP versions are the same. For endpoint groups,
we use the local subnet(s) IP versions, and peer CIDR(s) IP versions.
For legacy mode, we use the (sole) subnet IP version, and the peer
CIDR(s). All IP versions must be the same.
This method also checks peer_cidrs format(legacy mode),
MTU (based on the local IP version), and DPD settings.
"""
if not local_ip_version:
# Using endpoint groups
local_subnets = self._get_local_subnets(
context, ipsec_sitecon['local_epg_subnets'])
self._check_local_subnets_on_router(
context, vpnservice['router_id'], local_subnets)
local_ip_version = self._check_local_endpoint_ip_versions(
ipsec_sitecon['local_ep_group_id'], local_subnets)
peer_cidrs = self._get_peer_cidrs(ipsec_sitecon['peer_epg_cidrs'])
peer_ip_version = self._check_peer_endpoint_ip_versions(
ipsec_sitecon['peer_ep_group_id'], peer_cidrs)
else:
self._check_peer_cidrs(ipsec_sitecon['peer_cidrs'])
peer_ip_version = self._check_peer_cidrs_ip_versions(
ipsec_sitecon['peer_cidrs'])
self._validate_compatible_ip_versions(local_ip_version,
peer_ip_version)
self._check_dpd(ipsec_sitecon)
mtu = ipsec_sitecon.get('mtu')
if mtu:
self._check_mtu(context, mtu, local_ip_version)
def _check_router(self, context, router_id):
router = self.l3_plugin.get_router(context, router_id)
if not router.get(l3_db.EXTERNAL_GW_INFO):
raise vpnaas.RouterIsNotExternal(router_id=router_id)
def _check_subnet_id(self, context, router_id, subnet_id):
ports = self.core_plugin.get_ports(
context,
filters={
'fixed_ips': {'subnet_id': [subnet_id]},
'device_id': [router_id]})
if not ports:
raise vpnaas.SubnetIsNotConnectedToRouter(
subnet_id=subnet_id,
router_id=router_id)
def validate_vpnservice(self, context, vpnservice):
self._check_router(context, vpnservice['router_id'])
if vpnservice['subnet_id'] is not None:
self._check_subnet_id(context, vpnservice['router_id'],
vpnservice['subnet_id'])
def validate_ipsec_policy(self, context, ipsec_policy):
"""Reference implementation of validation for IPSec Policy.
Service driver can override and implement specific logic
for IPSec Policy validation.
"""
pass
def _validate_cidrs(self, cidrs):
"""Ensure valid IPv4/6 CIDRs."""
for cidr in cidrs:
msg = validators.validate_subnet(cidr)
if msg:
raise vpnaas.InvalidEndpointInEndpointGroup(
group_type=constants.CIDR_ENDPOINT, endpoint=cidr,
why=_("Invalid CIDR"))
def _validate_subnets(self, context, subnet_ids):
"""Ensure UUIDs OK and subnets exist."""
for subnet_id in subnet_ids:
msg = validators.validate_uuid(subnet_id)
if msg:
raise vpnaas.InvalidEndpointInEndpointGroup(
group_type=constants.SUBNET_ENDPOINT, endpoint=subnet_id,
why=_('Invalid UUID'))
try:
self.core_plugin.get_subnet(context, subnet_id)
except nexception.SubnetNotFound:
raise vpnaas.NonExistingSubnetInEndpointGroup(
subnet=subnet_id)
def validate_endpoint_group(self, context, endpoint_group):
"""Reference validator for endpoint group.
Ensures that there is at least one endpoint, all the endpoints in the
group are of the same type, and that the endpoints are "valid".
Note: Only called for create, as endpoints cannot be changed.
"""
endpoints = endpoint_group['endpoints']
if not endpoints:
raise vpnaas.MissingEndpointForEndpointGroup(group=endpoint_group)
group_type = endpoint_group['type']
if group_type == constants.CIDR_ENDPOINT:
self._validate_cidrs(endpoints)
elif group_type == constants.SUBNET_ENDPOINT:
self._validate_subnets(context, endpoints)
def validate_ike_policy(self, context, ike_policy):
"""Reference implementation of validation for IKE Policy.
Service driver can override and implement specific logic
for IKE Policy validation.
"""
pass

View File

@ -1,124 +0,0 @@
# (c) Copyright 2015 NEC Corporation, All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
from neutron.api.v2 import resource_helper
from neutron_lib.api import converters
from neutron_lib.api import extensions
from neutron_lib.db import constants as db_const
from neutron_lib.plugins import constants as nconstants
from neutron_vpnaas.services.vpn.common import constants
RESOURCE_ATTRIBUTE_MAP = {
'endpoint_groups': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {
'type:string': db_const.PROJECT_ID_FIELD_SIZE},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': db_const.NAME_FIELD_SIZE},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {
'type:string': db_const.DESCRIPTION_FIELD_SIZE},
'is_visible': True, 'default': ''},
'type': {'allow_post': True, 'allow_put': False,
'validate': {
'type:values': constants.VPN_SUPPORTED_ENDPOINT_TYPES,
},
'is_visible': True},
'endpoints': {'allow_post': True, 'allow_put': False,
'convert_to': converters.convert_to_list,
'is_visible': True},
},
}
class Vpn_endpoint_groups(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "VPN Endpoint Groups"
@classmethod
def get_alias(cls):
return "vpn-endpoint-groups"
@classmethod
def get_description(cls):
return "VPN endpoint groups support"
@classmethod
def get_updated(cls):
return "2015-08-04T10:00:00-00:00"
@classmethod
def get_resources(cls):
plural_mappings = resource_helper.build_plural_mappings(
{}, RESOURCE_ATTRIBUTE_MAP)
return resource_helper.build_resource_info(plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
nconstants.VPN,
register_quota=True,
translate_name=True)
def get_required_extensions(self):
return ["vpnaas"]
def update_attributes_map(self, attributes):
super(Vpn_endpoint_groups, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}
@six.add_metaclass(abc.ABCMeta)
class VPNEndpointGroupsPluginBase(object):
@abc.abstractmethod
def create_endpoint_group(self, context, endpoint_group):
pass
@abc.abstractmethod
def update_endpoint_group(self, context, endpoint_group_id,
endpoint_group):
pass
@abc.abstractmethod
def delete_endpoint_group(self, context, endpoint_group_id):
pass
@abc.abstractmethod
def get_endpoint_group(self, context, endpoint_group_id, fields=None):
pass
@abc.abstractmethod
def get_endpoint_groups(self, context, filters=None, fields=None):
pass

View File

@ -1,68 +0,0 @@
# Copyright 2017 Eayun, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from neutron_lib.api import extensions
from neutron_lib import exceptions as nexception
from neutron_vpnaas._i18n import _
class FlavorsPluginNotLoaded(nexception.NotFound):
message = _("Flavors plugin not found")
class NoProviderFoundForFlavor(nexception.NotFound):
message = _("No service provider found for flavor %(flavor_id)s")
EXTENDED_ATTRIBUTES_2_0 = {
'vpnservices': {
'flavor_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid_or_none': None},
'is_visible': True, 'default': None}
}
}
class Vpn_flavors(extensions.ExtensionDescriptor):
"""Extension class supporting flavors for vpnservices."""
@classmethod
def get_name(cls):
return "VPN Service Flavor Extension"
@classmethod
def get_alias(cls):
return 'vpn-flavors'
@classmethod
def get_description(cls):
return "Flavor support for vpnservices."
@classmethod
def get_updated(cls):
return "2017-04-19T00:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}
def get_required_extensions(self):
return ["vpnaas"]
def get_optional_extensions(self):
return ["flavors"]

View File

@ -1,591 +0,0 @@
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
from neutron_lib.api import converters
from neutron_lib.api import extensions
from neutron_lib.api import validators as validators
from neutron_lib.db import constants as db_const
from neutron_lib import exceptions as nexception
from neutron_lib.plugins import constants as nconstants
from neutron_lib.services import base as service_base
import six
from neutron.api.v2 import resource_helper
from neutron_vpnaas._i18n import _
class VPNServiceNotFound(nexception.NotFound):
message = _("VPNService %(vpnservice_id)s could not be found")
class IPsecSiteConnectionNotFound(nexception.NotFound):
message = _("ipsec_site_connection %(ipsec_site_conn_id)s not found")
class IPsecSiteConnectionDpdIntervalValueError(nexception.InvalidInput):
message = _("ipsec_site_connection %(attr)s is "
"equal to or less than dpd_interval")
class IPsecSiteConnectionMtuError(nexception.InvalidInput):
message = _("ipsec_site_connection MTU %(mtu)d is too small "
"for ipv%(version)s")
class IPsecSiteConnectionPeerCidrError(nexception.InvalidInput):
message = _("ipsec_site_connection peer cidr %(peer_cidr)s is "
"invalid CIDR")
class IKEPolicyNotFound(nexception.NotFound):
message = _("IKEPolicy %(ikepolicy_id)s could not be found")
class IPsecPolicyNotFound(nexception.NotFound):
message = _("IPsecPolicy %(ipsecpolicy_id)s could not be found")
class IKEPolicyInUse(nexception.InUse):
message = _("IKEPolicy %(ikepolicy_id)s is in use by existing "
"IPsecSiteConnection and can't be updated or deleted")
class VPNServiceInUse(nexception.InUse):
message = _("VPNService %(vpnservice_id)s is still in use")
class SubnetInUseByVPNService(nexception.InUse):
message = _("Subnet %(subnet_id)s is used by VPNService %(vpnservice_id)s")
class SubnetInUseByEndpointGroup(nexception.InUse):
message = _("Subnet %(subnet_id)s is used by endpoint group %(group_id)s")
class VPNStateInvalidToUpdate(nexception.BadRequest):
message = _("Invalid state %(state)s of vpnaas resource %(id)s"
" for updating")
class IPsecPolicyInUse(nexception.InUse):
message = _("IPsecPolicy %(ipsecpolicy_id)s is in use by existing "
"IPsecSiteConnection and can't be updated or deleted")
class DeviceDriverImportError(nexception.NeutronException):
message = _("Can not load driver :%(device_driver)s")
class SubnetIsNotConnectedToRouter(nexception.BadRequest):
message = _("Subnet %(subnet_id)s is not "
"connected to Router %(router_id)s")
class RouterIsNotExternal(nexception.BadRequest):
message = _("Router %(router_id)s has no external network gateway set")
class VPNPeerAddressNotResolved(nexception.InvalidInput):
message = _("Peer address %(peer_address)s cannot be resolved")
class ExternalNetworkHasNoSubnet(nexception.BadRequest):
message = _("Router's %(router_id)s external network has "
"no %(ip_version)s subnet")
class VPNEndpointGroupNotFound(nexception.NotFound):
message = _("Endpoint group %(endpoint_group_id)s could not be found")
class InvalidEndpointInEndpointGroup(nexception.InvalidInput):
message = _("Endpoint '%(endpoint)s' is invalid for group "
"type '%(group_type)s': %(why)s")
class MissingEndpointForEndpointGroup(nexception.BadRequest):
message = _("No endpoints specified for endpoint group '%(group)s'")
class NonExistingSubnetInEndpointGroup(nexception.InvalidInput):
message = _("Subnet %(subnet)s in endpoint group does not exist")
class MixedIPVersionsForIPSecEndpoints(nexception.BadRequest):
message = _("Endpoints in group %(group)s do not have the same IP "
"version, as required for IPSec site-to-site connection")
class MixedIPVersionsForPeerCidrs(nexception.BadRequest):
message = _("Peer CIDRs do not have the same IP version, as required "
"for IPSec site-to-site connection")
class MixedIPVersionsForIPSecConnection(nexception.BadRequest):
message = _("IP versions are not compatible between peer and local "
"endpoints")
class InvalidEndpointGroup(nexception.BadRequest):
message = _("Endpoint group%(suffix)s %(which)s cannot be specified, "
"when VPN Service has subnet specified")
class WrongEndpointGroupType(nexception.BadRequest):
message = _("Endpoint group %(which)s type is '%(group_type)s' and "
"should be '%(expected)s'")
class PeerCidrsInvalid(nexception.BadRequest):
message = _("Peer CIDRs cannot be specified, when using endpoint "
"groups")
class MissingPeerCidrs(nexception.BadRequest):
message = _("Missing peer CIDRs for IPsec site-to-site connection")
class MissingRequiredEndpointGroup(nexception.BadRequest):
message = _("Missing endpoint group%(suffix)s %(which)s for IPSec "
"site-to-site connection")
class EndpointGroupInUse(nexception.BadRequest):
message = _("Endpoint group %(group_id)s is in use and cannot be deleted")
def _validate_subnet_list_or_none(data, key_specs=None):
if data is not None:
return validators.validate_subnet_list(data, key_specs)
validators.add_validator('type:subnet_list_or_none',
_validate_subnet_list_or_none)
vpn_supported_initiators = ['bi-directional', 'response-only']
vpn_supported_encryption_algorithms = ['3des', 'aes-128',
'aes-192', 'aes-256']
vpn_dpd_supported_actions = [
'hold', 'clear', 'restart', 'restart-by-peer', 'disabled'
]
vpn_supported_transform_protocols = ['esp', 'ah', 'ah-esp']
vpn_supported_encapsulation_mode = ['tunnel', 'transport']
#TODO(nati) add kilobytes when we support it
vpn_supported_lifetime_units = ['seconds']
vpn_supported_pfs = ['group2', 'group5', 'group14']
vpn_supported_ike_versions = ['v1', 'v2']
vpn_supported_auth_mode = ['psk']
vpn_supported_auth_algorithms = ['sha1', 'sha256', 'sha384', 'sha512']
vpn_supported_phase1_negotiation_mode = ['main']
vpn_lifetime_limits = (60, validators.UNLIMITED)
positive_int = (0, validators.UNLIMITED)
RESOURCE_ATTRIBUTE_MAP = {
'vpnservices': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {
'type:string': db_const.PROJECT_ID_FIELD_SIZE},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': db_const.NAME_FIELD_SIZE},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {
'type:string': db_const.DESCRIPTION_FIELD_SIZE},
'is_visible': True, 'default': ''},
'subnet_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid_or_none': None},
'is_visible': True, 'default': None},
'router_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': converters.convert_to_boolean,
'is_visible': True},
'external_v4_ip': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'external_v6_ip': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True}
},
'ipsec_site_connections': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {
'type:string': db_const.PROJECT_ID_FIELD_SIZE},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': db_const.NAME_FIELD_SIZE},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {
'type:string': db_const.DESCRIPTION_FIELD_SIZE},
'is_visible': True, 'default': ''},
'local_id': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True, 'default': ''},
'peer_address': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True},
'peer_id': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True},
'peer_cidrs': {'allow_post': True, 'allow_put': True,
'convert_to': converters.convert_to_list,
'validate': {'type:subnet_list_or_none': None},
'is_visible': True,
'default': None},
'local_ep_group_id': {'allow_post': True, 'allow_put': True,
'validate': {'type:uuid_or_none': None},
'is_visible': True, 'default': None},
'peer_ep_group_id': {'allow_post': True, 'allow_put': True,
'validate': {'type:uuid_or_none': None},
'is_visible': True, 'default': None},
'route_mode': {'allow_post': False, 'allow_put': False,
'default': 'static',
'is_visible': True},
'mtu': {'allow_post': True, 'allow_put': True,
'default': '1500',
'validate': {'type:range': positive_int},
'convert_to': converters.convert_to_int,
'is_visible': True},
'initiator': {'allow_post': True, 'allow_put': True,
'default': 'bi-directional',
'validate': {'type:values': vpn_supported_initiators},
'is_visible': True},
'auth_mode': {'allow_post': False, 'allow_put': False,
'default': 'psk',
'validate': {'type:values': vpn_supported_auth_mode},
'is_visible': True},
'psk': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': None},
'is_visible': True},
'dpd': {'allow_post': True, 'allow_put': True,
'convert_to': converters.convert_none_to_empty_dict,
'is_visible': True,
'default': {},
'validate': {
'type:dict_or_empty': {
'action': {
'type:values': vpn_dpd_supported_actions,
},
'interval': {
'type:range': positive_int
},
'timeout': {
'type:range': positive_int
}}}},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': converters.convert_to_boolean,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'vpnservice_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
'ikepolicy_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True},
'ipsecpolicy_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True}
},
'ipsecpolicies': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {
'type:string': db_const.PROJECT_ID_FIELD_SIZE},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': db_const.NAME_FIELD_SIZE},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {
'type:string': db_const.DESCRIPTION_FIELD_SIZE},
'is_visible': True, 'default': ''},
'transform_protocol': {
'allow_post': True,
'allow_put': True,
'default': 'esp',
'validate': {
'type:values': vpn_supported_transform_protocols},
'is_visible': True},
'auth_algorithm': {
'allow_post': True,
'allow_put': True,
'default': 'sha1',
'validate': {
'type:values': vpn_supported_auth_algorithms
},
'is_visible': True},
'encryption_algorithm': {
'allow_post': True,
'allow_put': True,
'default': 'aes-128',
'validate': {
'type:values': vpn_supported_encryption_algorithms
},
'is_visible': True},
'encapsulation_mode': {
'allow_post': True,
'allow_put': True,
'default': 'tunnel',
'validate': {
'type:values': vpn_supported_encapsulation_mode
},
'is_visible': True},
'lifetime': {'allow_post': True, 'allow_put': True,
'convert_to': converters.convert_none_to_empty_dict,
'default': {},
'validate': {
'type:dict_or_empty': {
'units': {
'type:values': vpn_supported_lifetime_units,
},
'value': {
'type:range': vpn_lifetime_limits
}}},
'is_visible': True},
'pfs': {'allow_post': True, 'allow_put': True,
'default': 'group5',
'validate': {'type:values': vpn_supported_pfs},
'is_visible': True}
},
'ikepolicies': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {
'type:string': db_const.PROJECT_ID_FIELD_SIZE},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': db_const.NAME_FIELD_SIZE},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {
'type:string': db_const.DESCRIPTION_FIELD_SIZE},
'is_visible': True, 'default': ''},
'auth_algorithm': {'allow_post': True, 'allow_put': True,
'default': 'sha1',
'validate': {
'type:values': vpn_supported_auth_algorithms},
'is_visible': True},
'encryption_algorithm': {
'allow_post': True, 'allow_put': True,
'default': 'aes-128',
'validate': {'type:values': vpn_supported_encryption_algorithms},
'is_visible': True},
'phase1_negotiation_mode': {
'allow_post': True, 'allow_put': True,
'default': 'main',
'validate': {
'type:values': vpn_supported_phase1_negotiation_mode
},
'is_visible': True},
'lifetime': {'allow_post': True, 'allow_put': True,
'convert_to': converters.convert_none_to_empty_dict,
'default': {},
'validate': {
'type:dict_or_empty': {
'units': {
'type:values': vpn_supported_lifetime_units,
},
'value': {
'type:range': vpn_lifetime_limits,
}}},
'is_visible': True},
'ike_version': {'allow_post': True, 'allow_put': True,
'default': 'v1',
'validate': {
'type:values': vpn_supported_ike_versions},
'is_visible': True},
'pfs': {'allow_post': True, 'allow_put': True,
'default': 'group5',
'validate': {'type:values': vpn_supported_pfs},
'is_visible': True}
},
}
class Vpnaas(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "VPN service"
@classmethod
def get_alias(cls):
return "vpnaas"
@classmethod
def get_description(cls):
return "Extension for VPN service"
@classmethod
def get_namespace(cls):
return "https://wiki.openstack.org/Neutron/VPNaaS"
@classmethod
def get_updated(cls):
return "2013-05-29T10:00:00-00:00"
@classmethod
def get_resources(cls):
special_mappings = {'ikepolicies': 'ikepolicy',
'ipsecpolicies': 'ipsecpolicy'}
plural_mappings = resource_helper.build_plural_mappings(
special_mappings, RESOURCE_ATTRIBUTE_MAP)
plural_mappings['peer_cidrs'] = 'peer_cidr'
return resource_helper.build_resource_info(plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
nconstants.VPN,
register_quota=True,
translate_name=True)
@classmethod
def get_plugin_interface(cls):
return VPNPluginBase
def update_attributes_map(self, attributes):
super(Vpnaas, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}
@six.add_metaclass(abc.ABCMeta)
class VPNPluginBase(service_base.ServicePluginBase):
def get_plugin_name(self):
return nconstants.VPN
def get_plugin_type(self):
return nconstants.VPN
def get_plugin_description(self):
return 'VPN service plugin'
@abc.abstractmethod
def get_vpnservices(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_vpnservice(self, context, vpnservice_id, fields=None):
pass
@abc.abstractmethod
def create_vpnservice(self, context, vpnservice):
pass
@abc.abstractmethod
def update_vpnservice(self, context, vpnservice_id, vpnservice):
pass
@abc.abstractmethod
def delete_vpnservice(self, context, vpnservice_id):
pass
@abc.abstractmethod
def get_ipsec_site_connections(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_ipsec_site_connection(self, context,
ipsecsite_conn_id, fields=None):
pass
@abc.abstractmethod
def create_ipsec_site_connection(self, context, ipsec_site_connection):
pass
@abc.abstractmethod
def update_ipsec_site_connection(self, context,
ipsecsite_conn_id, ipsec_site_connection):
pass
@abc.abstractmethod
def delete_ipsec_site_connection(self, context, ipsecsite_conn_id):
pass
@abc.abstractmethod
def get_ikepolicy(self, context, ikepolicy_id, fields=None):
pass
@abc.abstractmethod
def get_ikepolicies(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def create_ikepolicy(self, context, ikepolicy):
pass
@abc.abstractmethod
def update_ikepolicy(self, context, ikepolicy_id, ikepolicy):
pass
@abc.abstractmethod
def delete_ikepolicy(self, context, ikepolicy_id):
pass
@abc.abstractmethod
def get_ipsecpolicies(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None):
pass
@abc.abstractmethod
def create_ipsecpolicy(self, context, ipsecpolicy):
pass
@abc.abstractmethod
def update_ipsecpolicy(self, context, ipsecpolicy_id, ipsecpolicy):
pass
@abc.abstractmethod
def delete_ipsecpolicy(self, context, ipsecpolicy_id):
pass

View File

@ -1,38 +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 neutron.services.provider_configuration
import neutron_vpnaas.services.vpn.agent
import neutron_vpnaas.services.vpn.device_drivers.ipsec
import neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec
def list_agent_opts():
return [
('vpnagent',
neutron_vpnaas.services.vpn.agent.vpn_agent_opts),
('ipsec',
neutron_vpnaas.services.vpn.device_drivers.ipsec.ipsec_opts),
('strongswan',
neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.
strongswan_opts),
('pluto',
neutron_vpnaas.services.vpn.device_drivers.ipsec.pluto_opts)
]
def list_opts():
return [
('service_providers',
neutron.services.provider_configuration.serviceprovider_opts)
]

View File

@ -1,74 +0,0 @@
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.agent.l3 import agent as l3_agent
from neutron.agent import l3_agent as entry
from oslo_config import cfg
from neutron_vpnaas._i18n import _
from neutron_vpnaas.services.vpn import vpn_service
vpn_agent_opts = [
cfg.MultiStrOpt(
'vpn_device_driver',
default=['neutron_vpnaas.services.vpn.device_drivers.'
'ipsec.OpenSwanDriver'],
sample_default=['neutron_vpnaas.services.vpn.device_drivers.ipsec.'
'OpenSwanDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'cisco_ipsec.CiscoCsrIPsecDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'vyatta_ipsec.VyattaIPSecDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'strongswan_ipsec.StrongSwanDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'fedora_strongswan_ipsec.FedoraStrongSwanDriver, '
'neutron_vpnaas.services.vpn.device_drivers.'
'libreswan_ipsec.LibreSwanDriver'],
help=_("The vpn device drivers Neutron will use")),
]
cfg.CONF.register_opts(vpn_agent_opts, 'vpnagent')
class VPNAgent(l3_agent.L3NATAgentWithStateReport):
"""VPNAgent class which can handle vpn service drivers."""
def __init__(self, host, conf=None):
super(VPNAgent, self).__init__(host=host, conf=conf)
self.agent_state['binary'] = 'neutron-vpn-agent'
self.service = vpn_service.VPNService(self)
self.device_drivers = self.service.load_device_drivers(host)
def process_state_change(self, router_id, state):
"""Enable the vpn process when router transitioned to master.
And disable vpn process for backup router.
"""
for device_driver in self.device_drivers:
if router_id in device_driver.processes:
process = device_driver.processes[router_id]
if state == 'master':
process.enable()
else:
process.disable()
def enqueue_state_change(self, router_id, state):
"""Handle HA router state changes for vpn process"""
self.process_state_change(router_id, state)
super(VPNAgent, self).enqueue_state_change(router_id, state)
def main():
entry.main(manager='neutron_vpnaas.services.vpn.agent.VPNAgent')

View File

@ -1,32 +0,0 @@
# Copyright 2015 Cisco Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# Endpoint group types
SUBNET_ENDPOINT = 'subnet'
CIDR_ENDPOINT = 'cidr'
VLAN_ENDPOINT = 'vlan'
NETWORK_ENDPOINT = 'network'
ROUTER_ENDPOINT = 'router'
# NOTE: Type usage...
# IPSec local endpoints - subnet, IPSec peer endpoints - cidr
# BGP VPN local endpoints - network
# Direct connect style endpoints - vlan
# IMPORTANT: The ordering of these is important, as it is used in an enum
# for the database (and migration script). Only add to this list.
VPN_SUPPORTED_ENDPOINT_TYPES = [
SUBNET_ENDPOINT, CIDR_ENDPOINT, NETWORK_ENDPOINT,
VLAN_ENDPOINT, ROUTER_ENDPOINT,
]

View File

@ -1,166 +0,0 @@
# Copyright (c) 2015 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import errno
import os
import sys
from eventlet.green import subprocess
from neutron.common import config
from neutron.common import utils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_rootwrap import wrapper
import six
from neutron_vpnaas._i18n import _
if six.PY3:
import configparser as ConfigParser
else:
import ConfigParser
LOG = logging.getLogger(__name__)
def setup_conf():
cli_opts = [
cfg.DictOpt('mount_paths',
required=True,
help=_('Dict of paths to bind-mount (source:target) '
'prior to launch subprocess.')),
cfg.ListOpt(
'cmd',
required=True,
help=_('Command line to execute as a subprocess '
'provided as comma-separated list of arguments.')),
cfg.StrOpt('rootwrap_config', default='/etc/neutron/rootwrap.conf',
help=_('Rootwrap configuration file.')),
]
conf = cfg.CONF
conf.register_cli_opts(cli_opts)
return conf
def execute(cmd):
if not cmd:
return
cmd = map(str, cmd)
LOG.debug("Running command: %s", cmd)
env = os.environ.copy()
obj = utils.subprocess_popen(cmd, shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env)
_stdout, _stderr = obj.communicate()
msg = ('Command: %(cmd)s Exit code: %(returncode)s '
'Stdout: %(stdout)s Stderr: %(stderr)s' %
{'cmd': cmd,
'returncode': obj.returncode,
'stdout': _stdout,
'stderr': _stderr})
LOG.debug(msg)
obj.stdin.close()
# Pass the output to calling process
sys.stdout.write(msg)
sys.stdout.flush()
return obj.returncode
def filter_command(command, rootwrap_config):
# Load rootwrap configuration
try:
rawconfig = ConfigParser.RawConfigParser()
rawconfig.read(rootwrap_config)
rw_config = wrapper.RootwrapConfig(rawconfig)
except ValueError as exc:
LOG.error('Incorrect value in %(config)s: %(exc)s',
{'config': rootwrap_config, 'exc': exc})
sys.exit(errno.EINVAL)
except ConfigParser.Error:
LOG.error('Incorrect configuration file: %(config)s',
{'config': rootwrap_config})
sys.exit(errno.EINVAL)
# Check if command matches any of the loaded filters
filters = wrapper.load_filters(rw_config.filters_path)
try:
wrapper.match_filter(filters, command, exec_dirs=rw_config.exec_dirs)
except wrapper.FilterMatchNotExecutable as exc:
LOG.error('Command %(command)s is not executable: '
'%(path)s (filter match = %(name)s)',
{'command': command,
'path': exc.match.exec_path,
'name': exc.match.name})
sys.exit(errno.EINVAL)
except wrapper.NoFilterMatched:
LOG.error('Unauthorized command: %(cmd)s (no filter matched)',
{'cmd': command})
sys.exit(errno.EPERM)
def execute_with_mount():
conf = setup_conf()
conf()
config.setup_logging()
if not conf.cmd:
LOG.error('No command provided, exiting')
return errno.EINVAL
if not conf.mount_paths:
LOG.error('No mount path provided, exiting')
return errno.EINVAL
# Both sudoers and rootwrap.conf will not exist in the directory /etc
# after bind-mount, so we can't use utils.execute(conf.cmd,
# run_as_root=True). That's why we have to check here if cmd matches
# CommandFilter
filter_command(conf.cmd, conf.rootwrap_config)
# Make sure the process is running in net namespace invoked by ip
# netns exec(/proc/[pid]/ns/net) which is since Linux 3.0,
# as we can't check mount namespace(/proc/[pid]/ns/mnt)
# which is since Linux 3.8. For more detail please refer the link
# http://man7.org/linux/man-pages/man7/namespaces.7.html
if os.path.samefile(os.path.join('/proc/1/ns/net'),
os.path.join('/proc', str(os.getpid()), 'ns/net')):
LOG.error('Cannot run without netns, exiting')
return errno.EINVAL
for path, new_path in conf.mount_paths.items():
if not os.path.isdir(new_path):
# Sometimes all directories are not ready
LOG.debug('%s is not directory', new_path)
continue
if os.path.isdir(path) and os.path.isabs(path):
return_code = execute(['mount', '--bind', new_path, path])
if return_code == 0:
LOG.info('%(new_path)s has been '
'bind-mounted in %(path)s',
{'new_path': new_path, 'path': path})
else:
LOG.error('Failed to bind-mount '
'%(new_path)s in %(path)s',
{'new_path': new_path, 'path': path})
return execute(conf.cmd)
def main():
sys.exit(execute_with_mount())
if __name__ == "__main__":
main()

View File

@ -1,22 +0,0 @@
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
IPSEC_DRIVER_TOPIC = 'ipsec_driver'
IPSEC_AGENT_TOPIC = 'ipsec_agent'
CISCO_IPSEC_DRIVER_TOPIC = 'cisco_csr_ipsec_driver'
CISCO_IPSEC_AGENT_TOPIC = 'cisco_csr_ipsec_agent'
BROCADE_IPSEC_DRIVER_TOPIC = 'brocade_vyatta_ipsec_driver'
BROCADE_IPSEC_AGENT_TOPIC = 'brocade_vyatta_ipsec_agent'

View File

@ -1,36 +0,0 @@
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class DeviceDriver(object):
def __init__(self, agent, host):
pass
@abc.abstractmethod
def sync(self, context, processes):
pass
@abc.abstractmethod
def create_router(self, process_id):
pass
@abc.abstractmethod
def destroy_router(self, process_id):
pass

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,70 +0,0 @@
# Copyright (c) 2015 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import os.path
from neutron_vpnaas.services.vpn.device_drivers import ipsec
class LibreSwanProcess(ipsec.OpenSwanProcess):
"""Libreswan Process manager class.
Libreswan needs nssdb initialised before running pluto daemon.
"""
def __init__(self, conf, process_id, vpnservice, namespace):
super(LibreSwanProcess, self).__init__(conf, process_id,
vpnservice, namespace)
def ensure_configs(self):
"""Generate config files which are needed for Libreswan.
Initialise the nssdb, otherwise pluto daemon will fail to run.
"""
# Since we set ipsec.secrets to be owned by root, the standard
# mechanisms for setting up the config files will get a permission
# problem when attempting to overwrite the file, so we need to
# remove it first.
secrets_file = self._get_config_filename('ipsec.secrets')
if os.path.exists(secrets_file):
os.remove(secrets_file)
super(LibreSwanProcess, self).ensure_configs()
# LibreSwan uses the capabilities library to restrict access to
# ipsec.secrets to users that have explicit access. Since pluto is
# running as root and the file has 0600 perms, we must set the
# owner of the file to root.
self._execute(['chown', '--from=%s' % os.getuid(), 'root:root',
secrets_file])
# Load the ipsec kernel module if not loaded
self._execute([self.binary, '_stackmanager', 'start'])
# checknss creates nssdb only if it is missing
# It is added in Libreswan version v3.10
# For prior versions use initnss
try:
self._execute([self.binary, 'checknss', self.etc_dir])
except RuntimeError:
self._execute([self.binary, 'initnss', self.etc_dir])
class LibreSwanDriver(ipsec.IPsecDriver):
def create_process(self, process_id, vpnservice, namespace):
return LibreSwanProcess(
self.conf,
process_id,
vpnservice,
namespace)

View File

@ -1,197 +0,0 @@
# Copyright (c) 2015 Canonical, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_config import cfg
from oslo_log import log as logging
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils
from neutron_lib import constants
from neutron_vpnaas._i18n import _
from neutron_vpnaas.services.vpn.device_drivers import ipsec
LOG = logging.getLogger(__name__)
TEMPLATE_PATH = os.path.dirname(os.path.abspath(__file__))
strongswan_opts = [
cfg.StrOpt(
'ipsec_config_template',
default=os.path.join(
TEMPLATE_PATH,
'template/strongswan/ipsec.conf.template'),
help=_('Template file for ipsec configuration.')),
cfg.StrOpt(
'strongswan_config_template',
default=os.path.join(
TEMPLATE_PATH,
'template/strongswan/strongswan.conf.template'),
help=_('Template file for strongswan configuration.')),
cfg.StrOpt(
'ipsec_secret_template',
default=os.path.join(
TEMPLATE_PATH,
'template/strongswan/ipsec.secret.template'),
help=_('Template file for ipsec secret configuration.')),
cfg.StrOpt(
'default_config_area',
default=os.path.join(
TEMPLATE_PATH,
'/etc/strongswan.d'),
help=_('The area where default StrongSwan configuration '
'files are located.'))
]
cfg.CONF.register_opts(strongswan_opts, 'strongswan')
NS_WRAPPER = 'neutron-vpn-netns-wrapper'
class StrongSwanProcess(ipsec.BaseSwanProcess):
# ROUTED means route created. (only for auto=route mode)
# CONNECTING means route created, connection tunnel is negotiating.
# INSTALLED means route created,
# also connection tunnel installed. (traffic can pass)
DIALECT_MAP = dict(ipsec.BaseSwanProcess.DIALECT_MAP)
STATUS_DICT = {
'ROUTED': constants.DOWN,
'CONNECTING': constants.DOWN,
'INSTALLED': constants.ACTIVE
}
STATUS_RE = '([a-f0-9\-]+).* (ROUTED|CONNECTING|INSTALLED)'
STATUS_NOT_RUNNING_RE = 'Command:.*ipsec.*status.*Exit code: [1|3] '
def __init__(self, conf, process_id, vpnservice, namespace):
self.DIALECT_MAP['v1'] = 'ikev1'
self.DIALECT_MAP['v2'] = 'ikev2'
self.DIALECT_MAP['sha256'] = 'sha256'
self._strongswan_piddir = self._get_strongswan_piddir()
LOG.debug("strongswan piddir is '%s'", (self._strongswan_piddir))
super(StrongSwanProcess, self).__init__(conf, process_id,
vpnservice, namespace)
def _get_strongswan_piddir(self):
return utils.execute(
cmd=[self.binary, "--piddir"], run_as_root=True).strip()
def _check_status_line(self, line):
"""Parse a line and search for status information.
If a given line contains status information for a connection,
extract the status and mark the connection as ACTIVE or DOWN
according to the STATUS_MAP.
"""
m = self.STATUS_PATTERN.search(line)
if m:
connection_id = m.group(1)
status = self.STATUS_MAP[m.group(2)]
return connection_id, status
return None, None
def _execute(self, cmd, check_exit_code=True, extra_ok_codes=None):
"""Execute command on namespace.
This execute is wrapped by namespace wrapper.
The namespace wrapper will bind /etc/ and /var/run
"""
ip_wrapper = ip_lib.IPWrapper(namespace=self.namespace)
return ip_wrapper.netns.execute(
[NS_WRAPPER,
'--mount_paths=/etc:%s/etc,%s:%s/var/run' % (
self.config_dir, self._strongswan_piddir, self.config_dir),
'--cmd=%s' % ','.join(cmd)],
check_exit_code=check_exit_code,
extra_ok_codes=extra_ok_codes)
def copy_and_overwrite(self, from_path, to_path):
# NOTE(toabctl): the agent may run as non-root user, so rm/copy as root
if os.path.exists(to_path):
utils.execute(
cmd=["rm", "-rf", to_path], run_as_root=True)
utils.execute(
cmd=["cp", "-a", from_path, to_path], run_as_root=True)
def ensure_configs(self):
"""Generate config files which are needed for StrongSwan.
If there is no directory, this function will create
dirs.
"""
self.ensure_config_dir(self.vpnservice)
self.ensure_config_file(
'ipsec.conf',
cfg.CONF.strongswan.ipsec_config_template,
self.vpnservice)
self.ensure_config_file(
'strongswan.conf',
cfg.CONF.strongswan.strongswan_config_template,
self.vpnservice)
self.ensure_config_file(
'ipsec.secrets',
cfg.CONF.strongswan.ipsec_secret_template,
self.vpnservice,
0o600)
self.copy_and_overwrite(cfg.CONF.strongswan.default_config_area,
self._get_config_filename('strongswan.d'))
def get_status(self):
return self._execute([self.binary, 'status'],
extra_ok_codes=[1, 3])
def restart(self):
"""Restart the process."""
self.reload()
def reload(self):
"""Reload the process.
Sends a USR1 signal to ipsec starter which in turn reloads the whole
configuration on the running IKE daemon charon based on the actual
ipsec.conf. Currently established connections are not affected by
configuration changes.
"""
self._execute([self.binary, 'reload'])
def start(self):
"""Start the process for only auto=route mode now.
Note: if there is no namespace yet,
just do nothing, and wait next event.
"""
if not self.namespace:
return
self._execute([self.binary, 'start'])
# initiate ipsec connection
for ipsec_site_conn in self.vpnservice['ipsec_site_connections']:
self._execute([self.binary, 'stroke', 'up-nb',
ipsec_site_conn['id']])
def stop(self):
self._execute([self.binary, 'stop'])
self.connection_status = {}
class StrongSwanDriver(ipsec.IPsecDriver):
def create_process(self, process_id, vpnservice, namespace):
return StrongSwanProcess(
self.conf,
process_id,
vpnservice,
namespace)

View File

@ -1,87 +0,0 @@
# Configuration for {{vpnservice.name}}
config setup
nat_traversal=yes
conn %default
ikelifetime=480m
keylife=60m
keyingtries=%forever
{% for ipsec_site_connection in vpnservice.ipsec_site_connections if ipsec_site_connection.admin_state_up
-%}
conn {{ipsec_site_connection.id}}
{% if ipsec_site_connection['local_ip_vers'] == 6 -%}
# To recognize the given IP addresses in this config
# as IPv6 addresses by pluto whack. Default is ipv4
connaddrfamily=ipv6
# openswan can't process defaultroute for ipv6.
# Assign gateway address as leftnexthop
leftnexthop={{ipsec_site_connection.external_ip}}
# rightnexthop is not mandatory for ipsec, so no need in ipv6.
{% else -%}
# NOTE: a default route is required for %defaultroute to work...
leftnexthop=%defaultroute
rightnexthop=%defaultroute
{% endif -%}
left={{ipsec_site_connection.external_ip}}
leftid={{ipsec_site_connection.local_id}}
auto={{ipsec_site_connection.initiator}}
# NOTE:REQUIRED
# [subnet]
{% if ipsec_site_connection['local_cidrs']|length == 1 -%}
leftsubnet={{ipsec_site_connection['local_cidrs'][0]}}
{% else -%}
leftsubnets={ {{ipsec_site_connection['local_cidrs']|join(' ')}} }
{% endif -%}
# [updown]
# What "updown" script to run to adjust routing and/or firewalling when
# the status of the connection changes (default "ipsec _updown").
# "--route yes" allows to specify such routing options as mtu and metric.
leftupdown="ipsec _updown --route yes"
######################
# ipsec_site_connections
######################
# [peer_address]
right={{ipsec_site_connection.peer_address}}
# [peer_id]
rightid={{ipsec_site_connection.peer_id}}
# [peer_cidrs]
rightsubnets={ {{ipsec_site_connection['peer_cidrs']|join(' ')}} }
# rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only)
# [mtu]
mtu={{ipsec_site_connection.mtu}}
# [dpd_action]
dpdaction={{ipsec_site_connection.dpd_action}}
# [dpd_interval]
dpddelay={{ipsec_site_connection.dpd_interval}}
# [dpd_timeout]
dpdtimeout={{ipsec_site_connection.dpd_timeout}}
# [auth_mode]
authby=secret
######################
# IKEPolicy params
######################
#ike version
ikev2={{ipsec_site_connection.ikepolicy.ike_version}}
# [encryption_algorithm]-[auth_algorithm]-[pfs]
ike={{ipsec_site_connection.ikepolicy.encryption_algorithm}}-{{ipsec_site_connection.ikepolicy.auth_algorithm}};{{ipsec_site_connection.ikepolicy.pfs}}
# [lifetime_value]
ikelifetime={{ipsec_site_connection.ikepolicy.lifetime_value}}s
# NOTE: it looks lifetime_units=kilobytes can't be enforced (could be seconds, hours, days...)
##########################
# IPsecPolicys params
##########################
# [transform_protocol]
auth={{ipsec_site_connection.ipsecpolicy.transform_protocol}}
{% if ipsec_site_connection.ipsecpolicy.transform_protocol == "ah" -%}
# AH protocol does not support encryption
# [auth_algorithm]-[pfs]
phase2alg={{ipsec_site_connection.ipsecpolicy.auth_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}}
{% else -%}
# [encryption_algorithm]-[auth_algorithm]-[pfs]
phase2alg={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}}
{% endif -%}
# [encapsulation_mode]
type={{ipsec_site_connection.ipsecpolicy.encapsulation_mode}}
# [lifetime_value]
lifetime={{ipsec_site_connection.ipsecpolicy.lifetime_value}}s
# lifebytes=100000 if lifetime_units=kilobytes (IKEv2 only)
{% endfor -%}

View File

@ -1,4 +0,0 @@
# Configuration for {{vpnservice.name}}
{% for ipsec_site_connection in vpnservice.ipsec_site_connections -%}
{{ipsec_site_connection.local_id}} {{ipsec_site_connection.peer_id}} : PSK "{{ipsec_site_connection.psk}}"
{% endfor %}

View File

@ -1,34 +0,0 @@
# Configuration for {{vpnservice.name}}
config setup
conn %default
ikelifetime=60m
keylife=20m
rekeymargin=3m
keyingtries=1
authby=psk
mobike=no
{% for ipsec_site_connection in vpnservice.ipsec_site_connections%}
conn {{ipsec_site_connection.id}}
keyexchange={{ipsec_site_connection.ikepolicy.ike_version}}
left={{ipsec_site_connection.external_ip}}
leftsubnet={{ipsec_site_connection['local_cidrs']|join(',')}}
leftid={{ipsec_site_connection.local_id}}
leftfirewall=yes
right={{ipsec_site_connection.peer_address}}
rightsubnet={{ipsec_site_connection['peer_cidrs']|join(',')}}
rightid={{ipsec_site_connection.peer_id}}
auto=route
dpdaction={{ipsec_site_connection.dpd_action}}
dpddelay={{ipsec_site_connection.dpd_interval}}s
dpdtimeout={{ipsec_site_connection.dpd_timeout}}s
ike={{ipsec_site_connection.ikepolicy.encryption_algorithm}}-{{ipsec_site_connection.ikepolicy.auth_algorithm}}-{{ipsec_site_connection.ikepolicy.pfs}}
ikelifetime={{ipsec_site_connection.ikepolicy.lifetime_value}}s
{%- if ipsec_site_connection.ipsecpolicy.transform_protocol == "ah" %}
ah={{ipsec_site_connection.ipsecpolicy.auth_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}}
{%- else %}
esp={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}}
{%- endif %}
lifetime={{ipsec_site_connection.ipsecpolicy.lifetime_value}}s
type={{ipsec_site_connection.ipsecpolicy.encapsulation_mode}}
{% endfor %}

View File

@ -1,3 +0,0 @@
# Configuration for {{vpnservice.name}}{% for ipsec_site_connection in vpnservice.ipsec_site_connections %}
{{ipsec_site_connection.local_id}} {{ipsec_site_connection.peer_id}} : PSK "{{ipsec_site_connection.psk}}"
{% endfor %}

View File

@ -1,8 +0,0 @@
charon {
load_modular = yes
plugins {
include strongswan.d/charon/*.conf
}
}
include strongswan.d/*.conf

View File

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

View File

@ -1,223 +0,0 @@
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.db import servicetype_db as st_db
from neutron.extensions import flavors
from neutron.services.flavors import flavors_plugin
from neutron.services import provider_configuration as pconf
from neutron.services import service_base
from neutron_lib import context as ncontext
from neutron_lib.plugins import constants
from neutron_lib.plugins import directory
from oslo_log import log as logging
from neutron_vpnaas.db.vpn import vpn_db
from neutron_vpnaas.extensions import vpn_flavors
LOG = logging.getLogger(__name__)
def add_provider_configuration(type_manager, service_type):
type_manager.add_provider_configuration(
service_type,
pconf.ProviderConfiguration('neutron_vpnaas'))
class VPNPlugin(vpn_db.VPNPluginDb):
"""Implementation of the VPN Service Plugin.
This class manages the workflow of VPNaaS request/response.
Most DB related works are implemented in class
vpn_db.VPNPluginDb.
"""
supported_extension_aliases = ["vpnaas",
"vpn-endpoint-groups",
"service-type",
"vpn-flavors"]
path_prefix = "/vpn"
class VPNDriverPlugin(VPNPlugin, vpn_db.VPNPluginRpcDbMixin):
"""VpnPlugin which supports VPN Service Drivers."""
#TODO(nati) handle ikepolicy and ipsecpolicy update usecase
def __init__(self):
super(VPNDriverPlugin, self).__init__()
self.service_type_manager = st_db.ServiceTypeManager.get_instance()
add_provider_configuration(self.service_type_manager, constants.VPN)
# Load the service driver from neutron.conf.
self.drivers, self.default_provider = service_base.load_drivers(
constants.VPN, self)
self._check_orphan_vpnservice_associations()
# Associate driver names to driver objects
for driver_name, driver in self.drivers.items():
driver.name = driver_name
LOG.info(("VPN plugin using service drivers: %(service_drivers)s, "
"default: %(default_driver)s"),
{'service_drivers': self.drivers.keys(),
'default_driver': self.default_provider})
# Try to find the flavor plugin only once
self._flavors_plugin = directory.get_plugin(constants.FLAVORS)
vpn_db.subscribe()
def _check_orphan_vpnservice_associations(self):
context = ncontext.get_admin_context()
vpnservices = self.get_vpnservices(context)
vpnservice_ids = [vpnservice['id'] for vpnservice in vpnservices]
stm = self.service_type_manager
provider_names = stm.get_provider_names_by_resource_ids(
context, vpnservice_ids)
lost_providers = set()
lost_vpnservices = []
for vpnservice_id, provider in provider_names.items():
if provider not in self.drivers:
lost_providers.add(provider)
lost_vpnservices.append(vpnservice_id)
if lost_providers or lost_vpnservices:
# Provider are kept internally, we need to inform users about
# the related VPN services.
msg = (
"Delete associated vpnservices %(vpnservices)s before "
"removing providers %(providers)s."
) % {'vpnservices': lost_vpnservices,
'providers': list(lost_providers)}
LOG.exception(msg)
raise SystemExit(msg)
# Deal with upgrade. Associate existing VPN services to default
# provider.
unasso_vpnservices = [
vpnservice_id for vpnservice_id in vpnservice_ids
if vpnservice_id not in provider_names]
if unasso_vpnservices:
LOG.info(
("Associating VPN services %(unasso_vpnservices)s to "
"default provider %(default_provider)s."),
{'unasso_vpnservices': unasso_vpnservices,
'default_provider': self.default_provider})
for vpnservice_id in unasso_vpnservices:
stm.add_resource_association(
context, constants.VPN,
self.default_provider, vpnservice_id)
def _get_provider_for_flavor(self, context, flavor_id):
if flavor_id:
if not self._flavors_plugin:
raise vpn_flavors.FlavorsPluginNotLoaded()
fl_db = flavors_plugin.FlavorsPlugin.get_flavor(
self._flavors_plugin, context, flavor_id)
if fl_db['service_type'] != constants.VPN:
raise flavors.InvalidFlavorServiceType(
service_type=fl_db['service_type'])
if not fl_db['enabled']:
raise flavors.FlavorDisabled()
providers = flavors_plugin.FlavorsPlugin.get_flavor_next_provider(
self._flavors_plugin, context, fl_db['id'])
provider = providers[0].get('provider')
if provider not in self.drivers:
raise vpn_flavors.NoProviderFoundForFlavor(flavor_id=flavor_id)
else:
# Use default provider
provider = self.default_provider
LOG.debug("Selected provider %s", provider)
return provider
def _get_driver_for_vpnservice(self, context, vpnservice):
stm = self.service_type_manager
provider_names = stm.get_provider_names_by_resource_ids(
context, [vpnservice['id']])
provider = provider_names.get(vpnservice['id'])
return self.drivers[provider]
def _get_driver_for_ipsec_site_connection(self, context,
ipsec_site_connection):
# Only vpnservice_id is required as the vpnservice should be already
# associated with a provider after its creation.
vpnservice = {'id': ipsec_site_connection['vpnservice_id']}
return self._get_driver_for_vpnservice(context, vpnservice)
def create_ipsec_site_connection(self, context, ipsec_site_connection):
driver = self._get_driver_for_ipsec_site_connection(
context, ipsec_site_connection['ipsec_site_connection'])
driver.validator.validate_ipsec_site_connection(
context,
ipsec_site_connection['ipsec_site_connection'])
ipsec_site_connection = super(
VPNDriverPlugin, self).create_ipsec_site_connection(
context, ipsec_site_connection)
driver.create_ipsec_site_connection(context, ipsec_site_connection)
return ipsec_site_connection
def delete_ipsec_site_connection(self, context, ipsec_conn_id):
ipsec_site_connection = self.get_ipsec_site_connection(
context, ipsec_conn_id)
super(VPNDriverPlugin, self).delete_ipsec_site_connection(
context, ipsec_conn_id)
driver = self._get_driver_for_ipsec_site_connection(
context, ipsec_site_connection)
driver.delete_ipsec_site_connection(context, ipsec_site_connection)
def update_ipsec_site_connection(
self, context,
ipsec_conn_id, ipsec_site_connection):
old_ipsec_site_connection = self.get_ipsec_site_connection(
context, ipsec_conn_id)
driver = self._get_driver_for_ipsec_site_connection(
context, old_ipsec_site_connection)
driver.validator.validate_ipsec_site_connection(
context,
ipsec_site_connection['ipsec_site_connection'])
ipsec_site_connection = super(
VPNDriverPlugin, self).update_ipsec_site_connection(
context,
ipsec_conn_id,
ipsec_site_connection)
driver.update_ipsec_site_connection(
context, old_ipsec_site_connection, ipsec_site_connection)
return ipsec_site_connection
def create_vpnservice(self, context, vpnservice):
provider = self._get_provider_for_flavor(
context, vpnservice['vpnservice'].get('flavor_id'))
vpnservice = super(
VPNDriverPlugin, self).create_vpnservice(context, vpnservice)
self.service_type_manager.add_resource_association(
context, constants.VPN, provider, vpnservice['id'])
driver = self.drivers[provider]
driver.create_vpnservice(context, vpnservice)
return vpnservice
def update_vpnservice(self, context, vpnservice_id, vpnservice):
old_vpn_service = self.get_vpnservice(context, vpnservice_id)
new_vpn_service = super(
VPNDriverPlugin, self).update_vpnservice(context, vpnservice_id,
vpnservice)
driver = self._get_driver_for_vpnservice(context, old_vpn_service)
driver.update_vpnservice(context, old_vpn_service, new_vpn_service)
return new_vpn_service
def delete_vpnservice(self, context, vpnservice_id):
vpnservice = self._get_vpnservice(context, vpnservice_id)
super(VPNDriverPlugin, self).delete_vpnservice(context, vpnservice_id)
driver = self._get_driver_for_vpnservice(context, vpnservice)
self.service_type_manager.del_resource_associations(
context, [vpnservice_id])
driver.delete_vpnservice(context, vpnservice)

View File

@ -1,112 +0,0 @@
# Copyright 2013, Nachi Ueno, NTT I3, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
from neutron.common import rpc as n_rpc
from neutron_lib import constants
from neutron_lib.plugins import directory
from oslo_log import log as logging
import oslo_messaging
import six
from neutron_vpnaas.services.vpn.service_drivers import driver_validator
LOG = logging.getLogger(__name__)
@six.add_metaclass(abc.ABCMeta)
class VpnDriver(object):
def __init__(self, service_plugin, validator=None):
self.service_plugin = service_plugin
if validator is None:
validator = driver_validator.VpnDriverValidator(self)
self.validator = validator
self.name = ''
@property
def l3_plugin(self):
return directory.get_plugin(constants.L3)
@property
def service_type(self):
pass
@abc.abstractmethod
def create_vpnservice(self, context, vpnservice):
pass
@abc.abstractmethod
def update_vpnservice(
self, context, old_vpnservice, vpnservice):
pass
@abc.abstractmethod
def delete_vpnservice(self, context, vpnservice):
pass
@abc.abstractmethod
def create_ipsec_site_connection(self, context, ipsec_site_connection):
pass
@abc.abstractmethod
def update_ipsec_site_connection(self, context, old_ipsec_site_connection,
ipsec_site_connection):
pass
@abc.abstractmethod
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
pass
class BaseIPsecVpnAgentApi(object):
"""Base class for IPSec API to agent."""
def __init__(self, topic, default_version, driver):
self.topic = topic
self.driver = driver
target = oslo_messaging.Target(topic=topic, version=default_version)
self.client = n_rpc.get_client(target)
def _agent_notification(self, context, method, router_id,
version=None, **kwargs):
"""Notify update for the agent.
This method will find where is the router, and
dispatch notification for the agent.
"""
admin_context = context if context.is_admin else context.elevated()
if not version:
version = self.target.version
l3_agents = self.driver.l3_plugin.get_l3_agents_hosting_routers(
admin_context, [router_id],
admin_state_up=True,
active=True)
for l3_agent in l3_agents:
LOG.debug('Notify agent at %(topic)s.%(host)s the message '
'%(method)s %(args)s',
{'topic': self.topic,
'host': l3_agent.host,
'method': method,
'args': kwargs})
cctxt = self.client.prepare(server=l3_agent.host, version=version)
cctxt.cast(context, method, **kwargs)
def vpnservice_updated(self, context, router_id, **kwargs):
"""Send update event of vpnservices."""
kwargs['router'] = {'id': router_id}
self._agent_notification(context, 'vpnservice_updated', router_id,
**kwargs)

View File

@ -1,265 +0,0 @@
# Copyright 2015, Nachi Ueno, NTT I3, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import netaddr
import oslo_messaging
import six
from neutron.db.models import l3agent
from neutron.db.models import servicetype
from neutron_lib import constants as lib_constants
from neutron_lib.plugins import directory
from neutron_vpnaas.db.vpn import vpn_models
from neutron_vpnaas.services.vpn import service_drivers
IPSEC = 'ipsec'
BASE_IPSEC_VERSION = '1.0'
class IPsecVpnDriverCallBack(object):
"""Callback for IPSecVpnDriver rpc."""
# history
# 1.0 Initial version
target = oslo_messaging.Target(version=BASE_IPSEC_VERSION)
def __init__(self, driver):
super(IPsecVpnDriverCallBack, self).__init__()
self.driver = driver
def _get_agent_hosting_vpn_services(self, context, host):
plugin = directory.get_plugin()
agent = plugin._get_agent_by_type_and_host(
context, lib_constants.AGENT_TYPE_L3, host)
agent_conf = plugin.get_configuration_dict(agent)
# Retrieve the agent_mode to check if this is the
# right agent to deploy the vpn service. In the
# case of distributed the vpn service should reside
# only on a dvr_snat node.
agent_mode = agent_conf.get('agent_mode', 'legacy')
if not agent.admin_state_up or agent_mode == 'dvr':
return []
query = context.session.query(vpn_models.VPNService)
query = query.join(vpn_models.IPsecSiteConnection)
query = query.join(l3agent.RouterL3AgentBinding,
l3agent.RouterL3AgentBinding.router_id ==
vpn_models.VPNService.router_id)
query = query.join(
servicetype.ProviderResourceAssociation,
servicetype.ProviderResourceAssociation.resource_id ==
vpn_models.VPNService.id)
query = query.filter(
l3agent.RouterL3AgentBinding.l3_agent_id == agent.id)
query = query.filter(
servicetype.ProviderResourceAssociation.provider_name ==
self.driver.name)
return query
def get_vpn_services_on_host(self, context, host=None):
"""Returns the vpnservices on the host."""
vpnservices = self._get_agent_hosting_vpn_services(
context, host)
plugin = self.driver.service_plugin
local_cidr_map = plugin._build_local_subnet_cidr_map(context)
return [self.driver.make_vpnservice_dict(vpnservice, local_cidr_map)
for vpnservice in vpnservices]
def update_status(self, context, status):
"""Update status of vpnservices."""
plugin = self.driver.service_plugin
plugin.update_status_by_agent(context, status)
class IPsecVpnAgentApi(service_drivers.BaseIPsecVpnAgentApi):
"""Agent RPC API for IPsecVPNAgent."""
target = oslo_messaging.Target(version=BASE_IPSEC_VERSION)
def __init__(self, topic, default_version, driver):
super(IPsecVpnAgentApi, self).__init__(
topic, default_version, driver)
@six.add_metaclass(abc.ABCMeta)
class BaseIPsecVPNDriver(service_drivers.VpnDriver):
"""Base VPN Service Driver class."""
def __init__(self, service_plugin, validator=None):
super(BaseIPsecVPNDriver, self).__init__(service_plugin, validator)
self.create_rpc_conn()
@property
def service_type(self):
return IPSEC
@abc.abstractmethod
def create_rpc_conn(self):
pass
def create_ipsec_site_connection(self, context, ipsec_site_connection):
vpnservice = self.service_plugin._get_vpnservice(
context, ipsec_site_connection['vpnservice_id'])
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
def update_ipsec_site_connection(
self, context, old_ipsec_site_connection, ipsec_site_connection):
vpnservice = self.service_plugin._get_vpnservice(
context, ipsec_site_connection['vpnservice_id'])
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
def delete_ipsec_site_connection(self, context, ipsec_site_connection):
vpnservice = self.service_plugin._get_vpnservice(
context, ipsec_site_connection['vpnservice_id'])
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
def create_ikepolicy(self, context, ikepolicy):
pass
def delete_ikepolicy(self, context, ikepolicy):
pass
def update_ikepolicy(self, context, old_ikepolicy, ikepolicy):
pass
def create_ipsecpolicy(self, context, ipsecpolicy):
pass
def delete_ipsecpolicy(self, context, ipsecpolicy):
pass
def update_ipsecpolicy(self, context, old_ipsec_policy, ipsecpolicy):
pass
def _get_gateway_ips(self, router):
"""Obtain the IPv4 and/or IPv6 GW IP for the router.
If there are multiples, (arbitrarily) use the first one.
"""
v4_ip = v6_ip = None
for fixed_ip in router.gw_port['fixed_ips']:
addr = fixed_ip['ip_address']
vers = netaddr.IPAddress(addr).version
if vers == 4:
if v4_ip is None:
v4_ip = addr
elif v6_ip is None:
v6_ip = addr
return v4_ip, v6_ip
def create_vpnservice(self, context, vpnservice_dict):
"""Get the gateway IP(s) and save for later use.
For the reference implementation, this side's tunnel IP (external_ip)
will be the router's GW IP. IPSec connections will use a GW IP of
the same version, as is used for the peer, so we will collect the
first IP for each version (if they exist) and save them.
"""
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_dict['id'])
v4_ip, v6_ip = self._get_gateway_ips(vpnservice.router)
vpnservice_dict['external_v4_ip'] = v4_ip
vpnservice_dict['external_v6_ip'] = v6_ip
self.service_plugin.set_external_tunnel_ips(context,
vpnservice_dict['id'],
v4_ip=v4_ip, v6_ip=v6_ip)
def update_vpnservice(self, context, old_vpnservice, vpnservice):
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
def delete_vpnservice(self, context, vpnservice):
self.agent_rpc.vpnservice_updated(context, vpnservice['router_id'])
def get_external_ip_based_on_peer(self, vpnservice, ipsec_site_con):
"""Use service's external IP, based on peer IP version."""
vers = netaddr.IPAddress(ipsec_site_con['peer_address']).version
if vers == 4:
ip_to_use = vpnservice.external_v4_ip
else:
ip_to_use = vpnservice.external_v6_ip
# TODO(pcm): Add validator to check that connection's peer address has
# a version that is available in service table, so can fail early and
# don't need a check here.
return ip_to_use
def make_vpnservice_dict(self, vpnservice, local_cidr_map):
"""Convert vpnservice information for vpn agent.
also converting parameter name for vpn agent driver
"""
vpnservice_dict = dict(vpnservice)
# Populate tenant_id for RPC compat
vpnservice_dict['tenant_id'] = vpnservice_dict['project_id']
vpnservice_dict['ipsec_site_connections'] = []
if vpnservice.subnet:
vpnservice_dict['subnet'] = dict(vpnservice.subnet)
else:
vpnservice_dict['subnet'] = None
# NOTE: Following is used for rolling upgrades, where agent may be
# at version N, and server at N+1. We need to populate the subnet
# with (only) the CIDR from the first connection's local endpoint
# group.
subnet_cidr = None
# Not removing external_ip from vpnservice_dict, as some providers
# may be still using it from vpnservice_dict. Will use whichever IP
# is specified.
vpnservice_dict['external_ip'] = (
vpnservice.external_v4_ip or vpnservice.external_v6_ip)
for ipsec_site_connection in vpnservice.ipsec_site_connections:
ipsec_site_connection_dict = dict(ipsec_site_connection)
try:
netaddr.IPAddress(ipsec_site_connection_dict['peer_id'])
if ipsec_site_connection_dict['local_id']:
netaddr.IPAddress(ipsec_site_connection_dict['local_id'])
except netaddr.core.AddrFormatError:
ipsec_site_connection_dict['peer_id'] = (
'@' + ipsec_site_connection_dict['peer_id'])
if ipsec_site_connection_dict['local_id']:
ipsec_site_connection_dict['local_id'] = (
'@' + ipsec_site_connection_dict['local_id'])
ipsec_site_connection_dict['ikepolicy'] = dict(
ipsec_site_connection.ikepolicy)
ipsec_site_connection_dict['ipsecpolicy'] = dict(
ipsec_site_connection.ipsecpolicy)
vpnservice_dict['ipsec_site_connections'].append(
ipsec_site_connection_dict)
if vpnservice.subnet:
local_cidrs = [vpnservice.subnet.cidr]
peer_cidrs = [
peer_cidr.cidr
for peer_cidr in ipsec_site_connection.peer_cidrs]
else:
local_cidrs = [local_cidr_map[ep.endpoint]
for ep in ipsec_site_connection.local_ep_group.endpoints]
peer_cidrs = [
ep.endpoint
for ep in ipsec_site_connection.peer_ep_group.endpoints]
if not subnet_cidr:
epg = ipsec_site_connection.local_ep_group
subnet_cidr = local_cidr_map[epg.endpoints[0].endpoint]
ipsec_site_connection_dict['peer_cidrs'] = peer_cidrs
ipsec_site_connection_dict['local_cidrs'] = local_cidrs
ipsec_site_connection_dict['local_ip_vers'] = netaddr.IPNetwork(
local_cidrs[0]).version
ipsec_site_connection_dict['external_ip'] = (
self.get_external_ip_based_on_peer(vpnservice,
ipsec_site_connection_dict))
if not vpnservice.subnet:
vpnservice_dict['subnet'] = {'cidr': subnet_cidr}
return vpnservice_dict

View File

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

View File

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

View File

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

View File

@ -1,29 +0,0 @@
# Copyright 2017 Eayun, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
class VpnDriverValidator(object):
"""Driver-specific validation routines for VPN resources."""
def __init__(self, driver):
self.driver = driver
@property
def l3_plugin(self):
return self.driver.l3_plugin
def validate_ipsec_site_connection(self, context, ipsec_sitecon):
"""Driver can override this for its additional validations."""
pass

Some files were not shown because too many files have changed in this diff Show More