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:
parent
797c046669
commit
608063640d
|
@ -1,7 +0,0 @@
|
|||
[run]
|
||||
branch = True
|
||||
source = neutron_vpnaas
|
||||
omit = neutron_vpnaas/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/neutron-vpnaas.git
|
11
.mailmap
11
.mailmap
|
@ -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
132
.pylintrc
|
@ -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
|
|
@ -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
|
|
@ -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>`_
|
|
@ -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
176
LICENSE
|
@ -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.
|
||||
|
|
@ -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.
|
19
README.rst
19
README.rst
|
@ -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>`_
|
|
@ -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>`_
|
|
@ -1,3 +0,0 @@
|
|||
This directory contains the neutron-vpnaas devstack plugin. Please
|
||||
see the devref for how to set up VPNaaS with devstack.
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
#
|
|
@ -1 +0,0 @@
|
|||
register_project_for_upgrade neutron-vpnaas
|
|
@ -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
|
|
@ -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.
|
||||
#
|
|
@ -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'
|
|
@ -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).
|
|
@ -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`
|
|
@ -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.
|
|
@ -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
|
|
@ -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>
|
|
@ -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/>
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||
[DEFAULT]
|
||||
output_file = etc/neutron_vpnaas.conf.sample
|
||||
wrap_width = 79
|
||||
|
||||
namespace = neutron.vpnaas
|
|
@ -1,5 +0,0 @@
|
|||
[DEFAULT]
|
||||
output_file = etc/vpn_agent.ini.sample
|
||||
wrap_width = 79
|
||||
|
||||
namespace = neutron.vpnaas.agent
|
|
@ -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')
|
|
@ -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)
|
|
@ -1,26 +0,0 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging as sys_logging
|
||||
|
||||
from oslo_reports import guru_meditation_report as gmr
|
||||
|
||||
from neutron_vpnaas import version
|
||||
|
||||
# During the call to gmr.TextGuruMeditation.setup_autorun(), Guru Meditation
|
||||
# Report tries to start logging. Set a handler here to accommodate this.
|
||||
logger = sys_logging.getLogger(None)
|
||||
if not logger.handlers:
|
||||
logger.addHandler(sys_logging.StreamHandler())
|
||||
|
||||
_version_string = version.version_info.release_string()
|
||||
gmr.TextGuruMeditation.setup_autorun(version=_version_string)
|
|
@ -1,14 +0,0 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
|
@ -1,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()
|
|
@ -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()
|
|
@ -1 +0,0 @@
|
|||
Generic single-database configuration.
|
|
@ -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'
|
|
@ -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()
|
|
@ -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"}
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||
b6a2519ab7dc
|
|
@ -1 +0,0 @@
|
|||
95601446dbcc
|
|
@ -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
|
|
@ -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')
|
|
@ -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)
|
|
@ -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)
|
|
@ -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))
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
|
@ -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'),
|
||||
)
|
|
@ -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()
|
||||
}
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
|
@ -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'])
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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')
|
|
@ -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
|
|
@ -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
|
|
@ -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"]
|
|
@ -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
|
|
@ -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)
|
||||
]
|
|
@ -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')
|
|
@ -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,
|
||||
]
|
|
@ -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()
|
|
@ -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'
|
|
@ -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
|
|
@ -1,291 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
import netaddr
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
import requests
|
||||
from requests import exceptions as r_exc
|
||||
|
||||
|
||||
TIMEOUT = 20.0
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
HEADER_CONTENT_TYPE_JSON = {'content-type': 'application/json'}
|
||||
URL_BASE = 'https://%(host)s/api/v1/%(resource)s'
|
||||
|
||||
# CSR RESTapi URIs
|
||||
URI_VPN_IPSEC_POLICIES = 'vpn-svc/ipsec/policies'
|
||||
URI_VPN_IPSEC_POLICIES_ID = URI_VPN_IPSEC_POLICIES + '/%s'
|
||||
URI_VPN_IKE_POLICIES = 'vpn-svc/ike/policies'
|
||||
URI_VPN_IKE_POLICIES_ID = URI_VPN_IKE_POLICIES + '/%s'
|
||||
URI_VPN_IKE_KEYRINGS = 'vpn-svc/ike/keyrings'
|
||||
URI_VPN_IKE_KEYRINGS_ID = URI_VPN_IKE_KEYRINGS + '/%s'
|
||||
URI_VPN_IKE_KEEPALIVE = 'vpn-svc/ike/keepalive'
|
||||
URI_VPN_SITE_TO_SITE = 'vpn-svc/site-to-site'
|
||||
URI_VPN_SITE_TO_SITE_ID = URI_VPN_SITE_TO_SITE + '/%s'
|
||||
URI_VPN_SITE_TO_SITE_STATE = URI_VPN_SITE_TO_SITE + '/%s/state'
|
||||
URI_VPN_SITE_ACTIVE_SESSIONS = URI_VPN_SITE_TO_SITE + '/active/sessions'
|
||||
URI_ROUTING_STATIC_ROUTES = 'routing-svc/static-routes'
|
||||
URI_ROUTING_STATIC_ROUTES_ID = URI_ROUTING_STATIC_ROUTES + '/%s'
|
||||
|
||||
|
||||
def make_route_id(cidr, interface):
|
||||
"""Build ID that will be used to identify route for later deletion."""
|
||||
net = netaddr.IPNetwork(cidr)
|
||||
return '%(network)s_%(prefix)s_%(interface)s' % {
|
||||
'network': net.network,
|
||||
'prefix': net.prefixlen,
|
||||
'interface': interface}
|
||||
|
||||
|
||||
class CsrRestClient(object):
|
||||
|
||||
"""REST CsrRestClient for accessing the Cisco Cloud Services Router."""
|
||||
|
||||
def __init__(self, settings):
|
||||
self.port = str(settings.get('protocol_port', 55443))
|
||||
self.host = ':'.join([settings.get('rest_mgmt_ip', ''), self.port])
|
||||
self.auth = (settings['username'], settings['password'])
|
||||
self.inner_if_name = settings.get('inner_if_name', '')
|
||||
self.outer_if_name = settings.get('outer_if_name', '')
|
||||
self.token = None
|
||||
self.vrf = settings.get('vrf', '')
|
||||
self.vrf_prefix = 'vrf/%s/' % self.vrf if self.vrf else ""
|
||||
self.status = requests.codes.OK
|
||||
self.timeout = settings.get('timeout')
|
||||
self.max_tries = 5
|
||||
self.session = requests.Session()
|
||||
|
||||
def _response_info_for(self, response, method):
|
||||
"""Return contents or location from response.
|
||||
|
||||
For a POST or GET with a 200 response, the response content
|
||||
is returned.
|
||||
|
||||
For a POST with a 201 response, return the header's location,
|
||||
which contains the identifier for the created resource.
|
||||
|
||||
If there is an error, return the response content, so that
|
||||
it can be used in error processing ('error-code', 'error-message',
|
||||
and 'detail' fields).
|
||||
"""
|
||||
if method in ('POST', 'GET') and self.status == requests.codes.OK:
|
||||
LOG.debug('RESPONSE: %s', response.json())
|
||||
return response.json()
|
||||
if method == 'POST' and self.status == requests.codes.CREATED:
|
||||
return response.headers.get('location', '')
|
||||
if self.status >= requests.codes.BAD_REQUEST and response.content:
|
||||
if b'error-code' in response.content:
|
||||
content = jsonutils.loads(response.content)
|
||||
LOG.debug("Error response content %s", content)
|
||||
return content
|
||||
|
||||
def _request(self, method, url, **kwargs):
|
||||
"""Perform REST request and save response info."""
|
||||
try:
|
||||
LOG.debug("%(method)s: Request for %(resource)s payload: "
|
||||
"%(payload)s",
|
||||
{'method': method.upper(), 'resource': url,
|
||||
'payload': kwargs.get('data')})
|
||||
start_time = time.time()
|
||||
response = self.session.request(method, url, verify=False,
|
||||
timeout=self.timeout, **kwargs)
|
||||
LOG.debug("%(method)s Took %(time).2f seconds to process",
|
||||
{'method': method.upper(),
|
||||
'time': time.time() - start_time})
|
||||
except (r_exc.Timeout, r_exc.SSLError) as te:
|
||||
# Should never see SSLError, unless requests package is old (<2.0)
|
||||
timeout_val = 0.0 if self.timeout is None else self.timeout
|
||||
LOG.warning("%(method)s: Request timeout%(ssl)s "
|
||||
"(%(timeout).3f sec) for CSR(%(host)s)",
|
||||
{'method': method,
|
||||
'timeout': timeout_val,
|
||||
'ssl': '(SSLError)'
|
||||
if isinstance(te, r_exc.SSLError) else '',
|
||||
'host': self.host})
|
||||
self.status = requests.codes.REQUEST_TIMEOUT
|
||||
except r_exc.ConnectionError:
|
||||
LOG.exception("%(method)s: Unable to connect to "
|
||||
"CSR(%(host)s)",
|
||||
{'method': method, 'host': self.host})
|
||||
self.status = requests.codes.NOT_FOUND
|
||||
except Exception as e:
|
||||
LOG.error("%(method)s: Unexpected error for CSR (%(host)s): "
|
||||
"%(error)s",
|
||||
{'method': method, 'host': self.host, 'error': e})
|
||||
self.status = requests.codes.INTERNAL_SERVER_ERROR
|
||||
else:
|
||||
self.status = response.status_code
|
||||
LOG.debug("%(method)s: Completed [%(status)s]",
|
||||
{'method': method, 'status': self.status})
|
||||
return self._response_info_for(response, method)
|
||||
|
||||
def authenticate(self):
|
||||
"""Obtain a token to use for subsequent CSR REST requests.
|
||||
|
||||
This is called when there is no token yet, or if the token has expired
|
||||
and attempts to use it resulted in an UNAUTHORIZED REST response.
|
||||
"""
|
||||
|
||||
url = URL_BASE % {'host': self.host, 'resource': 'auth/token-services'}
|
||||
headers = {'Content-Length': '0',
|
||||
'Accept': 'application/json'}
|
||||
headers.update(HEADER_CONTENT_TYPE_JSON)
|
||||
LOG.debug("%(auth)s with CSR %(host)s",
|
||||
{'auth': 'Authenticating' if self.token is None
|
||||
else 'Reauthenticating', 'host': self.host})
|
||||
self.token = None
|
||||
response = self._request("POST", url, headers=headers, auth=self.auth)
|
||||
if response:
|
||||
self.token = response['token-id']
|
||||
LOG.debug("Successfully authenticated with CSR %s", self.host)
|
||||
return True
|
||||
LOG.error("Failed authentication with CSR %(host)s [%(status)s]",
|
||||
{'host': self.host, 'status': self.status})
|
||||
|
||||
def _do_request(self, method, resource, payload=None, more_headers=None,
|
||||
full_url=False):
|
||||
"""Perform a REST request to a CSR resource.
|
||||
|
||||
If this is the first time interacting with the CSR, a token will
|
||||
be obtained. If the request fails, due to an expired token, the
|
||||
token will be obtained and the request will be retried once more.
|
||||
"""
|
||||
|
||||
if self.token is None:
|
||||
if not self.authenticate():
|
||||
return
|
||||
|
||||
if full_url:
|
||||
url = resource
|
||||
else:
|
||||
url = ('https://%(host)s/api/v1/%(resource)s' %
|
||||
{'host': self.host, 'resource': resource})
|
||||
headers = {'Accept': 'application/json', 'X-auth-token': self.token}
|
||||
if more_headers:
|
||||
headers.update(more_headers)
|
||||
if payload:
|
||||
payload = jsonutils.dumps(payload)
|
||||
response = self._request(method, url, data=payload, headers=headers)
|
||||
if self.status == requests.codes.UNAUTHORIZED:
|
||||
if not self.authenticate():
|
||||
return
|
||||
headers['X-auth-token'] = self.token
|
||||
response = self._request(method, url, data=payload,
|
||||
headers=headers)
|
||||
if self.status != requests.codes.REQUEST_TIMEOUT:
|
||||
return response
|
||||
LOG.error("%(method)s: Request timeout for CSR(%(host)s)",
|
||||
{'method': method, 'host': self.host})
|
||||
|
||||
def get_request(self, resource, full_url=False):
|
||||
"""Perform a REST GET requests for a CSR resource."""
|
||||
return self._do_request('GET', resource, full_url=full_url)
|
||||
|
||||
def post_request(self, resource, payload=None):
|
||||
"""Perform a POST request to a CSR resource."""
|
||||
return self._do_request('POST', resource, payload=payload,
|
||||
more_headers=HEADER_CONTENT_TYPE_JSON)
|
||||
|
||||
def put_request(self, resource, payload=None):
|
||||
"""Perform a PUT request to a CSR resource."""
|
||||
return self._do_request('PUT', resource, payload=payload,
|
||||
more_headers=HEADER_CONTENT_TYPE_JSON)
|
||||
|
||||
def delete_request(self, resource):
|
||||
"""Perform a DELETE request on a CSR resource."""
|
||||
return self._do_request('DELETE', resource,
|
||||
more_headers=HEADER_CONTENT_TYPE_JSON)
|
||||
|
||||
# VPN Specific APIs
|
||||
|
||||
def create_ike_policy(self, policy_info):
|
||||
base_ike_policy_info = {u'version': u'v1',
|
||||
u'local-auth-method': u'pre-share'}
|
||||
base_ike_policy_info.update(policy_info)
|
||||
return self.post_request(URI_VPN_IKE_POLICIES,
|
||||
payload=base_ike_policy_info)
|
||||
|
||||
def create_ipsec_policy(self, policy_info):
|
||||
base_ipsec_policy_info = {u'mode': u'tunnel'}
|
||||
base_ipsec_policy_info.update(policy_info)
|
||||
return self.post_request(URI_VPN_IPSEC_POLICIES,
|
||||
payload=base_ipsec_policy_info)
|
||||
|
||||
def create_pre_shared_key(self, psk_info):
|
||||
return self.post_request(self.vrf_prefix + URI_VPN_IKE_KEYRINGS,
|
||||
payload=psk_info)
|
||||
|
||||
def create_ipsec_connection(self, connection_info):
|
||||
base_conn_info = {
|
||||
u'vpn-type': u'site-to-site',
|
||||
u'ip-version': u'ipv4',
|
||||
u'local-device': {
|
||||
u'tunnel-ip-address': self.outer_if_name,
|
||||
u'ip-address': self.inner_if_name
|
||||
}
|
||||
}
|
||||
connection_info.update(base_conn_info)
|
||||
if self.vrf:
|
||||
connection_info[u'tunnel-vrf'] = self.vrf
|
||||
return self.post_request(self.vrf_prefix + URI_VPN_SITE_TO_SITE,
|
||||
payload=connection_info)
|
||||
|
||||
def configure_ike_keepalive(self, keepalive_info):
|
||||
base_keepalive_info = {u'periodic': True}
|
||||
keepalive_info.update(base_keepalive_info)
|
||||
return self.put_request(URI_VPN_IKE_KEEPALIVE, keepalive_info)
|
||||
|
||||
def create_static_route(self, route_info):
|
||||
return self.post_request(self.vrf_prefix + URI_ROUTING_STATIC_ROUTES,
|
||||
payload=route_info)
|
||||
|
||||
def delete_static_route(self, route_id):
|
||||
return self.delete_request(
|
||||
self.vrf_prefix + URI_ROUTING_STATIC_ROUTES_ID % route_id)
|
||||
|
||||
def set_ipsec_connection_state(self, tunnel, admin_up=True):
|
||||
"""Set the IPSec site-to-site connection (tunnel) admin state.
|
||||
|
||||
Note: When a tunnel is created, it will be admin up.
|
||||
"""
|
||||
info = {u'vpn-interface-name': tunnel, u'enabled': admin_up}
|
||||
return self.put_request(
|
||||
self.vrf_prefix + URI_VPN_SITE_TO_SITE_STATE % tunnel, info)
|
||||
|
||||
def delete_ipsec_connection(self, conn_id):
|
||||
return self.delete_request(
|
||||
self.vrf_prefix + URI_VPN_SITE_TO_SITE_ID % conn_id)
|
||||
|
||||
def delete_ipsec_policy(self, policy_id):
|
||||
return self.delete_request(URI_VPN_IPSEC_POLICIES_ID % policy_id)
|
||||
|
||||
def delete_ike_policy(self, policy_id):
|
||||
return self.delete_request(URI_VPN_IKE_POLICIES_ID % policy_id)
|
||||
|
||||
def delete_pre_shared_key(self, key_id):
|
||||
return self.delete_request(
|
||||
self.vrf_prefix + URI_VPN_IKE_KEYRINGS_ID % key_id)
|
||||
|
||||
def read_tunnel_statuses(self):
|
||||
results = self.get_request(self.vrf_prefix +
|
||||
URI_VPN_SITE_ACTIVE_SESSIONS)
|
||||
if self.status != requests.codes.OK or not results:
|
||||
return []
|
||||
tunnels = [(t[u'vpn-interface-name'], t[u'status'])
|
||||
for t in results['items']]
|
||||
return tunnels
|
|
@ -1,741 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.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)
|
|
@ -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
|
@ -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)
|
|
@ -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)
|
|
@ -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 -%}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -1,8 +0,0 @@
|
|||
charon {
|
||||
load_modular = yes
|
||||
plugins {
|
||||
include strongswan.d/charon/*.conf
|
||||
}
|
||||
}
|
||||
|
||||
include strongswan.d/*.conf
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -1,238 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron_lib.db import model_base
|
||||
from neutron_lib import exceptions as nexception
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log as logging
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import exc as sql_exc
|
||||
|
||||
from neutron_vpnaas._i18n import _
|
||||
from neutron_vpnaas.db.vpn import vpn_models
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# Note: Artificially limit these to reduce mapping table size and performance
|
||||
# Tunnel can be 0..7FFFFFFF, IKE policy can be 1..10000, IPSec policy can be
|
||||
# 1..31 characters long.
|
||||
MAX_CSR_TUNNELS = 10000
|
||||
MAX_CSR_IKE_POLICIES = 2000
|
||||
MAX_CSR_IPSEC_POLICIES = 2000
|
||||
|
||||
TUNNEL = 'Tunnel'
|
||||
IKE_POLICY = 'IKE Policy'
|
||||
IPSEC_POLICY = 'IPSec Policy'
|
||||
|
||||
MAPPING_LIMITS = {TUNNEL: (0, MAX_CSR_TUNNELS),
|
||||
IKE_POLICY: (1, MAX_CSR_IKE_POLICIES),
|
||||
IPSEC_POLICY: (1, MAX_CSR_IPSEC_POLICIES)}
|
||||
|
||||
|
||||
class CsrInternalError(nexception.NeutronException):
|
||||
message = _("Fatal - %(reason)s")
|
||||
|
||||
|
||||
class IdentifierMap(model_base.BASEV2):
|
||||
|
||||
"""Maps OpenStack IDs to compatible numbers for Cisco CSR."""
|
||||
|
||||
__tablename__ = 'cisco_csr_identifier_map'
|
||||
|
||||
ipsec_site_conn_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('ipsec_site_connections.id',
|
||||
ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
csr_tunnel_id = sa.Column(sa.Integer, nullable=False)
|
||||
csr_ike_policy_id = sa.Column(sa.Integer, nullable=False)
|
||||
csr_ipsec_policy_id = sa.Column(sa.Integer, nullable=False)
|
||||
|
||||
|
||||
def get_next_available_id(session, table_field, id_type):
|
||||
"""Find first unused id for the specified field in IdentifierMap table.
|
||||
|
||||
As entries are removed, find the first "hole" and return that as the
|
||||
next available ID. To improve performance, artificially limit
|
||||
the number of entries to a smaller range. Currently, these IDs are
|
||||
globally unique. Could enhance in the future to be unique per router
|
||||
(CSR).
|
||||
"""
|
||||
min_value = MAPPING_LIMITS[id_type][0]
|
||||
max_value = MAPPING_LIMITS[id_type][1]
|
||||
rows = session.query(table_field).order_by(table_field)
|
||||
used_ids = set([row[0] for row in rows])
|
||||
all_ids = set(range(min_value, max_value + min_value))
|
||||
available_ids = all_ids - used_ids
|
||||
if not available_ids:
|
||||
msg = _("No available Cisco CSR %(type)s IDs from "
|
||||
"%(min)d..%(max)d") % {'type': id_type,
|
||||
'min': min_value,
|
||||
'max': max_value}
|
||||
LOG.error(msg)
|
||||
raise IndexError(msg)
|
||||
return available_ids.pop()
|
||||
|
||||
|
||||
def get_next_available_tunnel_id(session):
|
||||
"""Find first available tunnel ID from 0..MAX_CSR_TUNNELS-1."""
|
||||
return get_next_available_id(session, IdentifierMap.csr_tunnel_id,
|
||||
TUNNEL)
|
||||
|
||||
|
||||
def get_next_available_ike_policy_id(session):
|
||||
"""Find first available IKE Policy ID from 1..MAX_CSR_IKE_POLICIES."""
|
||||
return get_next_available_id(session, IdentifierMap.csr_ike_policy_id,
|
||||
IKE_POLICY)
|
||||
|
||||
|
||||
def get_next_available_ipsec_policy_id(session):
|
||||
"""Find first available IPSec Policy ID from 1..MAX_CSR_IKE_POLICIES."""
|
||||
return get_next_available_id(session, IdentifierMap.csr_ipsec_policy_id,
|
||||
IPSEC_POLICY)
|
||||
|
||||
|
||||
def find_conn_with_policy(policy_field, policy_id, conn_id, session):
|
||||
"""Return ID of another connection (if any) that uses same policy ID."""
|
||||
qry = session.query(vpn_models.IPsecSiteConnection.id)
|
||||
match = qry.filter_request(
|
||||
policy_field == policy_id,
|
||||
vpn_models.IPsecSiteConnection.id != conn_id).first()
|
||||
if match:
|
||||
return match[0]
|
||||
|
||||
|
||||
def find_connection_using_ike_policy(ike_policy_id, conn_id, session):
|
||||
"""Return ID of another connection that uses same IKE policy ID."""
|
||||
return find_conn_with_policy(vpn_models.IPsecSiteConnection.ikepolicy_id,
|
||||
ike_policy_id, conn_id, session)
|
||||
|
||||
|
||||
def find_connection_using_ipsec_policy(ipsec_policy_id, conn_id, session):
|
||||
"""Return ID of another connection that uses same IPSec policy ID."""
|
||||
return find_conn_with_policy(vpn_models.IPsecSiteConnection.ipsecpolicy_id,
|
||||
ipsec_policy_id, conn_id, session)
|
||||
|
||||
|
||||
def lookup_policy(policy_type, policy_field, conn_id, session):
|
||||
"""Obtain specified policy's mapping from other connection."""
|
||||
try:
|
||||
return session.query(policy_field).filter_by(
|
||||
ipsec_site_conn_id=conn_id).one()[0]
|
||||
except sql_exc.NoResultFound:
|
||||
msg = _("Database inconsistency between IPSec connection and "
|
||||
"Cisco CSR mapping table (%s)") % policy_type
|
||||
raise CsrInternalError(reason=msg)
|
||||
|
||||
|
||||
def lookup_ike_policy_id_for(conn_id, session):
|
||||
"""Obtain existing Cisco CSR IKE policy ID from another connection."""
|
||||
return lookup_policy(IKE_POLICY, IdentifierMap.csr_ike_policy_id,
|
||||
conn_id, session)
|
||||
|
||||
|
||||
def lookup_ipsec_policy_id_for(conn_id, session):
|
||||
"""Obtain existing Cisco CSR IPSec policy ID from another connection."""
|
||||
return lookup_policy(IPSEC_POLICY, IdentifierMap.csr_ipsec_policy_id,
|
||||
conn_id, session)
|
||||
|
||||
|
||||
def determine_csr_policy_id(policy_type, conn_policy_field, map_policy_field,
|
||||
policy_id, conn_id, session):
|
||||
"""Use existing or reserve a new policy ID for Cisco CSR use.
|
||||
|
||||
TODO(pcm) FUTURE: Once device driver adds support for IKE/IPSec policy
|
||||
ID sharing, add call to find_conn_with_policy() to find used ID and
|
||||
then call lookup_policy() to find the current mapping for that ID.
|
||||
"""
|
||||
csr_id = get_next_available_id(session, map_policy_field, policy_type)
|
||||
LOG.debug("Reserved new CSR ID %(csr_id)d for %(policy)s "
|
||||
"ID %(policy_id)s", {'csr_id': csr_id,
|
||||
'policy': policy_type,
|
||||
'policy_id': policy_id})
|
||||
return csr_id
|
||||
|
||||
|
||||
def determine_csr_ike_policy_id(ike_policy_id, conn_id, session):
|
||||
"""Use existing, or reserve a new IKE policy ID for Cisco CSR."""
|
||||
return determine_csr_policy_id(IKE_POLICY,
|
||||
vpn_models.IPsecSiteConnection.ikepolicy_id,
|
||||
IdentifierMap.csr_ike_policy_id,
|
||||
ike_policy_id, conn_id, session)
|
||||
|
||||
|
||||
def determine_csr_ipsec_policy_id(ipsec_policy_id, conn_id, session):
|
||||
"""Use existing, or reserve a new IPSec policy ID for Cisco CSR."""
|
||||
return determine_csr_policy_id(
|
||||
IPSEC_POLICY,
|
||||
vpn_models.IPsecSiteConnection.ipsecpolicy_id,
|
||||
IdentifierMap.csr_ipsec_policy_id,
|
||||
ipsec_policy_id, conn_id, session)
|
||||
|
||||
|
||||
def get_tunnel_mapping_for(conn_id, session):
|
||||
try:
|
||||
entry = session.query(IdentifierMap).filter_by(
|
||||
ipsec_site_conn_id=conn_id).one()
|
||||
LOG.debug("Mappings for IPSec connection %(conn)s - "
|
||||
"tunnel=%(tunnel)s ike_policy=%(csr_ike)d "
|
||||
"ipsec_policy=%(csr_ipsec)d",
|
||||
{'conn': conn_id, 'tunnel': entry.csr_tunnel_id,
|
||||
'csr_ike': entry.csr_ike_policy_id,
|
||||
'csr_ipsec': entry.csr_ipsec_policy_id})
|
||||
return (entry.csr_tunnel_id, entry.csr_ike_policy_id,
|
||||
entry.csr_ipsec_policy_id)
|
||||
except sql_exc.NoResultFound:
|
||||
msg = _("Existing entry for IPSec connection %s not found in Cisco "
|
||||
"CSR mapping table") % conn_id
|
||||
raise CsrInternalError(reason=msg)
|
||||
|
||||
|
||||
def create_tunnel_mapping(context, conn_info):
|
||||
"""Create Cisco CSR IDs, using mapping table and OpenStack UUIDs."""
|
||||
conn_id = conn_info['id']
|
||||
ike_policy_id = conn_info['ikepolicy_id']
|
||||
ipsec_policy_id = conn_info['ipsecpolicy_id']
|
||||
tenant_id = conn_info['tenant_id']
|
||||
with context.session.begin():
|
||||
csr_tunnel_id = get_next_available_tunnel_id(context.session)
|
||||
csr_ike_id = determine_csr_ike_policy_id(ike_policy_id, conn_id,
|
||||
context.session)
|
||||
csr_ipsec_id = determine_csr_ipsec_policy_id(ipsec_policy_id, conn_id,
|
||||
context.session)
|
||||
map_entry = IdentifierMap(tenant_id=tenant_id,
|
||||
ipsec_site_conn_id=conn_id,
|
||||
csr_tunnel_id=csr_tunnel_id,
|
||||
csr_ike_policy_id=csr_ike_id,
|
||||
csr_ipsec_policy_id=csr_ipsec_id)
|
||||
try:
|
||||
context.session.add(map_entry)
|
||||
# Force committing to database
|
||||
context.session.flush()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
msg = _("Attempt to create duplicate entry in Cisco CSR "
|
||||
"mapping table for connection %s") % conn_id
|
||||
raise CsrInternalError(reason=msg)
|
||||
LOG.info("Mapped connection %(conn_id)s to Tunnel%(tunnel_id)d "
|
||||
"using IKE policy ID %(ike_id)d and IPSec policy "
|
||||
"ID %(ipsec_id)d",
|
||||
{'conn_id': conn_id, 'tunnel_id': csr_tunnel_id,
|
||||
'ike_id': csr_ike_id, 'ipsec_id': csr_ipsec_id})
|
||||
|
||||
|
||||
def delete_tunnel_mapping(context, conn_info):
|
||||
conn_id = conn_info['id']
|
||||
with context.session.begin():
|
||||
sess_qry = context.session.query(IdentifierMap)
|
||||
sess_qry.filter_by(ipsec_site_conn_id=conn_id).delete()
|
||||
LOG.info("Removed mapping for connection %s", conn_id)
|
|
@ -1,222 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron.common import rpc as n_rpc
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging
|
||||
|
||||
from neutron.db.models import servicetype
|
||||
|
||||
from neutron_vpnaas.db.vpn import vpn_models
|
||||
from neutron_vpnaas.services.vpn.common import topics
|
||||
from neutron_vpnaas.services.vpn import service_drivers
|
||||
from neutron_vpnaas.services.vpn.service_drivers import base_ipsec
|
||||
from neutron_vpnaas.services.vpn.service_drivers \
|
||||
import cisco_csr_db as csr_id_map
|
||||
from neutron_vpnaas.services.vpn.service_drivers import cisco_validator
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
IPSEC = 'ipsec'
|
||||
BASE_IPSEC_VERSION = '1.0'
|
||||
LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400},
|
||||
'IPSec Policy': {'min': 120, 'max': 2592000}}
|
||||
MIN_CSR_MTU = 1500
|
||||
MAX_CSR_MTU = 9192
|
||||
VRF_SUFFIX_LEN = 6
|
||||
|
||||
T2_PORT_NAME = 't2_p:'
|
||||
|
||||
|
||||
class CiscoCsrIPsecVpnDriverCallBack(object):
|
||||
|
||||
"""Handler for agent to plugin RPC messaging."""
|
||||
|
||||
# history
|
||||
# 1.0 Initial version
|
||||
|
||||
target = oslo_messaging.Target(version=BASE_IPSEC_VERSION)
|
||||
|
||||
def __init__(self, driver):
|
||||
super(CiscoCsrIPsecVpnDriverCallBack, self).__init__()
|
||||
self.driver = driver
|
||||
|
||||
def create_rpc_dispatcher(self):
|
||||
return n_rpc.PluginRpcDispatcher([self])
|
||||
|
||||
def get_vpn_services_using(self, context, router_id):
|
||||
query = context.session.query(vpn_models.VPNService)
|
||||
query = query.join(
|
||||
servicetype.ProviderResourceAssociation,
|
||||
servicetype.ProviderResourceAssociation.resource_id ==
|
||||
vpn_models.VPNService.id)
|
||||
query = query.join(vpn_models.IPsecSiteConnection)
|
||||
query = query.join(vpn_models.IKEPolicy)
|
||||
query = query.join(vpn_models.IPsecPolicy)
|
||||
query = query.join(vpn_models.IPsecPeerCidr)
|
||||
query = query.filter(vpn_models.VPNService.router_id == router_id)
|
||||
query = query.filter(
|
||||
servicetype.ProviderResourceAssociation.provider_name ==
|
||||
self.driver.name)
|
||||
return query.all()
|
||||
|
||||
def get_vpn_services_on_host(self, context, host=None):
|
||||
"""Returns info on the VPN services on the host."""
|
||||
routers = self.driver.l3_plugin.get_active_routers_for_host(context,
|
||||
host)
|
||||
host_vpn_services = []
|
||||
for router in routers:
|
||||
vpn_services = self.get_vpn_services_using(context, router['id'])
|
||||
for vpn_service in vpn_services:
|
||||
host_vpn_services.append(
|
||||
self.driver.make_vpnservice_dict(context, vpn_service,
|
||||
router))
|
||||
return host_vpn_services
|
||||
|
||||
def update_status(self, context, status):
|
||||
"""Update status of all vpnservices."""
|
||||
plugin = self.driver.service_plugin
|
||||
plugin.update_status_by_agent(context, status)
|
||||
|
||||
|
||||
class CiscoCsrIPsecVpnAgentApi(service_drivers.BaseIPsecVpnAgentApi):
|
||||
|
||||
"""API and handler for Cisco IPSec plugin to agent RPC messaging."""
|
||||
|
||||
target = oslo_messaging.Target(version=BASE_IPSEC_VERSION)
|
||||
|
||||
def __init__(self, topic, default_version, driver):
|
||||
super(CiscoCsrIPsecVpnAgentApi, self).__init__(
|
||||
topic, default_version, driver)
|
||||
|
||||
def _agent_notification(self, context, method, router_id,
|
||||
version=None, **kwargs):
|
||||
"""Notify update for the agent.
|
||||
|
||||
Find the host for the router being notified and then
|
||||
dispatches a notification for the VPN device driver.
|
||||
"""
|
||||
admin_context = context if context.is_admin else context.elevated()
|
||||
if not version:
|
||||
version = self.target.version
|
||||
host = self.driver.l3_plugin.get_host_for_router(admin_context,
|
||||
router_id)
|
||||
LOG.debug('Notify agent at %(topic)s.%(host)s the message '
|
||||
'%(method)s %(args)s for router %(router)s',
|
||||
{'topic': self.topic,
|
||||
'host': host,
|
||||
'method': method,
|
||||
'args': kwargs,
|
||||
'router': router_id})
|
||||
cctxt = self.client.prepare(server=host, version=version)
|
||||
cctxt.cast(context, method, **kwargs)
|
||||
|
||||
|
||||
class CiscoCsrIPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver):
|
||||
|
||||
"""Cisco CSR VPN Service Driver class for IPsec."""
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
super(CiscoCsrIPsecVPNDriver, self).__init__(
|
||||
service_plugin,
|
||||
cisco_validator.CiscoCsrVpnValidator(self))
|
||||
|
||||
def create_rpc_conn(self):
|
||||
self.endpoints = [CiscoCsrIPsecVpnDriverCallBack(self)]
|
||||
self.conn = n_rpc.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
|
|
@ -1,147 +0,0 @@
|
|||
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import netaddr
|
||||
from netaddr import core as net_exc
|
||||
from neutron_lib import exceptions as nexception
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron_vpnaas._i18n import _
|
||||
from neutron_vpnaas.services.vpn.service_drivers import driver_validator
|
||||
|
||||
|
||||
LIFETIME_LIMITS = {'IKE Policy': {'min': 60, 'max': 86400},
|
||||
'IPSec Policy': {'min': 120, 'max': 2592000}}
|
||||
MIN_CSR_MTU = 1500
|
||||
MAX_CSR_MTU = 9192
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CsrValidationFailure(nexception.BadRequest):
|
||||
message = _("Cisco CSR does not support %(resource)s attribute %(key)s "
|
||||
"with value '%(value)s'")
|
||||
|
||||
|
||||
class CiscoCsrVpnValidator(driver_validator.VpnDriverValidator):
|
||||
|
||||
"""Driver-specific validator methods for the Cisco CSR."""
|
||||
|
||||
def validate_lifetime(self, for_policy, policy_info):
|
||||
"""Ensure lifetime in secs and value is supported, based on policy."""
|
||||
units = policy_info['lifetime']['units']
|
||||
if units != 'seconds':
|
||||
raise CsrValidationFailure(resource=for_policy,
|
||||
key='lifetime:units',
|
||||
value=units)
|
||||
value = policy_info['lifetime']['value']
|
||||
if (value < LIFETIME_LIMITS[for_policy]['min'] or
|
||||
value > LIFETIME_LIMITS[for_policy]['max']):
|
||||
raise CsrValidationFailure(resource=for_policy,
|
||||
key='lifetime:value',
|
||||
value=value)
|
||||
|
||||
def validate_ike_version(self, policy_info):
|
||||
"""Ensure IKE policy is v1 for current REST API."""
|
||||
version = policy_info['ike_version']
|
||||
if version != 'v1':
|
||||
raise CsrValidationFailure(resource='IKE Policy',
|
||||
key='ike_version',
|
||||
value=version)
|
||||
|
||||
def validate_mtu(self, conn_info):
|
||||
"""Ensure the MTU value is supported."""
|
||||
mtu = conn_info['mtu']
|
||||
if mtu < MIN_CSR_MTU or mtu > MAX_CSR_MTU:
|
||||
raise CsrValidationFailure(resource='IPSec Connection',
|
||||
key='mtu',
|
||||
value=mtu)
|
||||
|
||||
def validate_public_ip_present(self, router):
|
||||
"""Ensure there is one gateway IP specified for the router used."""
|
||||
gw_port = router.gw_port
|
||||
if not gw_port or len(gw_port.fixed_ips) != 1:
|
||||
raise CsrValidationFailure(resource='IPSec Connection',
|
||||
key='router:gw_port:ip_address',
|
||||
value='missing')
|
||||
|
||||
def validate_peer_id(self, ipsec_conn):
|
||||
"""Ensure that an IP address is specified for peer ID."""
|
||||
# TODO(pcm) Should we check peer_address too?
|
||||
peer_id = ipsec_conn['peer_id']
|
||||
try:
|
||||
netaddr.IPAddress(peer_id)
|
||||
except net_exc.AddrFormatError:
|
||||
raise CsrValidationFailure(resource='IPSec Connection',
|
||||
key='peer_id', value=peer_id)
|
||||
|
||||
def validate_ipsec_encap_mode(self, ipsec_policy):
|
||||
"""Ensure IPSec policy encap mode is tunnel for current REST API."""
|
||||
mode = ipsec_policy['encapsulation_mode']
|
||||
if mode != 'tunnel':
|
||||
raise CsrValidationFailure(resource='IPsec Policy',
|
||||
key='encapsulation_mode',
|
||||
value=mode)
|
||||
|
||||
def validate_ike_auth_algorithm(self, ike_policy):
|
||||
"""Ensure IKE Policy auth algorithm is supported."""
|
||||
auth_algorithm = ike_policy.get('auth_algorithm')
|
||||
if auth_algorithm in ["sha384", "sha512"]:
|
||||
raise CsrValidationFailure(resource='IKE Policy',
|
||||
key='auth_algorithm',
|
||||
value=auth_algorithm)
|
||||
|
||||
def validate_ipsec_auth_algorithm(self, ipsec_policy):
|
||||
"""Ensure IPSec Policy auth algorithm is supported."""
|
||||
auth_algorithm = ipsec_policy.get('auth_algorithm')
|
||||
if auth_algorithm in ["sha384", "sha512"]:
|
||||
raise CsrValidationFailure(resource='IPsec Policy',
|
||||
key='auth_algorithm',
|
||||
value=auth_algorithm)
|
||||
|
||||
def validate_ipsec_site_connection(self, context, ipsec_sitecon):
|
||||
"""Validate IPSec site connection for Cisco CSR.
|
||||
|
||||
Do additional checks that relate to the Cisco CSR.
|
||||
"""
|
||||
service_plugin = self.driver.service_plugin
|
||||
|
||||
if 'ikepolicy_id' in ipsec_sitecon:
|
||||
ike_policy = service_plugin.get_ikepolicy(
|
||||
context, ipsec_sitecon['ikepolicy_id'])
|
||||
self.validate_lifetime('IKE Policy', ike_policy)
|
||||
self.validate_ike_version(ike_policy)
|
||||
self.validate_ike_auth_algorithm(ike_policy)
|
||||
|
||||
if 'ipsecpolicy_id' in ipsec_sitecon:
|
||||
ipsec_policy = service_plugin.get_ipsecpolicy(
|
||||
context, ipsec_sitecon['ipsecpolicy_id'])
|
||||
self.validate_lifetime('IPSec Policy', ipsec_policy)
|
||||
self.validate_ipsec_auth_algorithm(ipsec_policy)
|
||||
self.validate_ipsec_encap_mode(ipsec_policy)
|
||||
|
||||
if 'vpnservice_id' in ipsec_sitecon:
|
||||
vpn_service = service_plugin.get_vpnservice(
|
||||
context, ipsec_sitecon['vpnservice_id'])
|
||||
router = self.l3_plugin._get_router(
|
||||
context, vpn_service['router_id'])
|
||||
self.validate_public_ip_present(router)
|
||||
|
||||
if 'mtu' in ipsec_sitecon:
|
||||
self.validate_mtu(ipsec_sitecon)
|
||||
|
||||
if 'peer_id' in ipsec_sitecon:
|
||||
self.validate_peer_id(ipsec_sitecon)
|
||||
|
||||
LOG.debug("IPSec connection validated for Cisco CSR")
|
|
@ -1,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
Loading…
Reference in New Issue