././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/0000775000175000017500000000000000000000000014601 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/.coveragerc0000664000175000017500000000015100000000000016717 0ustar00zuulzuul00000000000000[run] branch = True source = neutron_vpnaas omit = neutron_vpnaas/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/.mailmap0000664000175000017500000000111600000000000016221 0ustar00zuulzuul00000000000000# Format is: # # lawrancejing Jiajun Liu Zhongyue Luo Kun Huang Zhenguo Niu Isaku Yamahata Isaku Yamahata Morgan Fainberg ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/.pylintrc0000664000175000017500000000733200000000000016453 0ustar00zuulzuul00000000000000# The format of this file isn't really documented; just use --generate-rcfile [MASTER] # Add 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, consider-using-set-comprehension, duplicate-code, inconsistent-return-statements, interface-not-implemented, no-else-raise, no-else-return, 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, useless-object-inheritance [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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/.stestr.conf0000664000175000017500000000011400000000000017046 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./neutron_vpnaas/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/.zuul.yaml0000664000175000017500000000647100000000000016552 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - openstack-cover-jobs-neutron - openstack-python3-jobs-neutron - periodic-stable-jobs-neutron - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - openstack-tox-py38: required-projects: - openstack/neutron - openstack-tox-py39: required-projects: - openstack/neutron - openstack-tox-py310: required-projects: - openstack/neutron - openstack-tox-py311: required-projects: - openstack/neutron - openstack-tox-docs: required-projects: - openstack/neutron - neutron-vpnaas-functional-sswan - neutron-tempest-plugin-vpnaas - neutron-tempest-plugin-vpnaas-ovn gate: jobs: - openstack-tox-py38: required-projects: - openstack/neutron - openstack-tox-py39: required-projects: - openstack/neutron - openstack-tox-py310: required-projects: - openstack/neutron - openstack-tox-py311: required-projects: - openstack/neutron - openstack-tox-docs: required-projects: - openstack/neutron - neutron-vpnaas-functional-sswan - neutron-tempest-plugin-vpnaas - neutron-tempest-plugin-vpnaas-ovn experimental: jobs: - neutron-vpnaas-rally - openstack-tox-py310-with-oslo-master: required-projects: - openstack/neutron - neutron-vpnaas-openstack-tox-py310-with-sqlalchemy-main periodic-weekly: jobs: - openstack-tox-py311: required-projects: - openstack/neutron - openstack-tox-py310-with-oslo-master: required-projects: - openstack/neutron - neutron-vpnaas-openstack-tox-py310-with-sqlalchemy-main - neutron-tempest-plugin-vpnaas - neutron-tempest-plugin-vpnaas-ovn - neutron-vpnaas-functional-sswan - job: name: neutron-vpnaas-functional-sswan parent: neutron-functional pre-run: playbooks/configure_functional_job.yaml vars: project_name: neutron-vpnaas tox_envlist: dsvm-functional-sswan - job: name: neutron-vpnaas-rally parent: rally-task-neutron timeout: 7800 vars: devstack_plugins: rally-openstack: https://opendev.org/openstack/rally-openstack neutron-vpnaas: https://opendev.org/openstack/neutron-vpnaas rally_task: rally-jobs/rally-configs/rally_config_dvr.yaml required-projects: - openstack/devstack-gate - openstack/neutron - openstack/neutron-vpnaas - openstack/rally - openstack/rally-openstack irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^neutron_vpnaas/tests/unit/.*$ - ^releasenotes/.*$ - job: name: neutron-vpnaas-openstack-tox-py310-with-sqlalchemy-main required-projects: - name: github.com/sqlalchemy/sqlalchemy override-checkout: main - name: github.com/sqlalchemy/alembic override-checkout: main - openstack/oslo.db - openstack/neutron - openstack/neutron-lib parent: openstack-tox-py310-with-oslo-master ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/AUTHORS0000664000175000017500000002664000000000000015661 0ustar00zuulzuul00000000000000Aaron Rosen Abhishek Raut Adam Harwell Aishwarya Thangappa Akihiro MOTOKI Akihiro Motoki Akihiro Motoki Al Miller Aleks Chirko Alessandro Pilotti Alessio Ababilov Alessio Ababilov Amir Sadoughi Andre Pech Andreas Jaeger Andreas Jaeger Andrew Boik Andrey Pavlov Angus Lees Ann Kamyshnikova Armando Migliaccio Arundhati Surpur Arvind Somy Arvind Somya Assaf Muller Bernard Cafarelli Bertrand Lallau Bertrand Lallau Bharath M Bhuvan Arumugam Bo Chi Bob Kukura Bob Melander Boden R Bodo Petermann Bogdan Tabor Brad Hall Brandon Logan Brandon Logan Brant Knudson Brent Eagles Brian Haley Brian Haley Brian Waldon Cao Xuan Hoang Carl Baldwin Cedric Brandily Chandan Kumar Chang Bo Guo Christian Berendt Chuck Short Clark Boylan Clint Byrum Corey Bryant Cyril Roelandt Dan Prince Dan Wendlandt Dao Cong Tien Dariusz Smigiel Davanum Srinivas Davanum Srinivas Dave Lapsley Deepak N Dirk Mueller Dmitriy Rabotyagov Dongcan Ye Doug Hellmann Doug Hellmann Doug Wiegley Doug Wiegley Duong Ha-Quang Edgar Magana Elena Ezhova Elod Illes Emilien Macchi Eugene Nikanorov Gary Kotton Gary Kotton German Eichberger Ghanshyam Mann Gordon Chung Guilherme Salgado Ha Van Tu Hareesh Puthalath He Jie Xu Hemanth Ravi Henry Gessau Henry Gessau Henry Gessau HenryVIII Hervé Beraud Hirofumi Ichihara Hoang Trung Hieu Hunt Xu Ian Wienand Ignacio Scopetta Ihar Hrachyshka Ionuț Arțăriși Irena Berezovsky Irina Isaku Yamahata Isaku Yamahata JJ Asghar Jacek Swiderski Jakub Libosvar James Arendt James E. Blair James E. Blair James E. Blair James Page Jason Kölker Jay Pipes Jeffrey Zhang Jeremy Stanley Jiajun Liu Joe Gordon Joe Heck John Davidge John Dunning Jordan Tardif Juliano Martinez Julien Danjou Justin Lund Ken'ichi Ohmichi Keshava Bharadwaj Kevin Benton Kevin Benton Kevin L. Mitchell Kris Lindgren Kun Huang Kyle Mestery Kyle Mestery Lewis Zhang Li Ma LiZekun <2954674728@qq.com> LiuNanke Luke Gorrie Luong Anh Tuan Ly Loi Major Hayden Mark McClain Mark McClain Mark McLoughlin Martin Hickey Maru Newby Maru Newby Mate Lakat Mathieu Rohon Matt Riedemann Matthew Kassawara Matthew Treinish Michael Johnson Michael Smith Miguel Angel Ajo Miguel Lavalle Mohammad Banikazemi Mohammed Naser Monty Taylor Morgan Fainberg Motohiro OTSUKA Nachi Ueno Nachi Ueno Nader Lahouti Nate Johnston Nguyen Hai Truong Nguyen Hung Phuong Nguyen Phuong An Nick Numan Siddique Oleg Bondarev Ondřej Nový OpenStack Release Bot Patryk Jakuszew Paul Michali Paul Michali Peng Zhi Xiong Praneet Bachheti Rajaram Mallya Ralf Haferkamp Reedip Banerjee Rich Curran Rodolfo Alonso Hernandez Roman Podoliaka Rui Zang Russell Bryant Ryan Moats Ryota MIBU Salvatore Orlando Salvatore Orlando Samer Deeb Santhosh Santhosh Kumar Sascha Peilicke Sascha Peilicke Sascha Peilicke Sean Dague Sean Dague Sean M. Collins Sean McGinnis Sergey Lukjanov Sergey Skripnick Sergey Vilgelm Shiv Haris Slawek Kaplonski Somik Behera Somik Behera Sridhar Ramaswamy Sridhar Ramaswamy Stephen Ma Steven Gonzales Sukhdev Sumit Naiksatam Sun Zhengnan Sushil Kumar Swaminathan Vasudevan Sylvain Afchain Takashi Kajinami Takashi NATSUME Terry Wilson Thierry Carrez Thomas Bechtold Thomas Bechtold Thomas Morin Tim Miller Tomoko Inoue Tony Breeds Trevor McCasland Trinath Somanchi Tyler Smith Van Hung Pham Vasyl Saienko Viktor Krivak Vladislav Belogrudov Vu Cong Tuan Wei Hu Weidong Shao Wu Wenxiang YAMAMOTO Takashi YAMAMOTO Takashi Yaguang Tang Yanping Qu Yatin Kumbhare Ying Liu Yong Sheng Gong Yong Sheng Gong Yoshihiro Kaneko Zang MingJie Zhang Hua Zhenguo Niu Zhenmei ZhiQiang Fan ZhiQiang Fan Zhongyue Luo ajmiller alexpilotti armando-migliaccio armando-migliaccio ashish-kumar-gupta berlin caoyuan changzhi chen-li chenxiangui elajkat fujioka yuuichi fumihiko kakuma fungi.admin gengchc2 ghanshyam gongysh gongysh gordon chung gugug hobo.kengo ji-xuepeng johndavidge justin Lund lawrancejing leejian0612 likui liu-sheng liuqing liuyamin llg8212 madhusudhan-kandadai mark mcclain mathieu-rohon melissaml nfedotov nick.zhuyj rabi rajat29 ricolin rohitagarwalla ronak rossella sanuptpm shanyunfan33 shihanzhang sridhargaddam sukhdev trinaths venkata anil vikas vinkesh banka wu.shiming wujun xiaoli yatinkarel zhangboye zhanghao zhanghao2 zhhuabj zhoulinhui zhuyijing ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/CONTRIBUTING.rst0000664000175000017500000000027600000000000017247 0ustar00zuulzuul00000000000000Please see the Neutron CONTRIBUTING.rst file for how to contribute to neutron-vpnaas: `Neutron CONTRIBUTING.rst `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/ChangeLog0000664000175000017500000016044300000000000016363 0ustar00zuulzuul00000000000000CHANGES ======= 24.0.1 ------ * Zuul: Remove references to centos-7 job nodes * Update TOX\_CONSTRAINTS\_FILE for stable/2024.1 * Update .gitreview for stable/2024.1 24.0.0.0rc1 ----------- * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria * tox: Drop envdir * reno: Update master for unmaintained/yoga * Update python classifier with py3.10 & py3.11 in setup.cfg * VPNaaS support for OVN * Remove unused tempest from test requirements * Make cisco\_csr\_identifier migration conditional * py311: Add py311 job and sqlalchemy-main job to weekly * Bump pylint version to 2.17.4 * Update master for stable/2023.2 23.0.0 ------ * [alembic] Alembic operations require keywords only arguments * Update master for stable/2023.1 22.0.0 ------ * [sqlalchemy-20] Add reader context to \`\`VPNPluginDb\`\` get methods * Fix pep8 and doc jobs for tox4 * CI: Add openstack-tox-py39-with-oslo-master to periodic weekly queue * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed 21.0.0 ------ * Adopt to oslo.db 12.1.0 * Tests: fix requirements for unit tests * Fix creating ipsec site connection * Migrate to neutron-lib released API definition for VPNaaS APIs * setup.cfg: Replace dashes with underscores * Changed minversion in tox to 3.18.0 * Update python testing as per zed cycle teting runtime * CI: Add Neutron to required-projects list * Drop lower-constraints.txt and its testing * cleanup: drop un-used test-requirements.txt * Add weekly jobs * Add Python3 zed unit tests * tests: restore functional testing * tests: fix functional tests * Register common config options by default * ci: temporarily disable functional tests * Update master for stable/yoga * remove unicode literal from code 20.0.0 ------ * Change BUILD\_OVS\_FROM\_SOURCE to Q\_BUILD\_OVS\_FROM\_GIT * l3ha: fix status updates * Fix failover with L3 HA * Use TOX\_CONSTRAINTS\_FILE * Remove BUILD\_OVS\_FROM\_SOURCE from the functional tests job * Add "update\_network" implementation to "L3AgentExtension" child classes * Drop install\_venv * req: Bump some requirements * Add Python3 yoga unit tests * Update master for stable/xena 19.0.0 ------ * Use payload for ROUTER BEFORE\_UPDATE event * Moving IRC network reference to OFTC * Fix functional jobs due to OVS file removal * Add ipsec.secrets reload function to strongSwan driver * Add Python3 xena unit tests * Update master for stable/wallaby 18.0.0 ------ * Switch to the new db engine facade * Fix inconsistency in requirements * Fix lower-constraints and pep8 for Focal * Add Python3 wallaby unit tests * Update master for stable/victoria * Use importlib to take place of imp module 17.0.0 ------ * Pin isort to 4.3.21 * Fix functional gate failure * Add aggressive negotiation mode for ikepolicy * Stop to use the \_\_future\_\_ module * Remove translation sections from setup.cfg * Switch to newer openstackdocstheme and reno versions * Add Ussuri milestone tag for alembic migration revisions * Fix unsubscriptable-object error * Bump default tox env from py37 to py38 * Add Python3 victoria unit tests * Update master for stable/ussuri 16.0.0.0rc1 ----------- * Fix invalid escape sequence warning * Cleanup py27 support * Fix the endpoint\_type column name and order * Using unittest.mock instead of mock * Migrate functional tests job to the zuul v3 syntax * Migrate neutron-vpnaas-dsvm-rally job to the zuul v3 syntax * Run neutron-vpn-netns-wrapper in venv * Deprecate \`\`ovs\_integration\_bridge\`\` 16.0.0.0b1 ---------- * Using policy constants from neutron-lib * Fix pep8 error in py37 * Fix functional gate failure * devstack: no need to use sudo for genconfig * Volunteer as driver maintainers * Drop using six library * Drop python 2 support and testing * Complete move of neutron-vpnaas tempest tests to tempest plugin * Use cidr from tempest config project\_network\_v6\_cidr * Use list of self.processes keys in Python2/3 * Install suitable service\_providers * Switch to Ussuri jobs * Skip check config changed while restart\_check\_config not enable * PDF documentation build * Update master for stable/train * Fix local\_id override in ipsec unit tests 15.0.0 ------ * Update api-ref location * Add Python 3 Train unit tests * Fix comment error in sync method * Switch neutron-vpnaas-tempest to Python 3 * Switch functional tests jobs to python3 * Make netns\_wrapper to be compatible with python3 * Fix missing "is\_sort\_key" for basic vpnaas attributes * Make CentOS gate voting again * Replace git.openstack.org URLs with opendev.org URLs * Add iptables command filter for functional test * OpenDev Migration Patch * Fix vpn agent delete router error * Dropping the py35 testing * Skip reporting status for HA backup routers * Execute neutron-vpn-netns-wrapper with rootwrap\_config argument * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 14.0.0 ------ * Replace neutron CLI with openstack CLI * add python 3.7 unit test job * stop using common db mixin methods * use rpc from neutron-lib * Check the router interface subnet whether used by vpn connection 14.0.0.0b1 ---------- * doc: Add policy reference * Convert policy.json into policy-in-code * Increment versioning with pbr instruction * Fix and skip gate failures * Change openstack-dev to openstack-discuss * Update min tox version to 2.3.2 * Call helper to convert bytes to str for Python3 in netns-wrapper * remove external\_network\_bridge option * use payloads for SUBNET BEFORE\_DELETE events * fix tox python3 overrides * use common rpc and exceptions from neutron-lib * add local pep8 tox target * opt in for neutron-lib consumption patches * add python 3.6 unit test job * switch documentation job to new PTI * Use templates for cover and lower-constraints * import zuul job settings from project-config * use setup\_extension in unit tests * Match IPSEC SA established state * Update reno for stable/rocky * Show team and repo badges on README 13.0.0 ------ * Function argument name not used correctly * use callback payloads for ROUTER/ROUTER\_GATEWAY BEFORE\_DELETE events 13.0.0.0b3 ---------- * add py3-dev tox target and update lower constraints zuul job * Add dummy ipsec driver for unit tests * update requirements for neutron-lib 1.18.0 * Switch to stestr * Make libreswan driver work with recent versions * Fix lower-constraints.txt * Fix functional tests gate * Fix flavors plugin not found error * Avoid using non-ASCII characters when generating config files 13.0.0.0b2 ---------- * Switch tempest zuul job to zuul v3 native * Remove unmaintained drivers * use rpc Connection rather than create\_connection 13.0.0.0b1 ---------- * use plugin names from neutron-lib plugin constants * Fix pep8 errors * Change back to using vpn\_agent.ini * remove unused plugin.get\_plugin\_name() * Improve docs to follow the standard structure * Fix patterns of unit test files in irrelevant-files * test: Register L3 option in L3 ext tests * Updated from global requirements * Updated from global requirements * add lower-constraints job * Ensure to install latest neutron from master * Updated from global requirements * Avoid tox-install.sh * use plugin common utils from neutron-lib * Updated from global requirements * Updated from global requirements * Ignore unnecessary changes for tempest job * Rearrange doc structure to standard layout * Update home-page url * Clean imports in code * Add Hunt Xu to list of driver maintainers * Add IPv6 VPNaaS scenario tests * Update reno for stable/queens * Enable sha384/sha512 auth algorithms for \*Swan drivers * Remove ikelifetime in %default section of ipsec.conf templates * Zuul: Remove project name * API tempest: get tenant id from client 12.0.0 ------ * Zuul: Remove project name * Updated from global requirements * Remove the redundant word * Drop the root requirement for LibreSwanDriver * Switch to neutron-tempest-plugin for tempest tests * [doc] Add testing VPNaaS with devstack * devstack: adapt to lib/neutron * [doc] add more info to contributor guide * [doc] Add more info about vpnaas team * [doc] Update for devstack configuration * Move legacy jobs to project * Updated from global requirements * Updated from global requirements * Fix usage of method ensure\_dir * Remove setting of version/release from releasenotes * Updated from global requirements * use flavors api def from neutron-lib * Updated from global requirements * Redundant alias in import statement * Updated from global requirements * VPN as a Service (VPNaaS) Agent * Switch to tempest.common.utils.requires\_ext * Replace the usage of some aliases in tempest * Cleanup test-requirements * Update for os-testr 1.0.0 / stestr * DB migration milestone for Pike * Updated from global requirements * Remove vestigate HUDSON\_PUBLISH\_DOCS reference * devstack: Use entrypoint name for service\_plugin * Fix to use "." to source script files * Update reno for stable/pike 11.0.0.0rc1 ----------- * Updated from global requirements * Devref for VMWare NSX-v IPsec VPN driver * Enable some off-by-default checks * Updated from global requirements * Replace test.attr with decorators.attr * Fix doc rendering for more easy to read * Update URLs in documents according to document migration * VPNaaS integration with services flavor framework * Use flake8-import-order plugin and clean up exceptions * Updated from global requirements * Optimize the link address * Drop MANIFEST.in - it's not needed by pbr * Switch from oslosphinx to openstackdocstheme * use service type constants from neutron\_lib plugins * Stop using deprecated 'message' attribute in Exception * Add myself to list of driver maintainers * Delete the IPSec before the router is deleted * Updated comments in tox.ini [flake8] * Updated from global requirements * Updated from global requirements * Updated from global requirements * consume neutron-lib callbacks * Enable vpnaas extension * Tag the alembic migration revisions for Ocata * Migrate neutron.plugins.common to neutron-lib * Updated from global requirements * Remove subunit-trace fork * Add a simple tempest scenario * Remove log translations from neutron-vpnaas * Rehome L3 exceptions to neutron-lib * Use the new path of agent config * doc: Add a list of driver maintainers * Use neutron\_lib's get\_random\_mac * Updated from global requirements * Use neutron-lib's context module instead of neutron * devref/devstack: Switch the default to strongswan * [Fix gate]Update test requirement * Add router\_id to query when check subnet is used by vpnservice * Update a py35 environment to tox and classifier * Use replace\_file from neutron-lib * devstack: Adapt to lib/neutron * Adopt to new model classes' locations * devstack: Add neutron server config explicitly * doc: Fix a warning * Update reno for stable/ocata * Updated from global requirements * Updated from global requirements * strongswan: Use non-blocking version of ipsec up * ipsec device driver: Sprinkle log\_method\_call 10.0.0 ------ * gate\_hook: Add "tempest" case * Updated from global requirements * Updated from global requirements * tests: Add 'agent' argument for LegacyRouter * Use CORE from neutron-lib * Updated from global requirements * Restore RPC after tenant\_id -> project\_id DB column rename * devstack: Switch the default to strongswan * Replace six.iteritems() with .items() * Use DB field sizes instead of \_MAX\_LEN constants * Remove PLURALS * Use ExtensionDescriptor from neutron-lib * Switch to using plugins directory in lieu of neutron manager * Updated from global requirements * Add sha384 and sha512 auth algorithms for vendor drivers * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Fix releasenotes index * Updated from global requirements * Validate peer\_cidrs for ipsec\_site\_connections * Fix a typo in vpnaas.filters,filters.template,and etc * Fix the types.set error for rally job run * Updated from global requirements * Enable release notes translation * Remove FWaaS dependency in VPNaaS devstack plugin * Updated from global requirements * Fix import breakage in functional test * Updated from global requirements * Updated from global requirements * Add vpnaas conf to Q\_PLUGIN\_EXTRA\_CONF\_FILES * Update reno for stable/newton 9.0.0.0rc1 ---------- * Updated from global requirements * Tag the alembic migration revisions for Newton * Make tests resilient to new project\_id field in API * Updated from global requirements 9.0.0.0b3 --------- * Use model\_base from neutron-lib * Readd tox\_install NEUTRON\_DIR * Add debug output to tox\_install.sh * Add support for Guru Meditation Reports for VPNaaS agents * Use temporary directory for neutron install * Move service plugin aliases from neutron to neutron-vpnaas * Updated from global requirements * Fix DeprecationWarnings part II * Adding tests for endpoint-group api * TrivialFix: Add validation for tenant\_id * TrivialFix: Cleanup imports in code * Revert "Update disable\_ssl\_certificate\_validation reference" * Update disable\_ssl\_certificate\_validation reference * Strongswan: Fix incorrect strongswan auth algorithm sha256 symbol * Constrain remaining tox targets * Updated from global requirements * Remove temporary local HasProject * Fix DeprecationWarnings part I * Enable DeprecationWarning in test environments * Add connection API cases * Updated from global requirements * Update imports (common.config -> conf.common) * Updated from global requirements * Fix API Tests * Add Libreswan installation guide in devstack.rst * Drop Newton tag from DB migration * Rename DB columns: tenant -> project * Add migrations testing to VPNaaS functional jobs 9.0.0.0b2 --------- * remove unused LOG * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix VPNaaS functional tests * Use tempest.lib tenants\_client * Replace tempest-lib to tempest in test-requirements.txt * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use strongswan piddir as bind mount dir 9.0.0.0b1 --------- * Copy/remove the strongswan.d config as root * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix grenade plugin * Add grenade upgrade hooks to stop agent * Set path for neutron conf dir in devstack if missing * Support local\_id configuration * Switch to using hacking checks from neutron-lib * Strongswan: complete the ipsec.conf * Openswan/Libreswan: Check config changes before restart 8.1.0 ----- * Updated from global requirements * Updated from global requirements * Don't use zuul-cloner for venv env, for periodic jobs * Openswan/Libreswan: support sha256 for auth algorithm * Fix doc build if git is absent * Ensure that PK values don't default to NULL * Updated from global requirements * OpenSwan: handle disconnect properly for multiple subnets 8.0.0 ----- * Constraint requirements using mitaka upper-constraints.txt file * VPNaaS returns 500 INTERNAL error with long names, descriptions 8.0.0.0rc2 ---------- * Update devstack plugin for dependent packages * Update devstack plugin for dependent packages * Update reno for stable/mitaka * Update .gitreview for stable/mitaka 8.0.0.0rc1 ---------- * neutron-vpnaas fix for 'tox -e api' test * Tag the alembic migration revisions for Mitaka * VPNaaS Fix bandit Jinja issue * Move db migration added during Mitaka to proper directory * Fix tox.ini constraints for post jobs * Updated from global requirements * Put py34 first in the env order of tox * vyatta: added missing agent console script 8.0.0.0b3 --------- * Track alembic heads * Remove unused pngmath Sphinx extension * VPNaaS: Cleanup constraints in tox.ini * Updated from global requirements * Updated from global requirements * VPNaaS: make use of neutron\_lib exceptions * Updated from global requirements * Use constraints on all targets * Fix configure script for functional tests * Switch from testr to ostestr * Single/Multinode VPNaaS Scenario Tests using Rally * Remove Foreign Key constraint during ALTER * Add "nonstandard-exception" to .pylintrc * Add VPNaaS API tests in neutron-vpnaas tree * Add an explicit BRANCH\_NAME to tox\_install.sh * Don't need builtins defined in tox.ini * Update translation setup * Updated from global requirements * Consume \_ from local \_1i8n * Updated from global requirements 8.0.0.0b2 --------- * Clean up removed hacking rule from [flake8] ignore lists * Add multi-node devstack support to VPNaaS * Convert warnings to errors * ipsec site connection status is blocked on "DOWN" * Add constraints targets support for neutron-vpnaas * Updated from global requirements * Fix for the deprecated library function * Updated from global requirements * LOG.warn -> LOG.warning * Make VPN endpoint groups aware from API * Don't use constrained environment for functional tests * Fix some inconsistency in docstrings * Updated from global requirements * tox\_install.sh: don't hide output from tox logs * Avoid duplicating tenant check when creating resources * Fix a typo from UPPER\_CONTRAINTS\_FILE to UPPER\_CONSTRAINTS\_FILE * Setup for translation * Remove Neutron VPNaaS static example configuration files * Deprecated tox -downloadcache option removed * Automatically generate neutron VPNaaS configuration files * Updated from global requirements * Updated from global requirements * Added constraints tox targets * Fix db error when running python34 Unit tests * Add reno for release notes management * Remove pylint from the default list of tox targets * Fix pylint/astroid breakage * Remove version from setup.cfg 8.0.0.0b1 --------- * Switch to internal \_i18n pattern, as per oslo\_i18n guidelines * Updated from global requirements * Fix wrong file name in setup.cfg * Prepare neutron "use\_namespaces" option removal * Updated from global requirements * Fix options for pluto per-peer logging * Updated from global requirements * Updated from global requirements * Set ZUUL\_BRANCH using BRANCH if not available * Remove unused logging module import * Set IPSec site connection Down if peer doesn't respond * VPNaaS: Remove unneeded test * Don't assume the order of endpoints on get is the same as on create * Don't add disabled ipsec connections to pluto * Support testing of multiple local subnets * Updated from global requirements * VPNaaS: Multiple Local Subnets feature * Remove references to router\_delete\_namespaces option in tests * Update list of modules supporting py34 tests * Switch to using neutron.common.utils:replace\_file() * Updated from global requirements * Updated from global requirements * Cleanup .ctl/.pid files for both OpenSwan and LibreSwan * Updated from global requirements * Removed new=True argument from create\_connection * Include VPN scenario test for two different tenants * Include alembic versions directory to the package 7.0.0 ----- * Add testresources used by oslo.db fixture * Add testresources used by oslo.db fixture * Updated from global requirements * Updated from global requirements * Include scenario test for checking VPN status * Remove root owned ipsec.secrets for ensure\_configs * Enable configuring LibreSwan in VPNaaS * Fix argument order for assertEqual to (expected, observed) * Updated from global requirements * Include README.rst for rally tests * VPN Scenario tests using Rally 7.0.0.0rc2 ---------- * Tag the alembic migration revisions for Liberty * Tag the alembic migration revisions for Liberty * VPNaaS: Provide Endpoint groups capability * Fix minor comment typos in VPNaaS * Use stable/liberty branch for neutron dep * Kill HEADS file 7.0.0.0rc1 ---------- * Update defaultbranch in .gitreview to stable/liberty * Open Mitaka development * Change ignore-errors to ignore\_errors * Updated from global requirements * VPNaaS Scenario tests using Rally * Make chown rootwrap filter ipsec.secrets file specific * tox.ini: switch to --subproject for check-migration * Added +x permission to gate hook scripts * Exclude neutron\_vpnaas/tests from coverage report * [OpenSwan] Enable usage of the MTU value of an IPSec connection * Use ping assertions from net\_utils in test\_scenario * VPNaaS: Restore coverage operation * Set owner to root for ipsec.secrets for LibreSwan * Support VPNaaS with L3 HA * Updated from global requirements * Support for VPN functional tests on Neutron commits * Fix AH-ESP transform protocol in IPSec Policy * Manage cleanup of .ctl/.pid files for LibreSwan * Implement ModelMigrationSyncTest * Drop for 'tenant\_id' column for 'cisco\_csr\_identifier\_map' table 7.0.0.0b3 --------- * VPNaaS: DevRef for multiple local subnets * Remove fall-back logic to service provider registration * Adopt the migration chaining and branch names from neutron * Explictly set file mode on IPSec pre-shared key files * Removing unused dependency: discover * Allow enabling detailed logging for OpenSwan * VPNaaS: Use new service name for devstack plugin * Fix UT fallout * Switch to using os-testr's copy of subunit2html * VPNaaS: Splitting out models from database class * Register provider configuration with ServiceTypeManager * Fix stale module import * [DevStack] Fix StrongSwan setup on Fedora * Add cisco\_csr\_rest\_client.py module for py34 support * Add test\_cisco\_ipsec.py module for py34 support * Updated from global requirements * Revert "Remove default service provider from conf file" * Rename a test method in test\_ipsec.py * Remove default service provider from conf file * Killed existing downgrade rules in migration scripts * script.py.mako: added license header and missing branch\_labels * Add test\_netns\_wrapper.py module for py34 support * Use oslo.log library instead of system logging module * Updated from global requirements * VPNaaS: Store local side's tunnel IP for later retrieval * Don't include encryption algorithm in phase2alg for the AH protocol * py34: Enable initial python34 testing for VPNaaS * VPNaaS: Correcting method name for functional test 7.0.0.0b2 --------- * py34: Fix usage of gettext.install * Register alembic\_migrations at install time * Updated from global requirements * StrongSwanProcess: redefine DIALECT\_MAP at class level * migrations: rearrange the tree to support split migration phases * Remove quantum untracked files from .gitignore * adopt neutron.common.utils.ensure\_dir * Revert "VPNaaS: Temporarily disable check\_migration" * VPNaaS: Enable devstack plugin for tests * VPNaaS DevStack Plugin support * VPNaaS: Temporarily disable check\_migration * Scenario test for vpnaas: ipsec-site-connection * Remove dependency on config file for db check\_migration * Set vpn agent's agent\_state['binary'] attribute * VPNaaS: Fix migration head * VPNaaS: Don't clone neutron automatically for tests * COMMON\_PREFIXES cleanup - patch 4/5 * Updated from global requirements * VPNaaS: Fix another import due to Neutron change * Fix breakage due to recent movements of Neutron modules * Switch to oslo.service * Updated from global requirements 7.0.0.0b1 --------- * Use DvrEdgeRouter instead of decomposed DvrRouter in test\_ipsec * Switch to oslo\_utils.uuidutils * Trim some unused test requirements * Update version for Liberty 7.0.0a0 ------- * Updated from global requirements * Resize cisco\_csr\_identifier\_map.ipsec\_site\_conn\_id * Updated from global requirements * Updated from global requirements * VPNaaS: And devref doc infrastructure * VPNaaS: Enable pylint duplicate-key check * Updated from global requirements * Enable random hash seeds * Python 3: use six.iteritems instead of dict.items * Do not assume order of mounts in execute\_with\_mount * Set owner of Q\_VPN\_CONF\_FILE file to STACK\_USER user * VPNaaS: Cleanup functional hook scripts * gate-neutron-vpnaas-pep8 failing for test\_cisco\_ipsec.py * Assign external\_ip based on ip version of peer\_address * Switch from MySQL-python to PyMySQL * Updated from global requirements * VPNaaS: Fix breakage in status reporting * VPNaaS: Revise functional test hooks * Add neutron-vpnaas/tests/unit/extensions/\_\_init\_\_ * Remove contextlib.nested from tests * IPv6 support for OpenSwan, Libreswan and Strongswan * Updated from global requirements * Libreswan driver support in VPNaaS * Updated from global requirements * Provide Fedora support for StrongSwan * Fix failures for integration tests 2015.1.0 -------- * VPNaaS: Refactor functional tests to use discover * update .gitreview for stable/kilo * Add Kilo release milestone * Add Kilo release milestone * Pin neutron to stable/kilo * VPNService takes names of device drivers from self.conf * VPNaaS Remove dependency on Neutron for unit test 2015.1.0rc1 ----------- * VPNaaS: Remove check for bash usage * VPNaaS: Reorganize test tree * Open Liberty development * VPNaaS: Refactoring to use callback mechanism * VPNaaS Fix unit test breakage * Use BaseSudoTestCase instead of BaseLinuxTestCase * Set ipsec connection to Error if peer fqdn can't be resolved * Updated from global requirements * Introduce Vyatta VPN agent cmd in monkey patched eventlet module * Add some unit tests for strongswan driver 2015.1.0b3 ---------- * tests: stop overwriting neutron BaseTestCase configuration files * Functional tests of ipsec strongswan vpnaas driver * IPsec strongSwan driver implemention * VPNaaS breakage by refactoring commit * Fix functional test breakage from DevStack change * VPNaaS: Enable StrongSwan in gate hook * Remove the reference for non-existent cisco.l3.plugging\_drivers * VPNaaS: device driver and agent refactoring * Decouple L3 and VPN during DVR router migration * VPNaaS: Fixing UT breakage * Updating alembic HEAD file according to the current code * Updated from global requirements * VPNaaS: Fix unit test breakage * Fix up the import path for vyatta.common to use networking\_brocade * Move pylint checks to pep8 testenv * VPNaaS: Restructure test dir layout * Decouple L3 and VPN service plugins during router operations * Migrate to oslo.log * Add test case for the 'Peer ID gets additional "@"' fix * VPNaaS refactor service driver to reuse VpnDriver code * Change L3 agent AdvancedService class to be non-singleton * Fix the ipsec conn issue when peer addr is fqdn * Updated from global requirements * Remove remaining root\_helper references * Implementation of Brocade Vyatta VPNaaS Plugin * Explicitly monkey patch VPN agent * Add IPSec encap mode validation to Cisco VPNaas * VPNaaS Enable coverage testing for functional tests * Reorder Neutron import statements in file * Stop storing and passing root\_helper * Fix breakage caused by removing deprecated root\_helper config in neutron * Add index on tenant\_id * Provide service info for RouterInUse exception * VPNaaS: Remove duplication for exception - part 1 * VPNaaS: Enable coverage tests * Fixed tests to use neutron\_vpnaas extensions and neutrons * oslo: migrate to namespace-less import paths 2015.1.0b2 ---------- * Updated from global requirements * Move config and extensions to service repo * Provide hooks for VPNaaS repo functional gate * Pass root\_helper to ip\_lib by keyword argument to prep for removal * Handle common boilerplate arguments to RouterInfo * Fix the neutron-vpnaas unit test failures * Updated from global requirements * Updated from global requirements * vpn namespace wrapper * Updated from global requirements * Migrate to oslo.concurrency * Updated from global requirements * Update hacking to 0.10 * Updated from global requirements * Adapt VPN agent to use new main for L3 Agent * Updated from global requirements * VPNaaS: Remove unneeded metaclass decorator * Fix VPN Service for Distributed Routers * Backward compatibility for vpnaas * Moved vpnaas.filters from main neutron repo * Cleaned up requirements.txt * Bump from global requirements * Added \_\_init\_\_.py so migrations can work * Fix gitignore of egg files properly * Do not list neutron in requirements.txt * VPNaas: L3 Agent restructure - observer hierarchy * VPNaaS: Unit tests using policy.conf * Update documentation files for VPNaaS 2015.1.0b1 ---------- * Do not restart vpn processes for every router update * VPNaaS: Advanced Services split - unit tests * Kill oslo-incubator files * tests: initialize admin context after super().setUp call * Init separate alembic migration chain * Remove erroneously commited egg files * Move classes out of l3\_agent.py * Fix python neutron path for neutron\_vpnaas * After the services split, get neutron-vpnaas Jenkins jobs passing * Point gitreview at correct repo * Split vpnaas services code into neutron-vpnaas * Workflow documentation is now in infra-manual * tox.ini: Prevent casual addition of bash dependency * Updated from global requirements * Convert several uses of RpcCallback * Get rid of py26 references: OrderedDict, httplib, xml testing * Updated the README.rst * pretty\_tox.sh: Portablity improvement * test\_dhcp\_agent: Fix no-op tests * Enable undefined-loop-variable pylint check * Fix incorrect exception order in \_execute\_request * Migrate to oslo.i18n * Migrate to oslo.middleware * Migrate to oslo.utils * Remove Python 2.6 classifier * Remove ryu plugin * Updated from global requirements * Drop RpcProxy usage from VPNaaS code * Show progress output while running unit tests * enable H401 hacking check * enable H237 check * Updated from global requirements * Drop several uses of RpcCallback * Updated from global requirements * Update i18n translation for neutron.agents log msg's * enable F812 check for flake8 * enable F811 check for flake8 * Support pudb as a different post mortem debugger * switch to oslo.serialization * Add rootwrap filters for ofagent * Cisco VPNaaS and L3 router plugin integration * Remove openvswitch core plugin entry point * Updated from global requirements * Purge use of "PRED and A or B" poor-mans-ternary * Remove use\_namespaces from RouterInfo Property * Updated from global requirements * Remove XML support * enable F402 check for flake8 * enable E713 in pep8 tests * Hyper-V: Remove useless use of "else" clause on for loop * Enable no-name-in-module pylint check * Move disabling of metadata and ipv6\_ra to \_destroy\_router\_namespace * Updated from global requirements * Remove duplicate import of constants module * Switch run-time import to using importutils.import\_module * Enable assignment-from-no-return pylint check * tox.ini: Avoid using bash where unnecessary * Empty files should not contain copyright or license * Remove single occurrence of lost-exception warning * Updated fileutils and its dependencies * VPNaaS Cisco unit test clean-up * remove E251 exemption from pep8 check * Update VPN logging to use new i18n functions * mock.assert\_called\_once() is not a valid method * Check for VPN Objects when deleting interfaces * Add pylint tox environment and disable all existing warnings * Updated from global requirements * Ignore top-level hidden dirs/files by default * Avoid constructing a RouterInfo object to get namespace name * Drop sslutils and versionutils modules * Refactor \_process\_routers to handle a single router * Remove all\_routers argument from \_process\_routers * Removed kombu from requirements * Updated from global requirements * Updated from global requirements * Remove sslutils from openstack.common * Fix setup of Neutron core plugin in VPNaaS UT * remove linuxbridge plugin * Open Kilo development * Implement ModelsMigrationsSync test from oslo.db * Do not assume order of report list elements * Fix entrypoint of OneConvergencePlugin plugin * Rework and enable VPNaaS UT for Cisco CSR REST * Set dsvm-functional job to use system packages * Separate Configuration from Freescale SDN ML2 mechanism Driver * Remove @author(s) from copyright statements * Add HA support to the l3 agent * Updated from global requirements * Adds ipset support for Security Groups * UTs: Disable auto deletion of ports/subnets/nets * Add requests\_mock to test-requirements.txt * Removed kombu from requirements * Supply missing cisco\_cfg\_agent.ini file * Updated from global requirements * Work toward Python 3.4 support and testing * Revert "Cisco DFA ML2 Mechanism Driver" * Big Switch: Separate L3 functions into L3 service * Remove reference to cisco\_cfg\_agent.ini from setup.cfg again * Adds router service plugin for CSR1kv * Support for extensions in ML2 * Cisco DFA ML2 Mechanism Driver * Adding mechanism driver in ML2 plugin for Nuage Networks * Fix state\_path in tests * Remove ovs dependency in embrane plugin * Use lockutils module for tox functional env * Cisco VPN with in-band CSR (interim solution) * Inline "for val in [ref]" statements * Updated from global requirements * VPNaaS: Enable UT cases with newer oslo.messaging * Add specific docs build option to tox * Fix bigswitch setup.cfg lines * Remove auto-generation of db schema from models at startup * Updated from global requirements * Use jsonutils instead of stdlib json * VPNaaS: Cisco fix validation for GW IP * Opencontrail plug-in implementation for core resources * Do not assume order of new\_peers list elements * Remove redundant topic from rpc calls * Add a tox test environment for random hashseed testing * Updated from global requirements * Move Cisco VPN RESTapi URI strings to constants * Remove reference to cisco\_cfg\_agent.ini from setup.cfg * Exit Firewall Agent if config is invalid * Fix spelling mistakes * Removed configobj from test requirements * Updated from global requirements * Functional tests work fine with random PYTHONHASHSEED * Set python hash seed to 0 in tox.ini * Configuration agent for Cisco devices * Updated from global requirements * Define some abstract methods in VpnDriver class * ML2 mechanism driver for SR-IOV capable NIC based switching, Part 2 * Modify L3 Agent for Distributed Routers * This patch changes the name of directory from mech\_arista to arista * ML2 mechanism driver for SR-IOV capable NIC based switching, Part 1 * Allow to import \_LC, \_LE, \_LI and \_LW functions directly * Make readme reference git.openstack.org not github * VPNaaS: Separate validation for Cisco impl * VPNaaS: separate out validation logic for ref impl * VPNaaS Cisco REST client enhance CSR create * Bump hacking to version 0.9.2 * Use auth\_token from keystonemiddleware * Change all occurences of no\_delete to do\_delete * Revert "VPNaaS REST Client UT Broken" * Extract CommonDBMixin to a separate file * Remove reference to setuptools\_git * Add a gate-specific tox env for functional tests * Add CONTRIBUTING.rst * Updated from global requirements * VPNaaS REST Client UT Broken * Updated from global requirements * Updated from global requirements * Fix example for running individual tests * Switch to using of oslo.db * remove unsupported middleware * Add config for performance gate job * Synced log module and its dependencies from olso-incubator * don't ignore rules that are already enforced * Moved rpc\_compat.py code back into rpc.py * Updated from global requirements * Updated from global requirements * ofagent: move main module from ryu repository * Remove the useless vim modelines * Removed 'rpc' and 'notifier' incubator modules * Removed create\_rpc\_dispatcher methods * Use openstack.common.lockutils module for locks in tox functional tests * Renamed consume\_in\_thread -> consume\_in\_threads * Port to oslo.messaging * Pass 'top' to remove\_rule so that rule matching succeeds * Updated from global requirements * Ignore emacs checkpoint files * Added missing core\_plugins symbolic names * Introduced rpc\_compat.create\_connection() * Introduce RpcCallback class * remove pep8 E122 exemption and correct style * remove E112 hacking exemption and fix errors * Updated from global requirements * Added RpcProxy class * Freescale SDN Mechanism Driver for ML2 Plugin * Remove run-time version checking for openvswitch features * Added missing plugin .ini files to setup.cfg * Updated from global requirements * Synced jsonutils from oslo-incubator * Cisco APIC ML2 mechanism driver, part 2 * NSX: get rid of the last Nicira/NVP bits * Metaclass Python 3.x Compatibility * Add missing translation support * Add mailmap entry * Updated from global requirements * Remove explicit dependency on amqplib * Remove duplicate module-rgx line in .pylintrc * Fix H302 violations * Fix H302 violations in unit tests * Cisco VPN device driver - support IPSec connection updates * Updated from global requirements * Fix H302 violations in db package and services * Updated from global requirements * Support enhancements to Cisco CSR VPN REST APIs * Exclude .ropeproject from flake8 checks * Enable flake8 E711 and E712 checking * Updated from global requirements * Sync service and systemd modules from oslo-incubator * Move bash whitelisting to pep8 testenv * Fix Jenkins translation jobs * Set ns\_name in RouterInfo as attribute * ignore build directory for pep8 * Enable hacking H301 check * Updated from global requirements * Remove last parts of Quantum compatibility shim * UT: do not hide an original error in test resource ctxtmgr * Open Juno development * Start using oslosphinx theme for docs * Cisco VPN driver correct reporting for admin state chg * Updated from global requirements * VPNaaS support for VPN service admin state change and reporting * add HEAD sentinel file that contains migration revision * Fix usage of save\_and\_reraise\_exception * Cisco VPN device driver post-merge cleanup * Bugfix and refactoring for ovs\_lib flow methods * Removes calls to mock.patch.stopall in unit tests * VPNaaS Device Driver for Cisco CSR * Updated from global requirements * Updated from global requirements * Updated from global requirements * One Convergence Neutron Plugin l3 ext support * One Convergence Neutron Plugin Implementation * BigSwitch: Add SSL Certificate Validation * VPNaaS Service Driver for Cisco CSR * Updated from global requirements * Add OpenDaylight ML2 MechanismDriver * Implementaion of Mechanism driver for Brocade VDX cluster of switches * Implement Mellanox ML2 MechanismDriver * Support advanced NVP IPsec VPN Service * Implement OpenFlow Agent mechanism driver * Finish off rebranding of the Nicira NVP plugin * BigSwitch: Add agent to support neutron sec groups * Adds the new IBM SDN-VE plugin * Updated from global requirements * Update License Headers to replace Nicira with VMware * Developer documentation * tests/service: consolidate setUp/tearDown logic * options: consolidate options definitions * Rename Neutron core/service plugins for VMware NSX * Updated from global requirements * Fix VPN agent does not handle multiple connections per vpn service * Sync minimum requirements * Copy cache package from oslo-incubator * tests/unit: Initialize core plugin in TestL3GwModeMixin * Fix typo in service\_drivers.ipsec * Remove dependent module py3kcompat * Use save\_and\_reraise\_exception when reraise exception * Add migration support from agent to NSX dhcp/metadata services * Remove psutil dependency * LBaaS: move agent based driver files into a separate dir * mailmap: update .mailmap * Return request-id in API response * Prepare for multiple cisco ML2 mech drivers * Support building wheels (PEP-427) * Use oslo.rootwrap library instead of local copy * Enables BigSwitch/Restproxy ML2 VLAN driver * Add an explicit tox job for functional tests * Base ML2 bulk support on the loaded drivers * Enable hacking H233 rule * Update RPC code from oslo * Configure plugins by name * Update lockutils and fixture in openstack.common * Rename nicira configuration elements to match new naming structure * Remove unused imports * Rename check\_nvp\_config utility tool * Corrects broken format strings in check\_i18n.py * Updates tox.ini to use new features * Updated from global requirements * Sync global requirements to pin sphinx to sphinx>=1.1.2,<1.2 * validate if the router has external gateway interface set * Add fwaas\_driver.ini to setup.cfg * Add vpnaas and debug filters to setup.cfg * Fix misspells * update error msg for invalid state to update vpn resources * Updates .gitignore * Update Zhenguo Niu's mailmap * Replace stubout with fixtures * Ensure get\_pid\_to\_kill works with rootwrap script * Apply six for metaclass * Updated from global requirements * Cleanup HACKING.rst * Fix import log\_handler error with publish\_errors set * Updated from global requirements * Updated from global requirements * Fix incorrect indentations found by Pep 1.4.6+ * Cleanup and make HACKING.rst DRYer * Add support for managing async processes * Use L3 api from vpn ipsec driver via service plugin * Fix access to lifetime dict in update\_ipsecpolicy method * Remove obsolete redhat-eventlet.patch * Enable Quota DB driver by default * Open Icehouse development * Updated from global requirements * Require oslo.config 1.2.0 final * Use built-in print() instead of print statement * Increase size of peer\_address attribute in VPNaaS * Add router ownership check on vpnservice creation * Fix error code for deletion of router which is in use by vpnservice * Add l2 population base classes * Adds support for L3 routing/NAT as a service plugin * Fix message i18n error * Install metering\_agent.ini and vpn\_agent.ini * fix conversion type missing * Ensure unit tests do not let looping calls roam freely * Enclose command args in with\_venv.sh * ML2 Mechanism Driver for Cisco Nexus * Reference driver implementation (IPsec) for VPNaaS * Verify MTU is valid for ipsec\_site\_connection * Implement ML2 port binding * Arista ML2 Mechanism driver * ML2 Mechanism Driver for Tail-f Network Control System (NCS) * Default to not capturing log output in tests * Make ipsec\_site\_connection dpd\_timeout == dpd\_interval return 400 * Add Neutron l3 metering agent * Update mailmap * Fix wrong example in HACKING.rst * Bumps hacking to 0.7.0 * remove binaries under bin * Fixes Windows setup dependency bug * Restore Babel to requirements.txt * Remove DHCP lease logic * Remove last vestiges of nose * Updated from global requirements * Ignore pbr\*.egg directory * Fix H102, H103 Apache 2.0 license hacking check error * Remove openstack.common.exception usage * Adds Babel dependency missing from 555d27c * Fix the alphabetical order in requirement files * VPNaaS datamodel IKEPolicy lifetime unit typo * Remove comments from requirements.txt (workaround pbr bug) * VPN as a Service (VPNaaS) API and DataModel * remove netifaces dependency of ryu-agent * Add gre tunneling support for the ML2 plugin * Add VXLAN tunneling support for the ML2 plugin * xenapi - rename quantum to neutron * Fix issue with pip installing oslo.config-1.2.0 * Initial Modular L2 Mechanism Driver implementation * Add cover/ to .gitignore * fix some missing change from quantum to neutron * git remove old non-working packaging files * Rename Quantum to Neutron * Rename quantum to neutron in .gitreview * Sync install\_venv\_common from oslo * Update to use OSLO db * Require greenlet 0.3.2 (or later) * Remove single-version-externally-managed in setup.cfg * Fix single-version-externally-mananged typo in setup.cfg * Allow use of lowercase section names in conf files * Require pbr 0.5.16 or newer * Update to the latest stevedore * Rename agent\_loadbalancer directory to loadbalancer * Remove unit tests that are no longer run * Update with latest OSLO code * Remove explicit distribute depend * Fix and enable H90x tests * Remove generic Exception when using assertRaises * Add \*.swo/swp to .gitignore * python3: Introduce py33 to tox.ini * Rename README to README.rst * Rename requires files to standard names * Initial Modular L2 plugin implementation * Revert dependency on oslo.config 1.2.0 * Perform a sync with oslo-incubator * Require oslo.config 1.2.0a2 * update mailmap * Revert "Fix ./run\_tests.sh --pep8" * Move to pbr * Docstrings formatted according to pep257 * relax amqplib and kombu version requirements * Fix ./run\_tests.sh --pep8 * blueprint mellanox-quantum-plugin * Update flake8 pinned versions * Let the cover venv run individual tests * Copy the RHEL6 eventlet workaround from Oslo * Remove locals() from strings substitutions * Enable automatic validation of many HACKING rules * Shorten the path of the nicira nvp plugin * Allow pdb debugging in manually-invoked tests * Reformat openstack-common.conf * Switch to flake8 from pep8 * Parallelize quantum unit testing: * blueprint cisco-single-config * Add lbaas\_agent files to setup.py * Add VIRTUAL\_ENV key to enviroment passed to patch\_tox\_env * Pin SQLAlchemy to 0.7.x * Sync latest Oslo components for updated copyright * drop rfc.sh * Replace "OpenStack LLC" with "OpenStack Foundation" * First havana commit * remove references to netstack in setup.py * Switch to final 1.1.0 oslo.config release * Update to Quantum Client 2.2.0 * Update tox.ini to support RHEL 6.x * Switch to oslo.config * Add common test base class to hold common things * Pin pep8 to 1.3.3 * Add initial testr support * LBaaS Agent Reference Implementation * Bump python-quantumclient version to 2.1.2 * Add scheduling feature basing on agent management extension * Remove compat cfg wrapper * Unpin PasteDeploy dependency version * Use testtools instead of unittest or unittest2 * Add midonet to setup.py * Sync latest install\_venv\_common.py with olso * Add check-nvp-config utility * Add unit test for ryu-agent * Use oslo-config-2013.1b3 * Adds Brocade Plugin implementation * Synchronize code from oslo * PLUMgrid quantum plugin * Update .coveragerc * Allow tools/install\_venv\_common.py to be run from within the source directory * Updated to latest oslo-version code * Use install\_venv\_common.py from oslo * Cisco plugin cleanup * Use babel to generate translation file * Update WebOb version to >=1.2 * Update latest OSLO * Adding multi switch support to the Cisco Nexus plugin * Adds support for deploying Quantum on Windows * Latest OSLO updates * Port to argparse based cfg * Add migration support to Quantum * Undo change to require WebOb 1.2.3, instead, require only >=1.0.8 * .gitignore cleanup * Upgrade WebOb to 1.2.3 * Logging module cleanup * Add OVS cleanup utility * Add tox artifacts to .gitignore * Add restproxy.ini to config\_path in setup.py * Add script for checking i18n message * l3 agent rpc * Add metadata\_agent.ini to config\_path in setup.py * Remove \_\_init\_\_.py from bin/ and tools/ * add metadata proxy support for Quantum Networks * Use auth\_token middleware in keystoneclient * Add QUANTUM\_ prefix for env used by quantum-debug * Make tox.ini run pep8 checks on bin * Explicitly include versioninfo in tarball * Import lockutils and fileutils from openstack-common * Updated openstack-common setup and version code * Ensure that the anyjson version is correct * Add eventlet\_backdoor and threadgroup from openstack-common * Add loopingcall from openstack-common * Added service from openstack-common * Drop lxml dependency * Add uuidutils module * Import order clean-up * pin sqlalchemy to 0.7 * Correct Intended Audience * Add OpenStack trove classifier for PyPI * Improve unit test times * l3\_nat\_agent was renamed to l3\_agent and this was missed * Support for several HA RabbitMQ servers * add missing files from setup.py * Create .mailmap file * Lower webob dep from v1.2.0 to v1.0.8 * Implements agent for Quantum Networking testing * Create utility to clean-up netns * Update rootwrap; track changes in nova/cinder * Execute unit tests for Cisco plugin with Quantum tests * Add lease expiration script support for dnsmasq * Add nosehtmloutput as a test dependency * quantum l3 + floating IP support * Updates pip requirements * NEC OpenFlow plugin support * remove old gflags config code * RPC support for OVS Plugin and Agent * Initial implemention of MetaPlugin * RPC support for Linux Bridge Plugin and Agent * Exempt openstack-common from pep8 check * fix bug lp:1025526,update iniparser.py to accept empty value * Introduce files from openstack common * fix bug lp:1019230,update rpc from openstack-common * implement dhcp agent for quantum * Use setuptools git plugin for file inclusion * Remove paste configuration details to a seperate file. blueprint use-common-cfg * Implements the blueprint use-common-cfg for the quantum service. More specifically uses global CONF for the quantum.conf file * Add authZ through incorporation of policy checks * Bug #1013967 - Quantum is breaking on tests with pep 1.3 * Use openstack.common.exception * API v2: mprove validation of post/put, rename few attributes * Add API v2 support * Fix up test running to match jenkins expectation * Add build\_sphinx options * Quantum should use openstack.common.jsonutils * Remove hardcoded version for pep8 from tools/test-requires * Quantum should use openstack.common.importutils * PEP8 fixes * Bug #1002605 * Parse linuxbridge plugins using openstack.common.cfg * Add HACKING.rst to tarball generation bug 1001220 * Include AUTHORS in release package * Change Resource.\_\_call\_\_() to not leak internal errors * Removed simplejson from pip-requires * Remove dependency on python-quantumclient * Add sphinx to the test build deps * Add HACKING.rst coding style doc * bug 963152: add a few missing files to sdist tarball * Fix path to python-quantumclient * Split out pip requires and aligned tox file * Fix missing files in sdist package [bug 954906] * Downgraded required version of WebOb to 1.0.8 * more files missing in sdist tarball * make sure pip-requires is included in setup.py sdist * remove pep8 and strict lxml version from setup.py * plugin: introduce ryu plugin * bug 934459: pip no longer supports -E * blueprint quantum-ovs-tunnel-agent * Initial commit: nvp plugin * Cleanup the source distribution * blueprint quantum-linux-bridge-plugin * Remove quantum CLI console script * Bug 925372: remove deprecated webob attributes (and also specify stable webob version in pip-requires) * Make tox config work * Pin versions to standard versions * Split out quantum.client and quantum.common * Quantum was missing depend on lxml * moving batch config out of quantum-server repo * Getting ready for the client split * Removed erroneous print from setup.py * Base version.py on glance * Fix lp bug 897882 * Install a good version of pip in the venv * Rename .quantum-venv to .venv * Remove plugin pip-requires * Bug #890028 * Fix for bug 900316 * Second round of packaging changes * Changes to make pip-based tests work with jenkins * Fix for bug 888811 * Fix for Bug #888820 - pip-requires file support for plugins * blueprint quantum-packaging * Add .gitreview config file for gerrit * Add code-coverage support to run\_tests.sh (lp860160) 2011.3 ------ * Add rfc.sh to help with gerrit workflow * merge tyler's unit tests for cisco plugin changes lp845140 * merge salv's no-cheetah CLI branch lp 842190 * merge sumit's branch for lp837752 * Merging latest from lp:quantum * Merging lo:~salvatore-orlando/quantum/quantum-api-auth * Updating CLI for not using Cheetah anymore. Now using a mechanism based on Python built-in templates * Merging Sumit's changes including fixes for multinic support, and CLI module for working with extensions * Merging from Cisco branch * Merging from lp:quantum * merge cisco consolidated plugin changes * Merging lp:~salvatore-orlando/quantum/bug834449 * merge trunk * Merging from lp:quantum * merge salvatore's new cli code * Addressing comments from Dan * Merging from quantum * merge cisco extensions branch * Merging from Sumit's branch, changes to VIF-driver and Scheduler; extension action names have been changed in response to Salvatore's review comments in the extensions branch review * Syncing with Cisco extensions branch * Merging from Sumit's branch, import ordering related changes * Merging the Cisco branch * Finishing cli work Fixing bug with XML deserialization * Merging lp:~salvatore-orlando/quantum/quantum-api-alignment * merge latest quantum branch and resolve conflicts * Merging lp:~asomya/quantum/lp833163 Fix for Bug #833163: Pep8 violations in recent packaging changes that were merged into trunk (Critical) * PEP8 fixes for setup.py * Merging lp:~cisco-openstack/quantum/802dot1qbh-vifdriver-scheduler * Merging lp:~cisco-openstack/quantum/l2network-plugin-persistence * Merging lp:quantum * merging with lp:quantum * Making Keystone version configurable * Merging branch: lp:~danwent/quantum/test-refactor * Syncing with lp:quantum * Merging fixes and changes batch-config script. Thanks lp:danwent ! * Merging lp:~asomya/quantum/lp824145 Fix for Bug#824145 : Adding a setup script for quantum * merge trunk pep8 fixes adapting CLI to API v1.0 Fixing wsgi to avoid failure with extensions * merge trunk * Pulling in changes from lp:quantum * Merging Cisco's contribution to Quantum. Thanks to various folks at Cisco Systems, Quantum will have plugins to integrate with Cisco UCS blade servers using 802.1Qbh, Cisco Nexus family of switches and the ability for Quantum plugin to have multiple switches/devices within a single Quantum plugin * Merging from Sumit's branch pylint fixes and incorporating review comments * Mergin from cisco brach * Merging from lp:quantum * Introducting cheetah Updating list\_nets in CLI Writing unit tests for list\_nets Stubbing out with FakeConnection now * Merging quantum extenions framework into trunk. Thanks rajaram vinkesh, deepak & santhosh for the great work! * lp Bug#824145 : Adding a setup script for quantum * skeleton for cli unit tests * merge trunk * Merged quantum trunk * - Adding setup script * force batch\_config.py to use json, as XML has issues (see bug: 798262) * update batch\_config.py to use new client lib, hooray for deleting code * Merging changes addressing Bug # 802772. Thanks lp:danwent ! * Merging bugfix for Bug 822890 - Added License file for Quantum code distribution * L2 Network Plugin Framework merge * Adding Apache Version 2.0 license file. This is the official license agreement under which Quantum code is available to the Open Source community * merge * merge heckj's pip-requires fixes * updates to pip-requires for CI * Merged quantum trunk * Merging changes from lp:quantum * Completing API spec alignment Unit tests aligned with changes in the API spec * Merging the brand new Quantum-client-library feature * Merging lp:quantum updates * persistence of l2network & ucs plugins using mysql - db\_conn.ini - configuration details of making a connection to the database - db\_test\_plugin.py - contains abstraction methods for storing database values in a dict and unit test cases for DB testing - l2network\_db.py - db methods for l2network models - l2network\_models.py - class definitions for the l2 network tables - ucs\_db.py - db methods for ucs models - ucs\_models.py - class definition for the ucs tables dynamic loading of the 2nd layer plugin db's based on passed arguments Create, Delete, Get, Getall, Update database methods at - Quantum, L2Network and Ucs Unit test cases for create, delete, getall and update operations for L2Network and Ucs plugins pep8 checks done branch based off revision 34 plugin-framework * Merged from trunk * merged the latest changes from plugin-framework branch - revision 39 conforming to the new cisco plugin directory structure and moving all db related modules into cisco/db folder updated db\_test\_plugin.py - added import of cisco constants module - added LOG.getLogger for logging component name - updated import module paths for l2network\_models/db and ucs\_models/db to use the new directory structure - updated (rearranged) imports section to obey openstack alphabetical placement convention updated db\_conn.ini - updated database name from cisco\_naas to quantum\_l2network unit test cases ran successfully and pep8 checks done again * merge branch for to fix bug817826 * Merging the latest changes from lp:quantum * fix bug 817826 and similar error in batch\_config.py * merge Salvatore's api branch with fixes for tests. Tweaking branch to remove unwanted bin/quantum.py as part of merge * Santhosh/Rajaram|latest merge from quantum and made extensions use options to load plugin * Apply fix for bug #797419 merging lp:~salvatore-orlando/quantum/bug797419 * Merging branch lp:~netstack/quantum/quantum-unit-tests * Merged from quantum trunk * Adapated plugin infrastructure to allow API to pass options to plugins Now using in-memory sqlite db for tests on FakePlugin teardown() now 'resets' the in-memory db Adding unit tests for APIs * Adding Routes>=1.12.3 to tools/pip-requires * Merging dan wendlandt's bugfixes for Bug #800466 and improvements that enable Quantum to seamlessly run on KVM! * more pep8 goodness * refactor batch\_config, allow multiple attaches with the empty string * merge and pep8 cleanup * Merging latest changes from parent repo - lp:network-service , Parent repo had approved merge proposal for merging lp:~santhom/network-service/quantum\_testing\_framework , which has now been merged into lp:network-service * Merging pep8 and functional test related changes lp:~santhom/network-service/quantum\_testing\_framework branch * add example to usage string for batch\_config.py * Bug fixes and clean-up, including supporting libvirt * Santhosh/Vinkesh | Added the testing framework. Moved the smoketest to tests/functional * Pushing initial started code based on Glance project and infrstructure work done by the melange team * Merging in latest changes from lp:quantum ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/HACKING.rst0000664000175000017500000000036600000000000016404 0ustar00zuulzuul00000000000000Neutron VPNaaS Style Commandments ================================= Please see the Neutron HACKING.rst file for style commandments for neutron-vpnaas: `Neutron HACKING.rst `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/LICENSE0000664000175000017500000002363700000000000015621 0ustar00zuulzuul00000000000000 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. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/PKG-INFO0000664000175000017500000000374300000000000015705 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: neutron-vpnaas Version: 24.0.1 Summary: OpenStack Networking VPN as a Service Home-page: https://docs.openstack.org/neutron-vpnaas/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: 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 opendev.org at: https://opendev.org/openstack/neutron-vpnaas. Please refer to Neutron documentation for more information: `Neutron README.rst `_ Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/neutron-vpnaas.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/README.rst0000664000175000017500000000146600000000000016277 0ustar00zuulzuul00000000000000Welcome! ======== 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 opendev.org at: https://opendev.org/openstack/neutron-vpnaas. Please refer to Neutron documentation for more information: `Neutron README.rst `_ Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/neutron-vpnaas.svg :target: https://governance.openstack.org/tc/reference/tags/index.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/TESTING.rst0000664000175000017500000000051200000000000016446 0ustar00zuulzuul00000000000000Testing 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 `_ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8913493 neutron-vpnaas-24.0.1/devstack/0000775000175000017500000000000000000000000016405 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/README.md0000664000175000017500000000017400000000000017666 0ustar00zuulzuul00000000000000This directory contains the neutron-vpnaas devstack plugin. Please see the devref for how to set up VPNaaS with devstack. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8913493 neutron-vpnaas-24.0.1/devstack/lib/0000775000175000017500000000000000000000000017153 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/lib/l3_agent0000664000175000017500000000114700000000000020575 0ustar00zuulzuul00000000000000# This file is completely based on one in the neutron repository here: # https://opendev.org/openstack/neutron/src/branch/master/devstack/lib/l2_agent NEUTRON_L3_CONF=${NEUTRON_L3_CONF:-$Q_L3_CONF_FILE} function plugin_agent_add_l3_agent_extension { local l3_agent_extension=$1 if [[ -z "$L3_AGENT_EXTENSIONS" ]]; then L3_AGENT_EXTENSIONS=$l3_agent_extension elif [[ ! ,${L3_AGENT_EXTENSIONS}, =~ ,${l3_agent_extension}, ]]; then L3_AGENT_EXTENSIONS+=",$l3_agent_extension" fi } function configure_l3_agent { iniset $NEUTRON_L3_CONF AGENT extensions "$L3_AGENT_EXTENSIONS" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/local.conf.sample0000664000175000017500000000061200000000000021625 0ustar00zuulzuul00000000000000[[local|localrc]] enable_plugin neutron-vpnaas https://opendev.org/openstack/neutron-vpnaas disable_service n-net enable_service q-svc enable_service q-agt enable_service q-dhcp enable_service q-l3 enable_service q-meta # Optional, to enable tempest configuration as part of devstack enable_service tempest # IPsec driver to use. Optional, defaults to strongswan. IPSEC_PACKAGE="strongswan" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/local_AIO.conf.sample0000664000175000017500000000074500000000000022324 0ustar00zuulzuul00000000000000[[local|localrc]] disable_service n-net enable_service q-svc enable_service q-agent enable_service q-dhcp enable_service q-l3 enable_service q-meta enable_plugin neutron-vpnaas https://opendev.org/openstack/neutron-vpnaas FIXED_RANGE=10.1.0.0/24 FIXED_NETWORK_SIZE=256 NETWORK_GATEWAY=10.1.0.1 PRIVATE_SUBNET_NAME=privateA PUBLIC_SUBNET_NAME=public-subnet FLOATING_RANGE=172.24.4.0/24 PUBLIC_NETWORK_GATEWAY=172.24.4.10 Q_FLOATING_ALLOCATION_POOL="start=172.24.4.11,end=172.24.4.29" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/ovn-local.conf.sample0000664000175000017500000000224300000000000022427 0ustar00zuulzuul00000000000000[[local|localrc]] DATABASE_PASSWORD=password RABBIT_PASSWORD=password SERVICE_PASSWORD=password SERVICE_TOKEN=password ADMIN_PASSWORD=password Q_AGENT=ovn Q_ML2_PLUGIN_MECHANISM_DRIVERS=ovn,logger Q_ML2_PLUGIN_TYPE_DRIVERS=local,flat,vlan,geneve Q_ML2_TENANT_NETWORK_TYPE=geneve LOGFILE="/opt/stack/logs/devstacklog.txt" enable_service ovn-northd enable_service ovn-controller enable_service q-ovn-metadata-agent enable_service q-ovn-vpn-agent enable_service q-svc enable_service q-log # Disable Neutron agents not used with OVN. disable_service q-agt disable_service q-l3 disable_service q-dhcp disable_service q-meta enable_plugin neutron https://opendev.org/openstack/neutron enable_plugin neutron-tempest-plugin https://opendev.org/openstack/neutron-tempest-plugin.git enable_plugin neutron-vpnaas https://opendev.org/openstack/neutron-vpnaas.git # Horizon (the web UI) is enabled by default. You may want to disable # it here to speed up DevStack a bit. enable_service horizon # disable_service cinder c-sch c-api c-vol c-bak #new # OVN_BUILD_MODULES=True #new # ENABLE_CHASSIS_AS_GW=True # IPsec driver to use. Optional, defaults to strongswan. IPSEC_PACKAGE="strongswan" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/plugin.sh0000664000175000017500000001302400000000000020237 0ustar00zuulzuul00000000000000# plugin.sh - DevStack plugin.sh dispatch script template VPNAAS_XTRACE=$(set +o | grep xtrace) set -o xtrace # Source L3 agent extension management LIBDIR=$DEST/neutron-vpnaas/devstack/lib source $LIBDIR/l3_agent NEUTRON_L3_CONF=${NEUTRON_L3_CONF:-$Q_L3_CONF_FILE} function is_ovn_enabled { [[ $Q_AGENT == "ovn" ]] && return 0 return 1 } function neutron_vpnaas_install { setup_develop $NEUTRON_VPNAAS_DIR if is_service_enabled q-l3 neutron-l3 q-ovn-vpn-agent; 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 } function neutron_vpnaas_configure_agent { plugin_agent_add_l3_agent_extension vpnaas configure_l3_agent if [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver elif [[ "$IPSEC_PACKAGE" == "libreswan" ]]; then iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.libreswan_ipsec.LibreSwanDriver else iniset_multiline $NEUTRON_L3_CONF vpnagent vpn_device_driver $NEUTRON_VPNAAS_DEVICE_DRIVER fi } function neutron_vpnaas_configure_ovn_agent { cp $NEUTRON_VPNAAS_DIR/etc/neutron_ovn_vpn_agent.ini.sample $OVN_VPNAGENT_CONF iniset $OVN_VPNAGENT_CONF DEFAULT interface_driver openvswitch iniset $OVN_VPNAGENT_CONF DEFAULT state_path $DATA_DIR/neutron iniset_rpc_backend neutron-vpnaas $OVN_VPNAGENT_CONF iniset $OVN_VPNAGENT_CONF agent root_helper "$Q_RR_COMMAND" if [[ "$Q_USE_ROOTWRAP_DAEMON" == "True" ]]; then iniset $OVN_VPNAGENT_CONF agent root_helper_daemon "$Q_RR_DAEMON_COMMAND" fi if [[ "$IPSEC_PACKAGE" == "strongswan" ]]; then iniset_multiline $OVN_VPNAGENT_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.ovn_ipsec.OvnStrongSwanDriver elif [[ "$IPSEC_PACKAGE" == "libreswan" ]]; then iniset_multiline $OVN_VPNAGENT_CONF vpnagent vpn_device_driver neutron_vpnaas.services.vpn.device_drivers.ovn_ipsec.OvnLibreSwanDriver else iniset_multiline $OVN_VPNAGENT_CONF vpnagent vpn_device_driver $NEUTRON_VPNAAS_DEVICE_DRIVER fi OVSDB_SERVER_LOCAL_HOST=$SERVICE_LOCAL_HOST if [[ "$SERVICE_IP_VERSION" == 6 ]]; then OVSDB_SERVER_LOCAL_HOST=[$OVSDB_SERVER_LOCAL_HOST] fi OVN_SB_REMOTE=${OVN_SB_REMOTE:-$OVN_PROTO:$SERVICE_HOST:6642} iniset $OVN_VPNAGENT_CONF ovs ovsdb_connection tcp:$OVSDB_SERVER_LOCAL_HOST:6640 iniset $OVN_VPNAGENT_CONF ovn ovn_sb_connection $OVN_SB_REMOTE if is_service_enabled tls-proxy; then iniset $OVN_VPNAGENT_CONF ovn \ ovn_sb_ca_cert $INT_CA_DIR/ca-chain.pem iniset $OVN_VPNAGENT_CONF ovn \ ovn_sb_certificate $INT_CA_DIR/$DEVSTACK_CERT_NAME.crt iniset $OVN_VPNAGENT_CONF ovn \ ovn_sb_private_key $INT_CA_DIR/private/$DEVSTACK_CERT_NAME.key fi } function neutron_vpnaas_configure_db { $NEUTRON_BIN_DIR/neutron-db-manage --subproject neutron-vpnaas --config-file $NEUTRON_CONF upgrade head } function neutron_vpnaas_generate_config_files { # Uses oslo config generator to generate VPNaaS sample configuration files (cd $NEUTRON_VPNAAS_DIR && exec ./tools/generate_config_file_samples.sh) } function neutron_vpnaas_start_vpnagent { NEUTRON_OVN_BIN_DIR=$(get_python_exec_prefix) NEUTRON_OVN_VPNAGENT_BINARY="neutron-ovn-vpn-agent" run_process q-ovn-vpn-agent "$NEUTRON_OVN_BIN_DIR/$NEUTRON_OVN_VPNAGENT_BINARY --config-file $OVN_VPNAGENT_CONF" # Format logging setup_logging $OVN_VPNAGENT_CONF } # 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 neutron-api; then echo_summary "Configuring neutron-vpnaas on controller" neutron_vpnaas_configure_db fi if is_service_enabled q-l3 neutron-l3; then echo_summary "Configuring neutron-vpnaas agent" neutron_vpnaas_configure_agent fi if is_service_enabled q-ovn-vpn-agent && is_ovn_enabled; then echo_summary "Configuring neutron-ovn-vpn-agent" neutron_vpnaas_configure_ovn_agent fi elif [[ "$1" == "stack" && "$2" == "extra" ]]; then if is_service_enabled q-ovn-vpn-agent && is_ovn_enabled; then neutron_vpnaas_start_vpnagent fi # NOP for clean step fi $VPNAAS_XTRACE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/settings0000664000175000017500000000273500000000000020177 0ustar00zuulzuul00000000000000# Settings for the VPNaaS devstack plugin # Plugin if [[ $Q_AGENT == "ovn" ]]; then VPN_PLUGIN=${VPN_PLUGIN:-"ovn-vpnaas"} else VPN_PLUGIN=${VPN_PLUGIN:-"vpnaas"} fi # Device driver IPSEC_PACKAGE=${IPSEC_PACKAGE:-"strongswan"} if [[ $Q_AGENT == "ovn" ]]; then NEUTRON_VPNAAS_DEVICE_DRIVER=${NEUTRON_VPNAAS_DEVICE_DRIVER:-"neutron_vpnaas.services.vpn.device_drivers.ovn_ipsec.OvnStrongSwanDriver"} else NEUTRON_VPNAAS_DEVICE_DRIVER=${NEUTRON_VPNAAS_DEVICE_DRIVER:-"neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec:StrongSwanDriver"} fi function _get_service_provider { local ipsec_package=$1 local name driver if [[ $Q_AGENT == "ovn" ]]; then driver="neutron_vpnaas.services.vpn.service_drivers.ovn_ipsec.IPsecOvnVPNDriver" else driver="neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver" fi if [ "$ipsec_package" = "libreswan" ]; then name="openswan" else name="strongswan" fi echo "VPN:${name}:${driver}:default" } # Service Driver, default value depends on IPSEC_PACKAGE. if [ -z "$NEUTRON_VPNAAS_SERVICE_PROVIDER" ]; then NEUTRON_VPNAAS_SERVICE_PROVIDER=$(_get_service_provider $IPSEC_PACKAGE) fi # Config files NEUTRON_CONF_DIR=${NEUTRON_CONF_DIR:-"/etc/neutron"} NEUTRON_VPNAAS_DIR=$DEST/neutron-vpnaas NEUTRON_VPNAAS_CONF_FILE=neutron_vpnaas.conf NEUTRON_VPNAAS_CONF=$NEUTRON_CONF_DIR/$NEUTRON_VPNAAS_CONF_FILE OVN_VPNAGENT_CONF=$NEUTRON_CONF_DIR/neutron_ovn_vpn_agent.ini ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8913493 neutron-vpnaas-24.0.1/devstack/upgrade/0000775000175000017500000000000000000000000020034 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/upgrade/resources.sh0000775000175000017500000000105700000000000022410 0ustar00zuulzuul00000000000000#!/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. # ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/upgrade/settings0000775000175000017500000000005400000000000021621 0ustar00zuulzuul00000000000000register_project_for_upgrade neutron-vpnaas ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/upgrade/shutdown.sh0000775000175000017500000000227400000000000022253 0ustar00zuulzuul00000000000000#!/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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/devstack/upgrade/upgrade.sh0000775000175000017500000000105700000000000022025 0ustar00zuulzuul00000000000000#!/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. # ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8913493 neutron-vpnaas-24.0.1/doc/0000775000175000017500000000000000000000000015346 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/requirements.txt0000664000175000017500000000013400000000000020630 0ustar00zuulzuul00000000000000sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8913493 neutron-vpnaas-24.0.1/doc/source/0000775000175000017500000000000000000000000016646 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8913493 neutron-vpnaas-24.0.1/doc/source/_static/0000775000175000017500000000000000000000000020274 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/_static/.placeholder0000664000175000017500000000000000000000000022545 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8913493 neutron-vpnaas-24.0.1/doc/source/admin/0000775000175000017500000000000000000000000017736 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/admin/index.rst0000664000175000017500000000036300000000000021601 0ustar00zuulzuul00000000000000==================== Administration Guide ==================== VPNaaS Flavors -------------- .. toctree:: :maxdepth: 3 .. todo:: Info on the different Swan flavors, how they are different, and what Operating Systems support them. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/conf.py0000664000175000017500000002114500000000000020150 0ustar00zuulzuul00000000000000# -*- 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 sys # 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', 'oslo_config.sphinxext', 'oslo_config.sphinxconfiggen', 'oslo_policy.sphinxext', 'oslo_policy.sphinxpolicygen', 'openstackdocstheme',] todo_include_todos = True # Add any paths that contain templates here, relative to this directory. templates_path = [] # 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 = 'Neutron VPNaaS' copyright = '2011-present, OpenStack Foundation.' # 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 = 'native' # 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', 'title', 'Authors name', 'manual' #man_pages = [ # ('man/neutron-server', 'neutron-server', 'Neutron Server', # ['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 # " v 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 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 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', 'doc-neutron-vpnaas.tex', 'Neutron VPN-as-a-Service Documentation', '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 # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False latex_domain_indices = False latex_elements = { 'extraclassoptions': 'openany,oneside', 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', } # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/neutron-vpnaas' openstackdocs_pdf_link = True openstackdocs_auto_name = False openstackdocs_bug_project = 'neutron' openstackdocs_bug_tag = 'doc' # -- Options for oslo_config.sphinxconfiggen --------------------------------- _config_generator_config_files = [ 'vpn_agent.ini', 'neutron_vpnaas.conf', ] def _get_config_generator_config_definition(conf): config_file_path = '../../etc/oslo-config-generator/%s' % conf # oslo_config.sphinxconfiggen appends '.conf.sample' to the filename, # strip file extentension (.conf or .ini). output_file_path = '_static/config_samples/%s' % conf.rsplit('.', 1)[0] return (config_file_path, output_file_path) config_generator_config_file = [ _get_config_generator_config_definition(conf) for conf in _config_generator_config_files ] # -- Options for oslo_policy.sphinxpolicygen --------------------------------- policy_generator_config_file = '../../etc/oslo-policy-generator/policy.conf' sample_policy_basename = '_static/neutron-vpnaas' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/doc/source/configuration/0000775000175000017500000000000000000000000021515 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/index.rst0000664000175000017500000000145400000000000023362 0ustar00zuulzuul00000000000000=================== Configuration Guide =================== Configuration ------------- This section provides a list of all possible options for each configuration file. Neutron VPNaaS uses the following configuration files for its various services. .. toctree:: :maxdepth: 1 neutron_vpnaas l3_agent neutron_ovn_vpn_agent The following are sample configuration files for Neutron VPNaaS services and utilities. These are generated from code and reflect the current state of code in the neutron-vpnaas repository. .. toctree:: :glob: :maxdepth: 1 samples/* Policy ------ Neutron VPNaaS, like most OpenStack projects, uses a policy language to restrict permissions on REST API actions. .. toctree:: :maxdepth: 1 Policy Reference Sample Policy File ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/l3_agent.rst0000664000175000017500000000031700000000000023744 0ustar00zuulzuul00000000000000============= vpn_agent.ini ============= This is a configuration file for the VPNaaS L3 agent extension of the neutron l3-agent. .. show-options:: :config-file: etc/oslo-config-generator/vpn_agent.ini ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/neutron_ovn_vpn_agent.rst0000664000175000017500000000033300000000000026663 0ustar00zuulzuul00000000000000========================= neutron_ovn_vpn_agent.ini ========================= This is a configuration file for the OVN VPN agent. .. show-options:: :config-file: etc/oslo-config-generator/neutron_ovn_vpn_agent.ini ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/neutron_vpnaas.rst0000664000175000017500000000021600000000000025310 0ustar00zuulzuul00000000000000=================== neutron_vpnaas.conf =================== .. show-options:: :config-file: etc/oslo-config-generator/neutron_vpnaas.conf ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/policy-sample.rst0000664000175000017500000000106400000000000025026 0ustar00zuulzuul00000000000000================================= Sample Neutron VPNaaS Policy File ================================= The following is a sample neutron-vpnaas policy file for adaptation and use. The sample policy can also be viewed in :download:`file form `. .. important:: The sample policy file is auto-generated from neutron-vpnaas when this documentation is built. You must ensure your version of neutron-vpnaas matches the version of this documentation. .. literalinclude:: /_static/neutron-vpnaas.policy.yaml.sample ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/policy.rst0000664000175000017500000000045600000000000023553 0ustar00zuulzuul00000000000000======================= neutron-vpnaas policies ======================= The following is an overview of all available policies in neutron-vpnaas. For a sample configuration file, refer to :doc:`/configuration/policy-sample`. .. show-policy:: :config-file: etc/oslo-policy-generator/policy.conf ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/doc/source/configuration/samples/0000775000175000017500000000000000000000000023161 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/samples/neutron_vpnaas.rst0000664000175000017500000000043400000000000026756 0ustar00zuulzuul00000000000000========================== Sample neutron_vpnaas.conf ========================== This sample configuration can also be viewed in `the raw format <../../_static/config_samples/neutron_vpnaas.conf.sample>`_. .. literalinclude:: ../../_static/config_samples/neutron_vpnaas.conf.sample ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/configuration/samples/vpn_agent.rst0000664000175000017500000000040000000000000025666 0ustar00zuulzuul00000000000000==================== Sample vpn_agent.ini ==================== This sample configuration can also be viewed in `the raw format <../../_static/config_samples/vpn_agent.conf.sample>`_. .. literalinclude:: ../../_static/config_samples/vpn_agent.conf.sample ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/doc/source/contributor/0000775000175000017500000000000000000000000021220 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/contributor/devstack.rst0000664000175000017500000000325300000000000023561 0ustar00zuulzuul00000000000000=============================== 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 VPNaaS L3 agent extension. 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 [BRANCH] ```` 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://opendev.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), ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/contributor/index.rst0000664000175000017500000000730100000000000023062 0ustar00zuulzuul00000000000000.. 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. Contributor Guide ================= In the Contributor 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. If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad in the `neutron`__ project with ``vpnaas`` tag added. New features should be filed on Launchpad in the `neutron`__ project with ``rfe`` tag added in order to get decision from `neutron drivers`_ team. Before doing that, it is recommended to check `Request for Feature Enhancements`_ (RFE) process. .. __: https://bugs.launchpad.net/neutron/+bugs?field.tag=vpnaas .. __: https://bugs.launchpad.net/neutron/+bugs?field.tag=rfe .. _`neutron drivers`: https://review.opendev.org/#/admin/groups/464,members .. _`Request for Feature Enhancements`: https://docs.openstack.org/neutron/latest/contributor/policies/blueprints.html#neutron-request-for-feature-enhancements To get in touch with the neutron-vpnaas community, look at the following resource: - Join the ``#openstack-vpnaas`` IRC channel on OFTC. This is where the VPNaaS team is available for discussion. - We will hold for `VPN-as-a-Service (bi-)weekly IRC meeting` when needed in the near further. These are great places to get recommendations on where to start contributing to neutron-vpnaas. VPNaaS Team ----------- .. toctree:: :maxdepth: 3 team VPNaaS Internals ----------------- .. toctree:: :maxdepth: 3 multiple-local-subnets VPNaaS Tests ------------ .. toctree:: :maxdepth: 3 vpnaas-tempest-test vpnaas-rally-test Testing ------- .. toctree:: :maxdepth: 3 devstack testing-with-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. About This Documentation ------------------------ 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/contributor/multiple-local-subnets.rst0000664000175000017500000002666400000000000026374 0ustar00zuulzuul00000000000000================================= 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: .. code-block:: none openstack vpn service create --router ROUTER --subnet SUBNET NAME openstack vpn ipsec site connection create --vpnservice VPNSERVICE --ikepolicy IKEPOLICY --ipsecpolicy 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 VPN_IPSEC_SITE_CONNECTION_NAME Changes to the API, to support multiple local subnets, are shown in **highlighted** text: .. code-block:: none :emphasize-lines: 2-6,17-18 openstack vpn service create --router ROUTER NAME openstack vpn endpoint group create --description OPTIONAL-DESCRIPTION --type={subnet,cidr,network,vlan,router} --value=ENDPOINT-OF-TYPE[,--value=ENDPOINT-OF-TYPE,...] ENDPOINT-GROUP-NAME openstack vpn ipsec site connection create --vpnservice VPNSERVICE --ikepolicy IKEPOLICY --ipsecpolicy 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-endpoint-group ENDPOINT-GROUP-UUID --peer-endpoint-group ENDPOINT-GROUP-UUID VPN_IPSEC_SITE_CONNECTION_NAME 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 ``--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. Examples -------- The original APIs to create one side of an IPSec connection with only one local and peer subnet: .. code-block:: none openstack vpn ike policy create ikepolicy openstack vpn ipsec policy create ipsecpolicy openstack vpn service create --router router1 --subnet privateA myvpn openstack vpn ipsec site connection create --vpnservice myvpn --ikepolicy ikepolicy --ipsecpolicy ipsecpolicy --peer-address 172.24.4.13 --peer-id 172.24.4.13 --peer-cidr 10.3.0.0/24 --psk secret vpnconnection1 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 **highlighted** below: .. code-block:: none :emphasize-lines: 4-12,20-21 openstack vpn ike policy create ikepolicy openstack vpn ipsec policy create ipsecpolicy openstack vpn service create --router router1 myvpn openstack vpn endpoint group create --type=subnet --value=privateA --value=privateB local-eps openstack vpn endpoint group create --type=cidr --value=10.3.0.0/24 peer-eps openstack vpn ipsec site connection create --vpnservice myvpn --ikepolicy ikepolicy --ipsecpolicy ipsecpolicy --peer-address 172.24.4.13 --peer-id 172.24.4.13 --psk secret --local-endpoint-group local-eps --peer-endpoint-group peer-eps vpnconnection1 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/contributor/team.rst0000664000175000017500000000414600000000000022705 0ustar00zuulzuul00000000000000===================================== Core reviewers and Driver maintainers ===================================== Core reviewers -------------- The `Neutron VPNaaS Core Reviewer Team `_ is responsible for many things that same as `Neutron team `_. 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 | +============================+===========================+==================+ | LibreSwanDriver | Dongcan Ye | yedongcan | +----------------------------+---------------------------+------------------+ | MidonetIPsecVPNDriver [#]_ | YAMAMOTO Takashi | yamamoto | +----------------------------+---------------------------+------------------+ | NSXvIPsecVpnDriver [#]_ | Roey Chen | roeyc | +----------------------------+---------------------------+------------------+ | OpenSwanDriver | Lingxian Kong | kong | +----------------------------+---------------------------+------------------+ | | Lingxian Kong | kong | | StrongSwanDriver +---------------------------+------------------+ | | Cao Xuan Hoang | hoangcx | +----------------------------+---------------------------+------------------+ .. [#] 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/contributor/testing-with-devstack.rst0000664000175000017500000003022600000000000026205 0ustar00zuulzuul00000000000000============================ Testing VPNaaS with devstack ============================ Installation ------------ In order to use Neutron-VPNaaS with `devstack `_ a single node setup, you'll need the following settings in your local.conf. .. literalinclude:: ../../../devstack/local.conf.sample You can find an example at `devstack/local.conf.sample `_ in the source tree. Quick Test Script ----------------- This quick test script creates two sites with a router, a network and a subnet connected with public network. Then, connect both sites via VPN. You can find an example at `tools/test_script.sh `_ in the source tree. Using Two DevStack Nodes for Testing ------------------------------------ You can use two DevStack nodes connected by a common "public" network to test VPNaaS. The second node can be set up with the same public network as the first node, except it will use a different gateway IP (and hence router IP). In this example, we'll assume we have two DevStack nodes (``East`` and ``West``), each running on hardware. .. note:: - You can do the same thing with multiple VM guests, if desired. - You can also create similar topology using two virtual routers with one devstack. Example Topology ^^^^^^^^^^^^^^^^ .. code-block:: none (10.1.0.0/24 - DevStack East) | | 10.1.0.1 [Neutron Router] | 172.24.4.226 | | 172.24.4.225 [Internet GW] | | [Internet GW] | 172.24.4.232 | | 172.24.4.233 [Neutron Router] | 10.2.0.1 | (10.2.0.0/24 DevStack West) DevStack Configuration ^^^^^^^^^^^^^^^^^^^^^^ For ``East`` you need to append the following lines to the local.conf, which will give you a private net of 10.1.0.0/24 and public network of 172.24.4.0/24 .. code-block:: none PUBLIC_SUBNET_NAME=yoursubnet PRIVATE_SUBNET_NAME=mysubnet FIXED_RANGE=10.1.0.0/24 NETWORK_GATEWAY=10.1.0.1 PUBLIC_NETWORK_GATEWAY=172.24.4.225 Q_FLOATING_ALLOCATION_POOL=start=172.24.4.226,end=172.24.4.231 For ``West`` you can add the following lines to local.conf to use a different local network, public GW (and implicitly router) IP. .. code-block:: none PUBLIC_SUBNET_NAME=yoursubnet PRIVATE_SUBNET_NAME=mysubnet FIXED_RANGE=10.2.0.0/24 NETWORK_GATEWAY=10.2.0.1 PUBLIC_NETWORK_GATEWAY=172.24.4.232 Q_FLOATING_ALLOCATION_POOL=start=172.24.4.233,end=172.24.4.238 VPNaaS Configuration ^^^^^^^^^^^^^^^^^^^^ With DevStack running on ``East`` and ``West`` and connectivity confirmed (make sure you can ping one router/GW from the other), you can perform these VPNaaS CLI commands. On ``East`` .. code-block:: none openstack vpn ike policy create ikepolicy1 openstack vpn ipsec policy create ipsecpolicy1 openstack vpn service create --description "My vpn service" \ --router router1 myvpn openstack vpn endpoint group create --type subnet --value mysubnet my-locals openstack vpn endpoint group create --type cidr --value 10.2.0.0/24 my-peers openstack vpn ipsec site connection create --vpnservice myvpn \ --ikepolicy ikepolicy1 --ipsecpolicy ipsecpolicy1 \ --peer-address 172.24.4.233 --peer-id 172.24.4.233 \ --local-endpoint-group my-locals --peer-endpoint-group my-peers \ --psk secret vpnconnection1 On ``West`` .. code-block:: none openstack vpn ike policy create ikepolicy1 openstack vpn ipsec policy create ipsecpolicy1 openstack vpn service create --description "My vpn service" \ --router router1 myvpn openstack vpn endpoint group create --type subnet --value mysubnet my-locals openstack vpn endpoint group create --type cidr --value 10.1.0.0/24 my-peers openstack vpn ipsec site connection create --vpnservice myvpn \ --ikepolicy ikepolicy1 --ipsecpolicy ipsecpolicy1 \ --peer-address 172.24.4.226 --peer-id 172.24.4.226 \ --local-endpoint-group my-locals --peer-endpoint-group my-peers \ --psk secret vpnconnection1 .. note:: Make sure setup security group (open icmp for vpn subnet etc) Verification ^^^^^^^^^^^^ You can spin up VMs on each node, and then from the VM ping to the other one. With tcpdump running on one of the nodes, you can see that pings appear as encrypted packets (ESP). Note that BOOTP, IGMP, and the keepalive packets between the two nodes are not encrypted (nor are pings between the two external IP addresses). Once stacked, VMs were created for testing, VPN IPsec commands used to establish connections between the nodes, and security group rules added to allow ICMP and SSH. Using single DevStack and two routers for testing ------------------------------------------------- Simple instructions on how to setup a test environment where a VPNaaS IPsec connection can be established using the reference implementation (StrongSwan). This example uses VirtualBox running on laptop to provide a VM for running DevStack. The idea here is to have a single OpenStack cloud created using DevStack, two routers (one created automatically), two private networks (one created automatically) 10.1.0.0/24 and 10.2.0.0/24, a VM in each private network, and establish a VPN connection between the two private nets, using the public network (172.24.4.0/24). Preparation ^^^^^^^^^^^ Create a VM (e.g. 4 GB RAM, 2 CPUs) running Ubuntu 16.04, with NAT I/F for access to the Internet. Clone a DevStack repo with latest. DevStack Configuration ^^^^^^^^^^^^^^^^^^^^^^ For single DevStack and two routers case, You can find an example at `devstack/local_AIO.conf.sample `_ in the source tree. Start up the cloud using ``./stack.sh`` and ensure it completes successfully. Once stacked, you can change ``RECLONE`` option in local.conf to No. Cloud Configuration ^^^^^^^^^^^^^^^^^^^ Once stacking is completed, you'll have a private network (10.1.0.0/24), and a router (router1). To prepare for establishing a VPN connection, a second network, subnet, and router needs to be created, and a VM spun up in each private network. .. code-block:: none # Create second net, subnet, router source ~/devstack/openrc admin demo openstack network create privateB openstack subnet create --network privateB --subnet-range 10.2.0.0/24 --gateway 10.2.0.1 subB openstack router create routerB openstack router add subnet routerB subB openstack router set --external-gateway public routerB # Start up a VM in the privateA subnet. PRIVATE_NET=`openstack network show private -c id -f value` openstack server create --flavor 1 --image cirros-0.3.5-x86_64-uec \ --nic net-id=$PRIVATE_NET peter # Start up a VM in the privateB subnet PRIVATE_NETB=`openstack network show privateB -c id -f value` openstack server create --flavor 1 --image cirros-0.3.5-x86_64-uec \ --nic net-id=$PRIVATE_NETB paul At this point, you can verify that you have basic connectivity. .. note:: DevStack will create a static route that will allow you to ping the private interface IP of router1 from privateB network. You can remove the route, if desired. IPsec Site-to-site Connection Creation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following commands will create the IPsec connection: .. code-block:: none # Create VPN connections openstack vpn ike policy create ikepolicy openstack vpn ipsec policy create ipsecpolicy openstack vpn service create --router router1 \ --description "My vpn service" myvpn openstack vpn endpoint group create --type subnet --value privateA my-localsA openstack vpn endpoint group create --type cidr --value 10.2.0.0/24 my-peersA openstack vpn ipsec site connection create --vpnservice myvpn \ --ikepolicy ikepolicy --ipsecpolicy ipsecpolicy \ --peer-address 172.24.4.13 --peer-id 172.24.4.13 \ --local-endpoint-group my-localsA --peer-endpoint-group my-peersA \ --psk secret vpnconnection1 openstack vpn service create --router routerB \ --description "My vpn serviceB" myvpnB openstack vpn endpoint group create --type subnet --value subB my-localsB openstack vpn endpoint group create --type cidr --value 10.1.0.0/24 my-peersB openstack vpn ipsec site connection create --vpnservice myvpnB \ --ikepolicy ikepolicy --ipsecpolicy ipsecpolicy \ --peer-address 172.24.4.11 --peer-id 172.24.4.11 \ --local-endpoint-group my-localsB --peer-endpoint-group my-peersB \ --psk secret vpnconnection2 At this point (once the connections become active - which can take up to 30 seconds or so), you should be able to ping from the VM in the privateA network, to the VM in the privateB network. You'll see encrypted packets, if you tcpdump using the qg-# interface from one of the router namespaces. If you delete one of the connections, you'll see that the pings fail (if all works out correctly :)). .. note:: Because routerB is created manually, its public IP address may change (172.24.4.13 in this case). Multiple Local Subnets ^^^^^^^^^^^^^^^^^^^^^^ Early in Mitaka, IPsec site-to-site connections will support multiple local subnets, in addition to the current multiple peer CIDRs. The multiple local subnet feature is triggered by not specifying a local subnet, when creating a VPN service. Backwards compatibility is maintained with single local subnets, by providing the subnet in the VPN service creation. To support multiple local subnets, a new capability has been provided (since Liberty), called "Endpoint Groups". Each endpoint group will define one or more endpoints of a specific type, and can be used to specify both local and peer endpoints for IPsec connections. The Endpoint Groups separate the "what gets connected" from the "how to connect" for a VPN service, and can be used for different flavors of VPN, in the future. An example: .. code-block:: none # Create VPN connections openstack vpn ike policy create ikepolicy openstack vpn ipsec policy create ipsecpolicy openstack vpn service create --router router1 \ --description "My vpn service" myvpnC To prepare for an IPsec site-to-site, one would create an endpoint group for the local subnets, and an endpoint group for the peer CIDRs, like so: .. code-block:: none openstack vpn endpoint group create --type subnet --value privateA --value privateA2 my-locals openstack vpn endpoint group create --type cidr --value 10.2.0.0/24 --value 20.2.0.0/24 my-peers where privateA and privateA2 are two local (private) subnets, and 10.2.0.0/24 and 20.2.0.0/24 are two CIDRs representing peer (private) subnets that will be used by a connection. Then, when creating the IPsec site-to-site connection, these endpoint group IDs would be specified, instead of the peer-cidrs attribute: .. code-block:: none openstack vpn ipsec site connection create --vpnservice myvpnC \ --ikepolicy ikepolicy --ipsecpolicy ipsecpolicy \ --peer-address 172.24.4.11 --peer-id 172.24.4.11 \ --local-endpoint-group my-locals --peer-endpoint-group my-peers \ --psk secret vpnconnection3 .. note:: - The validation logic makes sure that endpoint groups and peer CIDRs are not intermixed. - Endpoint group types are subnet, cidr, network, router, and vlan. However, only subnet and cidr are implemented (for IPsec use). - The endpoints in a group must be of the same type, although It can mix IP versions. - For IPsec connections, validation currently enforces that the local and peer endpoints all use the same IP version. - IPsec connection validation requires that local endpoints are subnets, and peer endpoints are CIDRs. - Migration will convert information for any existing VPN services and connections to endpoint groups. - The original APIs will work for backward compatibility. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/contributor/vpnaas-rally-test.rst0000664000175000017500000000561600000000000025350 0ustar00zuulzuul00000000000000=================== 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 `__. Create a rally deployment for your cloud and make sure it is active. .. code-block:: console rally deployment create --file=cloud_cred.json --name=MyCloud You can also create a rally deployment from the environment variables. .. code-block:: console rally deployment create --fromenv --name=MyCloud Create a folder structure as below .. code-block:: console sudo mkdir /opt/rally Create a symbolic link to the plugins directory .. code-block:: console cd /opt/rally sudo ln -s /opt/stack/neutron-vpnaas/rally-jobs/plugins Run the tests. You can run the tests in various combinations. * Single Node with DVR with admin credentials * Single Node with DVR with non admin credentials * Multi Node with DVR with admin credentials * Multi Node with DVR with non admin credentials * Single Node, Non DVR with admin credentials * 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: .. code-block:: console 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: .. code-block:: console 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/. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/contributor/vpnaas-tempest-test.rst0000664000175000017500000000411600000000000025700 0ustar00zuulzuul00000000000000==================== 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. .. code-block:: console tempest init ${your_tempest_dir} 2. Enter ${your_tempest_dir}: .. code-block:: console cd ${your_tempest_dir} 3. Check neutron_vpnaas_tests exist in tempest plugins. .. code-block:: console tempest list-plugins +----------------------+------------------------------------------------------+ | Name | EntryPoint | +----------------------+------------------------------------------------------+ | neutron_tests | neutron_tempest_plugin.plugin:NeutronTempestPlugin | | neutron_vpnaas_tests | neutron_vpnaas.tests.tempest.plugin:VPNTempestPlugin | +----------------------+------------------------------------------------------+ 4. Run neutron_vpnaas tests. .. code-block:: console 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: .. code-block:: none 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: .. code-block:: console tox -eall-plugin -- "^neutron_vpnaas.tests.tempest.api\." External Resources ================== For more information on the tempest, see: https://docs.openstack.org/tempest/latest/. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/index.rst0000664000175000017500000000334400000000000020513 0ustar00zuulzuul00000000000000.. 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 documentation! ======================================== Neutron VPNaaS 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 the OpenStack networking API. .. _`VPNaaS API`: https://docs.openstack.org/api-ref/network/v2/index.html#vpnaas-2-0-vpn-vpnservices-ikepolicies-ipsecpolicies-endpoint-groups-ipsec-site-connections Enjoy! Using VPNaaS ------------ .. toctree:: :maxdepth: 2 install/index configuration/index user/index admin/index API reference Release Notes For Contributors ---------------- .. toctree:: :maxdepth: 2 contributor/index .. only:: html Search ------ * :ref:`search` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/doc/source/install/0000775000175000017500000000000000000000000020314 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/install/index.rst0000664000175000017500000000047000000000000022156 0ustar00zuulzuul00000000000000============ Installation ============ The detail instruction to enable neutron VPNaaS is described in `the Networking Guide `__. Follow the instruction after installing ``neutron-vpnaas`` from distributor packages or PyPI. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/doc/source/user/0000775000175000017500000000000000000000000017624 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/doc/source/user/index.rst0000664000175000017500000000035600000000000021471 0ustar00zuulzuul00000000000000========== User Guide ========== Basic Usage ----------- The basic scenarios are explained in `the Networking Guide `__. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/etc/0000775000175000017500000000000000000000000015354 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/etc/README.txt0000664000175000017500000000047700000000000017062 0ustar00zuulzuul00000000000000To 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 ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/etc/neutron/0000775000175000017500000000000000000000000017046 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/etc/neutron/rootwrap.d/0000775000175000017500000000000000000000000021145 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/etc/neutron/rootwrap.d/vpnaas.filters0000664000175000017500000000176400000000000024037 0ustar00zuulzuul00000000000000# 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 sysctl_ip4_forward: RegExpFilter, sysctl, root, sysctl, -w, net.ipv4.ip_forward=1 sysctl_ip6_forward: RegExpFilter, sysctl, root, sysctl, -w, net.ipv6.conf.all.forwarding=1 rm: RegExpFilter, rm, root, rm, -rf, (.*/strongswan.d|.*/ipsec/[0-9a-z-]+) rm_file: RegExpFilter, rm, root, rm, -f, .*/ipsec.secrets 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|ipsec/[0-9a-z-]+/log) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/etc/oslo-config-generator/0000775000175000017500000000000000000000000021557 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/etc/oslo-config-generator/neutron_ovn_vpn_agent.ini0000664000175000017500000000016300000000000026675 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/neutron_ovn_vpn_agent.ini.sample wrap_width = 79 namespace = neutron.vpnaas.ovn_agent ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/etc/oslo-config-generator/neutron_vpnaas.conf0000664000175000017500000000014300000000000025466 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/neutron_vpnaas.conf.sample wrap_width = 79 namespace = neutron.vpnaas ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/etc/oslo-config-generator/vpn_agent.ini0000664000175000017500000000014300000000000024237 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/vpn_agent.ini.sample wrap_width = 79 namespace = neutron.vpnaas.agent ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/etc/oslo-policy-generator/0000775000175000017500000000000000000000000021611 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/etc/oslo-policy-generator/policy.conf0000664000175000017500000000011200000000000023751 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/policy.yaml.sample namespace = neutron-vpnaas ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/neutron_vpnaas/0000775000175000017500000000000000000000000017643 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/__init__.py0000664000175000017500000000125000000000000021752 0ustar00zuulzuul00000000000000# 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 gettext.install('neutron') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/_i18n.py0000664000175000017500000000204000000000000021127 0ustar00zuulzuul00000000000000# 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/0000775000175000017500000000000000000000000020741 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/__init__.py0000664000175000017500000000000000000000000023040 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/ovn/0000775000175000017500000000000000000000000021543 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/ovn/__init__.py0000664000175000017500000000000000000000000023642 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/ovn/vpn/0000775000175000017500000000000000000000000022346 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/ovn/vpn/__init__.py0000664000175000017500000000000000000000000024445 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/ovn/vpn/agent.py0000664000175000017500000001417200000000000024023 0ustar00zuulzuul00000000000000# Copyright 2017 Red Hat, Inc. # Copyright 2023 SysEleven GmbH # # 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 uuid from neutron.agent.linux import external_process from neutron.common.ovn import utils as ovn_utils from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config from oslo_log import log as logging from oslo_service import service from ovsdbapp.backend.ovs_idl import event as row_event from ovsdbapp.backend.ovs_idl import vlog from neutron_vpnaas.agent.ovn.vpn import ovsdb from neutron_vpnaas.services.vpn.common import constants from neutron_vpnaas.services.vpn import vpn_service LOG = logging.getLogger(__name__) OVN_VPNAGENT_UUID_NAMESPACE = uuid.UUID('e1ce3b12-b1e0-4c81-ba27-07c0fec9c12b') class ChassisCreateEventBase(row_event.RowEvent): """Row create event - Chassis name == our_chassis. On connection, we get a dump of all chassis so if we catch a creation of our own chassis it has to be a reconnection. In this case, we need to do a full sync to make sure that we capture all changes while the connection to OVSDB was down. """ table = None def __init__(self, vpn_agent): self.agent = vpn_agent self.first_time = True events = (self.ROW_CREATE,) super().__init__( events, self.table, (('name', '=', self.agent.chassis),)) self.event_name = self.__class__.__name__ def run(self, event, row, old): if self.first_time: self.first_time = False else: # NOTE(lucasagomes): Re-register the ovn vpn agent # with the local chassis in case its entry was re-created # (happens when restarting the ovn-controller) self.agent.register_vpn_agent() LOG.info("Connection to OVSDB established, doing a full sync") self.agent.sync() class ChassisCreateEvent(ChassisCreateEventBase): table = 'Chassis' class ChassisPrivateCreateEvent(ChassisCreateEventBase): table = 'Chassis_Private' class SbGlobalUpdateEvent(row_event.RowEvent): """Row update event on SB_Global table.""" def __init__(self, vpn_agent): self.agent = vpn_agent table = 'SB_Global' events = (self.ROW_UPDATE,) super().__init__(events, table, None) self.event_name = self.__class__.__name__ def run(self, event, row, old): table = ('Chassis_Private' if self.agent.has_chassis_private else 'Chassis') external_ids = {constants.OVN_AGENT_VPN_SB_CFG_KEY: str(row.nb_cfg)} self.agent.sb_idl.db_set( table, self.agent.chassis, ('external_ids', external_ids)).execute() class OvnVpnAgent(service.Service): def __init__(self, conf): super().__init__() self.conf = conf vlog.use_python_logger(max_level=config.get_ovn_ovsdb_log_level()) self._process_monitor = external_process.ProcessMonitor( config=self.conf, resource_type='ipsec') self.service = vpn_service.VPNService(self) self.device_drivers = self.service.load_device_drivers(self.conf.host) def _load_config(self): self.chassis = self._get_own_chassis_name() try: self.chassis_id = uuid.UUID(self.chassis) except ValueError: # OVS system-id could be a non UUID formatted string. self.chassis_id = uuid.uuid5(OVN_VPNAGENT_UUID_NAMESPACE, self.chassis) LOG.debug("Loaded chassis name %s (UUID: %s).", self.chassis, self.chassis_id) def start(self): super().start() self.ovs_idl = ovsdb.VPNAgentOvsIdl().start() self._load_config() tables = ('SB_Global', 'Chassis') events = (SbGlobalUpdateEvent(self), ) # TODO(lucasagomes): Remove this in the future. Try to register # the Chassis_Private table, if not present, fallback to the normal # Chassis table. # Open the connection to OVN SB database. self.has_chassis_private = False try: self.sb_idl = ovsdb.VPNAgentOvnSbIdl( chassis=self.chassis, tables=tables + ('Chassis_Private', ), events=events + (ChassisPrivateCreateEvent(self), )).start() self.has_chassis_private = True except AssertionError: self.sb_idl = ovsdb.VPNAgentOvnSbIdl( chassis=self.chassis, tables=tables, events=events + (ChassisCreateEvent(self), )).start() # Register the agent with its corresponding Chassis self.register_vpn_agent() # Do the initial sync. self.sync() def sync(self): for driver in self.device_drivers: driver.sync(driver.context, []) @ovn_utils.retry() def register_vpn_agent(self): # NOTE(lucasagomes): db_add() will not overwrite the UUID if # it's already set. table = ('Chassis_Private' if self.has_chassis_private else 'Chassis') # Generate unique, but consistent vpn agent id for chassis name agent_id = uuid.uuid5(self.chassis_id, 'vpn_agent') ext_ids = {constants.OVN_AGENT_VPN_ID_KEY: str(agent_id)} self.sb_idl.db_add(table, self.chassis, 'external_ids', ext_ids).execute(check_error=True) def _get_own_chassis_name(self): """Return the external_ids:system-id value of the Open_vSwitch table. As long as ovn-controller is running on this node, the key is guaranteed to exist and will include the chassis name. """ ext_ids = self.ovs_idl.db_get( 'Open_vSwitch', '.', 'external_ids').execute() return ext_ids['system-id'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/agent/ovn/vpn/ovsdb.py0000664000175000017500000000611500000000000024040 0ustar00zuulzuul00000000000000# Copyright 2017 Red Hat, Inc. # Copyright 2023 SysEleven GmbH # # 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.conf.plugins.ml2.drivers.ovn import ovn_conf as config from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor from oslo_log import log as logging from ovs.db import idl from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.schema.open_vswitch import impl_idl as idl_ovs import tenacity LOG = logging.getLogger(__name__) class VPNAgentOvnSbIdl(ovsdb_monitor.OvnIdl): SCHEMA = 'OVN_Southbound' def __init__(self, chassis=None, events=None, tables=None): connection_string = config.get_ovn_sb_connection() ovsdb_monitor._check_and_set_ssl_files(self.SCHEMA) helper = self._get_ovsdb_helper(connection_string) if tables is None: tables = ('Chassis', 'SB_Global') for table in tables: helper.register_table(table) try: super().__init__( None, connection_string, helper, leader_only=False) except TypeError: # TODO(bpetermann) We can remove this when we require ovs>=2.12.0 super().__init__(None, connection_string, helper) if chassis: table = ('Chassis_Private' if 'Chassis_Private' in tables else 'Chassis') self.set_table_condition(table, [['name', '==', chassis]]) if events: self.notify_handler.watch_events(events) @tenacity.retry( wait=tenacity.wait_exponential(max=180), reraise=True) def _get_ovsdb_helper(self, connection_string): return idlutils.get_schema_helper(connection_string, self.SCHEMA) def start(self): conn = connection.Connection( self, timeout=config.get_ovn_ovsdb_timeout()) return impl_idl_ovn.OvsdbSbOvnIdl(conn) class VPNAgentOvsIdl(object): def start(self): connection_string = config.cfg.CONF.ovs.ovsdb_connection helper = idlutils.get_schema_helper(connection_string, 'Open_vSwitch') tables = ('Open_vSwitch', 'Bridge', 'Port', 'Interface') for table in tables: helper.register_table(table) ovs_idl = idl.Idl( connection_string, helper, probe_interval=config.get_ovn_ovsdb_probe_interval()) conn = connection.Connection( ovs_idl, timeout=config.cfg.CONF.ovs.ovsdb_connection_timeout) return idl_ovs.OvsdbIdl(conn) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/neutron_vpnaas/api/0000775000175000017500000000000000000000000020414 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/neutron_vpnaas/api/rpc/0000775000175000017500000000000000000000000021200 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/api/rpc/agentnotifiers/0000775000175000017500000000000000000000000024221 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/api/rpc/agentnotifiers/vpn_rpc_agent_api.py0000664000175000017500000000434000000000000030252 0ustar00zuulzuul00000000000000# Copyright 2020, SysEleven GbmH # 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.api.rpc.agentnotifiers import utils as ag_utils from neutron_lib import rpc as n_rpc import oslo_messaging from neutron_vpnaas.services.vpn.common import topics # default messaging timeout is 60 sec, so 2 here is chosen to not block API # call for more than 2 minutes AGENT_NOTIFY_MAX_ATTEMPTS = 2 class VPNAgentNotifyAPI(object): """API for plugin to notify VPN agent.""" def __init__(self, topic=topics.IPSEC_AGENT_TOPIC): target = oslo_messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) def agent_updated(self, context, admin_state_up, host): cctxt = self.client.prepare(server=host) cctxt.cast(context, 'agent_updated', payload={'admin_state_up': admin_state_up}) def vpnservice_removed_from_agent(self, context, router_id, host): """Notify agent about removed VPN service(s) of a router.""" cctxt = self.client.prepare(server=host) cctxt.cast(context, 'vpnservice_removed_from_agent', router_id=router_id) def vpnservice_added_to_agent(self, context, router_ids, host): """Notify agent about added VPN service(s) of router(s).""" # need to use call here as we want to be sure agent received # notification and router will not be "lost". However using call() # itself is not a guarantee, calling code should handle exceptions and # retry cctxt = self.client.prepare(server=host) call = ag_utils.retry(cctxt.call, AGENT_NOTIFY_MAX_ATTEMPTS) call(context, 'vpnservice_added_to_agent', router_ids=router_ids) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/neutron_vpnaas/cmd/0000775000175000017500000000000000000000000020406 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/cmd/eventlet/0000775000175000017500000000000000000000000022234 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/cmd/eventlet/__init__.py0000664000175000017500000000011100000000000024336 0ustar00zuulzuul00000000000000from neutron.common import eventlet_utils eventlet_utils.monkey_patch() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/cmd/eventlet/ovn_agent.py0000664000175000017500000000123100000000000024563 0ustar00zuulzuul00000000000000# Copyright 2023 SysEleven GmbH # # 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 ovn_agent def main(): ovn_agent.main() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/0000775000175000017500000000000000000000000020230 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/__init__.py0000664000175000017500000000000000000000000022327 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/0000775000175000017500000000000000000000000022221 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/__init__.py0000664000175000017500000000000000000000000024320 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/0000775000175000017500000000000000000000000026051 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/README0000664000175000017500000000004600000000000026731 0ustar00zuulzuul00000000000000Generic single-database configuration.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/__init__.py0000664000175000017500000000121600000000000030162 0ustar00zuulzuul00000000000000# 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' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/env.py0000664000175000017500000000475000000000000027221 0ustar00zuulzuul00000000000000# 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/script.py.mako0000664000175000017500000000203500000000000030655 0ustar00zuulzuul00000000000000# Copyright ${create_date.year} # # 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"} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/0000775000175000017500000000000000000000000027721 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/0000775000175000017500000000000000000000000030447 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/expand/0000775000175000017500000000000000000000000031726 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024000000000000011451 xustar0000000000000000138 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/expand/22e0145ac80b_add_vpn_gateway_port.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/expand/22e0145a0000664000175000017500000000423300000000000032716 0ustar00zuulzuul00000000000000# Copyright 2016 MingShuang Xian/IBM # Copyright 2023 SysEleven GmbH # # 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 table for vpn gateway (gateway port and transit network) Revision ID: 22e0145ac80b Revises: 3b739d6906cf Create Date: 2016-09-18 09:01:18.660362 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '22e0145ac80b' down_revision = '3b739d6906cf' def upgrade(): op.create_table( 'vpn_ext_gws', sa.Column('id', sa.String(length=36), nullable=False, primary_key=True), sa.Column('project_id', sa.String(length=255), index=True), sa.Column('router_id', sa.String(length=36), nullable=False, unique=True), sa.Column('status', sa.String(length=16), nullable=False), sa.Column('gw_port_id', sa.String(length=36)), sa.Column('transit_port_id', sa.String(length=36)), sa.Column('transit_network_id', sa.String(length=36)), sa.Column('transit_subnet_id', sa.String(length=36)), sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(['router_id'], ['routers.id']), sa.ForeignKeyConstraint(['gw_port_id'], ['ports.id'], ondelete='SET NULL'), sa.ForeignKeyConstraint(['transit_port_id'], ['ports.id'], ondelete='SET NULL'), sa.ForeignKeyConstraint(['transit_network_id'], ['networks.id'], ondelete='SET NULL'), sa.ForeignKeyConstraint(['transit_subnet_id'], ['subnets.id'], ondelete='SET NULL'), ) ././@PaxHeader0000000000000000000000000000023100000000000011451 xustar0000000000000000131 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/expand/3b739d6906cf_vpn_scheduler.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/expand/3b739d690000664000175000017500000000242600000000000032747 0ustar00zuulzuul00000000000000# Copyright 2016 MingShuang Xian/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. # """vpn scheduler Revision ID: 3b739d6906cf Revises: 5f884db48ba9 Create Date: 2016-08-15 03:32:46.124718 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '3b739d6906cf' down_revision = '5f884db48ba9' def upgrade(): op.create_table( 'routervpnagentbindings', sa.Column('router_id', sa.String(length=36), unique=True, nullable=False), sa.Column('vpn_agent_id', sa.String(length=36), nullable=False), sa.ForeignKeyConstraint(['router_id'], ['routers.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('router_id', 'vpn_agent_id'), ) ././@PaxHeader0000000000000000000000000000022100000000000011450 xustar0000000000000000123 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/3ea02b2a773e_add_index_tenant_id.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/3ea02b2a773e_add_index0000664000175000017500000000214400000000000033451 0ustar00zuulzuul00000000000000# 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 """ from alembic import op # revision identifiers, used by Alembic. revision = '3ea02b2a773e' down_revision = 'start_neutron_vpnaas' 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/CONTRACT_HEAD0000664000175000017500000000001500000000000031636 0ustar00zuulzuul00000000000000e50641731f1a ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD0000664000175000017500000000001500000000000031400 0ustar00zuulzuul0000000000000022e0145ac80b ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/kilo_release.py0000664000175000017500000000151400000000000032732 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/0000775000175000017500000000000000000000000031373 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/0000775000175000017500000000000000000000000033210 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000026600000000000011461 xustar0000000000000000160 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/2c82e782d734_drop_tenant_id_in_cisco_csr_identifier_.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/2c82e0000664000175000017500000000227700000000000033766 0ustar00zuulzuul00000000000000# 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 import sqlalchemy as sa 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(): insp = sa.inspect(op.get_bind()) if 'cisco_csr_identifier_map' not in insp.get_table_names(): return op.drop_column('cisco_csr_identifier_map', 'tenant_id') ././@PaxHeader0000000000000000000000000000026000000000000011453 xustar0000000000000000154 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/333dfd6afaa2_populate_vpn_service_table_fields.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/333df0000664000175000017500000000641700000000000033765 0ustar00zuulzuul00000000000000# 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 """ from alembic import op import netaddr import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '333dfd6afaa2' down_revision = '56893333aa52' depends_on = '24f28869838b' 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). """ insp = sa.inspect(engine) if 'cisco_csr_identifier_map' not in insp.get_table_names(): return 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) ././@PaxHeader0000000000000000000000000000024400000000000011455 xustar0000000000000000142 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/56893333aa52_fix_identifier_map_fk.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/568930000664000175000017500000000415700000000000033640 0ustar00zuulzuul00000000000000# 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(): insp = sa.inspect(op.get_bind()) if 'cisco_csr_identifier_map' not in insp.get_table_names(): return # 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/0000775000175000017500000000000000000000000032652 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000025400000000000011456 xustar0000000000000000150 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/24f28869838b_add_fields_to_vpn_service_table.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/24f28860000664000175000017500000000234600000000000033525 0ustar00zuulzuul00000000000000# 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)) ././@PaxHeader0000000000000000000000000000022400000000000011453 xustar0000000000000000126 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/30018084ed99_initial.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/30018080000664000175000017500000000157400000000000033427 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/0000775000175000017500000000000000000000000031167 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/0000775000175000017500000000000000000000000033004 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024400000000000011455 xustar0000000000000000142 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/2cb4ee992b41_multiple_local_subnets.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/2cb4ee0000664000175000017500000001543700000000000034005 0ustar00zuulzuul00000000000000# (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() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/0000775000175000017500000000000000000000000032446 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024200000000000011453 xustar0000000000000000140 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/28ee739a7e4b_multiple_local_subnets.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/28ee739a0000664000175000017500000000404600000000000033544 0ustar00zuulzuul00000000000000# (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) ././@PaxHeader0000000000000000000000000000024200000000000011453 xustar0000000000000000140 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/41b509d10b5e_vpnaas_endpoint_groups.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/41b509d10000664000175000017500000000405200000000000033443 0ustar00zuulzuul00000000000000# (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 """ from alembic import op import sqlalchemy as sa from neutron_vpnaas.services.vpn.common import constants # revision identifiers, used by Alembic. revision = '41b509d10b5e' down_revision = '24f28869838b' 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'), ) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1712152350.883349 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/0000775000175000017500000000000000000000000031233 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/0000775000175000017500000000000000000000000033050 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024600000000000011457 xustar0000000000000000144 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/b6a2519ab7dc_rename_tenant_to_project.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/b6a2510000664000175000017500000000666000000000000033703 0ustar00zuulzuul00000000000000# 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, migration.PIKE, migration.QUEENS] _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(index_name=op.f(old_name), table_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() } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/0000775000175000017500000000000000000000000032512 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000023400000000000011454 xustar0000000000000000134 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/52783a36bd67_support_local_id.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/52783a360000664000175000017500000000225200000000000033440 0ustar00zuulzuul00000000000000# Copyright 2016 # # 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)) ././@PaxHeader0000000000000000000000000000023200000000000011452 xustar0000000000000000132 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/fe637dc3f042_support_sha256.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/fe637dc30000664000175000017500000000230000000000000033654 0ustar00zuulzuul00000000000000# Copyright 2016 # # 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/0000775000175000017500000000000000000000000031010 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/0000775000175000017500000000000000000000000032267 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000025700000000000011461 xustar0000000000000000153 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/38893903cbde_add_auth_algorithm_sha384_and_sha512.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/38893903c0000664000175000017500000000253100000000000033310 0ustar00zuulzuul00000000000000# Copyright 2016 # # 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/0000775000175000017500000000000000000000000030651 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/0000775000175000017500000000000000000000000032130 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024600000000000011457 xustar0000000000000000144 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/95601446dbcc_add_flavor_id_to_vpnservices.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/95601446db0000664000175000017500000000260700000000000033310 0ustar00zuulzuul00000000000000# 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 """ from alembic import op import sqlalchemy as sa from neutron.db import migration # revision identifiers, used by Alembic. revision = '95601446dbcc' down_revision = '38893903cbde' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.PIKE, migration.QUEENS, migration.ROCKY, migration.STEIN, migration.TRAIN, migration.USSURI] 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']) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/rocky/0000775000175000017500000000000000000000000031050 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8993495 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/rocky/contract/0000775000175000017500000000000000000000000032665 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000026000000000000011453 xustar0000000000000000154 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/rocky/contract/e50641731f1a_drop_cisco_csr_identifier_map_table.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/rocky/contract/e5064170000664000175000017500000000237300000000000033530 0ustar00zuulzuul00000000000000# Copyright 2018, Fujitsu Vietnam Limited # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # 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 cisco_csr_identifier_map table Revision ID: e50641731f1a Revises: b6a2519ab7dc Create Date: 2018-02-28 10:28:59.846652 """ from alembic import op import sqlalchemy as sa from neutron.db import migration # revision identifiers, used by Alembic. revision = 'e50641731f1a' down_revision = 'b6a2519ab7dc' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.ROCKY, migration.STEIN, migration.TRAIN, migration.USSURI] def upgrade(): insp = sa.inspect(op.get_bind()) if 'cisco_csr_identifier_map' not in insp.get_table_names(): return op.drop_table('cisco_csr_identifier_map') ././@PaxHeader0000000000000000000000000000020500000000000011452 xustar0000000000000000111 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/start_neutron_vpnaas.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/start_neutron_vpnaas.p0000664000175000017500000000153600000000000034366 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/victoria/0000775000175000017500000000000000000000000031541 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/victoria/expand/0000775000175000017500000000000000000000000033020 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000025600000000000011460 xustar0000000000000000152 path=neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/victoria/expand/5f884db48ba9_add_aggressive_negotiation_modes.py 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/migration/alembic_migrations/versions/victoria/expand/5f884d0000664000175000017500000000225500000000000033671 0ustar00zuulzuul00000000000000# Copyright 2020 cmss, 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 alembic import op import sqlalchemy as sa """add_aggressive_negotiation_modes Revision ID: 5f884db48ba9 Revises: 95601446dbcc Create Date: 2020-05-12 14:37:46.320070 """ # revision identifiers, used by Alembic. revision = '5f884db48ba9' down_revision = '95601446dbcc' phase1_negotiation_modes = sa.Enum('main', 'aggressive', name='ike_phase1_mode') def upgrade(): op.alter_column('ikepolicies', 'phase1_negotiation_mode', type_=phase1_negotiation_modes, existing_nullable=False) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/db/models/0000775000175000017500000000000000000000000021513 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/models/__init__.py0000664000175000017500000000000000000000000023612 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/models/head.py0000664000175000017500000000216100000000000022766 0ustar00zuulzuul00000000000000# 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_agentschedulers_db # noqa from neutron_vpnaas.db.vpn import vpn_db # noqa from neutron_vpnaas.db.vpn import vpn_ext_gw_db # noqa def get_metadata(): return head.model_base.BASEV2.metadata ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/db/vpn/0000775000175000017500000000000000000000000021033 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/vpn/__init__.py0000664000175000017500000000000000000000000023132 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/vpn/vpn_agentschedulers_db.py0000664000175000017500000004157100000000000026125 0ustar00zuulzuul00000000000000# Copyright (c) 2013 OpenStack Foundation. # Copyright (c) 2023 SysEleven GmbH. # 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 random from neutron.extensions import router_availability_zone as router_az from neutron import worker as neutron_worker from neutron_lib import context as ncontext from neutron_lib.db import api as db_api from neutron_lib.db import model_base from neutron_lib.plugins import constants as plugin_const from neutron_lib.plugins import directory from oslo_config import cfg from oslo_db import exception as db_exc from oslo_log import log as logging import oslo_messaging import sqlalchemy as sa from sqlalchemy import func from neutron_vpnaas._i18n import _ from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.extensions import vpn_agentschedulers from neutron_vpnaas.services.vpn.common.constants import AGENT_TYPE_VPN LOG = logging.getLogger(__name__) VPN_AGENTS_SCHEDULER_OPTS = [ cfg.StrOpt('vpn_scheduler_driver', default='neutron_vpnaas.scheduler.vpn_agent_scheduler' '.LeastRoutersScheduler', help=_('Driver to use for scheduling ' 'router to a VPN agent')), cfg.BoolOpt('vpn_auto_schedule', default=True, help=_('Allow auto scheduling of routers to VPN agent.')), cfg.BoolOpt('allow_automatic_vpnagent_failover', default=False, help=_('Automatically reschedule routers from offline VPN ' 'agents to online VPN agents.')), ] cfg.CONF.register_opts(VPN_AGENTS_SCHEDULER_OPTS) class RouterVPNAgentBinding(model_base.BASEV2): """Represents binding between neutron routers and VPN agents.""" router_id = sa.Column(sa.String(36), sa.ForeignKey("routers.id", ondelete='CASCADE'), primary_key=True, unique=True, nullable=False) vpn_agent_id = sa.Column(sa.String(36), primary_key=True, nullable=False) class VPNAgentSchedulerDbMixin( vpn_agentschedulers.VPNAgentSchedulerPluginBase): """Mixin class to add VPN agent scheduler extension to plugins using the VPN agent. """ vpn_scheduler = None agent_notifiers = {} @property def l3_plugin(self): return directory.get_plugin(plugin_const.L3) @property def core_plugin(self): return directory.get_plugin() def add_periodic_vpn_agent_status_check(self): if not cfg.CONF.allow_automatic_vpnagent_failover: LOG.info("Skipping periodic VPN agent status check because " "automatic rescheduling is disabled.") return interval = max(cfg.CONF.agent_down_time // 2, 1) # add random initial delay to allow agents to check in after the # neutron server first starts. random to offset multiple servers initial_delay = random.randint(interval, interval * 2) check_worker = neutron_worker.PeriodicWorker( self.reschedule_vpnservices_from_down_agents, interval, initial_delay) self.add_worker(check_worker) def reschedule_vpnservices_from_down_agents(self): """Reschedule VPN services from down VPN agents. VPN services are scheduled per router. """ context = ncontext.get_admin_context() try: down_bindings = self.get_down_router_bindings(context) agents_back_online = set() for binding in down_bindings: if binding.vpn_agent_id in agents_back_online: continue agent = self.core_plugin.get_agent(context, binding.vpn_agent_id) if agent['alive']: agents_back_online.add(binding.vpn_agent_id) continue LOG.warning( "Rescheduling vpn services for router %(router)s from " "agent %(agent)s because the agent is not alive.", {'router': binding.router_id, 'agent': binding.vpn_agent_id}) try: self.reschedule_router(context, binding.router_id, agent) except (vpn_agentschedulers.RouterReschedulingFailed, oslo_messaging.RemoteError): # Catch individual rescheduling errors here # so one broken one doesn't stop the iteration. LOG.exception("Failed to reschedule vpn services for " "router %s", binding.router_id) except Exception: # we want to be thorough and catch whatever is raised # to avoid loop abortion LOG.exception("Exception encountered during vpn service " "rescheduling.") @db_api.CONTEXT_READER def get_down_router_bindings(self, context): vpn_agents = self.get_vpn_agents(context, active=False) if not vpn_agents: return [] vpn_agent_ids = [vpn_agent['id'] for vpn_agent in vpn_agents] query = context.session.query(RouterVPNAgentBinding) query = query.filter( RouterVPNAgentBinding.vpn_agent_id.in_(vpn_agent_ids)) return query.all() def validate_agent_router_combination(self, context, agent, router): """Validate if the router can be correctly assigned to the agent. :raises: InvalidVPNAgent if attempting to assign router to an unsuitable agent (disabled, type != VPN, incompatible configuration) """ if agent['agent_type'] != AGENT_TYPE_VPN: raise vpn_agentschedulers.InvalidVPNAgent(id=agent['id']) @db_api.CONTEXT_READER def check_agent_router_scheduling_needed(self, context, agent, router): """Check if the scheduling of router's VPN services is needed. :raises: RouterHostedByVPNAgent if router is already assigned to a different agent. :returns: True if scheduling is needed, otherwise False """ router_id = router['id'] agent_id = agent['id'] query = context.session.query(RouterVPNAgentBinding) bindings = query.filter_by(router_id=router_id).all() if not bindings: return True for binding in bindings: if binding.vpn_agent_id == agent_id: # router already bound to the agent we need return False # Router is already bound to some agent raise vpn_agentschedulers.RouterHostedByVPNAgent( router_id=router_id, agent_id=bindings[0].vpn_agent_id) def create_router_to_agent_binding(self, context, router_id, agent_id): """Create router to VPN agent binding.""" try: with db_api.CONTEXT_WRITER.using(context): binding = RouterVPNAgentBinding() binding.vpn_agent_id = agent_id binding.router_id = router_id context.session.add(binding) except db_exc.DBDuplicateEntry: LOG.debug('VPN service of router %(router_id)s has already been ' 'scheduled to a VPN agent.', {'router_id': router_id}) return False except db_exc.DBReferenceError: LOG.debug('Router %s has already been removed ' 'by concurrent operation', router_id) return False LOG.debug('VPN service of router %(router_id)s is scheduled to ' 'VPN agent %(agent_id)s', {'router_id': router_id, 'agent_id': agent_id}) return True def add_router_to_vpn_agent(self, context, agent_id, router_id): """Add a VPN agent to host VPN services of a router.""" with db_api.CONTEXT_WRITER.using(context): router = self.l3_plugin.get_router(context, router_id) agent = self.core_plugin.get_agent(context, agent_id) self.validate_agent_router_combination(context, agent, router) if not self.check_agent_router_scheduling_needed( context, agent, router): return try: success = self.create_router_to_agent_binding( context, router['id'], agent['id']) except db_exc.DBError: success = False if not success: raise vpn_agentschedulers.RouterSchedulingFailed( router_id=router_id, agent_id=agent_id) # notify agent vpn_notifier = self.agent_notifiers.get(AGENT_TYPE_VPN) if vpn_notifier: vpn_notifier.vpnservice_added_to_agent( context, [router_id], agent['host']) # update port binding self.vpn_router_agent_binding_changed( context, router_id, agent['host']) def remove_router_from_vpn_agent(self, context, agent_id, router_id): """Remove the router from VPN agent. After removal, the VPN service(s) of the router will be non-hosted until there is an update which leads to re-schedule or the router is added to another agent manually. """ agent = self.core_plugin.get_agent(context, agent_id) self._unbind_router(context, router_id, agent_id) vpn_notifier = self.agent_notifiers.get(AGENT_TYPE_VPN) if vpn_notifier: vpn_notifier.vpnservice_removed_from_agent( context, router_id, agent['host']) def _unbind_router(self, context, router_id, agent_id): with db_api.CONTEXT_WRITER.using(context): query = context.session.query(RouterVPNAgentBinding) query = query.filter( RouterVPNAgentBinding.router_id == router_id, RouterVPNAgentBinding.vpn_agent_id == agent_id) return query.delete() def reschedule_router(self, context, router_id, cur_agent): """Reschedule router to a new VPN agent Remove the router from the agent currently hosting it and schedule it again """ with db_api.CONTEXT_WRITER.using(context): deleted = self._unbind_router(context, router_id, cur_agent['id']) if not deleted: # If nothing was deleted, the binding didn't exist anymore # because some other server deleted the binding concurrently. # Stop here. return new_agent = self.schedule_router(context, router_id) if not new_agent: # No new_agent means that another server scheduled the # router concurrently. Don't raise RouterReschedulingFailed. return self._notify_agents_router_rescheduled(context, router_id, cur_agent, new_agent) # update port binding self.vpn_router_agent_binding_changed( context, router_id, new_agent['host']) def _notify_agents_router_rescheduled(self, context, router_id, old_agent, new_agent): vpn_notifier = self.agent_notifiers.get(AGENT_TYPE_VPN) if not vpn_notifier: return old_host = old_agent['host'] new_host = new_agent['host'] if old_host != new_host: vpn_notifier.vpnservice_removed_from_agent( context, router_id, old_host) try: vpn_notifier.vpnservice_added_to_agent( context, [router_id], new_host) except oslo_messaging.MessagingException: self._unbind_router(context, router_id, new_agent['id']) raise vpn_agentschedulers.RouterReschedulingFailed( router_id=router_id) @db_api.CONTEXT_READER def list_routers_on_vpn_agent(self, context, agent_id): query = context.session.query(RouterVPNAgentBinding.router_id) query = query.filter(RouterVPNAgentBinding.vpn_agent_id == agent_id) router_ids = [item[0] for item in query] if router_ids: return {'routers': self.l3_plugin.get_routers(context, filters={'id': router_ids})} else: # Exception will be thrown if the requested agent does not exist. self.core_plugin.get_agent(context, agent_id) return {'routers': []} @db_api.CONTEXT_READER def get_vpn_agents_hosting_routers(self, context, router_ids, active=None): if not router_ids: return [] query = context.session.query(RouterVPNAgentBinding) query = query.filter(RouterVPNAgentBinding.router_id.in_(router_ids)) filters = {'id': [binding.vpn_agent_id for binding in query]} vpn_agents = self.core_plugin.get_agents(context, filters=filters) if active is not None: vpn_agents = [agent for agent in vpn_agents if agent['alive'] == active] return vpn_agents def list_vpn_agents_hosting_router(self, context, router_id): vpn_agents = self.get_vpn_agents_hosting_routers(context, [router_id]) return {'agents': vpn_agents} def get_vpn_agents(self, context, active=None, host=None): filters = {'agent_type': [AGENT_TYPE_VPN]} if host is not None: filters['host'] = [host] vpn_agents = self.core_plugin.get_agents(context, filters=filters) if active is None: return vpn_agents else: return [vpn_agent for vpn_agent in vpn_agents if vpn_agent['alive'] == active] def get_vpn_agent_on_host(self, context, host, active=None): agents = self.get_vpn_agents(context, active=active, host=host) if agents: return agents[0] @db_api.CONTEXT_READER def get_unscheduled_vpn_routers(self, context, router_ids=None): """Get IDs of routers which have unscheduled VPN services.""" query = context.session.query(vpn_models.VPNService.router_id) query = query.outerjoin( RouterVPNAgentBinding, vpn_models.VPNService.router_id == RouterVPNAgentBinding.router_id) query = query.filter(RouterVPNAgentBinding.vpn_agent_id.is_(None)) if router_ids: query = query.filter( vpn_models.VPNService.router_id.in_(router_ids)) return [router_id for router_id, in query.all()] def auto_schedule_routers(self, context, vpn_agent): if self.vpn_scheduler: return self.vpn_scheduler.auto_schedule_routers( self, context, vpn_agent) def schedule_router(self, context, router, candidates=None): """Schedule VPN services of a router to a VPN agent. Returns the chosen agent; None if another server scheduled the router concurrently. Raises RouterReschedulingFailed if no suitable agent is found. """ if self.vpn_scheduler: return self.vpn_scheduler.schedule( self, context, router, candidates=candidates) @db_api.CONTEXT_READER def get_vpn_agent_with_min_routers(self, context, agent_ids): """Return VPN agent with the least number of routers.""" if not agent_ids: return None query = context.session.query( RouterVPNAgentBinding.vpn_agent_id, func.count(RouterVPNAgentBinding.router_id).label('count')) query = query.group_by(RouterVPNAgentBinding.vpn_agent_id) query = query.order_by('count') query = query.filter(RouterVPNAgentBinding.vpn_agent_id.in_(agent_ids)) used_agent_ids = [agent_id for agent_id, _ in query.all()] unused_agent_ids = set(agent_ids) - set(used_agent_ids) if unused_agent_ids: return unused_agent_ids.pop() else: return used_agent_ids[0] def get_hosts_to_notify(self, context, router_id): """Returns all hosts to send notification about router update""" agents = self.get_vpn_agents_hosting_routers(context, [router_id], active=True) return [a['host'] for a in agents] class AZVPNAgentSchedulerDbMixin(VPNAgentSchedulerDbMixin, router_az.RouterAvailabilityZonePluginBase): """Mixin class to add availability_zone supported VPN agent scheduler.""" def get_router_availability_zones(self, router): return list({agent.availability_zone for agent in router.vpn_agents}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/vpn/vpn_db.py0000664000175000017500000011406600000000000022665 0ustar00zuulzuul00000000000000# (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 models_v2 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.db import api as db_api from neutron_lib.db import model_query from neutron_lib.db import utils as db_utils from neutron_lib.exceptions import l3 as l3_exception from neutron_lib.exceptions import vpn as vpn_exception from neutron_lib.plugins import constants as p_constants from neutron_lib.plugins import directory from neutron_lib.plugins import utils from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import uuidutils import sqlalchemy as sa from sqlalchemy.orm import exc from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.db.vpn import vpn_validator from neutron_vpnaas.extensions import 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): """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 db_api.CONTEXT_WRITER.using(context): v_db = self._get_resource(context, model, v_id) v_db.update({'status': status}) def _get_resource(self, context, model, v_id): try: r = model_query.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 vpn_exception.IPsecSiteConnectionNotFound( ipsec_site_conn_id=v_id ) elif issubclass(model, vpn_models.IKEPolicy): raise vpn_exception.IKEPolicyNotFound(ikepolicy_id=v_id) elif issubclass(model, vpn_models.IPsecPolicy): raise vpn_exception.IPsecPolicyNotFound( ipsecpolicy_id=v_id) elif issubclass(model, vpn_models.VPNService): raise vpn_exception.VPNServiceNotFound(vpnservice_id=v_id) elif issubclass(model, vpn_models.VPNEndpointGroup): raise vpn_exception.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 vpn_exception.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 db_utils.resource_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 db_api.CONTEXT_WRITER.using(context): #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 db_api.CONTEXT_WRITER.using(context): 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 db_api.CONTEXT_WRITER.using(context): 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 model_query.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 db_api.CONTEXT_WRITER.using(context): 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 vpn_exception.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 db_utils.resource_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 db_api.CONTEXT_WRITER.using(context): 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 db_api.CONTEXT_WRITER.using(context): validator.validate_ike_policy(context, ike) if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ikepolicy_id=ikepolicy_id).first(): raise vpn_exception.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 db_api.CONTEXT_WRITER.using(context): if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ikepolicy_id=ikepolicy_id).first(): raise vpn_exception.IKEPolicyInUse(ikepolicy_id=ikepolicy_id) ike_db = self._get_resource( context, vpn_models.IKEPolicy, ikepolicy_id) context.session.delete(ike_db) @db_api.CONTEXT_READER 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) @db_api.CONTEXT_READER def get_ikepolicies(self, context, filters=None, fields=None): return model_query.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 db_utils.resource_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 db_api.CONTEXT_WRITER.using(context): 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 db_api.CONTEXT_WRITER.using(context): validator.validate_ipsec_policy(context, ipsecp) if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ipsecpolicy_id=ipsecpolicy_id).first(): raise vpn_exception.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 db_api.CONTEXT_WRITER.using(context): if context.session.query(vpn_models.IPsecSiteConnection).filter_by( ipsecpolicy_id=ipsecpolicy_id).first(): raise vpn_exception.IPsecPolicyInUse( ipsecpolicy_id=ipsecpolicy_id) ipsec_db = self._get_resource( context, vpn_models.IPsecPolicy, ipsecpolicy_id) context.session.delete(ipsec_db) @db_api.CONTEXT_READER 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) @db_api.CONTEXT_READER def get_ipsecpolicies(self, context, filters=None, fields=None): return model_query.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 db_utils.resource_fields(res, fields) def create_vpnservice(self, context, vpnservice): vpns = vpnservice['vpnservice'] flavor_id = vpns.get('flavor_id', None) validator = self._get_validator() with db_api.CONTEXT_WRITER.using(context): 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 db_api.CONTEXT_WRITER.using(context): vpns_db = self._get_resource(context, vpn_models.VPNService, vpnservice_id) vpns_db.update(vpns) return self._make_vpnservice_dict(vpns_db) def set_vpnservice_status(self, context, vpnservice_id, status, updated_pending_status=False): vpns = {'status': status} with db_api.CONTEXT_WRITER.using(context): vpns_db = self._get_resource(context, vpn_models.VPNService, vpnservice_id) if (utils.in_pending_status(vpns_db.status) and not updated_pending_status): raise vpnaas.VPNStateInvalidToUpdate( id=vpnservice_id, state=vpns_db.status) vpns_db.update(vpns) return self._make_vpnservice_dict(vpns_db) def update_vpnservice(self, context, vpnservice_id, vpnservice): vpns = vpnservice['vpnservice'] with db_api.CONTEXT_WRITER.using(context): 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 db_api.CONTEXT_WRITER.using(context): if context.session.query(vpn_models.IPsecSiteConnection).filter_by( vpnservice_id=vpnservice_id ).first(): raise vpn_exception.VPNServiceInUse( vpnservice_id=vpnservice_id) vpns_db = self._get_resource(context, vpn_models.VPNService, vpnservice_id) context.session.delete(vpns_db) @db_api.CONTEXT_READER def _get_vpnservice(self, context, vpnservice_id): return self._get_resource(context, vpn_models.VPNService, vpnservice_id) @db_api.CONTEXT_READER 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) @db_api.CONTEXT_READER def get_vpnservices(self, context, filters=None, fields=None): return model_query.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 db_api.CONTEXT_READER.using(context): vpnservices = context.session.query( vpn_models.VPNService).filter_by( subnet_id=subnet_id, router_id=router_id).first() if vpnservices: raise vpn_exception.SubnetInUseByVPNService( subnet_id=subnet_id, vpnservice_id=vpnservices['id']) query = context.session.query(vpn_models.IPsecSiteConnection) query = query.join( vpn_models.VPNEndpointGroup, vpn_models.VPNEndpointGroup.id == vpn_models.IPsecSiteConnection.local_ep_group_id).filter( vpn_models.VPNEndpointGroup.endpoint_type == v_constants.SUBNET_ENDPOINT) query = query.join( vpn_models.VPNEndpoint, vpn_models.VPNEndpoint.endpoint_group_id == vpn_models.IPsecSiteConnection.local_ep_group_id).filter( vpn_models.VPNEndpoint.endpoint == subnet_id) query = query.join( vpn_models.VPNService, vpn_models.VPNService.id == vpn_models.IPsecSiteConnection.vpnservice_id).filter( vpn_models.VPNService.router_id == router_id) connection = query.first() if connection: raise vpn_exception.SubnetInUseByIPsecSiteConnection( subnet_id=subnet_id, ipsec_site_connection_id=connection['id']) def check_subnet_in_use_by_endpoint_group(self, context, subnet_id): with db_api.CONTEXT_READER.using(context): 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 vpn_exception.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 db_utils.resource_fields(res, fields) def create_endpoint_group(self, context, endpoint_group): group = endpoint_group['endpoint_group'] validator = self._get_validator() with db_api.CONTEXT_WRITER.using(context): 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 db_api.CONTEXT_WRITER.using(context): 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 db_api.CONTEXT_WRITER.using(context): 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) @db_api.CONTEXT_READER 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) @db_api.CONTEXT_READER def get_endpoint_groups(self, context, filters=None, fields=None): return model_query.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 vpn_exception.EndpointGroupInUse(group_id=group_id) def get_vpnservice_router_id(self, context, vpnservice_id): with db_api.CONTEXT_READER.using(context): vpnservice = self._get_vpnservice(context, vpnservice_id) return vpnservice['router_id'] @db_api.CONTEXT_READER def get_peer_cidrs_for_router(self, context, router_id): filters = {'router_id': [router_id]} vpnservices = model_query.get_collection_query( context, vpn_models.VPNService, filters=filters).all() cidrs = [] for vpnservice in vpnservices: for ipsec_site_connection in vpnservice.ipsec_site_connections: if ipsec_site_connection.peer_cidrs: for peer_cidr in ipsec_site_connection.peer_cidrs: cidrs.append(peer_cidr.cidr) if ipsec_site_connection.peer_ep_group is not None: for ep in ipsec_site_connection.peer_ep_group.endpoints: cidrs.append(ep.endpoint) return cidrs 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 db_api.CONTEXT_WRITER.using(context): for vpnservice in service_status_info_list: try: vpnservice_db = self._get_vpnservice( context, vpnservice['id']) except vpn_exception.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_router_gateway_callback(resource, event, trigger, payload=None): # the event payload objects vpn_plugin = directory.get_plugin(p_constants.VPN) if vpn_plugin: context = payload.context router_id = payload.resource_id if resource == resources.ROUTER_GATEWAY: vpn_plugin.check_router_in_use(context, router_id) elif resource == resources.ROUTER_INTERFACE: subnet_id = payload.metadata.get('subnet_id') vpn_plugin.check_subnet_in_use(context, subnet_id, router_id) def migration_callback(resource, event, trigger, payload): context = payload.context router = payload.latest_state 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, payload=None): """Respond to subnet based notifications - see if subnet in use.""" context = payload.context subnet_id = payload.resource_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_router_gateway_callback, resources.ROUTER_GATEWAY, events.BEFORE_DELETE) registry.subscribe( vpn_router_gateway_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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/vpn/vpn_ext_gw_db.py0000664000175000017500000002237200000000000024240 0ustar00zuulzuul00000000000000# (c) Copyright 2016 IBM Corporation, All Rights Reserved. # (c) Copyright 2023 SysEleven GmbH # 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.models import l3 as l3_models from neutron.db import models_v2 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.db import api as db_api from neutron_lib.db import model_base from neutron_lib.db import model_query from neutron_lib import exceptions as n_exc from neutron_lib.plugins import constants as plugin_const from neutron_lib.plugins import directory from oslo_log import log as logging from oslo_utils import uuidutils import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.orm import exc from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn.common import constants as v_constants LOG = logging.getLogger(__name__) class RouterIsNotVPNExternal(n_exc.BadRequest): message = _("Router %(router_id)s has no VPN external network gateway set") class RouterHasVPNExternal(n_exc.BadRequest): message = _( "Router %(router_id)s already has VPN external network gateway") class VPNNetworkInUse(n_exc.NetworkInUse): message = _("Network %(network_id)s is used by VPN service") class VPNExtGW(model_base.BASEV2, model_base.HasId, model_base.HasProject): __tablename__ = 'vpn_ext_gws' router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'), nullable=False, unique=True) status = sa.Column(sa.String(16), nullable=False) gw_port_id = sa.Column( sa.String(36), sa.ForeignKey('ports.id', ondelete='SET NULL')) transit_port_id = sa.Column( sa.String(36), sa.ForeignKey('ports.id', ondelete='SET NULL')) transit_network_id = sa.Column( sa.String(36), sa.ForeignKey('networks.id', ondelete='SET NULL')) transit_subnet_id = sa.Column( sa.String(36), sa.ForeignKey('subnets.id', ondelete='SET NULL')) gw_port = orm.relationship(models_v2.Port, lazy='joined', foreign_keys=[gw_port_id]) transit_port = orm.relationship(models_v2.Port, lazy='joined', foreign_keys=[transit_port_id]) transit_network = orm.relationship(models_v2.Network) transit_subnet = orm.relationship(models_v2.Subnet) router = orm.relationship(l3_models.Router) @registry.has_registry_receivers class VPNExtGWPlugin_db(object): """DB class to support vpn external ports configuration.""" @property def _core_plugin(self): return directory.get_plugin() @property def _vpn_plugin(self): return directory.get_plugin(plugin_const.VPN) @staticmethod @registry.receives(resources.PORT, [events.BEFORE_DELETE]) def _prevent_vpn_port_delete_callback(resource, event, trigger, payload=None): vpn_plugin = directory.get_plugin(plugin_const.VPN) if vpn_plugin: vpn_plugin.prevent_vpn_port_deletion(payload.context, payload.resource_id) @db_api.CONTEXT_READER def _id_used(self, context, id_column, resource_id): return context.session.query(VPNExtGW).filter( sa.and_( id_column == resource_id, VPNExtGW.status != lib_constants.PENDING_DELETE ) ).count() > 0 def prevent_vpn_port_deletion(self, context, port_id): """Checks to make sure a port is allowed to be deleted. Raises an exception if this is not the case. This should be called by any plugin when the API requests the deletion of a port, since some ports for L3 are not intended to be deleted directly via a DELETE to /ports, but rather via other API calls that perform the proper deletion checks. """ try: port = self._core_plugin.get_port(context, port_id) except n_exc.PortNotFound: # non-existent ports don't need to be protected from deletion return port_id_column = { v_constants.DEVICE_OWNER_VPN_ROUTER_GW: VPNExtGW.gw_port_id, v_constants.DEVICE_OWNER_TRANSIT_NETWORK: VPNExtGW.transit_port_id, }.get(port['device_owner']) if not port_id_column: # This is not a VPN port return if self._id_used(context, port_id_column, port_id): reason = _('has device owner %s') % port['device_owner'] raise n_exc.ServicePortInUse(port_id=port['id'], reason=reason) @staticmethod @registry.receives(resources.SUBNET, [events.BEFORE_DELETE]) def _prevent_vpn_subnet_delete_callback(resource, event, trigger, payload=None): vpn_plugin = directory.get_plugin(plugin_const.VPN) if vpn_plugin: vpn_plugin.prevent_vpn_subnet_deletion(payload.context, payload.resource_id) def prevent_vpn_subnet_deletion(self, context, subnet_id): if self._id_used(context, VPNExtGW.transit_subnet_id, subnet_id): reason = _('Subnet is used by VPN service') raise n_exc.SubnetInUse(subnet_id=subnet_id, reason=reason) @staticmethod @registry.receives(resources.NETWORK, [events.BEFORE_DELETE]) def _prevent_vpn_network_delete_callback(resource, event, trigger, payload=None): vpn_plugin = directory.get_plugin(plugin_const.VPN) if vpn_plugin: vpn_plugin.prevent_vpn_network_deletion(payload.context, payload.resource_id) def prevent_vpn_network_deletion(self, context, network_id): if self._id_used(context, VPNExtGW.transit_network_id, network_id): raise VPNNetworkInUse(network_id=network_id) def _make_vpn_ext_gw_dict(self, gateway_db): if not gateway_db: return None gateway = { 'id': gateway_db['id'], 'tenant_id': gateway_db['tenant_id'], 'router_id': gateway_db['router_id'], 'status': gateway_db['status'], } if gateway_db.gw_port: gateway['network_id'] = gateway_db.gw_port['network_id'] gateway['external_fixed_ips'] = [ {'subnet_id': ip["subnet_id"], 'ip_address': ip["ip_address"]} for ip in gateway_db.gw_port['fixed_ips'] ] for key in ('gw_port_id', 'transit_port_id', 'transit_network_id', 'transit_subnet_id'): value = gateway_db.get(key) if value: gateway[key] = value return gateway def _get_vpn_gw_by_router_id(self, context, router_id): try: gateway_db = context.session.query(VPNExtGW).filter( VPNExtGW.router_id == router_id).one() except exc.NoResultFound: return None return gateway_db @db_api.CONTEXT_READER def get_vpn_gw_by_router_id(self, context, router_id): return self._get_vpn_gw_by_router_id(context, router_id) @db_api.CONTEXT_READER def get_vpn_gw_dict_by_router_id(self, context, router_id, refresh=False): gateway_db = self._get_vpn_gw_by_router_id(context, router_id) if gateway_db and refresh: context.session.refresh(gateway_db) return self._make_vpn_ext_gw_dict(gateway_db) def create_gateway(self, context, gateway): info = gateway['gateway'] with db_api.CONTEXT_WRITER.using(context): gateway_db = VPNExtGW( id=uuidutils.generate_uuid(), tenant_id=info['tenant_id'], router_id=info['router_id'], status=lib_constants.PENDING_CREATE, gw_port_id=info.get('gw_port_id'), transit_port_id=info.get('transit_port_id'), transit_network_id=info.get('transit_network_id'), transit_subnet_id=info.get('transit_subnet_id')) context.session.add(gateway_db) return self._make_vpn_ext_gw_dict(gateway_db) def update_gateway(self, context, gateway_id, gateway): info = gateway['gateway'] with db_api.CONTEXT_WRITER.using(context): gateway_db = model_query.get_by_id(context, VPNExtGW, gateway_id) gateway_db.update(info) return self._make_vpn_ext_gw_dict(gateway_db) def delete_gateway(self, context, gateway_id): with db_api.CONTEXT_WRITER.using(context): query = context.session.query(VPNExtGW) return query.filter(VPNExtGW.id == gateway_id).delete() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/vpn/vpn_models.py0000664000175000017500000002220500000000000023554 0ustar00zuulzuul00000000000000# (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", 'aggressive', 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="endpoint_type"), nullable=False) endpoints = orm.relationship(VPNEndpoint, backref='endpoint_group', lazy='joined', cascade='all, delete, delete-orphan') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/db/vpn/vpn_validator.py0000664000175000017500000003541000000000000024260 0ustar00zuulzuul00000000000000# 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 exceptions as nexception from neutron_lib.exceptions import vpn as vpn_exception from neutron_lib.plugins import constants as plugin_const from neutron_lib.plugins import directory from neutron_vpnaas._i18n import _ 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(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 vpn_exception.IPsecSiteConnectionDpdIntervalValueError( attr='dpd_timeout') def _check_mtu(self, context, mtu, ip_version): if mtu < VpnReferenceValidator.IP_MIN_MTU[ip_version]: raise vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.InvalidEndpointGroup(which=which, suffix=suffix) else: if peer_cidrs: raise vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 vpn_exception.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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/extensions/0000775000175000017500000000000000000000000022042 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/extensions/__init__.py0000664000175000017500000000000000000000000024141 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/extensions/vpn_agentschedulers.py0000664000175000017500000001417600000000000026470 0ustar00zuulzuul00000000000000# (c) Copyright 2016 IBM 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 from neutron.api import extensions from neutron.api.v2 import resource from neutron import policy from neutron import wsgi from neutron_lib.api import extensions as lib_extensions from neutron_lib.api import faults as base from neutron_lib import exceptions from neutron_lib.plugins import constants as plugin_const from neutron_lib.plugins import directory from neutron_lib import rpc as n_rpc from oslo_log import log as logging import webob.exc LOG = logging.getLogger(__name__) VPN_ROUTER = 'vpn-router' VPN_ROUTERS = VPN_ROUTER + 's' VPN_AGENT = 'vpn-agent' VPN_AGENTS = VPN_AGENT + 's' class VPNRouterSchedulerController(wsgi.Controller): def get_plugin(self): plugin = directory.get_plugin(plugin_const.VPN) if not plugin: LOG.error('No plugin for VPN registered to handle VPN ' 'router scheduling') msg = 'The resource could not be found.' raise webob.exc.HTTPNotFound(msg) return plugin def index(self, request, **kwargs): plugin = self.get_plugin() policy.enforce(request.context, "get_%s" % VPN_ROUTERS, {}) return plugin.list_routers_on_vpn_agent( request.context, kwargs['agent_id']) def create(self, request, body, **kwargs): plugin = self.get_plugin() policy.enforce(request.context, "create_%s" % VPN_ROUTER, {}) agent_id = kwargs['agent_id'] router_id = body['router_id'] result = plugin.add_router_to_vpn_agent(request.context, agent_id, router_id) notify(request.context, 'vpn_agent.router.add', router_id, agent_id) return result def delete(self, request, id, **kwargs): plugin = self.get_plugin() policy.enforce(request.context, "delete_%s" % VPN_ROUTER, {}) agent_id = kwargs['agent_id'] result = plugin.remove_router_from_vpn_agent(request.context, agent_id, id) notify(request.context, 'vpn_agent.router.remove', id, agent_id) return result class VPNAgentsHostingRouterController(wsgi.Controller): def get_plugin(self): plugin = directory.get_plugin(plugin_const.VPN) if not plugin: LOG.error('VPN plugin not registered to handle agent scheduling') msg = 'The resource could not be found.' raise webob.exc.HTTPNotFound(msg) return plugin def index(self, request, **kwargs): plugin = self.get_plugin() policy.enforce(request.context, "get_%s" % VPN_AGENTS, {}) return plugin.list_vpn_agents_hosting_router( request.context, kwargs['router_id']) class Vpn_agentschedulers(lib_extensions.ExtensionDescriptor): """Extension class supporting VPN agent scheduler. """ @classmethod def get_name(cls): return "VPN Agent Scheduler" @classmethod def get_alias(cls): return "vpn-agent-scheduler" @classmethod def get_description(cls): return "Schedule VPN services of routers among VPN agents" @classmethod def get_updated(cls): return "2016-08-15T10:00:00-00:00" @classmethod def get_resources(cls): """Returns Ext Resources.""" exts = [] parent = dict(member_name="agent", collection_name="agents") controller = resource.Resource(VPNRouterSchedulerController(), base.FAULT_MAP) exts.append(extensions.ResourceExtension( VPN_ROUTERS, controller, parent)) parent = dict(member_name="router", collection_name="routers") controller = resource.Resource(VPNAgentsHostingRouterController(), base.FAULT_MAP) exts.append(extensions.ResourceExtension( VPN_AGENTS, controller, parent)) return exts def get_extended_resources(self, version): return {} class InvalidVPNAgent(exceptions.agent.AgentNotFound): message = "Agent %(id)s is not a VPN Agent or has been disabled" class RouterHostedByVPNAgent(exceptions.Conflict): message = ("The VPN service of router %(router_id)s has been already " "hosted by the VPN Agent %(agent_id)s.") class RouterSchedulingFailed(exceptions.Conflict): message = ("Failed scheduling router %(router_id)s to the VPN Agent " "%(agent_id)s.") class RouterReschedulingFailed(exceptions.Conflict): message = ("Failed rescheduling router %(router_id)s: " "No eligible VPN agent found.") class VPNAgentSchedulerPluginBase(object, metaclass=abc.ABCMeta): """REST API to operate the VPN agent scheduler. All methods must be in an admin context. """ @abc.abstractmethod def add_router_to_vpn_agent(self, context, id, router_id): pass @abc.abstractmethod def remove_router_from_vpn_agent(self, context, id, router_id): pass @abc.abstractmethod def list_routers_on_vpn_agent(self, context, id): pass @abc.abstractmethod def list_vpn_agents_hosting_router(self, context, router_id): pass def notify(context, action, router_id, agent_id): info = {'id': agent_id, 'router_id': router_id} notifier = n_rpc.get_notifier('router') notifier.info(context, action, {'agent': info}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/extensions/vpn_endpoint_groups.py0000664000175000017500000000371500000000000026524 0ustar00zuulzuul00000000000000# (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 from neutron_lib.api.definitions import vpn_endpoint_groups from neutron_lib.api import extensions from neutron_lib.plugins import constants as nconstants from neutron.api.v2 import resource_helper class Vpn_endpoint_groups(extensions.APIExtensionDescriptor): api_definition = vpn_endpoint_groups @classmethod def get_resources(cls): plural_mappings = resource_helper.build_plural_mappings( {}, vpn_endpoint_groups.RESOURCE_ATTRIBUTE_MAP) return resource_helper.build_resource_info( plural_mappings, vpn_endpoint_groups.RESOURCE_ATTRIBUTE_MAP, nconstants.VPN, register_quota=True, translate_name=True) class VPNEndpointGroupsPluginBase(object, metaclass=abc.ABCMeta): @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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/extensions/vpn_flavors.py0000664000175000017500000000220300000000000024750 0ustar00zuulzuul00000000000000# 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.definitions import vpn_flavors 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") class Vpn_flavors(extensions.APIExtensionDescriptor): """Extension class supporting flavors for vpnservices.""" api_definition = vpn_flavors ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/extensions/vpnaas.py0000664000175000017500000001115100000000000023703 0ustar00zuulzuul00000000000000# (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.definitions import vpn from neutron_lib.api import extensions from neutron_lib import exceptions as nexception from neutron_lib.plugins import constants as nconstants from neutron_lib.services import base as service_base from neutron.api.v2 import resource_helper from neutron_vpnaas._i18n import _ class RouteInUseByVPN(nexception.InUse): """Operational error indicating a route is used for VPN. :param destinations: Destination CIDRs that are peers for VPN """ message = _("Route(s) to %(destinations)s are used for VPN") class VPNGatewayNotReady(nexception.BadRequest): message = _("VPN gateway not ready") class VPNGatewayInError(nexception.Conflict): message = _("VPN gateway is in ERROR state. " "Please remove all errored VPN services and try again.") class NoVPNAgentAvailable(nexception.ServiceUnavailable): message = _("No VPN agent available") class Vpnaas(extensions.APIExtensionDescriptor): api_definition = vpn @classmethod def get_resources(cls): special_mappings = {'ikepolicies': 'ikepolicy', 'ipsecpolicies': 'ipsecpolicy'} plural_mappings = resource_helper.build_plural_mappings( special_mappings, vpn.RESOURCE_ATTRIBUTE_MAP) plural_mappings['peer_cidrs'] = 'peer_cidr' return resource_helper.build_resource_info( plural_mappings, vpn.RESOURCE_ATTRIBUTE_MAP, nconstants.VPN, register_quota=True, translate_name=True) @classmethod def get_plugin_interface(cls): return VPNPluginBase class VPNPluginBase(service_base.ServicePluginBase, metaclass=abc.ABCMeta): 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/opts.py0000664000175000017500000000373400000000000021211 0ustar00zuulzuul00000000000000# 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.conf.plugins.ml2.drivers.ovn.ovn_conf 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 import neutron_vpnaas.services.vpn.ovn_agent 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_ovn_agent_opts(): return [ ('vpnagent', neutron_vpnaas.services.vpn.ovn_agent.VPN_AGENT_OPTS), ('ovs', neutron_vpnaas.services.vpn.ovn_agent.OVS_OPTS), ('ovn', neutron.conf.plugins.ml2.drivers.ovn.ovn_conf.ovn_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) ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/policies/0000775000175000017500000000000000000000000021452 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/policies/__init__.py0000664000175000017500000000206700000000000023570 0ustar00zuulzuul00000000000000# 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 itertools from neutron_vpnaas.policies import endpoint_group from neutron_vpnaas.policies import ike_policy from neutron_vpnaas.policies import ipsec_policy from neutron_vpnaas.policies import ipsec_site_connection from neutron_vpnaas.policies import vpnservice def list_rules(): return itertools.chain( endpoint_group.list_rules(), ike_policy.list_rules(), ipsec_policy.list_rules(), ipsec_site_connection.list_rules(), vpnservice.list_rules(), ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/policies/endpoint_group.py0000664000175000017500000000355000000000000025063 0ustar00zuulzuul00000000000000# 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 oslo_policy import policy from neutron_lib import policy as base rules = [ policy.DocumentedRuleDefault( 'create_endpoint_group', base.RULE_ANY, 'Create a VPN endpoint group', [ { 'method': 'POST', 'path': '/vpn/endpoint-groups', }, ] ), policy.DocumentedRuleDefault( 'update_endpoint_group', base.RULE_ADMIN_OR_OWNER, 'Update a VPN endpoint group', [ { 'method': 'PUT', 'path': '/vpn/endpoint-groups/{id}', }, ] ), policy.DocumentedRuleDefault( 'delete_endpoint_group', base.RULE_ADMIN_OR_OWNER, 'Delete a VPN endpoint group', [ { 'method': 'DELETE', 'path': '/vpn/endpoint-groups/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_endpoint_group', base.RULE_ADMIN_OR_OWNER, 'Get VPN endpoint groups', [ { 'method': 'GET', 'path': '/vpn/endpoint-groups', }, { 'method': 'GET', 'path': '/vpn/endpoint-groups/{id}', }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/policies/ike_policy.py0000664000175000017500000000344500000000000024161 0ustar00zuulzuul00000000000000# 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 oslo_policy import policy from neutron_lib import policy as base rules = [ policy.DocumentedRuleDefault( 'create_ikepolicy', base.RULE_ANY, 'Create an IKE policy', [ { 'method': 'POST', 'path': '/vpn/ikepolicies', }, ] ), policy.DocumentedRuleDefault( 'update_ikepolicy', base.RULE_ADMIN_OR_OWNER, 'Update an IKE policy', [ { 'method': 'PUT', 'path': '/vpn/ikepolicies/{id}', }, ] ), policy.DocumentedRuleDefault( 'delete_ikepolicy', base.RULE_ADMIN_OR_OWNER, 'Delete an IKE policy', [ { 'method': 'DELETE', 'path': '/vpn/ikepolicies/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_ikepolicy', base.RULE_ADMIN_OR_OWNER, 'Get IKE policyies', [ { 'method': 'GET', 'path': '/vpn/ikepolicies', }, { 'method': 'GET', 'path': '/vpn/ikepolicies/{id}', }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/policies/ipsec_policy.py0000664000175000017500000000347600000000000024520 0ustar00zuulzuul00000000000000# 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 oslo_policy import policy from neutron_lib import policy as base rules = [ policy.DocumentedRuleDefault( 'create_ipsecpolicy', base.RULE_ANY, 'Create an IPsec policy', [ { 'method': 'POST', 'path': '/vpn/ipsecpolicies', }, ] ), policy.DocumentedRuleDefault( 'update_ipsecpolicy', base.RULE_ADMIN_OR_OWNER, 'Update an IPsec policy', [ { 'method': 'PUT', 'path': '/vpn/ipsecpolicies/{id}', }, ] ), policy.DocumentedRuleDefault( 'delete_ipsecpolicy', base.RULE_ADMIN_OR_OWNER, 'Delete an IPsec policy', [ { 'method': 'DELETE', 'path': '/vpn/ipsecpolicies/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_ipsecpolicy', base.RULE_ADMIN_OR_OWNER, 'Get IPsec policies', [ { 'method': 'GET', 'path': '/vpn/ipsecpolicies', }, { 'method': 'GET', 'path': '/vpn/ipsecpolicies/{id}', }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/policies/ipsec_site_connection.py0000664000175000017500000000366600000000000026405 0ustar00zuulzuul00000000000000# 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 oslo_policy import policy from neutron_lib import policy as base rules = [ policy.DocumentedRuleDefault( 'create_ipsec_site_connection', base.RULE_ANY, 'Create an IPsec site connection', [ { 'method': 'POST', 'path': '/vpn/ipsec-site-connections', }, ] ), policy.DocumentedRuleDefault( 'update_ipsec_site_connection', base.RULE_ADMIN_OR_OWNER, 'Update an IPsec site connection', [ { 'method': 'PUT', 'path': '/vpn/ipsec-site-connections/{id}', }, ] ), policy.DocumentedRuleDefault( 'delete_ipsec_site_connection', base.RULE_ADMIN_OR_OWNER, 'Delete an IPsec site connection', [ { 'method': 'DELETE', 'path': '/vpn/ipsec-site-connections/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_ipsec_site_connection', base.RULE_ADMIN_OR_OWNER, 'Get IPsec site connections', [ { 'method': 'GET', 'path': '/vpn/ipsec-site-connections', }, { 'method': 'GET', 'path': '/vpn/ipsec-site-connections/{id}', }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/policies/vpnservice.py0000664000175000017500000000345000000000000024212 0ustar00zuulzuul00000000000000# 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 oslo_policy import policy from neutron_lib import policy as base rules = [ policy.DocumentedRuleDefault( 'create_vpnservice', base.RULE_ANY, 'Create a VPN service', [ { 'method': 'POST', 'path': '/vpn/vpnservices', }, ] ), policy.DocumentedRuleDefault( 'update_vpnservice', base.RULE_ADMIN_OR_OWNER, 'Update a VPN service', [ { 'method': 'PUT', 'path': '/vpn/vpnservices/{id}', }, ] ), policy.DocumentedRuleDefault( 'delete_vpnservice', base.RULE_ADMIN_OR_OWNER, 'Delete a VPN service', [ { 'method': 'DELETE', 'path': '/vpn/vpnservices/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_vpnservice', base.RULE_ADMIN_OR_OWNER, 'Get VPN services', [ { 'method': 'GET', 'path': '/vpn/vpnservices', }, { 'method': 'GET', 'path': '/vpn/vpnservices/{id}', }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/scheduler/0000775000175000017500000000000000000000000021621 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/scheduler/vpn_agent_scheduler.py0000664000175000017500000001607300000000000026221 0ustar00zuulzuul00000000000000# (c) Copyright 2016 IBM 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 random from neutron.extensions import availability_zone as az_ext from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from oslo_config import cfg from oslo_log import log as logging from neutron_vpnaas.extensions import vpn_agentschedulers LOG = logging.getLogger(__name__) class VPNScheduler(object, metaclass=abc.ABCMeta): @property def l3_plugin(self): return directory.get_plugin(plugin_constants.L3) @abc.abstractmethod def schedule(self, plugin, context, router_id, candidates=None, hints=None): """Schedule the router to an active VPN agent. Schedule the router only if it is not already scheduled. """ pass def _get_unscheduled_routers(self, context, plugin, router_ids=None): """Get the list of routers with VPN services to be scheduled. If router IDs are omitted, look for all unscheduled routers. :param context: the context :param plugin: the core plugin :param router_ids: the list of routers to be checked for scheduling :returns: the list of routers to be scheduled """ unscheduled_router_ids = plugin.get_unscheduled_vpn_routers( context, router_ids=router_ids) if unscheduled_router_ids: return self.l3_plugin.get_routers( context, filters={'id': unscheduled_router_ids}) return [] def _get_routers_can_schedule(self, context, plugin, routers, vpn_agent): """Get the subset of routers whose VPN services can be scheduled on the VPN agent. """ # Assuming that only an active, enabled VPN agent is passed in, # all routers can be scheduled to it return routers def auto_schedule_routers(self, plugin, context, vpn_agent): """Schedule non-hosted routers to a VPN agent. :returns: True if routers have been successfully assigned to the agent """ unscheduled_routers = self._get_unscheduled_routers(context, plugin) target_routers = self._get_routers_can_schedule( context, plugin, unscheduled_routers, vpn_agent) if not target_routers: if unscheduled_routers: LOG.warning('No unscheduled routers compatible with VPN agent ' 'configuration on host %s', vpn_agent['host']) return [] self._bind_routers(context, plugin, target_routers, vpn_agent) return [router['id'] for router in target_routers] def _get_candidates(self, plugin, context, sync_router): """Return VPN agents where a router could be scheduled.""" active_vpn_agents = plugin.get_vpn_agents(context, active=True) if not active_vpn_agents: LOG.warning('No active VPN agents') return active_vpn_agents def _bind_routers(self, context, plugin, routers, vpn_agent): for router in routers: plugin.create_router_to_agent_binding( context, router['id'], vpn_agent['id']) def _schedule_router(self, plugin, context, router_id, candidates=None): current_vpn_agents = plugin.get_vpn_agents_hosting_routers( context, [router_id]) if current_vpn_agents: chosen_agent = current_vpn_agents[0] LOG.debug('VPN service of router %(router_id)s has already ' 'been hosted by VPN agent %(agent_id)s', {'router_id': router_id, 'agent_id': chosen_agent}) return chosen_agent sync_router = self.l3_plugin.get_router(context, router_id) candidates = candidates or self._get_candidates( plugin, context, sync_router) if not candidates: raise vpn_agentschedulers.RouterReschedulingFailed( router_id=router_id) chosen_agent = self._choose_vpn_agent(plugin, context, candidates) if plugin.create_router_to_agent_binding(context, router_id, chosen_agent['id']): return chosen_agent @abc.abstractmethod def _choose_vpn_agent(self, plugin, context, candidates): """Choose an agent from candidates based on a specific policy.""" pass class ChanceScheduler(VPNScheduler): """Randomly allocate an VPN agent for a router.""" def schedule(self, plugin, context, router_id, candidates=None): return self._schedule_router( plugin, context, router_id, candidates=candidates) def _choose_vpn_agent(self, plugin, context, candidates): return random.choice(candidates) class LeastRoutersScheduler(VPNScheduler): """Allocate to an VPN agent with the least number of routers bound.""" def schedule(self, plugin, context, router_id, candidates=None): return self._schedule_router( plugin, context, router_id, candidates=candidates) def _choose_vpn_agent(self, plugin, context, candidates): candidates_dict = {c['id']: c for c in candidates} chosen_agent_id = plugin.get_vpn_agent_with_min_routers( context, candidates_dict.keys()) return candidates_dict[chosen_agent_id] class AZLeastRoutersScheduler(LeastRoutersScheduler): """Availability zone aware scheduler.""" def _get_az_hints(self, router): return (router.get(az_ext.AZ_HINTS) or cfg.CONF.default_availability_zones) def _get_routers_can_schedule(self, context, plugin, routers, vpn_agent): """Overwrite VPNScheduler's method to filter by availability zone.""" target_routers = [] for r in routers: az_hints = self._get_az_hints(r) if not az_hints or vpn_agent['availability_zone'] in az_hints: target_routers.append(r) if not target_routers: return return super()._get_routers_can_schedule( context, plugin, target_routers, vpn_agent) def _get_candidates(self, plugin, context, sync_router): """Overwrite VPNScheduler's method to filter by availability zone.""" all_candidates = super()._get_candidates(plugin, context, sync_router) candidates = [] az_hints = self._get_az_hints(sync_router) for agent in all_candidates: if not az_hints or agent['availability_zone'] in az_hints: candidates.append(agent) return candidates ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/services/0000775000175000017500000000000000000000000021466 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/__init__.py0000664000175000017500000000000000000000000023565 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/0000775000175000017500000000000000000000000022271 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/__init__.py0000664000175000017500000000000000000000000024370 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/agent.py0000664000175000017500000001002600000000000023740 0ustar00zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, Inc. # Copyright 2017, Fujitsu Limited # 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.agent import l3_extension from oslo_config import cfg from oslo_log import log as logging from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn import vpn_service LOG = logging.getLogger(__name__) 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.' 'strongswan_ipsec.StrongSwanDriver, ' '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_extension.L3AgentExtension): """VPNaaS Agent support to be used by Neutron L3 agent.""" def initialize(self, connection, driver_type): LOG.debug("Loading VPNaaS") def consume_api(self, agent_api): LOG.debug("Loading consume_api for VPNaaS") self.agent_api = agent_api def __init__(self, host, conf): LOG.debug("Initializing VPNaaS agent") self.agent_api = None self.conf = conf self.host = host self.service = vpn_service.VPNService(self) self.device_drivers = self.service.load_device_drivers(self.host) def add_router(self, context, data): """Handles router add event""" ri = self.agent_api.get_router_info(data['id']) if ri is not None: for device_driver in self.device_drivers: device_driver.create_router(ri) device_driver.sync(context, [ri.router]) else: LOG.debug("Router %s was concurrently deleted while " "creating VPN for it", data['id']) def update_router(self, context, data): """Handles router update event""" for device_driver in self.device_drivers: device_driver.sync(context, [data]) def delete_router(self, context, data): """Handles router delete event""" for device_driver in self.device_drivers: device_driver.destroy_router(data['id']) def ha_state_change(self, context, data): """Enable the vpn process when router transitioned to master. And disable vpn process for backup router. """ router_id = data['router_id'] state = data['state'] for device_driver in self.device_drivers: if router_id in device_driver.processes: # NOTE(mnaser): We need to update the router object so it has # the new HA state so we can do status updates. device_driver.routers[router_id].ha_state = state process = device_driver.processes[router_id] if state in ('master', 'primary'): process.enable() else: process.disable() def update_network(self, context, data): pass class L3WithVPNaaS(VPNAgent): def __init__(self, conf=None): if conf: self.conf = conf else: self.conf = cfg.CONF super(L3WithVPNaaS, self).__init__( host=self.conf.host, conf=self.conf) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9033496 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/common/0000775000175000017500000000000000000000000023561 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/common/__init__.py0000664000175000017500000000000000000000000025660 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/common/constants.py0000664000175000017500000000322400000000000026150 0ustar00zuulzuul00000000000000# 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_lib import constants # 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, VLAN_ENDPOINT, NETWORK_ENDPOINT, ROUTER_ENDPOINT, ] AGENT_TYPE_VPN = "VPN Agent" DEVICE_OWNER_VPN_ROUTER_GW = constants.DEVICE_OWNER_NETWORK_PREFIX + \ "vpn_router_gateway" DEVICE_OWNER_TRANSIT_NETWORK = constants.DEVICE_OWNER_NETWORK_PREFIX + \ "vpn_namespace" OVN_AGENT_VPN_SB_CFG_KEY = 'neutron:ovn-vpnagent-sb-cfg' OVN_AGENT_VPN_DESC_KEY = 'neutron:description-vpnagent' OVN_AGENT_VPN_ID_KEY = 'neutron:ovn-vpnagent-id' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/common/netns_wrapper.py0000664000175000017500000001346600000000000027034 0ustar00zuulzuul00000000000000# 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 configparser as ConfigParser import errno import os import sys from eventlet.green import subprocess from neutron.common import config from neutron.common import utils from neutron_lib.utils import helpers from oslo_config import cfg from oslo_log import log as logging from oslo_rootwrap import wrapper from neutron_vpnaas._i18n import _ 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 = list(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() _stdout = helpers.safe_decode_utf8(_stdout) _stderr = helpers.safe_decode_utf8(_stderr) 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(): config.register_common_config_options() 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/common/topics.py0000664000175000017500000000131000000000000025427 0ustar00zuulzuul00000000000000# 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' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/0000775000175000017500000000000000000000000025266 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/__init__.py0000664000175000017500000000174000000000000027401 0ustar00zuulzuul00000000000000# 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 class DeviceDriver(object, metaclass=abc.ABCMeta): 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/ipsec.py0000664000175000017500000012736400000000000026760 0ustar00zuulzuul00000000000000# 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 base64 import copy import filecmp import os import re import shutil import socket import sys import eventlet import jinja2 import netaddr from neutron.agent.linux import ip_lib from neutron.agent.linux import utils as agent_utils from neutron_lib.api import validators from neutron_lib import constants from neutron_lib import context from neutron_lib.exceptions import vpn as vpn_exception from neutron_lib.plugins import utils as plugin_utils from neutron_lib import rpc as n_rpc from neutron_lib.utils import file as file_utils from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging import oslo_messaging from oslo_service import loopingcall from oslo_utils import encodeutils from oslo_utils import fileutils 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__) TEMPLATE_PATH = os.path.dirname(os.path.abspath(__file__)) ipsec_opts = [ cfg.StrOpt( 'config_base_dir', default='$state_path/ipsec', help=_('Location to store ipsec server config files')), cfg.IntOpt('ipsec_status_check_interval', default=60, help=_("Interval for checking ipsec status")), cfg.BoolOpt('enable_detailed_logging', default=False, help=_("Enable detail logging for ipsec pluto process. " "If the flag set to True, the detailed logging will " "be written into config_base_dir//log. " "Note: This setting applies to OpenSwan and LibreSwan " "only. StrongSwan logs to syslog.")), ] cfg.CONF.register_opts(ipsec_opts, 'ipsec') openswan_opts = [ cfg.StrOpt( 'ipsec_config_template', default=os.path.join( TEMPLATE_PATH, 'template/openswan/ipsec.conf.template'), help=_('Template file for ipsec configuration')), cfg.StrOpt( 'ipsec_secret_template', default=os.path.join( TEMPLATE_PATH, 'template/openswan/ipsec.secret.template'), help=_('Template file for ipsec secret configuration')) ] cfg.CONF.register_opts(openswan_opts, 'openswan') pluto_opts = [ cfg.IntOpt('shutdown_check_timeout', default=1, help=_('Initial interval in seconds for checking if pluto ' 'daemon is shutdown'), deprecated_group='libreswan'), cfg.IntOpt('shutdown_check_retries', default=5, help=_('The maximum number of retries for checking for ' 'pluto daemon shutdown'), deprecated_group='libreswan'), cfg.FloatOpt('shutdown_check_back_off', default=1.5, help=_('A factor to increase the retry interval for ' 'each retry'), deprecated_group='libreswan'), cfg.BoolOpt('restart_check_config', default=False, help=_('Enable this flag to avoid from unnecessary restart'), deprecated_group='libreswan') ] cfg.CONF.register_opts(pluto_opts, 'pluto') JINJA_ENV = None IPSEC_CONNS = 'ipsec_site_connections' # *Swan supports Base64 encoded binary values as PSKs. In such cases, # a character sequence beginning with 0s is interpreted as Base64 # encoded binary data. # - StrongSwan # - https://wiki.strongswan.org/projects/strongswan/wiki/PskSecret # - LibreSwan # - https://libreswan.org/man/ipsec.secrets.5.html # - https://libreswan.org/man/ipsec_ttodata.3.html # - OpenSwan (no online documents, see manpages sources in the repository) # - https://github.com/xelerance/Openswan PSK_BASE64_PREFIX = '0s' def _get_template(template_file): global JINJA_ENV if not JINJA_ENV: templateLoader = jinja2.FileSystemLoader(searchpath="/") JINJA_ENV = jinja2.Environment(loader=templateLoader, autoescape=True) return JINJA_ENV.get_template(template_file) class BaseSwanProcess(object, metaclass=abc.ABCMeta): """Swan Family Process Manager This class manages start/restart/stop ipsec process. This class create/delete config template """ binary = "ipsec" CONFIG_DIRS = [ 'var/run', 'log', 'etc', 'etc/ipsec.d/aacerts', 'etc/ipsec.d/acerts', 'etc/ipsec.d/cacerts', 'etc/ipsec.d/certs', 'etc/ipsec.d/crls', 'etc/ipsec.d/ocspcerts', 'etc/ipsec.d/policies', 'etc/ipsec.d/private', 'etc/ipsec.d/reqs', 'etc/pki/nssdb/' ] DIALECT_MAP = { "3des": "3des", "aes-128": "aes128", "aes-256": "aes256", "aes-192": "aes192", "sha256": "sha2_256", "sha384": "sha2_384", "sha512": "sha2_512", "group2": "modp1024", "group5": "modp1536", "group14": "modp2048", "group15": "modp3072", "bi-directional": "start", "response-only": "add", "v2": "insist", "v1": "never" } NS_WRAPPER = 'neutron-vpn-netns-wrapper' STATUS_DICT = { 'erouted': constants.ACTIVE, 'unrouted': constants.DOWN } STATUS_RE = r'\d\d\d "([a-f0-9\-]+).* (unrouted|erouted);' STATUS_NOT_RUNNING_RE = 'Command:.*ipsec.*status.*Exit code: [1|3]$' STATUS_IPSEC_SA_ESTABLISHED_RE = ( r'\d{3} #\d+: "([a-f0-9\-]+).*established.*newest IPSEC') STATUS_IPSEC_SA_ESTABLISHED_RE2 = ( r'\d{3} #\d+: "([a-f0-9\-\/x]+).*established.*newest IPSEC') def __init__(self, conf, process_id, vpnservice, namespace): self.conf = conf self.id = process_id self.updated_pending_status = False self.namespace = namespace self.connection_status = {} self.config_dir = os.path.join( self.conf.ipsec.config_base_dir, self.id) self.etc_dir = os.path.join(self.config_dir, 'etc') self.log_dir = os.path.join(self.config_dir, 'log') self.update_vpnservice(vpnservice) self.STATUS_PATTERN = re.compile(self.STATUS_RE) self.STATUS_NOT_RUNNING_PATTERN = re.compile( self.STATUS_NOT_RUNNING_RE) self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN = re.compile( self.STATUS_IPSEC_SA_ESTABLISHED_RE) self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN2 = re.compile( self.STATUS_IPSEC_SA_ESTABLISHED_RE2) self.STATUS_MAP = self.STATUS_DICT def translate_dialect(self): if not self.vpnservice: return for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: self._dialect(ipsec_site_conn, 'initiator') self._dialect(ipsec_site_conn['ikepolicy'], 'ike_version') for key in ['encryption_algorithm', 'auth_algorithm', 'pfs']: self._dialect(ipsec_site_conn['ikepolicy'], key) self._dialect(ipsec_site_conn['ipsecpolicy'], key) if (('local_id' not in ipsec_site_conn.keys()) or (not ipsec_site_conn['local_id'])): ipsec_site_conn['local_id'] = ipsec_site_conn['external_ip'] def base64_encode_psk(self): if not self.vpnservice: return for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: psk = ipsec_site_conn['psk'] encoded_psk = base64.b64encode(encodeutils.safe_encode(psk)) # NOTE(huntxu): base64.b64encode returns an instance of 'bytes' # in Python 3, convert it to a str. For Python 2, after calling # safe_decode, psk is converted into a unicode not containing any # non-ASCII characters so it doesn't matter. psk = encodeutils.safe_decode(encoded_psk, incoming='utf_8') ipsec_site_conn['psk'] = PSK_BASE64_PREFIX + psk def get_ns_wrapper(self): """ Check if we're inside a virtualenv. If we are, then we should respect this and launch wrapper from venv as well. """ if (hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)): ns_wrapper = os.path.join(sys.prefix, "bin/", self.NS_WRAPPER) else: ns_wrapper = self.NS_WRAPPER return ns_wrapper def update_vpnservice(self, vpnservice): self.vpnservice = vpnservice self.translate_dialect() self.base64_encode_psk() def _dialect(self, obj, key): obj[key] = self.DIALECT_MAP.get(obj[key], obj[key]) @abc.abstractmethod def ensure_configs(self): pass def ensure_config_file(self, kind, template, vpnservice, file_mode=None): """Update config file, based on current settings for service.""" config_str = self._gen_config_content(template, vpnservice) config_file_name = self._get_config_filename(kind) if file_mode is None: file_utils.replace_file(config_file_name, config_str) else: file_utils.replace_file(config_file_name, config_str, file_mode) def remove_config(self): """Remove whole config file.""" agent_utils.execute( cmd=["rm", "-rf", self.config_dir], run_as_root=True) def _get_config_filename(self, kind): config_dir = self.etc_dir return os.path.join(config_dir, kind) def ensure_config_dir(self, vpnservice): """Create config directory if it does not exist.""" fileutils.ensure_tree(self.config_dir, 0o755) for subdir in self.CONFIG_DIRS: dir_path = os.path.join(self.config_dir, subdir) fileutils.ensure_tree(dir_path, 0o755) def _gen_config_content(self, template_file, vpnservice): template = _get_template(template_file) return template.render( {'vpnservice': vpnservice, 'state_path': self.conf.state_path}) def _get_rootwrap_config(self): if 'neutron-rootwrap' in cfg.CONF.AGENT.root_helper: rh_tokens = cfg.CONF.AGENT.root_helper.split(' ') if len(rh_tokens) == 3 and os.path.exists(rh_tokens[2]): return rh_tokens[2] return None @abc.abstractmethod def get_status(self): pass @property def status(self): if self.active: return constants.ACTIVE return constants.DOWN @property def active(self): """Check if the process is active or not.""" if not self.namespace: return False try: status = self.get_status() self._extract_and_record_connection_status(status) if not self.connection_status: return False except RuntimeError: return False return True def update(self): """Update Status based on vpnservice configuration.""" # Disable the process if a vpnservice is disabled or it has no # enabled IPSec site connections. vpnservice_has_active_ipsec_site_conns = any( [ipsec_site_conn['admin_state_up'] for ipsec_site_conn in self.vpnservice['ipsec_site_connections']]) if (not self.vpnservice['admin_state_up'] or not vpnservice_has_active_ipsec_site_conns): self.disable() else: self.enable() if plugin_utils.in_pending_status(self.vpnservice['status']): self.updated_pending_status = True self.vpnservice['status'] = self.status for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: if plugin_utils.in_pending_status(ipsec_site_conn['status']): conn_id = ipsec_site_conn['id'] conn_status = self.connection_status.get(conn_id) if not conn_status: continue conn_status['updated_pending_status'] = True ipsec_site_conn['status'] = conn_status['status'] def enable(self): """Enabling the process.""" try: self.ensure_configs() if self.active: self.restart() else: self.start() except RuntimeError: LOG.exception( "Failed to enable vpn process on router %s", self.id) def disable(self): """Disabling the process.""" try: if self.active: self.stop() self.remove_config() except RuntimeError: LOG.exception( "Failed to disable vpn process on router %s", self.id) @abc.abstractmethod def restart(self): """Restart process.""" @abc.abstractmethod def start(self): """Start process.""" @abc.abstractmethod def stop(self): """Stop process.""" def _check_status_line(self, line): """Parse a line and search for status information. If a connection has an established Security Association, it will be considered ACTIVE. Otherwise, even if a status line shows that a connection is active, it will be marked as DOWN-ed. """ # pluto is not running so just exit if self.STATUS_NOT_RUNNING_PATTERN.search(line): self.connection_status = {} raise StopIteration() m = self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN.search(line) if m: connection_id = m.group(1) return connection_id, constants.ACTIVE else: m = self.STATUS_PATTERN.search(line) if m: connection_id = m.group(1) return connection_id, constants.DOWN return None, None def _extract_and_record_connection_status(self, status_output): if not status_output: self.connection_status = {} return for line in status_output.split('\n'): try: conn_id, conn_status = self._check_status_line(line) except StopIteration: break if conn_id: self._record_connection_status(conn_id, conn_status) def _record_connection_status(self, connection_id, status, force_status_update=False): conn_info = self.connection_status.get(connection_id) if not conn_info: self.connection_status[connection_id] = { 'status': status, 'updated_pending_status': force_status_update } else: conn_info['status'] = status if force_status_update: conn_info['updated_pending_status'] = True class OpenSwanProcess(BaseSwanProcess): """OpenSwan Process manager class. This process class uses three commands (1) ipsec pluto: IPsec IKE keying daemon (2) ipsec addconn: Adds new ipsec addconn (3) ipsec whack: control interface for IPSEC keying daemon """ def __init__(self, conf, process_id, vpnservice, namespace): super(OpenSwanProcess, self).__init__(conf, process_id, vpnservice, namespace) self.secrets_file = os.path.join( self.etc_dir, 'ipsec.secrets') self.config_file = os.path.join( self.etc_dir, 'ipsec.conf') self.pid_path = os.path.join( self.config_dir, 'var', 'run', 'pluto') self.pid_file = '%s.pid' % self.pid_path def _execute(self, cmd, check_exit_code=True, extra_ok_codes=None): """Execute command on namespace.""" ip_wrapper = ip_lib.IPWrapper(namespace=self.namespace) return ip_wrapper.netns.execute(cmd, check_exit_code=check_exit_code, extra_ok_codes=extra_ok_codes) def ensure_configs(self): """Generate config files which are needed for OpenSwan. If there is no directory, this function will create dirs. """ self.ensure_config_dir(self.vpnservice) self.ensure_config_file( 'ipsec.conf', self.conf.openswan.ipsec_config_template, self.vpnservice) self.ensure_config_file( 'ipsec.secrets', self.conf.openswan.ipsec_secret_template, self.vpnservice, 0o600) def _copy_configs(self): if not cfg.CONF.pluto.restart_check_config: return config_file_name = self._get_config_filename('ipsec.conf') if os.path.isfile(config_file_name): shutil.copyfile(config_file_name, config_file_name + '.old') config_file_name = self._get_config_filename('ipsec.secrets') if os.path.isfile(config_file_name): shutil.copyfile(config_file_name, config_file_name + '.old') os.chmod(config_file_name + '.old', 0o600) def _process_running(self): """Checks if process is still running.""" # If no PID file, we assume the process is not running. if not os.path.exists(self.pid_file): return False try: # We take an ask-forgiveness-not-permission approach and rely # on throwing to tell us something. If the pid file exists, # delve into the process information and check if it matches # our expected command line. with open(self.pid_file, 'r') as f: pid = f.readline().strip() with open('/proc/%s/cmdline' % pid) as cmd_line_file: cmd_line = cmd_line_file.readline() if self.pid_path in cmd_line and 'pluto' in cmd_line: # Okay the process is probably a pluto process # and it contains the pid_path in the command # line... could be a race. Log to error and return # that it is *NOT* okay to clean up files. We are # logging to error instead of debug because it # indicates something bad has happened and this is # valuable information for figuring it out. LOG.error('Process %(pid)s exists with command ' 'line %(cmd_line)s.', {'pid': pid, 'cmd_line': cmd_line}) return True except IOError as e: # This is logged as "info" instead of error because it simply # means that we couldn't find the files to check on them. LOG.info('Unable to find control files on startup for ' 'router %(router)s: %(msg)s', {'router': self.id, 'msg': e}) return False def _cleanup_control_files(self): try: ctl_file = '%s.ctl' % self.pid_path LOG.debug('Removing %(pidfile)s and %(ctlfile)s', {'pidfile': self.pid_file, 'ctlfile': ctl_file}) if os.path.exists(self.pid_file): os.remove(self.pid_file) if os.path.exists(ctl_file): os.remove(ctl_file) except OSError as e: LOG.error('Unable to remove pluto control ' 'files for router %(router)s. %(msg)s', {'router': self.id, 'msg': e}) def get_status(self): return self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--status'], extra_ok_codes=[1, 3]) def _config_changed(self): secrets_file = os.path.join( self.etc_dir, 'ipsec.secrets') config_file = os.path.join( self.etc_dir, 'ipsec.conf') if not os.path.isfile(secrets_file + '.old'): return True if not os.path.isfile(config_file + '.old'): return True if not filecmp.cmp(secrets_file, secrets_file + '.old'): return True if not filecmp.cmp(config_file, config_file + '.old'): return True return False def restart(self): """Restart the process.""" if cfg.CONF.pluto.restart_check_config and not self._config_changed(): return # stop() followed immediately by a start() runs the risk that the # current pluto daemon has not had a chance to shutdown. We check # the current process information to see if the daemon is still # running and if so, wait a short interval and retry. self.stop() wait_interval = cfg.CONF.pluto.shutdown_check_timeout for i in range(cfg.CONF.pluto.shutdown_check_retries): if not self._process_running(): self._cleanup_control_files() break eventlet.sleep(wait_interval) wait_interval *= cfg.CONF.pluto.shutdown_check_back_off else: LOG.warning('Server appears to still be running, restart ' 'of router %s may fail', self.id) self.start() return def _resolve_fqdn(self, fqdn): # The first addrinfo member from the list returned by # socket.getaddrinfo is used for the address resolution. # The code doesn't filter for ipv4 or ipv6 address. try: addrinfo = socket.getaddrinfo(fqdn, None)[0] return addrinfo[-1][0] except socket.gaierror: LOG.exception("Peer address %s cannot be resolved", fqdn) def _get_nexthop(self, address, connection_id): # check if address is an ip address or fqdn invalid_ip_address = validators.validate_ip_address(address) if invalid_ip_address: ip_addr = self._resolve_fqdn(address) if not ip_addr: self._record_connection_status(connection_id, constants.ERROR, force_status_update=True) raise vpn_exception.VPNPeerAddressNotResolved( peer_address=address) else: ip_addr = address routes = self._execute(['ip', 'route', 'get', ip_addr]) if routes.find('via') >= 0: return routes.split(' ')[2] return address def _virtual_privates(self, vpnservice): """Returns line of virtual_privates. virtual_private contains the networks that are allowed as subnet for the remote client. """ virtual_privates = [] nets = [] for ipsec_site_conn in vpnservice['ipsec_site_connections']: nets += ipsec_site_conn['local_cidrs'] nets += ipsec_site_conn['peer_cidrs'] for net in nets: version = netaddr.IPNetwork(net).version virtual_privates.append('%%v%s:%s' % (version, net)) virtual_privates.sort() return ','.join(virtual_privates) def _gen_config_content(self, template_file, vpnservice): template = _get_template(template_file) virtual_privates = self._virtual_privates(vpnservice) return template.render( {'vpnservice': vpnservice, 'virtual_privates': virtual_privates}) def start_pluto(self): cmd = [self.binary, 'pluto', '--ctlbase', self.pid_path, '--ipsecdir', self.etc_dir, '--use-netkey', '--uniqueids', '--nat_traversal', '--secretsfile', self.secrets_file] if self.conf.ipsec.enable_detailed_logging: cmd += ['--perpeerlog', '--perpeerlogbase', self.log_dir] self._execute(cmd) def add_ipsec_connection(self, nexthop, conn_id): self._execute([self.binary, 'addconn', '--ctlbase', '%s.ctl' % self.pid_path, '--defaultroutenexthop', nexthop, '--config', self.config_file, conn_id ]) def start_whack_listening(self): #TODO(nati) fix this when openswan is fixed #Due to openswan bug, this command always exit with 3 self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--listen' ], check_exit_code=False) def shutdown_whack(self): self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--shutdown' ]) def initiate_connection(self, conn_name): self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--name', conn_name, '--asynchronous', '--initiate' ]) def terminate_connection(self, conn_name): self._execute([self.binary, 'whack', '--ctlbase', self.pid_path, '--name', conn_name, '--terminate' ]) def start(self): """Start the process. Note: if there is not namespace yet, just do nothing, and wait next event. """ if not self.namespace: return # NOTE: The restart operation calls the parent's start() instead of # this one to avoid having to special case the startup file check. # If anything is added to this method that needs to run whenever # a restart occurs, it should be either added to the restart() # override or things refactored to special-case start() when # called from restart(). # If, by any reason, ctl and pid files weren't cleaned up, pluto # won't be able to rewrite them and will fail to start. So we check # to see if the process is running and if not, attempt a cleanup. # In either case we fall through to allow the pluto process to # start or fail in the usual way. if not self._process_running(): self._cleanup_control_files() #start pluto IKE keying daemon self.start_pluto() #add connections for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: # Don't add a connection if its admin state is down if not ipsec_site_conn['admin_state_up']: continue nexthop = self._get_nexthop(ipsec_site_conn['peer_address'], ipsec_site_conn['id']) self.add_ipsec_connection(nexthop, ipsec_site_conn['id']) #start whack ipsec keying daemon self.start_whack_listening() for ipsec_site_conn in self.vpnservice['ipsec_site_connections']: if (not ipsec_site_conn['initiator'] == 'start' or not ipsec_site_conn['admin_state_up']): continue #initiate ipsec connection self.initiate_connection(ipsec_site_conn['id']) self._copy_configs() def get_established_connections(self): connections = [] status_output = self.get_status() if not status_output: return connections for line in status_output.split('\n'): if self.STATUS_NOT_RUNNING_PATTERN.search(line): return connections m = self.STATUS_IPSEC_SA_ESTABLISHED_PATTERN2.search(line) if m: connection = m.group(1) if connection in connections: continue connections.append(connection) return connections def disconnect(self): if not self.namespace: return if not self.vpnservice: return connections = self.get_established_connections() for conn_name in connections: self.terminate_connection(conn_name) def stop(self): #Stop process using whack #Note this will also stop pluto self.disconnect() self.shutdown_whack() self.connection_status = {} class IPsecVpnDriverApi(object): """IPSecVpnDriver RPC api.""" @log_helpers.log_method_call def __init__(self, topic): target = oslo_messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) @log_helpers.log_method_call def get_vpn_services_on_host(self, context, host): """Get list of vpnservices. The vpnservices including related ipsec_site_connection, ikepolicy and ipsecpolicy on this host """ cctxt = self.client.prepare() return cctxt.call(context, 'get_vpn_services_on_host', host=host) @log_helpers.log_method_call def update_status(self, context, status): """Update local status. This method call updates status attribute of VPNServices. """ cctxt = self.client.prepare() return cctxt.call(context, 'update_status', status=status) class IPsecDriver(device_drivers.DeviceDriver, metaclass=abc.ABCMeta): """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) Replace vpn_service with config arg, once all driver # implementations no longer need vpn_service. self.conf = vpn_service.conf self.host = host self.conn = n_rpc.Connection() self.context = context.get_admin_context_without_session() self.topic = topics.IPSEC_AGENT_TOPIC node_topic = '%s.%s' % (self.topic, self.host) self.processes = {} self.routers = {} self.process_status_cache = {} self.endpoints = [self] self.conn.create_consumer(node_topic, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = IPsecVpnDriverApi(topics.IPSEC_DRIVER_TOPIC) self.process_status_cache_check = loopingcall.FixedIntervalLoopingCall( self.report_status, self.context) self.process_status_cache_check.start( interval=self.conf.ipsec.ipsec_status_check_interval) def get_namespace(self, router_id): """Get namespace of router. :router_id: router_id :returns: namespace string. Note: If the router is a DVR, then the SNAT namespace will be provided. If the router does not exist, return None. """ router = self.routers.get(router_id) if not router: return # For DVR, use SNAT namespace # TODO(pcm): Use router object method to tell if DVR, when available if router.router['distributed']: return router.snat_namespace.name return router.ns_name def get_router_based_iptables_manager(self, router): """Returns router based iptables manager In DVR routers the IPsec VPN service should run inside the snat namespace. So the iptables manager used for snat namespace is different from the iptables manager used for the qr namespace in a non dvr based router. This function will check the router type and then will return the right iptables manager. If DVR enabled router it will return the snat_iptables_manager otherwise it will return the legacy iptables_manager. """ # TODO(pcm): Use router object method to tell if DVR, when available if router.router['distributed']: return router.snat_iptables_manager return router.iptables_manager def add_nat_rule(self, router_id, chain, rule, top=False): """Add nat rule in namespace. :param router_id: router_id :param chain: a string of chain name :param rule: a string of rule :param top: if top is true, the rule will be placed on the top of chain Note if there is no router, this method does nothing """ router = self.routers.get(router_id) if not router: return iptables_manager = self.get_router_based_iptables_manager(router) iptables_manager.ipv4['nat'].add_rule(chain, rule, top=top) def remove_nat_rule(self, router_id, chain, rule, top=False): """Remove nat rule in namespace. :param router_id: router_id :param chain: a string of chain name :param rule: a string of rule :param top: unused needed to have same argument with add_nat_rule """ router = self.routers.get(router_id) if not router: return iptables_manager = self.get_router_based_iptables_manager(router) iptables_manager.ipv4['nat'].remove_rule(chain, rule, top=top) def iptables_apply(self, router_id): """Apply IPtables. :param router_id: router_id This method do nothing if there is no router """ router = self.routers.get(router_id) if not router: return iptables_manager = self.get_router_based_iptables_manager(router) iptables_manager.apply() def _update_nat(self, vpnservice, func): """Setting up nat rule in iptables. We need to setup nat rule for ipsec packet. :param vpnservice: vpnservices :param func: self.add_nat_rule or self.remove_nat_rule """ router_id = vpnservice['router_id'] for ipsec_site_connection in vpnservice['ipsec_site_connections']: for local_cidr in ipsec_site_connection['local_cidrs']: # This ipsec rule is not needed for ipv6. if netaddr.IPNetwork(local_cidr).version == 6: continue for peer_cidr in ipsec_site_connection['peer_cidrs']: func(router_id, 'POSTROUTING', '-s %s -d %s -m policy ' '--dir out --pol ipsec ' '-j ACCEPT ' % (local_cidr, peer_cidr), top=True) self.iptables_apply(router_id) @log_helpers.log_method_call def vpnservice_updated(self, context, **kwargs): """Vpnservice updated rpc handler VPN Service Driver will call this method when vpnservices updated. Then this method start sync with server. """ router = kwargs.get('router', None) self.sync(context, [router] if router else []) @abc.abstractmethod def create_process(self, process_id, vpnservice, namespace): pass def ensure_process(self, process_id, vpnservice=None): """Ensuring process. If the process doesn't exist, it will create process and store it in self.process """ process = self.processes.get(process_id) if not process or not process.namespace: namespace = self.get_namespace(process_id) process = self.create_process( process_id, vpnservice, namespace) self.processes[process_id] = process elif vpnservice: process.update_vpnservice(vpnservice) return process def create_router(self, router): """Handling create router event. Agent calls this method, when the process namespace is ready. Note: process_id == router_id == vpnservice_id """ process_id = router.router_id self.routers[process_id] = router if process_id in self.processes: # In case of vpnservice is created # before router's namespace process = self.processes[process_id] self._update_nat(process.vpnservice, self.add_nat_rule) # Don't run ipsec process for backup HA router if router.router['ha'] and router.ha_state == 'backup': return process.enable() def destroy_process(self, process_id): """Destroy process. Disable the process, remove the nat rule, and remove the process manager for the processes that no longer are running vpn service. """ if process_id in self.processes: process = self.processes[process_id] process.disable() vpnservice = process.vpnservice if vpnservice: self._update_nat(vpnservice, self.remove_nat_rule) del self.processes[process_id] def destroy_router(self, process_id): """Handling destroy_router event. Agent calls this method, when the process namespace is deleted. """ self.destroy_process(process_id) if process_id in self.routers: del self.routers[process_id] def get_process_status_cache(self, process): if not self.process_status_cache.get(process.id): self.process_status_cache[process.id] = { 'status': None, 'id': process.vpnservice['id'], 'updated_pending_status': False, 'ipsec_site_connections': {}} return self.process_status_cache[process.id] def is_status_updated(self, process, previous_status): if process.updated_pending_status: return True if process.status != previous_status['status']: return True if (process.connection_status != previous_status['ipsec_site_connections']): return True def unset_updated_pending_status(self, process): process.updated_pending_status = False for connection_status in process.connection_status.values(): connection_status['updated_pending_status'] = False def copy_process_status(self, process): return { 'id': process.vpnservice['id'], 'status': process.status, 'updated_pending_status': process.updated_pending_status, 'ipsec_site_connections': copy.deepcopy(process.connection_status) } def update_downed_connections(self, process_id, new_status): """Update info to be reported, if connections just went down. If there is no longer any information for a connection, because it has been removed (e.g. due to an admin down of VPN service or IPSec connection), but there was previous status information for the connection, mark the connection as down for reporting purposes. """ if process_id in self.process_status_cache: for conn in self.process_status_cache[process_id][IPSEC_CONNS]: if conn not in new_status[IPSEC_CONNS]: new_status[IPSEC_CONNS][conn] = { 'status': constants.DOWN, 'updated_pending_status': True } def should_be_reported(self, context, process): if (context.is_admin or process.vpnservice["tenant_id"] == context.tenant_id): return True @log_helpers.log_method_call def report_status(self, context): status_changed_vpn_services = [] for process_id, process in list(self.processes.items()): # NOTE(mnaser): It's not necessary to check status for processes # of a backup L3 agent router = self.routers.get(process_id) if router and router.router['ha'] and router.ha_state == 'backup': LOG.debug("%s router in backup state, skipping", process_id) continue if not self.should_be_reported(context, process): continue previous_status = self.get_process_status_cache(process) if self.is_status_updated(process, previous_status): new_status = self.copy_process_status(process) self.update_downed_connections(process.id, new_status) status_changed_vpn_services.append(new_status) self.process_status_cache[process.id] = ( self.copy_process_status(process)) # We need unset updated_pending status after it # is reported to the server side self.unset_updated_pending_status(process) if status_changed_vpn_services: self.agent_rpc.update_status( context, status_changed_vpn_services) @log_helpers.log_method_call @lockutils.synchronized('vpn-agent', 'neutron-') def sync(self, context, routers): """Sync status with server side. :param context: context object for RPC call :param routers: Router objects which is created in this sync event There could be many failure cases should be considered including the followings. 1) Agent class restarted 2) Failure on process creation 3) VpnService is deleted during agent down 4) RPC failure In order to handle these failure cases, This driver takes simple sync strategies. """ vpnservices = self.agent_rpc.get_vpn_services_on_host( context, self.host) router_ids = [vpnservice['router_id'] for vpnservice in vpnservices] sync_router_ids = [router['id'] for router in routers] self._sync_vpn_processes(vpnservices, sync_router_ids) self._delete_vpn_processes(sync_router_ids, router_ids) self._cleanup_stale_vpn_processes(router_ids) self.report_status(context) def _sync_vpn_processes(self, vpnservices, sync_router_ids): # Ensure the ipsec process is enabled only for # - the vpn services which are not yet in self.processes # - vpn services whose router id is in 'sync_router_ids' for vpnservice in vpnservices: if vpnservice['router_id'] not in self.processes or ( vpnservice['router_id'] in sync_router_ids): process = self.ensure_process(vpnservice['router_id'], vpnservice=vpnservice) self._update_nat(vpnservice, self.add_nat_rule) router = self.routers.get(vpnservice['router_id']) if not router: continue # For HA router, spawn vpn process on master router # and terminate vpn process on backup router if router.router['ha'] and router.ha_state == 'backup': process.disable() else: process.update() def _delete_vpn_processes(self, sync_router_ids, vpn_router_ids): # Delete any IPSec processes that are # associated with routers, but are not running the VPN service. for process_id in sync_router_ids: if process_id not in vpn_router_ids: self.destroy_process(process_id) def _cleanup_stale_vpn_processes(self, vpn_router_ids): # Delete any IPSec processes running # VPN that do not have an associated router. process_ids = [pid for pid in self.processes if pid not in vpn_router_ids] for process_id in process_ids: self.destroy_process(process_id) class OpenSwanDriver(IPsecDriver): def create_process(self, process_id, vpnservice, namespace): return OpenSwanProcess( self.conf, process_id, vpnservice, namespace) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/libreswan_ipsec.py0000664000175000017500000001277000000000000031020 0ustar00zuulzuul00000000000000# 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.agent.linux import ip_lib 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. """ # pylint: disable=useless-super-delegation def __init__(self, conf, process_id, vpnservice, namespace): self._rootwrap_cfg = self._get_rootwrap_config() super(LibreSwanProcess, self).__init__(conf, process_id, vpnservice, namespace) def _ipsec_execute(self, cmd, check_exit_code=True, extra_ok_codes=None): """Execute ipsec 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) mount_paths = {'/etc': '%s/etc' % self.config_dir, '/var/run': '%s/var/run' % self.config_dir} mount_paths_str = ','.join( "%s:%s" % (source, target) for source, target in mount_paths.items()) ns_wrapper = self.get_ns_wrapper() return ip_wrapper.netns.execute( [ns_wrapper, '--mount_paths=%s' % mount_paths_str, ('--rootwrap_config=%s' % self._rootwrap_cfg if self._rootwrap_cfg else ''), '--cmd=%s,%s' % (self.binary, ','.join(cmd))], check_exit_code=check_exit_code, extra_ok_codes=extra_ok_codes) def _ensure_needed_files(self): # addconn reads from /etc/hosts and /etc/resolv.conf. As /etc would be # bind-mounted, create these two empty files in the target directory. with open('%s/etc/hosts' % self.config_dir, 'a'): pass with open('%s/etc/resolv.conf' % self.config_dir, 'a'): pass 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): self._execute(['rm', '-f', 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]) # Libreswan needs to write logs to this directory. self._execute(['chown', '--from=%s' % os.getuid(), 'root:root', self.log_dir]) self._ensure_needed_files() # Load the ipsec kernel module if not loaded self._ipsec_execute(['_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._ipsec_execute(['checknss']) except RuntimeError: self._ipsec_execute(['initnss']) def get_status(self): return self._ipsec_execute(['whack', '--status'], extra_ok_codes=[1, 3]) def start_pluto(self): cmd = ['pluto', '--use-netkey', '--uniqueids'] if self.conf.ipsec.enable_detailed_logging: cmd += ['--perpeerlog', '--perpeerlogbase', self.log_dir] self._ipsec_execute(cmd) def add_ipsec_connection(self, nexthop, conn_id): # Connections will be automatically added as auto=start/add for # initiator=bi-directional/response-only specified in the config. pass def start_whack_listening(self): # NOTE(huntxu): This is a workaround for with a weak (len<8) secret, # "ipsec whack --listen" will exit with 3. self._ipsec_execute(['whack', '--listen'], extra_ok_codes=[3]) def shutdown_whack(self): self._ipsec_execute(['whack', '--shutdown']) def initiate_connection(self, conn_name): self._ipsec_execute( ['whack', '--name', conn_name, '--asynchronous', '--initiate']) def terminate_connection(self, conn_name): self._ipsec_execute(['whack', '--name', conn_name, '--terminate']) class LibreSwanDriver(ipsec.IPsecDriver): def create_process(self, process_id, vpnservice, namespace): return LibreSwanProcess( self.conf, process_id, vpnservice, namespace) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/ovn_ipsec.py0000664000175000017500000003336300000000000027635 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Yi Jing Zhu, IBM. # Copyright (c) 2023 SysEleven GmbH # 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 neutron.agent.common import utils as agent_common_utils from neutron.agent.linux import ip_lib from neutron_lib import constants as lib_constants from neutron_lib import context as nctx from oslo_concurrency import lockutils from oslo_log import log as logging from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn.device_drivers import ipsec from neutron_vpnaas.services.vpn.device_drivers import libreswan_ipsec from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec PORT_PREFIX_INTERNAL = 'vr' PORT_PREFIX_EXTERNAL = 'vg' PORT_PREFIXES = { 'internal': PORT_PREFIX_INTERNAL, 'external': PORT_PREFIX_EXTERNAL, } LOG = logging.getLogger(__name__) class DeviceManager(object): """Device Manager for ports in qvpn-xx namespace. It is a veth pair, one side in qvpn and the other side is attached to ovs. """ OVN_NS_PREFIX = "qvpn-" def __init__(self, conf, host, plugin, context): self.conf = conf self.host = host self.plugin = plugin self.context = context self.driver = agent_common_utils.load_interface_driver(conf) def get_interface_name(self, port, ptype): suffix = port['id'] return (PORT_PREFIXES[ptype] + suffix)[:self.driver.DEV_NAME_LEN] def get_namespace_name(self, process_id): return self.OVN_NS_PREFIX + process_id def get_existing_process_ids(self): """Return the process IDs derived from the existing VPN namespaces.""" return [ns[len(self.OVN_NS_PREFIX):] for ns in ip_lib.list_network_namespaces() if ns.startswith(self.OVN_NS_PREFIX)] def set_default_route(self, namespace, subnet, device_name): device = ip_lib.IPDevice(device_name, namespace=namespace) gateway = device.route.get_gateway(ip_version=subnet['ip_version']) if gateway: gateway = gateway.get('gateway') new_gateway = subnet['gateway_ip'] if gateway == new_gateway: return device.route.add_gateway(subnet['gateway_ip']) def add_routes(self, namespace, cidrs, via): device = ip_lib.IPDevice(None, namespace=namespace) for cidr in cidrs: device.route.add_route(cidr, via=via, metric=100, proto='static') def delete_routes(self, namespace, cidrs, via): device = ip_lib.IPDevice(None, namespace=namespace) for cidr in cidrs: device.route.delete_route(cidr, via=via, metric=100, proto='static') def list_routes(self, namespace, via=None): device = ip_lib.IPDevice(None, namespace=namespace) return device.route.list_routes( lib_constants.IP_VERSION_4, proto='static', via=via) def del_static_routes(self, namespace): device = ip_lib.IPDevice(None, namespace=namespace) routes = device.route.list_routes( lib_constants.IP_VERSION_4, proto='static') for r in routes: device.route.delete_route(r['cidr'], via=r['via']) def _del_port(self, process_id, ptype): namespace = self.get_namespace_name(process_id) prefix = PORT_PREFIXES[ptype] device = ip_lib.IPDevice(None, namespace=namespace) ports = device.addr.list() for p in ports: if not p['name'].startswith(prefix): continue interface_name = p['name'] self.driver.unplug(interface_name, namespace=namespace) def del_internal_port(self, process_id): self._del_port(process_id, 'internal') def del_external_port(self, process_id): self._del_port(process_id, 'external') def setup_external(self, process_id, network_details): network = network_details["external_network"] vpn_port = network_details['gw_port'] ns_name = self.get_namespace_name(process_id) interface_name = self.get_interface_name(vpn_port, 'external') if not ip_lib.ensure_device_is_ready(interface_name, namespace=ns_name): try: self.driver.plug(network['id'], vpn_port['id'], interface_name, vpn_port['mac_address'], namespace=ns_name, mtu=network.get('mtu'), prefix=PORT_PREFIX_EXTERNAL) except Exception: LOG.exception('plug external port %s failed', vpn_port) return None ip_cidrs = [] subnets = [] for fixed_ip in vpn_port['fixed_ips']: subnet_id = fixed_ip['subnet_id'] subnet = self.plugin.get_subnet_info(subnet_id) net = netaddr.IPNetwork(subnet['cidr']) ip_cidr = '%s/%s' % (fixed_ip['ip_address'], net.prefixlen) ip_cidrs.append(ip_cidr) subnets.append(subnet) self.driver.init_l3(interface_name, ip_cidrs, namespace=ns_name) for subnet in subnets: self.set_default_route(ns_name, subnet, interface_name) return interface_name def setup_internal(self, process_id, network_details): vpn_port = network_details["transit_port"] ns_name = self.get_namespace_name(process_id) interface_name = self.get_interface_name(vpn_port, 'internal') if not ip_lib.ensure_device_is_ready(interface_name, namespace=ns_name): try: self.driver.plug('', vpn_port['id'], interface_name, vpn_port['mac_address'], namespace=ns_name, prefix=PORT_PREFIX_INTERNAL) except Exception: LOG.exception('plug internal port %s failed', vpn_port['id']) return None ip_cidrs = [] for fixed_ip in vpn_port['fixed_ips']: ip_cidr = '%s/%s' % (fixed_ip['ip_address'], 28) ip_cidrs.append(ip_cidr) self.driver.init_l3(interface_name, ip_cidrs, namespace=ns_name) return interface_name class NamespaceManager(object): def __init__(self, use_ipv6=False): self.ip_wrapper_root = ip_lib.IPWrapper() self.use_ipv6 = use_ipv6 def exists(self, name): return ip_lib.network_namespace_exists(name) def create(self, name): ip_wrapper = self.ip_wrapper_root.ensure_namespace(name) cmd = ['sysctl', '-w', 'net.ipv4.ip_forward=1'] ip_wrapper.netns.execute(cmd) if self.use_ipv6: cmd = ['sysctl', '-w', 'net.ipv6.conf.all.forwarding=1'] ip_wrapper.netns.execute(cmd) def delete(self, name): try: self.ip_wrapper_root.netns.delete(name) except RuntimeError: msg = 'Failed trying to delete namespace: %s' LOG.exception(msg, name) class OvnOpenSwanProcess(ipsec.OpenSwanProcess): pass class OvnStrongSwanProcess(strongswan_ipsec.StrongSwanProcess): pass class OvnLibreSwanProcess(libreswan_ipsec.LibreSwanProcess): pass class IPsecOvnDriverApi(ipsec.IPsecVpnDriverApi): def __init__(self, topic): super().__init__(topic) self.admin_ctx = nctx.get_admin_context_without_session() def get_vpn_transit_network_details(self, router_id): cctxt = self.client.prepare() return cctxt.call(self.admin_ctx, 'get_vpn_transit_network_details', router_id=router_id) def get_subnet_info(self, subnet_id): cctxt = self.client.prepare() return cctxt.call(self.admin_ctx, 'get_subnet_info', subnet_id=subnet_id) class OvnIPsecDriver(ipsec.IPsecDriver): def __init__(self, vpn_service, host): self.nsmgr = NamespaceManager() super().__init__(vpn_service, host) self.agent_rpc = IPsecOvnDriverApi(topics.IPSEC_DRIVER_TOPIC) self.devmgr = DeviceManager(self.conf, self.host, self.agent_rpc, self.context) get_router_based_iptables_manager = None def get_namespace(self, router_id): """Get namespace for VPN services of router. :router_id: router_id :returns: namespace string. """ return self.devmgr.get_namespace_name(router_id) def _cleanup_namespace(self, router_id): ns_name = self.devmgr.get_namespace_name(router_id) if not self.nsmgr.exists(ns_name): return self.devmgr.del_internal_port(router_id) self.devmgr.del_external_port(router_id) self.nsmgr.delete(ns_name) def _ensure_namespace(self, router_id, network_details): ns_name = self.get_namespace(router_id) if not self.nsmgr.exists(ns_name): self.nsmgr.create(ns_name) # set up vpn external port on provider net self.devmgr.setup_external(router_id, network_details) # set up vpn internal port on transit net self.devmgr.setup_internal(router_id, network_details) return ns_name def destroy_process(self, process_id): LOG.info('process %s is destroyed', process_id) namespace = self.devmgr.get_namespace_name(process_id) # If the namespace exists but the process_id is not in the table # there may be an active swan process from a previous run of the agent # which does not have a process object in memory. # To be able to clean it up we need to create a dummy process object # here (without a vpnservice), so that destroy_process will stop # the swan. if self.nsmgr.exists(namespace) and process_id not in self.processes: self.ensure_process(process_id) super().destroy_process(process_id) self._cleanup_namespace(process_id) def create_router(self, router): pass def destroy_router(self, process_id): pass def _update_nat(self, vpnservice, func): pass def _update_route(self, vpnservice, network_details): router_id = vpnservice['router_id'] gateway_ip = network_details['transit_gateway_ip'] namespace = self.devmgr.get_namespace_name(router_id) old_local_cidrs = set() for route in self.devmgr.list_routes(namespace, via=gateway_ip): old_local_cidrs.add(route['cidr']) new_local_cidrs = set() for ipsec_site_conn in vpnservice['ipsec_site_connections']: new_local_cidrs.update(ipsec_site_conn['local_cidrs']) self.devmgr.delete_routes(namespace, old_local_cidrs - new_local_cidrs, gateway_ip) self.devmgr.add_routes(namespace, new_local_cidrs - old_local_cidrs, gateway_ip) def _sync_vpn_processes(self, vpnservices, sync_router_ids): # Ensure the ipsec process is enabled only for # - the vpn services which are not yet in self.processes # - vpn services whose router id is in 'sync_router_ids' for vpnservice in vpnservices: router_id = vpnservice['router_id'] if router_id not in self.processes or router_id in sync_router_ids: net_details = self.agent_rpc.get_vpn_transit_network_details( router_id) self._ensure_namespace(router_id, net_details) self._update_route(vpnservice, net_details) process = self.ensure_process(router_id, vpnservice=vpnservice) process.update() def _cleanup_stale_vpn_processes(self, vpn_router_ids): super()._cleanup_stale_vpn_processes(vpn_router_ids) # Look for additional namespaces on this node that we don't know # and that should be deleted for router_id in self.devmgr.get_existing_process_ids(): if router_id not in vpn_router_ids: self.destroy_process(router_id) @lockutils.synchronized('vpn-agent', 'neutron-') def vpnservice_removed_from_agent(self, context, router_id): # must run under the same lock as sync() self.destroy_process(router_id) def vpnservice_added_to_agent(self, context, router_ids): routers = [{'id': router_id} for router_id in router_ids] self.sync(context, routers) class OvnStrongSwanDriver(OvnIPsecDriver): def create_process(self, process_id, vpnservice, namespace): return OvnStrongSwanProcess( self.conf, process_id, vpnservice, namespace) class OvnOpenSwanDriver(OvnIPsecDriver): def create_process(self, process_id, vpnservice, namespace): return OvnOpenSwanProcess( self.conf, process_id, vpnservice, namespace) class OvnLibreSwanDriver(OvnIPsecDriver): def create_process(self, process_id, vpnservice, namespace): return OvnLibreSwanProcess( self.conf, process_id, vpnservice, namespace) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py0000664000175000017500000001703500000000000031236 0ustar00zuulzuul00000000000000# 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') 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 = r'([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() self._rootwrap_cfg = self._get_rootwrap_config() 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) ns_wrapper = self.get_ns_wrapper() 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), ('--rootwrap_config=%s' % self._rootwrap_cfg if self._rootwrap_cfg else ''), '--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_secrets() self.reload() def reload_secrets(self): """Reload the ipsec.secrets file. Flushes and rereads all secrets defined in ipsec.secrets. This needs to be done each time when a new site connection is associated with a VPN service which already hosts a site connection - 'ipsec reload' does not reload the secrets and new connections will not authenticate properly. """ self._execute([self.binary, 'rereadsecrets']) 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/0000775000175000017500000000000000000000000027101 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/0000775000175000017500000000000000000000000030733 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.templa0000664000175000017500000000744200000000000034175 0ustar00zuulzuul00000000000000# Configuration for {{vpnservice.id}} config setup nat_traversal=yes virtual_private={{virtual_privates}} conn %default 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}} {% if ipsec_site_connection.ikepolicy.phase1_negotiation_mode == "aggressive" -%} aggressive=yes {% endif -%} # [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] phase2={{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 -%} ././@PaxHeader0000000000000000000000000000021000000000000011446 xustar0000000000000000114 path=neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.secret.template 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.secret.temp0000664000175000017500000000034200000000000034210 0ustar00zuulzuul00000000000000# Configuration for {{vpnservice.id}} {% 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 %} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/0000775000175000017500000000000000000000000031306 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000021000000000000011446 xustar0000000000000000114 path=neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.template 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.temp0000664000175000017500000000326000000000000034225 0ustar00zuulzuul00000000000000# Configuration for {{vpnservice.id}} config setup conn %default 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.ikepolicy.phase1_negotiation_mode == "aggressive" %} aggressive=yes {%- endif %} {%- 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 %} ././@PaxHeader0000000000000000000000000000021200000000000011450 xustar0000000000000000116 path=neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.secret.template 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.secret.te0000664000175000017500000000034000000000000034224 0ustar00zuulzuul00000000000000# Configuration for {{vpnservice.id}}{% 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 %} ././@PaxHeader0000000000000000000000000000021500000000000011453 xustar0000000000000000119 path=neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/strongswan.conf.template 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/strongswan.conf0000664000175000017500000000022200000000000034356 0ustar00zuulzuul00000000000000charon { load_modular = yes plugins { include strongswan.d/charon/*.conf } } include strongswan.d/*.conf ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/ovn/0000775000175000017500000000000000000000000023073 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/ovn/__init__.py0000664000175000017500000000000000000000000025172 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/ovn/agent_monitor.py0000664000175000017500000000560400000000000026317 0ustar00zuulzuul00000000000000# Copyright 2023 SysEleven GmbH # # 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.plugins.ml2.drivers.ovn.agent import neutron_agent from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from neutron_vpnaas.services.vpn.common import constants class OVNVPNAgent(neutron_agent.NeutronAgent): agent_type = constants.AGENT_TYPE_VPN binary = "neutron-ovn-vpn-agent" @property def nb_cfg(self): return int(self.chassis_private.external_ids.get( constants.OVN_AGENT_VPN_SB_CFG_KEY, 0)) @staticmethod def id_from_chassis_private(chassis_private): return chassis_private.external_ids.get( constants.OVN_AGENT_VPN_ID_KEY) @property def agent_id(self): return self.id_from_chassis_private(self.chassis_private) @property def description(self): return self.chassis_private.external_ids.get( constants.OVN_AGENT_VPN_DESC_KEY, '') class ChassisVPNAgentWriteEvent(ovsdb_monitor.ChassisAgentEvent): events = (ovsdb_monitor.BaseEvent.ROW_CREATE, ovsdb_monitor.BaseEvent.ROW_UPDATE) @staticmethod def _vpnagent_nb_cfg(row): return int( row.external_ids.get(constants.OVN_AGENT_VPN_SB_CFG_KEY, -1)) @staticmethod def agent_id(row): return row.external_ids.get(constants.OVN_AGENT_VPN_ID_KEY) def match_fn(self, event, row, old=None): if not self.agent_id(row): # Don't create a cached object with an agent_id of 'None' return False if event == self.ROW_CREATE: return True try: return self._vpnagent_nb_cfg(row) != self._vpnagent_nb_cfg(old) except (AttributeError, KeyError): return False def run(self, event, row, old): neutron_agent.AgentCache().update(constants.AGENT_TYPE_VPN, row, clear_down=True) class OVNVPNAgentMonitor(object): def watch_agent_events(self): l3_plugin = directory.get_plugin(plugin_constants.L3) sb_ovn = l3_plugin._sb_ovn if sb_ovn: idl = sb_ovn.ovsdb_connection.idl if isinstance(idl, ovsdb_monitor.OvnSbIdl): idl.notify_handler.watch_event( ChassisVPNAgentWriteEvent(idl.driver)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/ovn_agent.py0000664000175000017500000000501700000000000024626 0ustar00zuulzuul00000000000000# Copyright 2013, Nachi Ueno, NTT I3, Inc. # Copyright 2023, SysEleven GmbH # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys from neutron.common import config as common_config from neutron.conf.agent import common as agent_config from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from oslo_config import cfg from oslo_log import log as logging from oslo_service import service from neutron_vpnaas._i18n import _ from neutron_vpnaas.agent.ovn.vpn import agent LOG = logging.getLogger(__name__) VPN_AGENT_OPTS = [ cfg.MultiStrOpt( 'vpn_device_driver', default=['neutron_vpnaas.services.vpn.device_drivers.' 'ovn_ipsec.OvnStrongSwanDriver'], sample_default=['neutron_vpnaas.services.vpn.device_drivers.' 'ovn_ipsec.OvnStrongSwanDriver'], help=_("The OVN VPN device drivers Neutron will use")), ] OVS_OPTS = [ cfg.StrOpt('ovsdb_connection', default='unix:/usr/local/var/run/openvswitch/db.sock', help=_('The connection string for the native OVSDB backend.\n' 'Use tcp:IP:PORT for TCP connection.\n' 'Use unix:FILE for unix domain socket connection.')), cfg.IntOpt('ovsdb_connection_timeout', default=180, help=_('Timeout in seconds for the OVSDB ' 'connection transaction')) ] def register_opts(conf): common_config.register_common_config_options() agent_config.register_interface_driver_opts_helper(conf) agent_config.register_interface_opts(conf) agent_config.register_availability_zone_opts_helper(conf) ovn_conf.register_opts() conf.register_opts(VPN_AGENT_OPTS, 'vpnagent') conf.register_opts(OVS_OPTS, 'ovs') def main(): register_opts(cfg.CONF) common_config.init(sys.argv[1:]) agent_config.setup_logging() agent_config.setup_privsep() agt = agent.OvnVpnAgent(cfg.CONF) service.launch(cfg.CONF, agt, restart_method='mutate').wait() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/ovn_plugin.py0000664000175000017500000000611100000000000025022 0ustar00zuulzuul00000000000000# (c) Copyright 2016 IBM Corporation # (c) Copyright 2023 SysEleven GmbH # 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.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from oslo_config import cfg from oslo_utils import importutils from neutron_vpnaas.api.rpc.agentnotifiers import vpn_rpc_agent_api as nfy_api from neutron_vpnaas.db.vpn import vpn_agentschedulers_db as agent_db from neutron_vpnaas.db.vpn.vpn_db import VPNPluginDb from neutron_vpnaas.db.vpn import vpn_ext_gw_db from neutron_vpnaas.services.vpn.common import constants from neutron_vpnaas.services.vpn.ovn import agent_monitor from neutron_vpnaas.services.vpn.plugin import VPNDriverPlugin class VPNOVNPlugin(VPNPluginDb, vpn_ext_gw_db.VPNExtGWPlugin_db, agent_db.AZVPNAgentSchedulerDbMixin, agent_monitor.OVNVPNAgentMonitor): """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. """ def __init__(self): self.vpn_scheduler = importutils.import_object( cfg.CONF.vpn_scheduler_driver) self.add_periodic_vpn_agent_status_check() self.agent_notifiers[constants.AGENT_TYPE_VPN] = \ nfy_api.VPNAgentNotifyAPI() super().__init__() registry.subscribe(self.post_fork_initialize, resources.PROCESS, events.AFTER_INIT) def check_router_in_use(self, context, router_id): pass def post_fork_initialize(self, resource, event, trigger, payload=None): self.watch_agent_events() def vpn_router_agent_binding_changed(self, context, router_id, host): pass supported_extension_aliases = ["vpnaas", "vpn-endpoint-groups", "service-type", "vpn-agent-scheduler"] path_prefix = "/vpn" class VPNOVNDriverPlugin(VPNOVNPlugin, VPNDriverPlugin): def vpn_router_agent_binding_changed(self, context, router_id, host): super().vpn_router_agent_binding_changed(context, router_id, host) filters = {'router_id': [router_id]} vpnservices = self.get_vpnservices(context, filters=filters) for vpnservice in vpnservices: driver = self._get_driver_for_vpnservice(context, vpnservice) driver.update_port_bindings(context, router_id, host) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/plugin.py0000664000175000017500000002356100000000000024150 0ustar00zuulzuul00000000000000 # (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.services import provider_configuration as pconf from neutron.services import service_base from neutron_lib import context as ncontext from neutron_lib import exceptions as lib_exc from neutron_lib.exceptions import flavors as flav_exc 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}) vpn_db.subscribe() @property def _flavors_plugin(self): return directory.get_plugin(constants.FLAVORS) def start_rpc_listeners(self): servers = [] for driver_name, driver in self.drivers.items(): if hasattr(driver, 'start_rpc_listeners'): servers.extend(driver.start_rpc_listeners()) return servers 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 self._flavors_plugin is None: raise vpn_flavors.FlavorsPluginNotLoaded() fl_db = self._flavors_plugin.get_flavor(context, flavor_id) if fl_db['service_type'] != constants.VPN: raise lib_exc.InvalidServiceType( service_type=fl_db['service_type']) if not fl_db['enabled']: raise flav_exc.FlavorDisabled() providers = self._flavors_plugin.get_flavor_next_provider( 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/service_drivers/0000775000175000017500000000000000000000000025467 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/service_drivers/__init__.py0000664000175000017500000000722200000000000027603 0ustar00zuulzuul00000000000000# 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_lib.plugins import constants from neutron_lib.plugins import directory from neutron_lib import rpc as n_rpc from oslo_log import log as logging import oslo_messaging from neutron_vpnaas.services.vpn.service_drivers import driver_validator LOG = logging.getLogger(__name__) class VpnDriver(object, metaclass=abc.ABCMeta): 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/service_drivers/base_ipsec.py0000664000175000017500000002576200000000000030152 0ustar00zuulzuul00000000000000# 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 from neutron.db.models import l3agent from neutron.db.models import servicetype from neutron_lib import constants as lib_constants from neutron_lib.db import api as db_api 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 @db_api.CONTEXT_READER 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) # pylint: disable=useless-super-delegation def __init__(self, topic, default_version, driver): super(IPsecVpnAgentApi, self).__init__( topic, default_version, driver) class BaseIPsecVPNDriver(service_drivers.VpnDriver, metaclass=abc.ABCMeta): """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): router_id = self.service_plugin.get_vpnservice_router_id( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated(context, router_id) def update_ipsec_site_connection( self, context, old_ipsec_site_connection, ipsec_site_connection): router_id = self.service_plugin.get_vpnservice_router_id( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated(context, router_id) def delete_ipsec_site_connection(self, context, ipsec_site_connection): router_id = self.service_plugin.get_vpnservice_router_id( context, ipsec_site_connection['vpnservice_id']) self.agent_rpc.vpnservice_updated(context, 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 @db_api.CONTEXT_WRITER 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/service_drivers/driver_validator.py0000664000175000017500000000175000000000000031404 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/service_drivers/ipsec.py0000664000175000017500000000306100000000000027144 0ustar00zuulzuul00000000000000# 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. from neutron_lib import rpc as n_rpc from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn.service_drivers import base_ipsec from neutron_vpnaas.services.vpn.service_drivers import ipsec_validator IPSEC = 'ipsec' BASE_IPSEC_VERSION = '1.0' class IPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver): """VPN Service Driver class for IPsec.""" def __init__(self, service_plugin): super(IPsecVPNDriver, self).__init__( service_plugin, ipsec_validator.IpsecVpnValidator(self)) def create_rpc_conn(self): self.endpoints = [base_ipsec.IPsecVpnDriverCallBack(self)] self.conn = n_rpc.Connection() self.conn.create_consumer( topics.IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = base_ipsec.IPsecVpnAgentApi( topics.IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/service_drivers/ipsec_validator.py0000664000175000017500000000412500000000000031213 0ustar00zuulzuul00000000000000# Copyright 2015 Awcloud 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 import exceptions as nexception from neutron_vpnaas._i18n import _ from neutron_vpnaas.services.vpn.service_drivers import driver_validator class IpsecValidationFailure(nexception.BadRequest): message = _("IPSec does not support %(resource)s attribute %(key)s " "with value '%(value)s'") class IpsecVpnValidator(driver_validator.VpnDriverValidator): """Driver-specific validator methods for the Openswan, Strongswan and Libreswan. """ def _check_transform_protocol(self, context, transform_protocol): """Restrict selecting ah-esp as IPSec Policy transform protocol. For those *Swan implementations, the 'ah-esp' transform protocol is not supported and therefore the request should be rejected. """ if transform_protocol == "ah-esp": raise IpsecValidationFailure( resource='IPsec Policy', key='transform_protocol', value=transform_protocol) def validate_ipsec_policy(self, context, ipsec_policy): transform_protocol = ipsec_policy.get('transform_protocol') self._check_transform_protocol(context, transform_protocol) def validate_ipsec_site_connection(self, context, ipsec_sitecon): if 'ipsecpolicy_id' in ipsec_sitecon: ipsec_policy = self.driver.service_plugin.get_ipsecpolicy( context, ipsec_sitecon['ipsecpolicy_id']) self.validate_ipsec_policy(context, ipsec_policy) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/service_drivers/ovn_ipsec.py0000664000175000017500000005015600000000000030035 0ustar00zuulzuul00000000000000# Copyright 2016, Yi Jing Zhu, IBM. # Copyright 2023, SysEleven GmbH # 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 neutron_lib.api.definitions import portbindings 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 import context as nctx from neutron_lib.db import api as db_api from neutron_lib import exceptions as n_exc from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from neutron_lib.plugins import utils as p_utils from neutron_lib import rpc as n_rpc from oslo_config import cfg from oslo_db import exception as o_exc from oslo_log import log as logging from neutron_vpnaas.db.vpn import vpn_agentschedulers_db as agent_db from neutron_vpnaas.db.vpn.vpn_ext_gw_db import RouterIsNotVPNExternal from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.common import constants as v_constants from neutron_vpnaas.services.vpn.common import topics from neutron_vpnaas.services.vpn.service_drivers import base_ipsec LOG = logging.getLogger(__name__) IPSEC = 'ipsec' BASE_IPSEC_VERSION = '1.0' TRANSIT_NETWORK_PREFIX = 'vpn-transit-network-' TRANSIT_SUBNET_PREFIX = 'vpn-transit-subnet-' TRANSIT_PORT_PREFIX = 'vpn-ns-' VPN_GW_PORT_PREFIX = 'vpn-gw-' VPN_TRANSIT_LIP = '169.254.0.1' VPN_TRANSIT_RIP = '169.254.0.2' VPN_TRANSIT_CIDR = '169.254.0.0/28' HIDDEN_PROJECT_ID = '' class IPsecVpnOvnDriverCallBack(base_ipsec.IPsecVpnDriverCallBack): def __init__(self, driver): super().__init__(driver) self.admin_ctx = nctx.get_admin_context() @property def core_plugin(self): return self.driver.core_plugin @property def service_plugin(self): return self.driver.service_plugin def _get_vpn_gateway(self, context, router_id): return self.service_plugin.get_vpn_gw_by_router_id(context, router_id) def get_vpn_transit_network_details(self, context, router_id): vpn_gw = self._get_vpn_gateway(context, router_id) network_id = vpn_gw.gw_port['network_id'] external_network = self.core_plugin.get_network(context, network_id) details = { 'gw_port': vpn_gw.gw_port, 'transit_port': vpn_gw.transit_port, 'transit_gateway_ip': VPN_TRANSIT_LIP, 'external_network': external_network, } return details def get_subnet_info(self, context, subnet_id=None): try: return self.core_plugin.get_subnet(context, subnet_id) except n_exc.SubnetNotFound: return None def _get_agent_hosting_vpn_services(self, context, host): agent = self.service_plugin.get_vpn_agent_on_host(context, host) if not agent: return [] # We're here because a VPN agent asked for the VPN services it's # hosting. This means, the agent is alive. This is a chance to # schedule VPN services of routers that are still unscheduled. if cfg.CONF.vpn_auto_schedule: self.service_plugin.auto_schedule_routers(context, agent) query = context.session.query(vpn_models.VPNService) query = query.join(vpn_models.IPsecSiteConnection) query = query.join(agent_db.RouterVPNAgentBinding, agent_db.RouterVPNAgentBinding.router_id == vpn_models.VPNService.router_id) query = query.filter( agent_db.RouterVPNAgentBinding.vpn_agent_id == agent['id']) return query @registry.has_registry_receivers class BaseOvnIPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver): def __init__(self, service_plugin): self._l3_plugin = None self._core_plugin = None super().__init__(service_plugin) @property def l3_plugin(self): if self._l3_plugin is None: self._l3_plugin = directory.get_plugin(plugin_constants.L3) return self._l3_plugin @property def core_plugin(self): if self._core_plugin is None: self._core_plugin = directory.get_plugin() return self._core_plugin @registry.receives(resources.ROUTER, [events.PRECOMMIT_UPDATE]) def _handle_router_precommit_update(self, resource, event, trigger, payload): """Check that a router update won't remove routes we need for VPN.""" LOG.debug("Router %s PRECOMMIT_UPDATE event: %s", payload.resource_id, payload.request_body) router_id = payload.resource_id context = payload.context router_data = payload.request_body routes_removed = router_data.get('routes_removed') if not routes_removed: return removed_cidrs = {r['destination'] for r in routes_removed} vpn_cidrs = set( self.service_plugin.get_peer_cidrs_for_router(context, router_id)) conflict_cidrs = removed_cidrs.intersection(vpn_cidrs) if conflict_cidrs: raise vpnaas.RouteInUseByVPN( destinations=", ".join(conflict_cidrs)) def get_vpn_gw_port_name(self, router_id): return VPN_GW_PORT_PREFIX + router_id def get_vpn_namespace_port_name(self, router_id): return TRANSIT_PORT_PREFIX + router_id def get_transit_network_name(self, router_id): return TRANSIT_NETWORK_PREFIX + router_id def get_transit_subnet_name(self, router_id): return TRANSIT_SUBNET_PREFIX + router_id def make_transit_network(self, router_id, tenant_id, agent_host, gateway_update): context = nctx.get_admin_context() network_data = { 'tenant_id': HIDDEN_PROJECT_ID, 'name': self.get_transit_network_name(router_id), 'admin_state_up': True, 'shared': False, } network = p_utils.create_network(self.core_plugin, context, {'network': network_data}) gateway_update['transit_network_id'] = network['id'] # The subnet tenant_id must be of the user, otherwise updating the # router by the user may fail (it needs access to all subnets) subnet_data = { 'tenant_id': tenant_id, 'name': self.get_transit_subnet_name(router_id), 'gateway_ip': VPN_TRANSIT_LIP, 'cidr': VPN_TRANSIT_CIDR, 'network_id': network['id'], 'ip_version': 4, 'enable_dhcp': False, } subnet = p_utils.create_subnet(self.core_plugin, context, {'subnet': subnet_data}) gateway_update['transit_subnet_id'] = subnet['id'] self.l3_plugin.add_router_interface(context, router_id, {'subnet_id': subnet['id']}) fixed_ip = {'subnet_id': subnet['id'], 'ip_address': VPN_TRANSIT_RIP} port_data = { 'tenant_id': HIDDEN_PROJECT_ID, 'network_id': network['id'], 'fixed_ips': [fixed_ip], 'device_id': subnet['id'], 'device_owner': v_constants.DEVICE_OWNER_TRANSIT_NETWORK, 'admin_state_up': True, portbindings.HOST_ID: agent_host, 'name': self.get_vpn_namespace_port_name(router_id) } port = p_utils.create_port(self.core_plugin, context, {"port": port_data}) gateway_update['transit_port_id'] = port['id'] def _del_port(self, context, port_id): try: self.core_plugin.delete_port(context, port_id, l3_port_check=False) except n_exc.PortNotFound: pass def _remove_router_interface(self, context, router_id, subnet_id): try: self.l3_plugin.remove_router_interface( context, router_id, {'subnet_id': subnet_id}) except (n_exc.l3.RouterInterfaceNotFoundForSubnet, n_exc.SubnetNotFound): pass def _del_subnet(self, context, subnet_id): try: self.core_plugin.delete_subnet(context, subnet_id) except n_exc.SubnetNotFound: pass def _del_network(self, context, network_id): try: self.core_plugin.delete_network(context, network_id) except n_exc.NetworkNotFound: pass def del_transit_network(self, gw): context = nctx.get_admin_context() router_id = gw['router_id'] port_id = gw.get('transit_port_id') if port_id: self._del_port(context, port_id) subnet_id = gw.get('transit_subnet_id') if subnet_id: self._remove_router_interface(context, router_id, subnet_id) self._del_subnet(context, subnet_id) network_id = gw.get('transit_network_id') if network_id: self._del_network(context, network_id) def make_gw_port(self, router_id, network_id, agent_host, gateway_update): context = nctx.get_admin_context() port_data = {'tenant_id': HIDDEN_PROJECT_ID, 'network_id': network_id, 'fixed_ips': lib_constants.ATTR_NOT_SPECIFIED, 'device_id': router_id, 'device_owner': v_constants.DEVICE_OWNER_VPN_ROUTER_GW, 'admin_state_up': True, portbindings.HOST_ID: agent_host, 'name': self.get_vpn_gw_port_name(router_id)} gw_port = p_utils.create_port(self.core_plugin, context.elevated(), {'port': port_data}) if not gw_port['fixed_ips']: LOG.debug('No IPs available for external network %s', network_id) gateway_update['gw_port_id'] = gw_port['id'] def del_gw_port(self, gateway): context = nctx.get_admin_context() port_id = gateway.get('gw_port_id') if port_id: self._del_port(context, port_id) def _get_peer_cidrs(self, vpnservice): cidrs = [] for ipsec_site_connection in vpnservice.ipsec_site_connections: if ipsec_site_connection.peer_cidrs: for peer_cidr in ipsec_site_connection.peer_cidrs: cidrs.append(peer_cidr.cidr) if ipsec_site_connection.peer_ep_group is not None: for ep in ipsec_site_connection.peer_ep_group.endpoints: cidrs.append(ep.endpoint) return cidrs def _routes_update(self, cidrs, nexthop): routes = [{'destination': cidr, 'nexthop': nexthop} for cidr in cidrs] return {'router': {'routes': routes}} def _update_static_routes(self, context, ipsec_site_connection): vpnservice = self.service_plugin.get_vpnservice( context, ipsec_site_connection['vpnservice_id']) router_id = vpnservice['router_id'] gw = self.service_plugin.get_vpn_gw_by_router_id(context, router_id) nexthop = gw.transit_port['fixed_ips'][0]['ip_address'] router = self.l3_plugin.get_router(context, router_id) old_routes = router.get('routes', []) old_cidrs = set([r['destination'] for r in old_routes if r['nexthop'] == nexthop]) new_cidrs = set( self.service_plugin.get_peer_cidrs_for_router(context, router_id)) to_remove = old_cidrs - new_cidrs if to_remove: self.l3_plugin.remove_extraroutes(context, router_id, self._routes_update(to_remove, nexthop)) to_add = new_cidrs - old_cidrs if to_add: self.l3_plugin.add_extraroutes(context, router_id, self._routes_update(to_add, nexthop)) 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. """ gateway = self.service_plugin.get_vpn_gw_dict_by_router_id( nctx.get_admin_context(), router['id']) if gateway is None or gateway['external_fixed_ips'] is None: raise RouterIsNotVPNExternal(router_id=router['id']) v4_ip = v6_ip = None for fixed_ip in gateway['external_fixed_ips']: addr = fixed_ip['ip_address'] vers = netaddr.IPAddress(addr).version if vers == lib_constants.IP_VERSION_4: if v4_ip is None: v4_ip = addr elif v6_ip is None: v6_ip = addr return v4_ip, v6_ip def _update_gateway(self, context, gateway_id, **kwargs): gateway = {'gateway': kwargs} return self.service_plugin.update_gateway(context, gateway_id, gateway) @db_api.retry_if_session_inactive() def _ensure_gateway(self, context, vpnservice): gw = self.service_plugin.get_vpn_gw_dict_by_router_id( context, vpnservice['router_id'], refresh=True) if not gw: gateway = {'gateway': { 'router_id': vpnservice['router_id'], 'tenant_id': vpnservice['tenant_id'], }} # create_gateway may raise oslo_db.exception.DBDuplicateEntry # if someone else created one in the meantime return self.service_plugin.create_gateway(context, gateway) if gw['status'] == lib_constants.ERROR: raise vpnaas.VPNGatewayInError() # Raise an exception if an existing gateway is in status # PENDING_CREATE or PENDING_DELETE. # One of the next retries should succeed. if gw['status'] != lib_constants.ACTIVE: raise o_exc.RetryRequest(vpnaas.VPNGatewayNotReady()) return gw @db_api.CONTEXT_WRITER def _setup(self, context, vpnservice_dict): router_id = vpnservice_dict['router_id'] agent = self.service_plugin.schedule_router(context, router_id) if not agent: raise vpnaas.NoVPNAgentAvailable agent_host = agent['host'] gateway = self._ensure_gateway(context, vpnservice_dict) # If the gateway status is ACTIVE the ports have been created already if gateway['status'] == lib_constants.ACTIVE: return vpnservice = self.service_plugin._get_vpnservice(context, vpnservice_dict['id']) network_id = vpnservice.router.gw_port.network_id gateway_update = {} # keeps track of already-created IDs try: self.make_gw_port(router_id, network_id, agent_host, gateway_update) self.make_transit_network(router_id, vpnservice_dict['tenant_id'], agent_host, gateway_update) except Exception: self._update_gateway(context, gateway['id'], status=lib_constants.ERROR, **gateway_update) raise self._update_gateway(context, gateway['id'], status=lib_constants.ACTIVE, **gateway_update) def _cleanup(self, context, router_id): gw = self.service_plugin.get_vpn_gw_dict_by_router_id(context, router_id) if not gw: return self._update_gateway(context, gw['id'], status=lib_constants.PENDING_DELETE) try: self.del_gw_port(gw) self.del_transit_network(gw) self.service_plugin.delete_gateway(context, gw['id']) except Exception: LOG.exception("Cleanup of VPN gateway for router %s failed.", router_id) self._update_gateway(context, gw['id'], status=lib_constants.ERROR) raise def create_vpnservice(self, context, vpnservice_dict): try: self._setup(context, vpnservice_dict) except Exception: LOG.exception("Setting up the VPN gateway for router %s failed.", vpnservice_dict['router_id']) self.service_plugin.set_vpnservice_status( context, vpnservice_dict['id'], lib_constants.ERROR, updated_pending_status=True) raise super().create_vpnservice(context, vpnservice_dict) def delete_vpnservice(self, context, vpnservice): router_id = vpnservice['router_id'] super().delete_vpnservice(context, vpnservice) services = self.service_plugin.get_vpnservices(context) router_ids = [s['router_id'] for s in services] if router_id not in router_ids: self._cleanup(context, router_id) def create_ipsec_site_connection(self, context, ipsec_site_connection): self._update_static_routes(context, ipsec_site_connection) super().create_ipsec_site_connection(context, ipsec_site_connection) def delete_ipsec_site_connection(self, context, ipsec_site_connection): self._update_static_routes(context, ipsec_site_connection) super().delete_ipsec_site_connection(context, ipsec_site_connection) def update_ipsec_site_connection( self, context, old_ipsec_site_connection, ipsec_site_connection): self._update_static_routes(context, ipsec_site_connection) super().update_ipsec_site_connection( context, old_ipsec_site_connection, ipsec_site_connection) def _update_port_binding(self, context, port_id, host): port_data = {'binding:host_id': host} self.core_plugin.update_port(context, port_id, {'port': port_data}) def update_port_bindings(self, context, router_id, host): gw = self.service_plugin.get_vpn_gw_dict_by_router_id(context, router_id) if not gw: return port_id = gw.get('gw_port_id') if port_id: self._update_port_binding(context, port_id, host) port_id = gw.get('transit_port_id') if port_id: self._update_port_binding(context, port_id, host) class IPsecOvnVpnAgentApi(base_ipsec.IPsecVpnAgentApi): 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 vpn_agents = self.driver.service_plugin.get_vpn_agents_hosting_routers( admin_context, [router_id], active=True) for vpn_agent in vpn_agents: LOG.debug('Notify agent at %(topic)s.%(host)s the message ' '%(method)s %(args)s', {'topic': self.topic, 'host': vpn_agent['host'], 'method': method, 'args': kwargs}) cctxt = self.client.prepare(server=vpn_agent['host'], version=version) cctxt.cast(context, method, **kwargs) class IPsecOvnVPNDriver(BaseOvnIPsecVPNDriver): """VPN Service Driver class for IPsec.""" def create_rpc_conn(self): self.agent_rpc = IPsecOvnVpnAgentApi( topics.IPSEC_AGENT_TOPIC, BASE_IPSEC_VERSION, self) def start_rpc_listeners(self): self.endpoints = [IPsecVpnOvnDriverCallBack(self)] self.conn = n_rpc.Connection() self.conn.create_consumer( topics.IPSEC_DRIVER_TOPIC, self.endpoints, fanout=False) return self.conn.consume_in_threads() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/services/vpn/vpn_service.py0000664000175000017500000000337400000000000025175 0ustar00zuulzuul00000000000000# Copyright 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. from neutron.services import provider_configuration as provconfig from neutron_lib.exceptions import vpn as vpn_exception from oslo_log import log as logging from oslo_utils import importutils LOG = logging.getLogger(__name__) DEVICE_DRIVERS = 'device_drivers' class VPNService(object): """VPN Service observer.""" def __init__(self, l3_agent): self.conf = l3_agent.conf def load_device_drivers(self, host): """Loads one or more device drivers for VPNaaS.""" drivers = [] for device_driver in self.conf.vpnagent.vpn_device_driver: device_driver = provconfig.get_provider_driver_class( device_driver, DEVICE_DRIVERS) try: drivers.append(importutils.import_object(device_driver, self, host)) LOG.debug('Loaded VPNaaS device driver: %s', device_driver) except ImportError: raise vpn_exception.DeviceDriverImportError( device_driver=device_driver) return drivers ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/0000775000175000017500000000000000000000000021005 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/__init__.py0000664000175000017500000000000000000000000023104 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/base.py0000664000175000017500000000202200000000000022265 0ustar00zuulzuul00000000000000# Copyright 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. # from neutron.tests import base as n_base from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_plugin from neutron.tests.unit.extensions import base as test_api_v2_extension class BaseTestCase(n_base.BaseTestCase): pass class ExtensionTestCase(test_api_v2_extension.ExtensionTestCase): pass class NeutronDbPluginV2TestCase(test_db_plugin.NeutronDbPluginV2TestCase): pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/contrib/0000775000175000017500000000000000000000000022445 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/contrib/README0000664000175000017500000000022700000000000023326 0ustar00zuulzuul00000000000000The files in this directory are intended for use by the infra jobs that run the various functional test suite in the gate for the neutron-vpnaas repo. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/contrib/filters.template0000664000175000017500000000137300000000000025656 0ustar00zuulzuul00000000000000# neutron-rootwrap command filters to support functional testing. It # is NOT intended to be used outside of a test environment. # # This file should be owned by (and only-writable by) the root user [Filters] # '$BASE_PATH' is intended to be replaced with the expected tox path # (e.g. /opt/stack/new/neutron/.tox/dsvm-functional) by the neutron # functional jenkins job. This ensures that tests can kill the # processes that they launch with their containing tox environment's # python. kill_tox_python: KillFilter, root, $BASE_PATH/bin/python, -9 # enable ping from namespace ping_filter: CommandFilter, ping, root # enable curl from namespace curl_filter: CommandFilter, curl, root tee_filter: CommandFilter, tee, root tee_kill: KillFilter, root, tee, -9 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/contrib/functional-test-rootwrap.conf0000664000175000017500000000223200000000000030305 0ustar00zuulzuul00000000000000# Configuration for neutron-rootwrap # This file should be owned by (and only-writable by) the root user [DEFAULT] # List of directories to load filter definitions from (separated by ','). # These directories MUST all be only writeable by root ! filters_path=/etc/neutron/rootwrap.d,/usr/share/neutron/rootwrap # List of directories to search executables in, in case filters do not # explicitely specify a full path (separated by ',') # If not specified, defaults to system PATH environment variable. # These directories MUST all be only writeable by root ! exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin # Enable logging to syslog # Default value is False use_syslog=False # Which syslog facility to use. # Valid values include auth, authpriv, syslog, local0, local1... # Default value is 'syslog' syslog_log_facility=syslog # Which messages to log. # INFO means log all usage # ERROR means only log unsuccessful attempts syslog_log_level=ERROR [xenapi] # XenAPI configuration is only required by the L2 agent if it is to # target a XenServer/XCP compute host's dom0. xenapi_connection_url= xenapi_connection_username=root xenapi_connection_password= ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/contrib/functional-testing.filters0000664000175000017500000000414100000000000027654 0ustar00zuulzuul00000000000000# neutron-rootwrap command filters to support functional testing. It # is NOT intended to be used outside of a test environment. # # This file should be owned by (and only-writable by) the root user [Filters] # enable ping from namespace ping_filter: CommandFilter, ping, root ping6_filter: CommandFilter, ping6, root # enable curl from namespace curl_filter: CommandFilter, curl, root tee_filter: CommandFilter, tee, root tee_kill: KillFilter, root, tee, -9 nc_filter: CommandFilter, nc, root # netcat has different binaries depending on linux distribution nc_kill: KillFilter, root, nc, -9 ncbsd_kill: KillFilter, root, nc.openbsd, -9 ncat_kill: KillFilter, root, ncat, -9 ss_filter: CommandFilter, ss, root # arping arping: CommandFilter, arping, root # l3_agent sysctl: CommandFilter, sysctl, root route: CommandFilter, route, root radvd: CommandFilter, radvd, root # metadata proxy metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root # RHEL invocation of the metadata proxy will report /usr/bin/python kill_metadata: KillFilter, root, python, -9 kill_metadata7: KillFilter, root, python2.7, -9 kill_radvd_usr: KillFilter, root, /usr/sbin/radvd, -9, -HUP kill_radvd: KillFilter, root, /sbin/radvd, -9, -HUP # ip_lib ip: IpFilter, ip, root find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.* ip_exec: IpNetnsExecFilter, ip, root # For ip monitor kill_ip_monitor: KillFilter, root, ip, -9 # ovs_lib (if OVSInterfaceDriver is used) ovs-vsctl: CommandFilter, ovs-vsctl, root # iptables_manager iptables: CommandFilter, iptables, root iptables-save: CommandFilter, iptables-save, root iptables-restore: CommandFilter, iptables-restore, root ip6tables-save: CommandFilter, ip6tables-save, root ip6tables-restore: CommandFilter, ip6tables-restore, root # Keepalived keepalived: CommandFilter, keepalived, root kill_keepalived: KillFilter, root, /usr/sbin/keepalived, -HUP, -15, -9 # l3 agent to delete floatingip's conntrack state conntrack: CommandFilter, conntrack, root # keepalived state change monitor keepalived_state_change: CommandFilter, neutron-keepalived-state-change, root ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/contrib/gate_hook.sh0000775000175000017500000000201000000000000024735 0ustar00zuulzuul00000000000000#!/usr/bin/env bash set -ex VENV=${1:-"dsvm-functional"} DEVSTACK_LOCAL_CONFIG="NETWORK_API_EXTENSIONS=all" DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin neutron-vpnaas https://opendev.org/openstack/neutron-vpnaas" export DEVSTACK_LOCAL_CONFIG case $VENV in dsvm-functional | dsvm-functional-sswan) # The following need to be set before sourcing # configure_for_func_testing. GATE_DEST=$BASE/new GATE_STACK_USER=stack NEUTRON_PATH=$GATE_DEST/neutron PROJECT_NAME=neutron-vpnaas NEUTRON_VPN_PATH=$GATE_DEST/$PROJECT_NAME DEVSTACK_PATH=$GATE_DEST/devstack IS_GATE=True USE_CONSTRAINT_ENV=False Q_BUILD_OVS_FROM_GIT=False source $NEUTRON_VPN_PATH/tools/configure_for_vpn_func_testing.sh # Make the workspace owned by the stack user sudo chown -R $STACK_USER:$STACK_USER $BASE configure_host_for_vpn_func_testing ;; api|tempest) $BASE/new/devstack-gate/devstack-vm-gate.sh ;; esac ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/contrib/post_test_hook.sh0000775000175000017500000000260400000000000026052 0ustar00zuulzuul00000000000000#!/usr/bin/env bash set -xe NEUTRON_VPNAAS_DIR="$BASE/new/neutron-vpnaas" TEMPEST_CONFIG_DIR="$BASE/new/tempest/etc" SCRIPTS_DIR="/usr/os-testr-env/bin" VENV=${1:-"dsvm-functional"} function generate_testr_results { # Give job user rights to access tox logs sudo -H -u $owner chmod o+rw . sudo -H -u $owner chmod o+rw -R .stestr if [ -f ".stestr/0" ] ; then .tox/$VENV/bin/subunit-1to2 < .stestr/0 > ./stestr.subunit $SCRIPTS_DIR/subunit2html ./stestr.subunit testr_results.html gzip -9 ./stestr.subunit gzip -9 ./testr_results.html sudo mv ./*.gz /opt/stack/logs/ fi } case $VENV in dsvm-functional | dsvm-functional-sswan) owner=stack sudo_env= # Set owner permissions according to job's requirements. cd $NEUTRON_VPNAAS_DIR sudo chown -R $owner:stack $NEUTRON_VPNAAS_DIR echo "Running neutron $VENV test suite" set +e sudo -H -u $owner $sudo_env tox -e $VENV --notest # Development version of neutron is not installed from g-r. # We need to install neutron master explicitly. sudo -H -u $owner $sudo_env .tox/$VENV/bin/pip install -e ../neutron sudo -H -u $owner $sudo_env tox -e $VENV testr_exit_code=$? set -e # Collect and parse results generate_testr_results exit $testr_exit_code ;; esac ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9073498 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/0000775000175000017500000000000000000000000023147 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/__init__.py0000664000175000017500000000000000000000000025246 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/common/0000775000175000017500000000000000000000000024437 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/common/README0000664000175000017500000000037000000000000025317 0ustar00zuulzuul00000000000000This area holds tests that are run for all functional jobs. The load_tests() method in each implementation specific area will include tests in this area. Do not place a test module in this area, if it is specific to one particular implementation. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/common/__init__.py0000664000175000017500000000000000000000000026536 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/common/ovn_base.py0000664000175000017500000004766600000000000026630 0ustar00zuulzuul00000000000000# 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 unittest import mock import netaddr from neutron.agent.linux import ip_lib from neutron.common import config as common_config from neutron.common.ovn import constants as ovn_const from neutron.conf.agent import common as agent_conf from neutron.conf import common as common_conf from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.conf.plugins.ml2.drivers import ovs_conf from neutron.tests.common import net_helpers from neutron.tests.functional import base from neutron_lib import constants as lib_constants from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from neutron_lib.utils import helpers from oslo_config import cfg from ovsdbapp.backend.ovs_idl import event from neutron_vpnaas.agent.ovn.vpn import agent from neutron_vpnaas.agent.ovn.vpn import ovsdb from neutron_vpnaas.services.vpn.common import constants as vpn_const from neutron_vpnaas.services.vpn.device_drivers import ipsec from neutron_vpnaas.services.vpn import ovn_agent from neutron_vpnaas.services.vpn.service_drivers import ovn_ipsec OVS_INTERFACE_DRIVER = 'neutron.agent.linux.interface.OVSInterfaceDriver' IPSEC_SERVICE_PROVIDER = ('VPN:ovn:neutron_vpnaas.services.vpn.' 'service_drivers.ovn_ipsec.IPsecOvnVPNDriver:' 'default') VPN_PLUGIN = 'neutron_vpnaas.services.vpn.ovn_plugin.VPNOVNDriverPlugin' PUBLIC_NET = netaddr.IPNetwork('19.4.4.0/24') LOCAL_NETS = list(netaddr.IPNetwork('10.0.0.0/16').subnet(24)) PEER_NET = netaddr.IPNetwork('10.1.0.0/16') PEER_ADDR = '19.4.5.6' class VPNAgentHealthEvent(event.WaitEvent): event_name = 'VPNAgentHealthEvent' def __init__(self, chassis, sb_cfg, table, timeout=5): self.chassis = chassis self.sb_cfg = sb_cfg super().__init__( (self.ROW_UPDATE,), table, (('name', '=', self.chassis),), timeout=timeout) def matches(self, event, row, old=None): if not super().matches(event, row, old): return False return int(row.external_ids.get( vpn_const.OVN_AGENT_VPN_SB_CFG_KEY, 0)) >= self.sb_cfg class OvnSiteInfo: def __init__(self, parent, index, ext_net, ext_sub): self.ext_net = ext_net self.ext_sub = ext_sub self.parent = parent self.context = parent.context self.fmt = parent.fmt self.index = index def create_base(self): router_data = { 'name': 'r%d' % self.index, 'admin_state_up': True, 'tenant_id': self.parent._tenant_id, 'external_gateway_info': { 'enable_snat': True, 'network_id': self.ext_net['id'], 'external_fixed_ips': [ {'ip_address': str(PUBLIC_NET[4 + 2 * self.index]), 'subnet_id': self.ext_sub['id']} ] } } self.router = self.parent.l3_plugin.create_router( self.context, {'router': router_data}) # local subnet private_net = LOCAL_NETS[self.index] self.local_cidr = str(private_net) net = self.parent._make_network(self.fmt, 'local%d' % self.index, True) self.local_net = net['network'] sub = self.parent._make_subnet(self.fmt, net, private_net[1], self.local_cidr, enable_dhcp=False) self.local_sub = sub['subnet'] interface_info = {'subnet_id': self.local_sub['id']} self.parent.l3_plugin.add_router_interface( self.context, self.router['id'], interface_info) def create_vpnservice(self): plugin = self.parent.vpn_plugin data = { 'tenant_id': self.parent._tenant_id, 'name': 'my-service', 'description': 'new service', 'subnet_id': self.local_sub['id'], 'router_id': self.router['id'], 'flavor_id': None, 'admin_state_up': True, } self.vpnservice = plugin.create_vpnservice(self.context, {'vpnservice': data}) self.local_addr = self.vpnservice['external_v4_ip'] data = { 'tenant_id': self.parent._tenant_id, 'name': 'ikepolicy%d' % self.index, 'description': '', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'main', 'ike_version': 'v1', 'pfs': 'group5', 'lifetime': {'units': 'seconds', 'value': 3600}, } self.ikepolicy = plugin.create_ikepolicy(self.context, {'ikepolicy': data}) data = { 'tenant_id': self.parent._tenant_id, 'name': 'ipsecpolicy%d' % self.index, 'description': '', 'transform_protocol': 'esp', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'pfs': 'group5', 'lifetime': {'units': 'seconds', 'value': 3600}, } self.ipsecpolicy = plugin.create_ipsecpolicy(self.context, {'ipsecpolicy': data}) def create_site_connection(self, peer_addr, peer_cidr): data = { 'tenant_id': self.parent._tenant_id, 'name': 'conn%d' % self.index, 'description': '', 'local_id': self.local_addr, 'peer_address': peer_addr, 'peer_id': peer_addr, 'peer_cidrs': [peer_cidr], 'mtu': 1500, 'initiator': 'bi-directional', 'auth_mode': 'psk', 'psk': 'secret', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120, }, 'admin_state_up': True, 'vpnservice_id': self.vpnservice['id'], 'ikepolicy_id': self.ikepolicy['id'], 'ipsecpolicy_id': self.ipsecpolicy['id'], 'local_ep_group_id': None, 'peer_ep_group_id': None, } self.siteconn = self.parent.vpn_plugin.create_ipsec_site_connection( self.context, {'ipsec_site_connection': data}) class TestOvnVPNAgentBase(base.TestOVNFunctionalBase): FAKE_CHASSIS_HOST = 'ovn-host-fake' def setUp(self): cfg.CONF.set_override('service_provider', [IPSEC_SERVICE_PROVIDER], group='service_providers') service_plugins = {'vpnaas_plugin': VPN_PLUGIN} super().setUp(service_plugins=service_plugins) common_config.register_common_config_options() self.mock_ovsdb_idl = mock.Mock() mock_instance = mock.Mock() mock_instance.start.return_value = self.mock_ovsdb_idl mock_ovs_idl = mock.patch.object(ovsdb, 'VPNAgentOvsIdl').start() mock_ovs_idl.return_value = mock_instance self.vpn_plugin = directory.get_plugin(plugin_constants.VPN) # normally called in post_for_initialize self.vpn_plugin.watch_agent_events() self.vpn_service_driver = self.vpn_plugin.drivers['ovn'] self.handler = self.sb_api.idl.notify_handler self.agent = self._start_vpn_agent() self.agent_driver = self.agent.device_drivers[0] def _start_vpn_agent(self): # Set up a ConfigOpts separate to cfg.CONF in order to avoid conflicts # with other tests. # The OVN VPN agent registers a different variant of # vpnagent.vpn_device_drivers than the L3 agent extension. conf = agent_conf.setup_conf() conf.register_opts(ovn_conf.ovn_opts, group='ovn') conf.register_opts(ipsec.ipsec_opts, 'ipsec') common_conf.register_core_common_config_opts(conf) ovs_conf.register_ovs_opts(conf) ovn_agent.register_opts(conf) agent_conf.register_process_monitor_opts(conf) agent_conf.setup_privsep() conf.set_override('state_path', self.get_default_temp_dir().path) conf.set_override('interface_driver', OVS_INTERFACE_DRIVER) conf.set_override('vpn_device_driver', [self.VPN_DEVICE_DRIVER], group='vpnagent') ovn_sb_db = self.ovsdb_server_mgr.get_ovsdb_connection_path('sb') conf.set_override('ovn_sb_connection', ovn_sb_db, group='ovn') self.chassis_name = self.add_fake_chassis(self.FAKE_CHASSIS_HOST) mock.patch.object(agent.OvnVpnAgent, '_get_own_chassis_name', return_value=self.chassis_name).start() conf.set_override('host', self.FAKE_CHASSIS_HOST) self.br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge conf.set_override('integration_bridge', self.br_int.br_name, 'OVS') # name prefix for namespaces managed by vpn agent # will be patched into device driver to make sure concurrent # tests don't interfere with each other # (a vpn agent will normally remove all unknown qvpn- namespaces) self.ns_prefix = 'qvpn-test-%s-' % helpers.get_random_string(8) agt = agent.OvnVpnAgent(conf) driver = agt.device_drivers[0] driver.agent_rpc = mock.Mock() # let initial sync get an empty list of vpnservices driver.agent_rpc.get_vpn_services_on_host.return_value = [] driver.devmgr.plugin = driver.agent_rpc driver.devmgr.OVN_NS_PREFIX = self.ns_prefix agt.start() self.addCleanup(agt.ovs_idl.ovsdb_connection.stop) self.addCleanup(agt.sb_idl.ovsdb_connection.stop) # let agent remove remaining vpn namespaces in cleanup self.addCleanup(driver._cleanup_stale_vpn_processes, []) return agt @property def agent_chassis_table(self): if self.agent.has_chassis_private: return 'Chassis_Private' return 'Chassis' def _make_ext_network(self): network = self._make_network( self.fmt, 'external-net', True, as_admin=True, arg_list=('router:external', 'provider:network_type', 'provider:physical_network'), **{'router:external': True, 'provider:network_type': 'flat', 'provider:physical_network': 'public'}) pools = [{'start': PUBLIC_NET[2], 'end': PUBLIC_NET[253]}] gateway = PUBLIC_NET[1] cidr = str(PUBLIC_NET) subnet = self._make_subnet(self.fmt, network, gateway, cidr, allocation_pools=pools, enable_dhcp=False) return network['network'], subnet['subnet'] def _find_lswitch_by_neutron_name(self, name): for row in self.nb_api._tables['Logical_Switch'].rows.values(): if (row.external_ids.get( ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY) == name): return row def _find_transit_lswitch(self, router_id): name = ovn_ipsec.TRANSIT_NETWORK_PREFIX + router_id return self._find_lswitch_by_neutron_name(name) def _match_extids(self, row, expected): for key, value in expected.items(): if row.external_ids.get(key) != value: return False return True def _find_transit_ns_port(self, router_id, ports): name = ovn_ipsec.TRANSIT_PORT_PREFIX + router_id extids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: name} for row in ports: if self._match_extids(row, extids): return row def _find_transit_router_port(self, router_id, network_name, ports): extids = { ovn_const.OVN_DEVID_EXT_ID_KEY: router_id, ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: 'network:router_interface', ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY: network_name, } for row in ports: if self._match_extids(row, extids): return row def _find_vpn_gw_port(self, router_id, ports): name = ovn_ipsec.VPN_GW_PORT_PREFIX + router_id extids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: name} for row in ports: if self._match_extids(row, extids): return row def _find_lrouter_by_neutron_id(self, router_id): for row in self.nb_api._tables['Logical_Router'].rows.values(): if row.name == "neutron-" + router_id: return row def test_agent(self): chassis_row = self.sb_api.db_find( self.agent_chassis_table, ('name', '=', self.chassis_name)).execute( check_error=True)[0] # Assert that, prior to creating a resource the VPN agent # didn't populate the external_ids from the Chassis self.assertNotIn(vpn_const.OVN_AGENT_VPN_SB_CFG_KEY, chassis_row['external_ids']) # Let's list the agents to force the nb_cfg to be bumped on NB # db, which will automatically increment the nb_cfg counter on # NB_Global and make ovn-controller copy it over to SB_Global. Upon # this event, VPN agent will update the external_ids on its # Chassis row to signal that it's healthy. row_event = VPNAgentHealthEvent(self.chassis_name, 1, self.agent_chassis_table) self.handler.watch_event(row_event) self.new_list_request('agents').get_response(self.api) # If we do not time out waiting for the event, then we are assured # that the VPN agent has populated the external_ids from the # chassis with the nb_cfg, 1 revisions when listing the agents. self.assertTrue(row_event.wait()) def test_service(self): r = self.new_list_request('agents').get_response(self.api) ext_net, ext_sub = self._make_ext_network() server = ovn_ipsec.IPsecVpnOvnDriverCallBack(self.vpn_service_driver) # Mock the controller side RPC client (prepare and cast) # to be able to check that "vpnservice_updated" will be called prepare_mock = mock.Mock() prepared_mock = mock.Mock() self.vpn_service_driver.agent_rpc.client.prepare = prepare_mock prepare_mock.return_value = prepared_mock # Create a site (router, network, subnet, vpnservice, site conn) site = OvnSiteInfo(self, 1, ext_net, ext_sub) site.create_base() site.create_vpnservice() site.create_site_connection(PEER_ADDR, str(PEER_NET)) # Check that the vpnservice_updated RPC was triggered towards # the agent prepare_mock.assert_called_once_with( server=self.FAKE_CHASSIS_HOST, version=self.vpn_service_driver.agent_rpc.target.version) prepared_mock.cast.assert_called_once_with( self.context, 'vpnservice_updated', router={'id': site.router['id']}) # Mock the agent->controller RPCs. Let them return data from the # actual VPN plugin def get_vpn_services_on_host(ctx, host): r = server.get_vpn_services_on_host(self.context, host) return r def get_vpn_transit_network_details(router_id): return server.get_vpn_transit_network_details( self.context, router_id) def get_subnet_info(subnet_id): return server.get_subnet_info(self.context, subnet_id) r = self.agent_driver.agent_rpc r.get_vpn_services_on_host.side_effect = get_vpn_services_on_host r.get_vpn_transit_network_details.side_effect = \ get_vpn_transit_network_details r.get_subnet_info.side_effect = get_subnet_info # Call the agent's vpnservice_updated as if it was coming from # the controller. for driver in self.agent.device_drivers: driver.vpnservice_updated(driver.context, router={'id': site.router['id']}) # Check that transit network and VPN gateway port are set up correctly # - transit network exists # - router port in transit network exists # - transit network port to be bound to chassis exists and # host is assigned # - VPN gateway port exists and host is assigned # - static route exists towards peer CIDR # expect transit network in NB transit_row = self._find_transit_lswitch(site.router['id']) self.assertIsNotNone(transit_row) # check the transit network router port exists transit_router_port = self._find_transit_router_port( site.router['id'], transit_row.name, transit_row.ports) self.assertIsNotNone(transit_router_port) # check that the namespace port in the transit network exists transit_ns_port = self._find_transit_ns_port(site.router['id'], transit_row.ports) self.assertIsNotNone(transit_ns_port) # check that the port has the requested-host option requested_host = transit_ns_port.options.get( ovn_const.LSP_OPTIONS_REQUESTED_CHASSIS_KEY) self.assertEqual(requested_host, self.FAKE_CHASSIS_HOST) # get vpn gateway port via external network lswitch ext_row = self._find_lswitch_by_neutron_name("external-net") self.assertIsNotNone(ext_row) vpn_gw_port = self._find_vpn_gw_port(site.router['id'], ext_row.ports) self.assertIsNotNone(vpn_gw_port) # check that vpn gateway port has the requested-host option requested_host = vpn_gw_port.options.get( ovn_const.LSP_OPTIONS_REQUESTED_CHASSIS_KEY) self.assertEqual(requested_host, self.FAKE_CHASSIS_HOST) # check that static route towards peer cidr is set router_row = self._find_lrouter_by_neutron_id(site.router['id']) self.assertIsNotNone(router_row) for r in router_row.static_routes: if r.ip_prefix == str(PEER_NET): route = r break else: route = None self.assertIsNotNone(route) self.assertEqual(route.nexthop, ovn_ipsec.VPN_TRANSIT_RIP) # Check agent side # - network namespace # - routes towards transit network's gateway IP # - devices and their IP addresses in the namespace ns_name = self.ns_prefix + site.router['id'] devlen = lib_constants.LINUX_DEV_LEN transit_dev = ('vr' + transit_ns_port.name)[:devlen] gw_dev = ('vg' + vpn_gw_port.name)[:devlen] self.assertTrue(ip_lib.network_namespace_exists(ns_name)) device = ip_lib.IPDevice(None, namespace=ns_name) routes = device.route.list_routes(lib_constants.IP_VERSION_4, proto='static', via=ovn_ipsec.VPN_TRANSIT_LIP) self.assertEqual(len(routes), 1) self.assertEqual(routes[0]['via'], ovn_ipsec.VPN_TRANSIT_LIP) self.assertEqual(routes[0]['cidr'], site.local_cidr) self.assertEqual(routes[0]['device'], transit_dev) # check addresses in namespace addrs = device.addr.list(ip_version=lib_constants.IP_VERSION_4) addrs_dict = {a['name']: a for a in addrs} self.assertIn(transit_dev, addrs_dict) self.assertEqual( addrs_dict[transit_dev]['cidr'], transit_ns_port.external_ids[ovn_const.OVN_CIDRS_EXT_ID_KEY]) self.assertIn(gw_dev, addrs_dict) self.assertEqual( addrs_dict[gw_dev]['cidr'], vpn_gw_port.external_ids[ovn_const.OVN_CIDRS_EXT_ID_KEY]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/common/test_migrations_sync.py0000664000175000017500000000435400000000000031266 0ustar00zuulzuul00000000000000# 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. from oslo_config import cfg from neutron.db.migration.alembic_migrations import external from neutron.db.migration import cli as migration from neutron.tests.functional.db import test_migrations from neutron.tests.unit import testlib_api from neutron_vpnaas.db.models import head EXTERNAL_TABLES = set(external.TABLES) - set(external.VPNAAS_TABLES) VERSION_TABLE = 'alembic_version_vpnaas' class _TestModelsMigrationsVPNAAS(test_migrations._TestModelsMigrations): def db_sync(self, engine): cfg.CONF.set_override('connection', engine.url, group='database') for conf in migration.get_alembic_configs(): self.alembic_config = conf self.alembic_config.neutron_config = cfg.CONF migration.do_alembic_command(conf, 'upgrade', 'heads') def get_metadata(self): return head.get_metadata() def include_object(self, object_, name, type_, reflected, compare_to): if type_ == 'table' and (name.startswith('alembic') or name == VERSION_TABLE or name in EXTERNAL_TABLES): return False if type_ == 'index' and reflected and name.startswith("idx_autoinc_"): return False return True class TestModelsMigrationsMysql(testlib_api.MySQLTestCaseMixin, _TestModelsMigrationsVPNAAS, testlib_api.SqlTestCaseLight): pass class TestModelsMigrationsPostgresql(testlib_api.PostgreSQLTestCaseMixin, _TestModelsMigrationsVPNAAS, testlib_api.SqlTestCaseLight): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/common/test_scenario.py0000664000175000017500000007202000000000000027654 0ustar00zuulzuul00000000000000# 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 copy import functools from unittest import mock import netaddr from neutron.agent.common import ovs_lib from neutron.agent.l3 import l3_agent_extensions_manager as ext_manager from neutron.agent.l3 import namespaces as n_namespaces from neutron.agent.l3 import router_info from neutron.agent import l3_agent as l3_agent_main from neutron.agent.linux import external_process from neutron.agent.linux import interface from neutron.agent.linux import ip_lib from neutron.agent.linux import utils as linux_utils from neutron.common import config as common_config from neutron.common import utils as common_utils from neutron.conf.agent import common as agent_config from neutron.conf import common as conf_common from neutron.services.provider_configuration import serviceprovider_opts from neutron.tests.common import l3_test_common from neutron.tests.common import net_helpers from neutron.tests.functional.agent.l3 import framework from neutron_lib import constants from neutron_lib.utils import net as n_utils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import uuidutils import testtools from neutron_vpnaas.services.vpn import agent as vpn_agent from neutron_vpnaas.services.vpn.agent import vpn_agent_opts from neutron_vpnaas.services.vpn.device_drivers import ipsec _uuid = uuidutils.generate_uuid FAKE_IKE_POLICY = { 'auth_algorithm': 'sha1', "ike_version": "v1", 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'phase1_negotiation_mode': 'main', 'lifetime_units': 'seconds', 'lifetime_value': 3600 } FAKE_IPSEC_POLICY = { "encapsulation_mode": "tunnel", "encryption_algorithm": "aes-128", "pfs": "group5", "lifetime_units": "seconds", "lifetime_value": 3600, "transform_protocol": "esp", "auth_algorithm": "sha1", } FAKE_VPN_SERVICE = { "id": _uuid(), "router_id": _uuid(), "status": constants.PENDING_CREATE, "admin_state_up": True, 'external_ip': "172.24.4.8" } FAKE_IPSEC_CONNECTION = { "vpnservice_id": _uuid(), "status": "PENDING_CREATE", "psk": "969022489", "initiator": "bi-directional", "admin_state_up": True, "auth_mode": "psk", 'external_ip': "172.24.4.8", "peer_cidrs": ["10.100.255.224/28"], "mtu": 1500, "dpd_action": "hold", "dpd_interval": 30, "dpd_timeout": 120, "route_mode": "static", "ikepolicy": FAKE_IKE_POLICY, "ipsecpolicy": FAKE_IPSEC_POLICY, "peer_address": "172.24.4.8", "peer_id": "172.24.4.8", "id": _uuid() } FAKE_IKE_POLICY_SHA256 = { 'auth_algorithm': 'sha256', "ike_version": "v1", 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'phase1_negotiation_mode': 'main', 'lifetime_units': 'seconds', 'lifetime_value': 3600 } FAKE_IPSEC_POLICY_SHA256 = { "encapsulation_mode": "tunnel", "encryption_algorithm": "aes-128", "pfs": "group5", "lifetime_units": "seconds", "lifetime_value": 3600, "transform_protocol": "esp", "auth_algorithm": "sha256", } FAKE_IPSEC_CONNECTION_SHA256 = { "vpnservice_id": _uuid(), "status": "PENDING_CREATE", "psk": "969022489", "initiator": "bi-directional", "admin_state_up": True, "auth_mode": "psk", 'external_ip': "172.24.4.8", "peer_cidrs": ["10.100.255.224/28"], "mtu": 1500, "dpd_action": "hold", "dpd_interval": 30, "dpd_timeout": 120, "route_mode": "static", "ikepolicy": FAKE_IKE_POLICY_SHA256, "ipsecpolicy": FAKE_IPSEC_POLICY_SHA256, "peer_address": "172.24.4.8", "peer_id": "172.24.4.8", "id": _uuid() } PUBLIC_NET = netaddr.IPNetwork('19.4.4.0/24') PRIVATE_NET = netaddr.IPNetwork('35.4.0.0/16') FAKE_PUBLIC_SUBNET_ID = _uuid() FAKE_PRIVATE_SUBNET_ID = _uuid() FAKE_ROUTER = { 'enable_snat': True, 'gw_port': { 'network_id': _uuid(), 'subnets': [ { 'cidr': str(PUBLIC_NET), 'gateway_ip': str(PUBLIC_NET[1]), 'id': FAKE_PUBLIC_SUBNET_ID } ], 'fixed_ips': [ { 'subnet_id': FAKE_PUBLIC_SUBNET_ID, 'prefixlen': PUBLIC_NET.prefixlen, } ], }, 'distributed': False, '_floatingips': [], 'routes': [] } # It's a long name. NON_ASCII_VPNSERVICE_NAME = u'\u9577\u3044\u540d\u524d\u3067\u3059' # I'm doing very well. NON_ASCII_PSK = u'\u00e7a va tr\u00e9s bien' def get_ovs_bridge(br_name): return ovs_lib.OVSBridge(br_name) Vm = collections.namedtuple('Vm', ['namespace', 'port_ip']) class SiteInfo(object): """Holds info on the router, ports, service, and connection.""" def __init__(self, public_net, private_nets): self.public_net = public_net self.private_nets = private_nets self.generate_router_info() self._prepare_vpn_service_info() def _get_random_mac(self): mac_base = cfg.CONF.base_mac.split(':') return n_utils.get_random_mac(mac_base) def _generate_private_interface_for_router(self, subnet): subnet_id = _uuid() return { 'id': _uuid(), 'admin_state_up': True, 'network_id': _uuid(), 'mtu': 1500, 'mac_address': self._get_random_mac(), 'subnets': [ { 'ipv6_ra_mode': None, 'cidr': str(subnet), 'gateway_ip': str(subnet[1]), 'id': subnet_id, 'ipv6_address_mode': None } ], 'fixed_ips': [ { 'subnet_id': subnet_id, 'prefixlen': 24, 'ip_address': str(subnet[4]) } ] } def generate_router_info(self): self.info = copy.deepcopy(FAKE_ROUTER) self.info['id'] = _uuid() self.info['project_id'] = _uuid() self.info['_interfaces'] = [ self._generate_private_interface_for_router(subnet) for subnet in self.private_nets] self.info['gw_port']['id'] = _uuid() self.info['gw_port']['fixed_ips'][0]['ip_address'] = str( self.public_net) self.info['gw_port']['mac_address'] = self._get_random_mac() self.info['ha'] = False def _prepare_vpn_service_info(self): self.vpn_service = copy.deepcopy(FAKE_VPN_SERVICE) self.vpn_service.update({'id': _uuid(), 'router_id': self.info['id'], 'external_ip': str(self.public_net)}) def prepare_ipsec_conn_info(self, peer, connection=FAKE_IPSEC_CONNECTION, local_id=None, peer_id=None): ipsec_connection = copy.deepcopy(connection) local_cidrs = [str(s) for s in self.private_nets] peer_cidrs = [str(s) for s in peer.private_nets] ipsec_connection.update({ 'id': _uuid(), 'vpnservice_id': self.vpn_service['id'], 'external_ip': self.vpn_service['external_ip'], 'peer_cidrs': peer_cidrs, 'peer_address': peer.vpn_service['external_ip'], 'peer_id': peer.vpn_service['external_ip'], 'local_cidrs': local_cidrs, 'local_ip_vers': 4 }) if local_id: ipsec_connection['local_id'] = local_id if peer_id: ipsec_connection['peer_id'] = peer_id self.vpn_service['ipsec_site_connections'] = [ipsec_connection] class SiteInfoWithHaRouter(SiteInfo): def __init__(self, public_net, private_nets, host, failover_host): self.host = host self.failover_host = failover_host self.get_ns_name = mock.patch.object(n_namespaces.RouterNamespace, '_get_ns_name').start() super(SiteInfoWithHaRouter, self).__init__(public_net, private_nets) def generate_router_info(self): super(SiteInfoWithHaRouter, self).generate_router_info() self.info['ha'] = True self.info['ha_vr_id'] = 1 self.info[constants.HA_INTERFACE_KEY] = ( l3_test_common.get_ha_interface()) # Mock router namespace name, for when router is created self.get_ns_name.return_value = "qrouter-{0}-{1}".format( self.info['id'], self.host) def generate_backup_router_info(self): # Clone router info, using different HA interface (using same ID) info = copy.deepcopy(self.info) info[constants.HA_INTERFACE_KEY] = ( l3_test_common.get_ha_interface(ip='169.254.192.2', mac='22:22:22:22:22:22')) # Mock router namespace name, for when router is created self.get_ns_name.return_value = "qrouter-{0}-{1}".format( info['id'], self.failover_host) return info class TestIPSecBase(framework.L3AgentTestFramework): NESTED_NAMESPACE_SEPARATOR = '@' def setUp(self): super(TestIPSecBase, self).setUp() common_config.register_common_config_options() mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.' 'IPsecVpnDriverApi').start() # avoid report_status running periodically mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall').start() # Both the vpn agents try to use execute_rootwrap_daemon's socket # simultaneously during test cleanup, but execute_rootwrap_daemon has # limitations with simultaneous reads. So avoid using # root_helper_daemon and instead use root_helper # https://bugs.launchpad.net/neutron/+bug/1482622 cfg.CONF.set_override('root_helper_daemon', None, group='AGENT') # Mock the method below because it causes Exception: # RuntimeError: Second simultaneous read on fileno 5 detected. # Unless you really know what you're doing, make sure that only # one greenthread can read any particular socket. Consider using # a pools.Pool. If you do know what you're doing and want to disable # this error, call eventlet.debug.hub_prevent_multiple_readers(False) # Can reproduce the exception in the test only ip_lib.send_ip_addr_adv_notif = mock.Mock() self.vpn_agent = vpn_agent.L3WithVPNaaS(self.conf) self.driver = self.vpn_agent.device_drivers[0] self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[]) self.driver.report_status = mock.Mock() self.private_nets = list(PRIVATE_NET.subnet(24)) def _connect_agents(self, agent1, agent2): """Simulate both agents in the same host. For packet flow between resources connected to these two agents, agent's ovs bridges are connected through patch ports. """ br_int_1 = get_ovs_bridge(agent1.conf.OVS.integration_bridge) br_int_2 = get_ovs_bridge(agent2.conf.OVS.integration_bridge) net_helpers.create_patch_ports(br_int_1, br_int_2) def _get_config_opts(self): """Register default config options""" config = cfg.ConfigOpts() config.register_opts(conf_common.core_opts) config.register_opts(conf_common.core_cli_opts) config.register_opts(serviceprovider_opts, 'service_providers') config.register_opts(vpn_agent_opts, 'vpnagent') config.register_opts(ipsec.ipsec_opts, 'ipsec') config.register_opts(ipsec.openswan_opts, 'openswan') logging.register_options(config) agent_config.register_process_monitor_opts(config) ext_manager.register_opts(config) return config def _configure_agent(self, host): """Override specific config options""" config = self._get_config_opts() l3_agent_main.register_opts(config) cfg.CONF.set_override('debug', True) agent_config.setup_logging() config.set_override('extensions', ['vpnaas'], 'agent') config.set_override( 'interface_driver', 'neutron.agent.linux.interface.OVSInterfaceDriver') br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge config.set_override('integration_bridge', br_int.br_name, 'OVS') temp_dir = self.get_new_temp_dir() get_temp_file_path = functools.partial(self.get_temp_file_path, root=temp_dir) config.set_override('state_path', temp_dir.path) config.set_override('metadata_proxy_socket', get_temp_file_path('metadata_proxy')) config.set_override('ha_confs_path', get_temp_file_path('ha_confs')) config.set_override('external_pids', get_temp_file_path('external/pids')) config.set_override('host', host) ipsec_config_base_dir = '%s/%s' % (temp_dir.path, 'ipsec') config.set_override('config_base_dir', ipsec_config_base_dir, group='ipsec') # Assign ip address to br-int port because it is a gateway ex_port = ip_lib.IPDevice(br_int.br_name) ex_port.addr.add(str(PUBLIC_NET[1])) return config def _setup_failover_agent(self): self.failover_agent = self._configure_agent('agent2') self._connect_agents(self.vpn_agent, self.failover_agent) self.failover_driver = self.failover_agent.device_drivers[0] self.failover_driver.agent_rpc.get_vpn_services_on_host = ( mock.Mock(return_value=[])) self.failover_driver.report_status = mock.Mock() def create_router(self, agent, info): """Create router for agent from router info.""" self.addCleanup(agent._safe_router_removed, info['id']) # Generate unique internal and external router device names using the # agent's hostname. This is to allow multiple HA router replicas to # co-exist on the same machine, otherwise they'd all use the same # device names and OVS would freak out(OVS won't allow a port with # same name connected to two bridges). def _append_suffix(dev_name): # If dev_name = 'xyz123' and the suffix is 'agent2' then the result # will be 'xy-nt2' return "{0}-{1}".format(dev_name[:-4], agent.host[-3:]) def get_internal_device_name(port_id): return _append_suffix( (n_namespaces.INTERNAL_DEV_PREFIX + port_id) [:interface.LinuxInterfaceDriver.DEV_NAME_LEN]) def get_external_device_name(port_id): return _append_suffix( (n_namespaces.EXTERNAL_DEV_PREFIX + port_id) [:interface.LinuxInterfaceDriver.DEV_NAME_LEN]) mock_get_internal_device_name = mock.patch.object( router_info.RouterInfo, 'get_internal_device_name').start() mock_get_internal_device_name.side_effect = get_internal_device_name mock_get_external_device_name = mock.patch.object( router_info.RouterInfo, 'get_external_device_name').start() mock_get_external_device_name.side_effect = get_external_device_name # NOTE(huntxu): with commit 88f5e11d8bf, neutron plugs new ports as # dead vlan(4095). During functional tests, all the ports are untagged. # So need to remove such tag during functional testing. original_plug_new = interface.OVSInterfaceDriver.plug_new def plug_new(self, *args, **kwargs): original_plug_new(self, *args, **kwargs) bridge = (kwargs.get('bridge') or args[4] or self.conf.OVS.integration_bridge) device_name = kwargs.get('device_name') or args[2] ovsbr = ovs_lib.OVSBridge(bridge) ovsbr.clear_db_attribute('Port', device_name, 'tag') with mock.patch( 'neutron.agent.linux.interface.OVSInterfaceDriver.plug_new', autospec=True ) as ovs_plug_new: ovs_plug_new.side_effect = plug_new agent._process_added_router(info) return agent.router_info[info['id']] def _port_first_ip_cidr(self, port): fixed_ip = port['fixed_ips'][0] return common_utils.ip_to_cidr(fixed_ip['ip_address'], fixed_ip['prefixlen']) def create_ports_for(self, site): """Creates namespaces and ports for simulated VM. There will be a unique namespace for each port, which is representing a VM for the test. """ bridge = get_ovs_bridge(self.vpn_agent.conf.OVS.integration_bridge) site.vm = [] for internal_port in site.router.internal_ports: router_ip_cidr = self._port_first_ip_cidr(internal_port) port_ip_cidr = net_helpers.increment_ip_cidr(router_ip_cidr, 1) client_ns = self.useFixture( net_helpers.NamespaceFixture()).ip_wrapper namespace = client_ns.namespace port = self.useFixture( net_helpers.OVSPortFixture(bridge, namespace)).port port.addr.add(port_ip_cidr) port.route.add_gateway(router_ip_cidr.partition('/')[0]) site.vm.append(Vm(namespace, port_ip_cidr.partition('/')[0])) def create_site(self, public_net, private_nets, l3ha=False): """Build router(s), namespaces, and ports for a site. For HA, we'll create a backup router and wait for both routers to be ready, so that we can test pings after failover. """ if l3ha: site = SiteInfoWithHaRouter(public_net, private_nets, self.agent.host, self.failover_agent.host) else: site = SiteInfo(public_net, private_nets) site.router = self.create_router(self.agent, site.info) if l3ha: backup_info = site.generate_backup_router_info() site.backup_router = self.create_router(self.failover_agent, backup_info) linux_utils.wait_until_true( lambda: site.router.ha_state in ('master', 'primary')) linux_utils.wait_until_true( lambda: site.backup_router.ha_state == 'backup') self.create_ports_for(site) return site def prepare_ipsec_site_connections(self, site1, site2): """Builds info for connections in both directions in prep for sync.""" site1.prepare_ipsec_conn_info(site2) site2.prepare_ipsec_conn_info(site1) def prepare_ipsec_site_connections_sha256(self, site1, site2): """Builds info for connections in both directions in prep for sync.""" site1.prepare_ipsec_conn_info(site2, FAKE_IPSEC_CONNECTION_SHA256) site2.prepare_ipsec_conn_info(site1, FAKE_IPSEC_CONNECTION_SHA256) def prepare_ipsec_site_connections_local_id(self, site1, site2): """Builds info for connections in both directions in prep for sync.""" site1.prepare_ipsec_conn_info(site2, local_id='@site1.com', peer_id='@site2.com') site2.prepare_ipsec_conn_info(site1, local_id='@site2.com', peer_id='@site1.com') def sync_to_create_ipsec_connections(self, site1, site2): """Perform a sync, so that connections are created.""" # Provide service info to sync self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[site1.vpn_service, site2.vpn_service]) local_router_id = site1.router.router_id peer_router_id = site2.router.router_id self.driver.sync(mock.Mock(), [{'id': local_router_id}, {'id': peer_router_id}]) self.agent._process_updated_router(site1.router.router) self.agent._process_updated_router(site2.router.router) self.addCleanup(self.driver._delete_vpn_processes, [local_router_id, peer_router_id], []) def sync_failover_agent(self, site): """Perform a sync on failover agent associated w/backup router.""" self.failover_driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[site.vpn_service]) self.failover_driver.sync(mock.Mock(), [{'id': site.backup_router.router_id}]) def check_ping(self, from_site, to_site, instance=0, success=True): if success: net_helpers.assert_ping(from_site.vm[instance].namespace, to_site.vm[instance].port_ip, timeout=8, count=4) else: net_helpers.assert_no_ping(from_site.vm[instance].namespace, to_site.vm[instance].port_ip, timeout=8, count=4) def _failover_ha_router(self, router1, router2): """Cause a failover of HA router. Fail the agent1's HA router. Agent1's HA router will transition to backup and agent2's HA router will become master. Wait for the failover to complete. """ device_name = router1.get_ha_device_name() ha_device = ip_lib.IPDevice(device_name, router1.ns_name) ha_device.link.set_down() linux_utils.wait_until_true( lambda: router2.ha_state in ('master', 'primary')) linux_utils.wait_until_true(lambda: router1.ha_state == 'backup') def _ipsec_process_exists(self, conf, router, pid_files): """Check if *Swan process has started up.""" for pid_file in pid_files: pm = external_process.ProcessManager( conf, "ipsec", router.ns_name, pid_file=pid_file) if pm.active: break return pm.active def _wait_for_ipsec_startup(self, router, driver, conf, should_run=True): """Wait for new IPSec process on failover agent to start up.""" # check for both strongswan and openswan processes path = driver.processes[router.router_id].config_dir pid_files = ['%s/var/run/charon.pid' % path, '%s/var/run/pluto.pid' % path] linux_utils.wait_until_true( lambda: should_run == self._ipsec_process_exists( conf, router, pid_files)) @staticmethod def _update_vpnservice(site, **kwargs): site.vpn_service.update(kwargs) @staticmethod def _update_ipsec_connection(site, **kwargs): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection.update(kwargs) class TestIPSecScenario(TestIPSecBase): @testtools.skip('bug/1598466') def test_single_ipsec_connection(self): site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_single_ipsec_connection_sha256(self): site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections_sha256(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_single_ipsec_connection_local_id(self): site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections_local_id(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_ipsec_site_connections_with_mulitple_subnets(self): """Check with a pair of subnets on each end of connection.""" site1 = self.create_site(PUBLIC_NET[4], self.private_nets[1:3]) site2 = self.create_site(PUBLIC_NET[5], self.private_nets[3:5]) # Just check from each VM, not every combination for i in [0, 1]: self.check_ping(site1, site2, instance=i, success=False) self.check_ping(site2, site1, instance=i, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) for i in [0, 1]: self.check_ping(site1, site2, instance=i) self.check_ping(site2, site1, instance=i) @testtools.skip('bug/1598466') def test_ipsec_site_connections_with_l3ha_routers(self): """Test ipsec site connection with HA routers. This test creates two agents. First agent will have Legacy and HA routers. Second agent will host only HA router. We setup ipsec connection between legacy and HA router. When HA router is created, agent1 will have master router and agent2 will have backup router. Ipsec connection will be established between legacy router and agent1's master HA router. Then we fail the agent1's master HA router. Agent1's HA router will transition to backup and agent2's HA router will become master. Now ipsec connection will be established between legacy router and agent2's master HA router """ self._setup_failover_agent() site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]], l3ha=True) # No ipsec connection between legacy router and HA routers self.check_ping(site1, site2, 0, success=False) self.check_ping(site2, site1, 0, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.sync_failover_agent(site2) # Test ipsec connection between legacy router and agent2's HA router self.check_ping(site1, site2, 0) self.check_ping(site2, site1, 0) self._failover_ha_router(site2.router, site2.backup_router) self._wait_for_ipsec_startup(site2.backup_router, self.failover_driver, self.failover_agent.conf) # Test ipsec connection between legacy router and agent2's HA router self.check_ping(site1, site2, 0) self.check_ping(site2, site1, 0) @testtools.skip('bug/1598466') def _test_admin_state_up(self, update_method): # Create ipsec connection between two sites site1 = self.create_site(PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(PUBLIC_NET[5], [self.private_nets[2]]) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) # Disable resource on one of the sites and check that # ping no longer passes. update_method(site1, admin_state_up=False) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2, 0, success=False) self.check_ping(site2, site1, 0, success=False) # Validate that ipsec process for the disabled site was terminated. self._wait_for_ipsec_startup(site1.router, self.driver, self.vpn_agent.conf, should_run=False) # Change admin_state_up of the disabled resource back to True and # check that everything works again. update_method(site1, admin_state_up=True) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) @testtools.skip('bug/1598466') def test_ipsec_site_connections_update_admin_state_up(self): """Test updating admin_state_up of ipsec site connections.""" self._test_admin_state_up(self._update_ipsec_connection) @testtools.skip('bug/1598466') def test_vpnservice_update_admin_state_up(self): """Test updating admin_state_up of a vpn service.""" self._test_admin_state_up(self._update_vpnservice) ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/openswan/0000775000175000017500000000000000000000000025001 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/openswan/README0000664000175000017500000000020000000000000025651 0ustar00zuulzuul00000000000000This area holds tests for the OpenSwan implementation (only). It will also run tests in neutron_vpnaas/tests/functional/common. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/openswan/__init__.py0000664000175000017500000000171200000000000027113 0ustar00zuulzuul00000000000000# 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 def load_tests(loader, tests, pattern): this_dir = os.path.dirname(__file__) openswan_tests = loader.discover(start_dir=this_dir, pattern=pattern) tests.addTests(openswan_tests) common_dir = os.path.abspath(os.path.join(this_dir, "../common")) common_tests = loader.discover(start_dir=common_dir, pattern=pattern) tests.addTests(common_tests) return tests ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py0000664000175000017500000001713300000000000031624 0ustar00zuulzuul00000000000000# Copyright (c) 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 unittest import mock from neutron.agent.linux import ip_lib from neutron.agent.linux import utils as linux_utils from neutron_vpnaas.services.vpn.device_drivers import ipsec from neutron_vpnaas.tests.functional.common import test_scenario from oslo_config import cfg class TestOpenSwanDeviceDriver(test_scenario.TestIPSecBase): """Test the OpenSwan reference implementation of the device driver.""" # NOTE: Tests may be added/removed/changed, when this is fleshed out # in future commits. def _ping_mtu(self, from_site, to_site, size, instance=0): """Pings ip address using packets of given size and with DF=1. In order to ping it uses following cli command: ip netns exec ping -c 4 -M do -s """ namespace = from_site.vm[instance].namespace ip = to_site.vm[instance].port_ip try: cmd = ['ping', '-c', 4, '-M', 'do', '-s', size, ip] cmd = ip_lib.add_namespace_to_cmd(cmd, namespace) linux_utils.execute(cmd, run_as_root=True) return True except RuntimeError: return False def test_process_created_on_ipsec_connection_create(self): """Check that pluto process is running.""" pass def test_connection_status_with_one_side_of_ipsec_connection(self): """Check status of connection, with only one end created. Expect that the status will indicate that the connection is down. """ pass def test_process_gone_on_ipsec_connection_delete(self): """Verify that there is no longer a process, upon deletion.""" pass def test_cached_status_on_create_and_delete(self): """Test that the status is cached.""" pass def test_status_reporting(self): """Test status reported correctly to agent.""" pass def _override_mtu_for_site(self, site, mtu): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['mtu'] = mtu def test_ipsec_site_connections_mtu_enforcement(self): """Test that mtu of ipsec site connections is enforced.""" site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) # Set up non-default mtu value self._override_mtu_for_site(site1, 1200) self._override_mtu_for_site(site2, 1200) self.sync_to_create_ipsec_connections(site1, site2) # Validate that ip packets with 1172 (1200) bytes of data pass self.assertTrue(self._ping_mtu(site1, site2, 1172)) self.assertTrue(self._ping_mtu(site2, site1, 1172)) # Validate that ip packets with 1173 (1201) bytes of data are dropped self.assertFalse(self._ping_mtu(site1, site2, 1173)) self.assertFalse(self._ping_mtu(site2, site1, 1173)) def test_no_config_change_skip_restart(self): """Test when config is not changed, then restart should be skipped""" site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) with mock.patch.object(ipsec.OpenSwanProcess, 'start') as my_start: ipsec.OpenSwanProcess.active = mock.patch.object( ipsec.OpenSwanProcess, 'active', return_value=True).start() ipsec.OpenSwanProcess._config_changed = mock.patch.object( ipsec.OpenSwanProcess, '_config_changed', return_value=False).start() self.sync_to_create_ipsec_connections(site1, site2) # when restart_check_config is not set, start will be # called in first sync self.assertEqual(1, my_start.call_count) my_start.reset_mock() cfg.CONF.set_override('restart_check_config', True, group='pluto') self.sync_to_create_ipsec_connections(site1, site2) # after restart_check_config enabled, then start will # not be called, since no config changes my_start.assert_not_called() ipsec.OpenSwanProcess.active.stop() ipsec.OpenSwanProcess._config_changed.stop() cfg.CONF.set_override('restart_check_config', False, group='pluto') def test_openswan_connection_with_non_ascii_vpnservice_name(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) site1.vpn_service.update( {'name': test_scenario.NON_ASCII_VPNSERVICE_NAME}) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) def test_openswan_connection_with_non_ascii_psk(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._update_ipsec_connection(site1, psk=test_scenario.NON_ASCII_PSK) self._update_ipsec_connection(site2, psk=test_scenario.NON_ASCII_PSK) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) def test_openswan_connection_with_wrong_non_ascii_psk(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._update_ipsec_connection(site1, psk=test_scenario.NON_ASCII_PSK) self._update_ipsec_connection(site2, psk=test_scenario.NON_ASCII_PSK[:-1]) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/openswan/test_ovn_openswan.py0000664000175000017500000000146600000000000031135 0ustar00zuulzuul00000000000000# Copyright 2023 SysEleven GmbH # # 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.tests.functional.common import test_ovn class TestOvnOpenSwan(test_ovn.TestOvnVPNAgentBase): VPN_DEVICE_DRIVER = ('neutron_vpnaas.services.vpn.device_drivers.' 'ovn_ipsec.OvnOpenSwanDriver') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/requirements.txt0000664000175000017500000000101400000000000026427 0ustar00zuulzuul00000000000000# Additional requirements for functional tests # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. psycopg2 PyMySQL>=0.6.2 # MIT License # Make sure that mock is installed in functional environment for # neutron. This needs to be removed from master as soon as new neutron # is released from Victoria, as mock is removed in development branch. mock>=2.0.0 # BSD ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/strongswan/0000775000175000017500000000000000000000000025354 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/strongswan/README0000664000175000017500000000020200000000000026226 0ustar00zuulzuul00000000000000This area holds tests for the StrongSwan implementation (only). It will also run tests in neutron_vpnaas/tests/functional/common. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/strongswan/__init__.py0000664000175000017500000000167500000000000027476 0ustar00zuulzuul00000000000000# 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 def load_tests(loader, tests, pattern): this_dir = os.path.dirname(__file__) strongswan_tests = loader.discover(start_dir=this_dir, pattern=pattern) tests.addTests(strongswan_tests) common_dir = os.path.join(this_dir, "../common") common_tests = loader.discover(start_dir=common_dir, pattern=pattern) tests.addTests(common_tests) return tests ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/strongswan/test_netns_wrapper.py0000664000175000017500000000431300000000000031655 0ustar00zuulzuul00000000000000# 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 re from neutron.agent.linux import utils from neutron.conf.agent import common as config from neutron.tests.common import net_helpers from neutron.tests.functional import base WRAPPER_SCRIPT = 'neutron-vpn-netns-wrapper' STATUS_PATTERN = re.compile('Command:.*ip.*addr.*show.*Exit code: 0') class TestNetnsWrapper(base.BaseSudoTestCase): def setUp(self): super(TestNetnsWrapper, self).setUp() config.setup_logging() self.fake_ns = 'func-8f1b728c-6eca-4042-9b6b-6ef66ab9352a' self.mount_paths = ('--mount_paths=/etc:/var/lib/neutron' '/vpnaas/%(ns)s/etc,/var/run:/var/lib' '/neutron/vpnaas/%(ns)s/var/run') self.fake_pth = self.mount_paths % {'ns': self.fake_ns} def test_netns_wrap_success(self): client_ns = self.useFixture(net_helpers.NamespaceFixture()).ip_wrapper ns = client_ns.namespace pth = self.mount_paths % {'ns': ns} cmd = WRAPPER_SCRIPT, pth, '--cmd=ip,addr,show' output = client_ns.netns.execute(cmd) self.assertTrue(STATUS_PATTERN.search(output)) def test_netns_wrap_fail_without_netns(self): cmd = [WRAPPER_SCRIPT, self.fake_pth, '--cmd=ip,addr,show'] self.assertRaises(RuntimeError, utils.execute, cmd=cmd, run_as_root=True) def test_netns_wrap_unauthorized_command(self): cmd = [WRAPPER_SCRIPT, self.fake_pth, '--cmd=nofiltercommand'] self.assertRaises(RuntimeError, utils.execute, cmd=cmd, run_as_root=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/strongswan/test_ovn_strongswan.py0000664000175000017500000000147200000000000032060 0ustar00zuulzuul00000000000000# Copyright 2023 SysEleven GmbH # # 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.tests.functional.common import ovn_base class TestOvnStrongSwan(ovn_base.TestOvnVPNAgentBase): VPN_DEVICE_DRIVER = ('neutron_vpnaas.services.vpn.device_drivers.' 'ovn_ipsec.OvnStrongSwanDriver') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/functional/strongswan/test_strongswan_driver.py0000664000175000017500000003011000000000000032540 0ustar00zuulzuul00000000000000# 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 unittest import mock from neutron.agent.l3 import agent as neutron_l3_agent from neutron.agent.l3 import legacy_router from neutron.conf.agent.l3 import config as l3_config from neutron.tests.functional import base from neutron_lib import constants from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.services.vpn import agent as vpn_agent from neutron_vpnaas.services.vpn.device_drivers import ipsec from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec from neutron_vpnaas.tests.functional.common import test_scenario _uuid = uuidutils.generate_uuid FAKE_ROUTER_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION1_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION2_ID = _uuid() FAKE_IKE_POLICY = { 'ike_version': 'v1', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5' } FAKE_IPSEC_POLICY = { 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5' } FAKE_VPN_SERVICE = { 'id': _uuid(), 'router_id': FAKE_ROUTER_ID, 'name': 'myvpn', 'admin_state_up': True, 'status': constants.PENDING_CREATE, 'external_ip': '50.0.0.4', 'subnet': {'cidr': '10.0.0.0/24'}, 'ipsec_site_connections': [ {'peer_cidrs': ['20.0.0.0/24', '30.0.0.0/24'], 'id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'external_ip': '50.0.0.4', 'peer_address': '30.0.0.5', 'peer_id': '30.0.0.5', 'psk': 'password', 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'status': constants.PENDING_CREATE}, {'peer_cidrs': ['40.0.0.0/24', '50.0.0.0/24'], 'external_ip': '50.0.0.4', 'peer_address': '50.0.0.5', 'peer_id': '50.0.0.5', 'psk': 'password', 'id': FAKE_IPSEC_SITE_CONNECTION2_ID, 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'status': constants.PENDING_CREATE}] } DESIRED_CONN_STATUS = {FAKE_IPSEC_SITE_CONNECTION1_ID: {'status': 'DOWN', 'updated_pending_status': False}, FAKE_IPSEC_SITE_CONNECTION2_ID: {'status': 'DOWN', 'updated_pending_status': False}} FAKE_IKE_POLICY2 = { 'ike_version': 'v1', 'encryption_algorithm': 'aes-256', 'auth_algorithm': 'sha1', 'pfs': 'group2', 'lifetime_value': 1800 } FAKE_IPSEC_POLICY2 = { 'encryption_algorithm': 'aes-256', 'auth_algorithm': 'sha1', 'pfs': 'group2', 'transform_protocol': 'esp', 'lifetime_value': 1800, 'encapsulation_mode': 'tunnel' } class TestStrongSwanDeviceDriver(base.BaseSudoTestCase): """Test the StrongSwan reference implementation of the device driver.""" def setUp(self): super(TestStrongSwanDeviceDriver, self).setUp() self.conf = cfg.CONF self.conf.register_opts(l3_config.OPTS) self.conf.register_opts(ipsec.ipsec_opts, 'ipsec') self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') self.conf.set_override('state_path', '/tmp') ri_kwargs = {'router': {'id': FAKE_ROUTER_ID}, 'agent_conf': self.conf, 'interface_driver': mock.sentinel.interface_driver} self.router = legacy_router.LegacyRouter(router_id=FAKE_ROUTER_ID, agent=mock.Mock(), **ri_kwargs) self.router.router['distributed'] = False self.router_id = FAKE_VPN_SERVICE['router_id'] looping_call_p = mock.patch( 'oslo_service.loopingcall.FixedIntervalLoopingCall') looping_call_p.start() vpn_service = mock.Mock() vpn_service.conf = self.conf self.driver = strongswan_ipsec.StrongSwanDriver( vpn_service, host=mock.sentinel.host) self.driver.routers[FAKE_ROUTER_ID] = self.router self.driver.agent_rpc = mock.Mock() self.driver._update_nat = mock.Mock() self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ FAKE_VPN_SERVICE] self.addCleanup(self.driver.destroy_router, self.router_id) self.router.router_namespace.create() self.addCleanup(self.router.router_namespace.delete) def test_process_lifecycle(self): """ Lifecycle test that validates that the strongswan process could be launched, that a connection could be successfully initiated through it, and then that it could be terminated and clean up after itself. """ process = self.driver.ensure_process(self.router_id, FAKE_VPN_SERVICE) process.enable() self.assertTrue(process.active) self.assertIn(self.router_id, self.driver.processes) self.assertEqual(DESIRED_CONN_STATUS, process.connection_status) self.assertIsNotNone(process.namespace) conf_dir = os.path.join(self.conf.ipsec.config_base_dir, self.router_id) self.assertTrue(os.path.exists(conf_dir)) process.disable() self.assertFalse(process.active) self.assertFalse(process.connection_status) self.assertFalse(os.path.exists(conf_dir)) class TestStrongSwanScenario(test_scenario.TestIPSecBase): def setUp(self): super(TestStrongSwanScenario, self).setUp() self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') VPNAAS_STRONGSWAN_DEVICE = ('neutron_vpnaas.services.vpn.' 'device_drivers.strongswan_ipsec.' 'StrongSwanDriver') cfg.CONF.set_override('vpn_device_driver', [VPNAAS_STRONGSWAN_DEVICE], 'vpnagent') self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1', self.conf) self.vpn_agent = vpn_agent.L3WithVPNaaS(self.conf) vpn_service = mock.Mock() vpn_service.conf = self.conf self.driver = strongswan_ipsec.StrongSwanDriver( vpn_service, host=mock.sentinel.host) def _override_ikepolicy_for_site(self, site, ikepolicy): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['ikepolicy'] = ikepolicy def _override_ipsecpolicy_for_site(self, site, ipsecpolicy): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['ipsecpolicy'] = ipsecpolicy def _override_dpd_for_site(self, site, dpdaction, dpddelay, dpdtimeout): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['dpd_action'] = dpdaction ipsec_connection['dpd_interval'] = dpddelay ipsec_connection['dpd_timeout'] = dpdtimeout def _override_auth_algorithm_for_site(self, site, auth): ipsec_connection = site.vpn_service['ipsec_site_connections'][0] ipsec_connection['ipsecpolicy']['auth_algorithm'] = auth ipsec_connection['ikepolicy']['auth_algorithm'] = auth def test_strongswan_connection_with_non_default_value(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._override_ikepolicy_for_site(site1, FAKE_IKE_POLICY2) self._override_ikepolicy_for_site(site2, FAKE_IKE_POLICY2) self._override_ipsecpolicy_for_site(site1, FAKE_IPSEC_POLICY2) self._override_ipsecpolicy_for_site(site2, FAKE_IPSEC_POLICY2) self._override_dpd_for_site(site1, 'hold', 60, 240) self._override_dpd_for_site(site2, 'hold', 60, 240) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) def _test_strongswan_connection_with_auth_algo(self, auth_algo): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._override_auth_algorithm_for_site(site1, auth_algo) self._override_auth_algorithm_for_site(site2, auth_algo) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) def test_strongswan_connection_with_sha256(self): self._test_strongswan_connection_with_auth_algo('sha256') def test_strongswan_connection_with_sha384(self): self._test_strongswan_connection_with_auth_algo('sha384') def test_strongswan_connection_with_sha512(self): self._test_strongswan_connection_with_auth_algo('sha512') def test_strongswan_connection_with_non_ascii_vpnservice_name(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) site1.vpn_service.update( {'name': test_scenario.NON_ASCII_VPNSERVICE_NAME}) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) def test_strongswan_connection_with_non_ascii_psk(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._update_ipsec_connection(site1, psk=test_scenario.NON_ASCII_PSK) self._update_ipsec_connection(site2, psk=test_scenario.NON_ASCII_PSK) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2) self.check_ping(site2, site1) def test_strongswan_connection_with_wrong_non_ascii_psk(self): site1 = self.create_site(test_scenario.PUBLIC_NET[4], [self.private_nets[1]]) site2 = self.create_site(test_scenario.PUBLIC_NET[5], [self.private_nets[2]]) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) self.prepare_ipsec_site_connections(site1, site2) self._update_ipsec_connection(site1, psk=test_scenario.NON_ASCII_PSK) self._update_ipsec_connection(site2, psk=test_scenario.NON_ASCII_PSK[:-1]) self.sync_to_create_ipsec_connections(site1, site2) self.check_ping(site1, site2, success=False) self.check_ping(site2, site1, success=False) ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/0000775000175000017500000000000000000000000021764 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/__init__.py0000664000175000017500000000126700000000000024103 0ustar00zuulzuul00000000000000# 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. from oslo_config import cfg cfg.CONF.use_stderr = False ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/0000775000175000017500000000000000000000000022351 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/__init__.py0000664000175000017500000000000000000000000024450 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/vpn/0000775000175000017500000000000000000000000023154 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/vpn/__init__.py0000664000175000017500000000000000000000000025253 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/vpn/test_vpn_agentschedulers_db.py0000664000175000017500000005417200000000000031306 0ustar00zuulzuul00000000000000# Copyright 2023 SysEleven GmbH. # # 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 unittest import mock from neutron.api import extensions from neutron.common.ovn import constants as ovn_constants from neutron import policy from neutron.tests.common import helpers from neutron.tests.unit.api import test_extensions from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin from neutron.tests.unit.extensions import test_l3 from neutron.tests.unit import testlib_api from neutron import wsgi from neutron_lib import context from neutron_lib import exceptions as n_exc from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from neutron_lib import rpc as n_rpc from oslo_db import exception as db_exc import oslo_messaging from oslo_utils import uuidutils from sqlalchemy import orm from webob import exc from neutron_vpnaas.api.rpc.agentnotifiers import vpn_rpc_agent_api from neutron_vpnaas.extensions import vpn_agentschedulers from neutron_vpnaas.services.vpn.common import constants from neutron_vpnaas.tests.unit.db.vpn import test_vpn_db VPN_HOSTA = "host-1" VPN_HOSTB = "host-2" class VPNAgentSchedulerTestMixIn(object): def _request_list(self, path, admin_context=True, expected_code=exc.HTTPOk.code): req = self._path_req(path, admin_context=admin_context) res = req.get_response(self.ext_api) self.assertEqual(expected_code, res.status_int) return self.deserialize(self.fmt, res) def _path_req(self, path, method='GET', data=None, query_string=None, admin_context=True): content_type = 'application/%s' % self.fmt body = None if data is not None: # empty dict is valid body = wsgi.Serializer().serialize(data, content_type) if admin_context: return testlib_api.create_request( path, body, content_type, method, query_string=query_string) else: return testlib_api.create_request( path, body, content_type, method, query_string=query_string, context=context.Context('', 'tenant_id')) def _path_create_request(self, path, data, admin_context=True): return self._path_req(path, method='POST', data=data, admin_context=admin_context) def _path_show_request(self, path, admin_context=True): return self._path_req(path, admin_context=admin_context) def _path_delete_request(self, path, admin_context=True): return self._path_req(path, method='DELETE', admin_context=admin_context) def _path_update_request(self, path, data, admin_context=True): return self._path_req(path, method='PUT', data=data, admin_context=admin_context) def _list_routers_hosted_by_vpn_agent(self, agent_id, expected_code=exc.HTTPOk.code, admin_context=True): path = "/agents/%s/%s.%s" % (agent_id, vpn_agentschedulers.VPN_ROUTERS, self.fmt) return self._request_list(path, expected_code=expected_code, admin_context=admin_context) def _add_router_to_vpn_agent(self, id, router_id, expected_code=exc.HTTPCreated.code, admin_context=True): path = "/agents/%s/%s.%s" % (id, vpn_agentschedulers.VPN_ROUTERS, self.fmt) req = self._path_create_request(path, {'router_id': router_id}, admin_context=admin_context) res = req.get_response(self.ext_api) self.assertEqual(expected_code, res.status_int) def _list_vpn_agents_hosting_router(self, router_id, expected_code=exc.HTTPOk.code, admin_context=True): path = "/routers/%s/%s.%s" % (router_id, vpn_agentschedulers.VPN_AGENTS, self.fmt) return self._request_list(path, expected_code=expected_code, admin_context=admin_context) def _remove_router_from_vpn_agent(self, id, router_id, expected_code=exc.HTTPNoContent.code, admin_context=True): path = "/agents/%s/%s/%s.%s" % (id, vpn_agentschedulers.VPN_ROUTERS, router_id, self.fmt) req = self._path_delete_request(path, admin_context=admin_context) res = req.get_response(self.ext_api) self.assertEqual(expected_code, res.status_int) class VPNAgentSchedulerTestCaseBase(test_vpn_db.VPNTestMixin, test_l3.L3NatTestCaseMixin, VPNAgentSchedulerTestMixIn, test_plugin.NeutronDbPluginV2TestCase): fmt = 'json' def setUp(self): # NOTE(ivasilevskaya) mocking this way allows some control over mocked # client like further method mocking with asserting calls self.client_mock = mock.MagicMock(name="mocked client") mock.patch.object( n_rpc, 'get_client').start().return_value = self.client_mock service_plugins = { 'vpnaas_plugin': 'neutron_vpnaas.services.vpn.ovn_plugin.' 'VPNOVNPlugin'} plugin_str = 'neutron.tests.unit.extensions.test_l3.TestL3NatIntPlugin' super().setUp(plugin_str, service_plugins=service_plugins) ext_mgr = extensions.PluginAwareExtensionManager.get_instance() self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr) self.adminContext = context.get_admin_context() self.core_plugin = directory.get_plugin() self.core_plugin.get_agents = \ mock.MagicMock(side_effect=self._get_agents) self.core_plugin.get_agent = \ mock.MagicMock(side_effect=self._get_agent) self._agents = {} self._vpn_agents_by_host = {} self.service_plugin = directory.get_plugin(plugin_constants.VPN) policy.init() def _get_agents(self, context, filters=None): if not filters: return self._agents.values() agents = [] for agent in self._agents.values(): for key, values in filters.items(): if agent[key] not in values: break else: agents.append(agent) return agents def _get_agent(self, context, agent_id): try: return self._agents[agent_id] except KeyError: raise n_exc.agent.AgentNotFound(id=agent_id) def _get_any_metadata_agent_id(self): for agent in self._agents.values(): if agent['agent_type'] == ovn_constants.OVN_METADATA_AGENT: return agent['id'] def _take_down_vpn_agent(self, host): self._vpn_agents_by_host[host]['alive'] = False def _get_another_agent_host(self, host): for agent in self._vpn_agents_by_host.values(): if agent['host'] != host: return agent['host'] def _register_agent_states(self): self._register_vpn_agent(host=VPN_HOSTA) self._register_vpn_agent(host=VPN_HOSTB) self._register_metadata_agent(host=VPN_HOSTA) self._register_metadata_agent(host=VPN_HOSTB) def _register_vpn_agent(self, host=None): agent = { 'id': uuidutils.generate_uuid(), 'binary': "neutron-ovn-vpn-agent", 'host': host, 'availability_zone': helpers.DEFAULT_AZ, 'topic': 'n/a', 'configurations': {}, 'start_flag': True, 'agent_type': constants.AGENT_TYPE_VPN, 'alive': True, 'admin_state_up': True} self._agents[agent['id']] = agent self._vpn_agents_by_host[host] = agent def _register_metadata_agent(self, host=None): agent = { 'id': uuidutils.generate_uuid(), 'binary': "neutron-ovn-metadata-agent", 'host': host, 'availability_zone': helpers.DEFAULT_AZ, 'topic': 'n/a', 'configurations': {}, 'start_flag': True, 'agent_type': ovn_constants.OVN_METADATA_AGENT, 'alive': True, 'admin_state_up': True} self._agents[agent['id']] = agent class VPNAgentSchedulerTestCase(VPNAgentSchedulerTestCaseBase): def _take_down_agent_and_run_reschedule(self, host): self._take_down_vpn_agent(host) plugin = directory.get_plugin(plugin_constants.VPN) plugin.reschedule_vpnservices_from_down_agents() def _get_agent_host_by_router(self, router_id): agents = self._list_vpn_agents_hosting_router(router_id) return agents['agents'][0]['host'] def test_schedule_router(self): self._register_agent_states() with self.router() as router: router_id = router['router']['id'] self.service_plugin.schedule_router(self.adminContext, router_id) host = self._get_agent_host_by_router(router_id) self.assertIn(host, (VPN_HOSTA, VPN_HOSTB)) def test_router_rescheduler_catches_rpc_db_and_reschedule_exceptions(self): self._register_agent_states() agent_a_id = self._vpn_agents_by_host[VPN_HOSTA]['id'] with self.router() as router: router_id = router['router']['id'] self._add_router_to_vpn_agent(agent_a_id, router_id) mock.patch.object( self.service_plugin, 'reschedule_router', side_effect=[ db_exc.DBError(), oslo_messaging.RemoteError(), vpn_agentschedulers.RouterReschedulingFailed( router_id='f'), ValueError('this raises'), Exception() ]).start() self._take_down_agent_and_run_reschedule(VPN_HOSTA) # DBError self._take_down_agent_and_run_reschedule(VPN_HOSTA) # RemoteError self._take_down_agent_and_run_reschedule(VPN_HOSTA) # schedule err self._take_down_agent_and_run_reschedule(VPN_HOSTA) # Value error self._take_down_agent_and_run_reschedule(VPN_HOSTA) # Exception def test_router_rescheduler_catches_exceptions_on_fetching_bindings(self): with mock.patch('neutron_lib.context.get_admin_context') as get_ctx: mock_ctx = mock.Mock() get_ctx.return_value = mock_ctx mock_ctx.session.query.side_effect = db_exc.DBError() # check that no exception is raised self.service_plugin.reschedule_vpnservices_from_down_agents() def test_router_rescheduler_iterates_after_reschedule_failure(self): self._register_agent_states() agent_a = self.service_plugin.get_vpn_agent_on_host( self.adminContext, VPN_HOSTA) with self.vpnservice() as s1, self.vpnservice() as s2: # schedule the services to agent A self.service_plugin.auto_schedule_routers( self.adminContext, agent_a) rs_mock = mock.patch.object( self.service_plugin, 'reschedule_router', side_effect=vpn_agentschedulers.RouterReschedulingFailed( router_id='f'), ).start() self._take_down_agent_and_run_reschedule(VPN_HOSTA) # make sure both had a reschedule attempt even though first failed router_id_1 = s1['vpnservice']['router_id'] router_id_2 = s2['vpnservice']['router_id'] rs_mock.assert_has_calls( [mock.call(mock.ANY, router_id_1, agent_a), mock.call(mock.ANY, router_id_2, agent_a)], any_order=True) def test_router_is_not_rescheduled_from_alive_agent(self): self._register_agent_states() agent_a_id = self._vpn_agents_by_host[VPN_HOSTA]['id'] with self.router() as router: router_id = router['router']['id'] self._add_router_to_vpn_agent(agent_a_id, router_id) patch_func_str = ('neutron_vpnaas.db.vpn.vpn_agentschedulers_db.' 'VPNAgentSchedulerDbMixin.reschedule_router') with mock.patch(patch_func_str) as rr: # take down the unrelated agent and run reschedule check self._take_down_agent_and_run_reschedule(VPN_HOSTB) self.assertFalse(rr.called) def test_router_reschedule_from_dead_agent(self): self._register_agent_states() agent_a_id = self._vpn_agents_by_host[VPN_HOSTA]['id'] with self.router() as router: router_id = router['router']['id'] self._add_router_to_vpn_agent(agent_a_id, router_id) host_before = self._get_agent_host_by_router(router_id) self._take_down_agent_and_run_reschedule(VPN_HOSTA) host_after = self._get_agent_host_by_router(router_id) self.assertEqual(VPN_HOSTA, host_before) self.assertEqual(VPN_HOSTB, host_after) def test_router_reschedule_succeeded_after_failed_notification(self): self._register_agent_states() agent_a = self.service_plugin.get_vpn_agent_on_host( self.adminContext, VPN_HOSTA) with self.vpnservice() as service: # schedule the vpn routers to agent A self.service_plugin.auto_schedule_routers( self.adminContext, agent_a) ctxt_mock = mock.MagicMock() call_mock = mock.MagicMock( side_effect=[oslo_messaging.MessagingTimeout, None]) ctxt_mock.call = call_mock self.client_mock.prepare = mock.MagicMock(return_value=ctxt_mock) self._take_down_agent_and_run_reschedule(VPN_HOSTA) self.assertEqual(2, call_mock.call_count) # make sure vpn service was rescheduled even when first attempt # failed to notify VPN agent router_id = service['vpnservice']['router_id'] host = self._get_agent_host_by_router(router_id) vpn_agents = self._list_vpn_agents_hosting_router(router_id) self.assertEqual(1, len(vpn_agents['agents'])) self.assertEqual(VPN_HOSTB, host) def test_router_reschedule_failed_notification_all_attempts(self): self._register_agent_states() agent_a = self.service_plugin.get_vpn_agent_on_host( self.adminContext, VPN_HOSTA) with self.vpnservice() as vpnservice: # schedule the vpn routers to agent A self.service_plugin.auto_schedule_routers( self.adminContext, agent_a) # mock client.prepare and context.call ctxt_mock = mock.MagicMock() call_mock = mock.MagicMock( side_effect=oslo_messaging.MessagingTimeout) ctxt_mock.call = call_mock self.client_mock.prepare = mock.MagicMock(return_value=ctxt_mock) # perform operations self._take_down_agent_and_run_reschedule(VPN_HOSTA) self.assertEqual( vpn_rpc_agent_api.AGENT_NOTIFY_MAX_ATTEMPTS, call_mock.call_count) router_id = vpnservice['vpnservice']['router_id'] vpn_agents = self._list_vpn_agents_hosting_router(router_id) self.assertEqual(0, len(vpn_agents['agents'])) def test_router_auto_schedule_with_hosted(self): self._register_agent_states() agent_a = self.service_plugin.get_vpn_agent_on_host( self.adminContext, VPN_HOSTA) agent_b = self.service_plugin.get_vpn_agent_on_host( self.adminContext, VPN_HOSTB) with self.vpnservice() as vpnservice: self._register_agent_states() ret_a = self.service_plugin.auto_schedule_routers( self.adminContext, agent_a) ret_b = self.service_plugin.auto_schedule_routers( self.adminContext, agent_b) router_id = vpnservice['vpnservice']['router_id'] vpn_agents = self._list_vpn_agents_hosting_router(router_id) host = self._get_agent_host_by_router(router_id) self.assertTrue(len(ret_a)) self.assertIn(router_id, ret_a) self.assertFalse(len(ret_b)) self.assertEqual(1, len(vpn_agents['agents'])) self.assertEqual(VPN_HOSTA, host) def test_add_router_to_vpn_agent(self): self._register_agent_states() agent_a = self.service_plugin.get_vpn_agent_on_host( self.adminContext, VPN_HOSTA) agent_a_id = agent_a['id'] agent_b = self.service_plugin.get_vpn_agent_on_host( self.adminContext, VPN_HOSTB) agent_b_id = agent_b['id'] with self.router() as router: router_id = router['router']['id'] num_before_add = len( self._list_routers_hosted_by_vpn_agent( agent_a_id)['routers']) self._add_router_to_vpn_agent(agent_a_id, router_id) # add router again to same agent is fine self._add_router_to_vpn_agent(agent_a_id, router_id) # add router to a second agent is a conflict self._add_router_to_vpn_agent(agent_b_id, router_id, expected_code=exc.HTTPConflict.code) num_after_add = len( self._list_routers_hosted_by_vpn_agent( agent_a_id)['routers']) self.assertEqual(0, num_before_add) self.assertEqual(1, num_after_add) def test_add_router_to_vpn_agent_wrong_type(self): self._register_agent_states() agent_id = self._get_any_metadata_agent_id() with self.router() as router: router_id = router['router']['id'] # add_router_to_vpn_agent with a metadata agent id shall fail self._add_router_to_vpn_agent( agent_id, router_id, expected_code=exc.HTTPNotFound.code) def _test_add_router_to_vpn_agent_db_error(self, exception): self._register_agent_states() agent_id = self._vpn_agents_by_host[VPN_HOSTA]['id'] with self.router() as router, \ mock.patch.object(orm.Session, 'add', side_effect=exception): router_id = router['router']['id'] self._add_router_to_vpn_agent( agent_id, router_id, expected_code=exc.HTTPConflict.code) def test_add_router_to_vpn_agent_duplicate(self): self._test_add_router_to_vpn_agent_db_error(db_exc.DBDuplicateEntry) def test_add_router_to_vpn_agent_reference_error(self): self._test_add_router_to_vpn_agent_db_error( db_exc.DBReferenceError('', '', '', '')) def test_add_router_to_vpn_agent_db_error(self): self._test_add_router_to_vpn_agent_db_error(db_exc.DBError) def test_list_routers_hosted_by_vpn_agent_with_invalid_agent(self): invalid_agentid = 'non_existing_agent' self._list_routers_hosted_by_vpn_agent(invalid_agentid, exc.HTTPNotFound.code) def test_remove_router_from_vpn_agent(self): self._register_agent_states() agent_id = self._vpn_agents_by_host[VPN_HOSTA]['id'] with self.router() as router: router_id = router['router']['id'] self._add_router_to_vpn_agent(agent_id, router_id) routers = self._list_routers_hosted_by_vpn_agent(agent_id) num_before = len(routers['routers']) self._remove_router_from_vpn_agent(agent_id, router_id) routers = self._list_routers_hosted_by_vpn_agent(agent_id) num_after = len(routers['routers']) self.assertEqual(1, num_before) self.assertEqual(0, num_after) def test_remove_router_from_vpn_agent_wrong_agent(self): self._register_agent_states() agent_a_id = self._vpn_agents_by_host[VPN_HOSTA]['id'] agent_b_id = self._vpn_agents_by_host[VPN_HOSTB]['id'] with self.router() as router: router_id = router['router']['id'] self._add_router_to_vpn_agent(agent_a_id, router_id) routers = self._list_routers_hosted_by_vpn_agent(agent_a_id) num_before = len(routers['routers']) # try to remove router from wrong agent is not an error self._remove_router_from_vpn_agent(agent_b_id, router_id) routers = self._list_routers_hosted_by_vpn_agent(agent_a_id) num_after = len(routers['routers']) self.assertEqual(1, num_before) self.assertEqual(1, num_after) def test_remove_router_from_vpn_agent_unknown_agent(self): self._register_agent_states() agent_a_id = self._vpn_agents_by_host[VPN_HOSTA]['id'] with self.router() as router: router_id = router['router']['id'] self._add_router_to_vpn_agent(agent_a_id, router_id) routers = self._list_routers_hosted_by_vpn_agent(agent_a_id) num_before = len(routers['routers']) # try to remove router from unknown agent is an error self._remove_router_from_vpn_agent( 'unknown-agent', router_id, expected_code=exc.HTTPNotFound.code) routers = self._list_routers_hosted_by_vpn_agent(agent_a_id) num_after = len(routers['routers']) self.assertEqual(1, num_before) self.assertEqual(1, num_after) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py0000664000175000017500000033550100000000000026044 0ustar00zuulzuul00000000000000# (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. import contextlib import copy import os from unittest import mock from neutron.api import extensions as api_extensions from neutron.common import config from neutron.db import agentschedulers_db from neutron.db import l3_agentschedulers_db from neutron.db import servicetype_db as sdb from neutron import extensions as nextensions from neutron.scheduler import l3_agent_scheduler from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_plugin from neutron.tests.unit.extensions import test_l3 as test_l3_plugin from neutron_lib.api.definitions import vpn from neutron_lib.callbacks import events from neutron_lib import constants as lib_constants from neutron_lib import context from neutron_lib.db import api as db_api from neutron_lib.exceptions import l3 as l3_exception from neutron_lib.exceptions import vpn as vpn_exception from neutron_lib.plugins import constants as nconstants from neutron_lib.plugins import directory from oslo_db import exception as db_exc from oslo_utils import uuidutils import webob.exc from neutron_vpnaas.db.vpn import vpn_db from neutron_vpnaas.db.vpn import vpn_models from neutron_vpnaas.services.vpn.common import constants from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.tests import base from neutron_vpnaas import extensions DB_CORE_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' DB_VPN_PLUGIN_KLASS = "neutron_vpnaas.services.vpn.plugin.VPNPlugin" FLAVOR_PLUGIN_KLASS = "neutron.services.flavors.flavors_plugin.FlavorsPlugin" ROOTDIR = os.path.normpath(os.path.join( os.path.dirname(__file__), '..', '..', '..', '..')) extensions_path = ':'.join(extensions.__path__ + nextensions.__path__) _uuid = uuidutils.generate_uuid class TestVpnCorePlugin(test_l3_plugin.TestL3NatIntPlugin, l3_agentschedulers_db.L3AgentSchedulerDbMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin): def __init__(self, configfile=None): super(TestVpnCorePlugin, self).__init__() self.router_scheduler = l3_agent_scheduler.ChanceScheduler() class VPNTestMixin(object): resource_prefix_map = dict( (k.replace('_', '-'), "/vpn") for k in vpn.RESOURCE_ATTRIBUTE_MAP ) def _create_ikepolicy(self, fmt, name='ikepolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', phase1_negotiation_mode='main', lifetime_units='seconds', lifetime_value=3600, ike_version='v1', pfs='group5', expected_res_status=None, **kwargs): data = {'ikepolicy': { 'name': name, 'auth_algorithm': auth_algorithm, 'encryption_algorithm': encryption_algorithm, 'phase1_negotiation_mode': phase1_negotiation_mode, 'lifetime': { 'units': lifetime_units, 'value': lifetime_value}, 'ike_version': ike_version, 'pfs': pfs, 'tenant_id': self._tenant_id }} if kwargs.get('description') is not None: data['ikepolicy']['description'] = kwargs['description'] ikepolicy_req = self.new_create_request('ikepolicies', data, fmt) ikepolicy_res = ikepolicy_req.get_response(self.ext_api) if expected_res_status: self.assertEqual(ikepolicy_res.status_int, expected_res_status) return ikepolicy_res @contextlib.contextmanager def ikepolicy(self, fmt=None, name='ikepolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', phase1_negotiation_mode='main', lifetime_units='seconds', lifetime_value=3600, ike_version='v1', pfs='group5', do_delete=True, **kwargs): if not fmt: fmt = self.fmt res = self._create_ikepolicy(fmt, name, auth_algorithm, encryption_algorithm, phase1_negotiation_mode, lifetime_units, lifetime_value, ike_version, pfs, **kwargs) if res.status_int >= 400: raise webob.exc.HTTPClientError(code=res.status_int) ikepolicy = self.deserialize(fmt or self.fmt, res) yield ikepolicy if do_delete: self._delete('ikepolicies', ikepolicy['ikepolicy']['id']) def _create_ipsecpolicy(self, fmt, name='ipsecpolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', encapsulation_mode='tunnel', transform_protocol='esp', lifetime_units='seconds', lifetime_value=3600, pfs='group5', expected_res_status=None, **kwargs): data = {'ipsecpolicy': {'name': name, 'auth_algorithm': auth_algorithm, 'encryption_algorithm': encryption_algorithm, 'encapsulation_mode': encapsulation_mode, 'transform_protocol': transform_protocol, 'lifetime': {'units': lifetime_units, 'value': lifetime_value}, 'pfs': pfs, 'tenant_id': self._tenant_id}} if kwargs.get('description') is not None: data['ipsecpolicy']['description'] = kwargs['description'] ipsecpolicy_req = self.new_create_request('ipsecpolicies', data, fmt) ipsecpolicy_res = ipsecpolicy_req.get_response(self.ext_api) if expected_res_status: self.assertEqual(ipsecpolicy_res.status_int, expected_res_status) return ipsecpolicy_res @contextlib.contextmanager def ipsecpolicy(self, fmt=None, name='ipsecpolicy1', auth_algorithm='sha1', encryption_algorithm='aes-128', encapsulation_mode='tunnel', transform_protocol='esp', lifetime_units='seconds', lifetime_value=3600, pfs='group5', do_delete=True, **kwargs): if not fmt: fmt = self.fmt res = self._create_ipsecpolicy(fmt, name, auth_algorithm, encryption_algorithm, encapsulation_mode, transform_protocol, lifetime_units, lifetime_value, pfs, **kwargs) if res.status_int >= 400: raise webob.exc.HTTPClientError(code=res.status_int) ipsecpolicy = self.deserialize(fmt or self.fmt, res) yield ipsecpolicy if do_delete: self._delete('ipsecpolicies', ipsecpolicy['ipsecpolicy']['id']) def _create_vpnservice(self, fmt, name, admin_state_up, router_id, subnet_id, expected_res_status=None, **kwargs): tenant_id = kwargs.get('tenant_id', self._tenant_id) data = {'vpnservice': {'name': name, 'subnet_id': subnet_id, 'router_id': router_id, 'admin_state_up': admin_state_up, 'tenant_id': tenant_id}} if kwargs.get('description') is not None: data['vpnservice']['description'] = kwargs['description'] if kwargs.get('flavor_id') is not None: data['vpnservice']['flavor_id'] = kwargs['flavor_id'] vpnservice_req = self.new_create_request('vpnservices', data, fmt) if (kwargs.get('set_context') and 'tenant_id' in kwargs): # create a specific auth context for this request vpnservice_req.environ['neutron.context'] = context.Context( '', kwargs['tenant_id']) vpnservice_res = vpnservice_req.get_response(self.ext_api) if expected_res_status: self.assertEqual(vpnservice_res.status_int, expected_res_status) return vpnservice_res @contextlib.contextmanager def vpnservice(self, fmt=None, name='vpnservice1', subnet=None, router=None, admin_state_up=True, do_delete=True, plug_subnet=True, external_subnet_cidr='192.168.100.0/24', external_router=True, **kwargs): if not fmt: fmt = self.fmt with test_db_plugin.optional_ctx(subnet, self.subnet) as tmp_subnet, \ test_db_plugin.optional_ctx(router, self.router) as tmp_router, \ self.subnet(cidr=external_subnet_cidr) as public_sub: if external_router: self._set_net_external( public_sub['subnet']['network_id']) self._add_external_gateway_to_router( tmp_router['router']['id'], public_sub['subnet']['network_id']) tmp_router['router']['external_gateway_info'] = { 'network_id': public_sub['subnet']['network_id']} if plug_subnet: self._router_interface_action( 'add', tmp_router['router']['id'], tmp_subnet['subnet']['id'], None) res = self._create_vpnservice(fmt, name, admin_state_up, router_id=(tmp_router['router'] ['id']), subnet_id=(tmp_subnet['subnet'] ['id']), **kwargs) vpnservice = self.deserialize(fmt or self.fmt, res) if res.status_int < 400: yield vpnservice if do_delete and vpnservice.get('vpnservice'): self._delete('vpnservices', vpnservice['vpnservice']['id']) if plug_subnet: self._router_interface_action( 'remove', tmp_router['router']['id'], tmp_subnet['subnet']['id'], None) if external_router: external_gateway = tmp_router['router'].get( 'external_gateway_info') if external_gateway: network_id = external_gateway['network_id'] self._remove_external_gateway_from_router( tmp_router['router']['id'], network_id) if res.status_int >= 400: raise webob.exc.HTTPClientError( code=res.status_int, detail=vpnservice) self._delete('subnets', public_sub['subnet']['id']) if not subnet: self._delete('subnets', tmp_subnet['subnet']['id']) def _create_ipsec_site_connection(self, fmt, name='test', peer_address='192.168.1.10', peer_id='192.168.1.10', peer_cidrs=None, mtu=1500, psk='abcdefg', initiator='bi-directional', dpd_action='hold', dpd_interval=30, dpd_timeout=120, vpnservice_id='fake_id', ikepolicy_id='fake_id', ipsecpolicy_id='fake_id', admin_state_up=True, local_ep_group_id=None, peer_ep_group_id=None, expected_res_status=None, **kwargs): data = { 'ipsec_site_connection': {'name': name, 'peer_address': peer_address, 'peer_id': peer_id, 'peer_cidrs': peer_cidrs, 'mtu': mtu, 'psk': psk, 'initiator': initiator, 'dpd': { 'action': dpd_action, 'interval': dpd_interval, 'timeout': dpd_timeout, }, 'vpnservice_id': vpnservice_id, 'ikepolicy_id': ikepolicy_id, 'ipsecpolicy_id': ipsecpolicy_id, 'admin_state_up': admin_state_up, 'tenant_id': self._tenant_id, 'local_ep_group_id': local_ep_group_id, 'peer_ep_group_id': peer_ep_group_id} } if kwargs.get('description') is not None: data['ipsec_site_connection'][ 'description'] = kwargs['description'] ipsec_site_connection_req = self.new_create_request( 'ipsec-site-connections', data, fmt ) ipsec_site_connection_res = ipsec_site_connection_req.get_response( self.ext_api ) if expected_res_status: self.assertEqual( ipsec_site_connection_res.status_int, expected_res_status ) return ipsec_site_connection_res @contextlib.contextmanager def ipsec_site_connection(self, fmt=None, name='ipsec_site_connection1', peer_address='192.168.1.10', peer_id='192.168.1.10', peer_cidrs=None, mtu=1500, psk='abcdefg', initiator='bi-directional', dpd_action='hold', dpd_interval=30, dpd_timeout=120, vpnservice=None, ikepolicy=None, ipsecpolicy=None, admin_state_up=True, do_delete=True, local_ep_group_id=None, peer_ep_group_id=None, **kwargs): if not fmt: fmt = self.fmt with test_db_plugin.optional_ctx(vpnservice, self.vpnservice ) as tmp_vpnservice, \ test_db_plugin.optional_ctx(ikepolicy, self.ikepolicy ) as tmp_ikepolicy, \ test_db_plugin.optional_ctx(ipsecpolicy, self.ipsecpolicy ) as tmp_ipsecpolicy: vpnservice_id = tmp_vpnservice['vpnservice']['id'] ikepolicy_id = tmp_ikepolicy['ikepolicy']['id'] ipsecpolicy_id = tmp_ipsecpolicy['ipsecpolicy']['id'] if not peer_cidrs and not local_ep_group_id: # Must be legacy usage - pick default to use peer_cidrs = ['10.0.0.0/24'] res = self._create_ipsec_site_connection(fmt, name, peer_address, peer_id, peer_cidrs, mtu, psk, initiator, dpd_action, dpd_interval, dpd_timeout, vpnservice_id, ikepolicy_id, ipsecpolicy_id, admin_state_up, local_ep_group_id, peer_ep_group_id, **kwargs) if res.status_int >= 400: raise webob.exc.HTTPClientError(code=res.status_int) ipsec_site_connection = self.deserialize( fmt or self.fmt, res ) yield ipsec_site_connection if do_delete: self._delete( 'ipsec-site-connections', ipsec_site_connection[ 'ipsec_site_connection']['id'] ) def _check_ipsec_site_connection(self, ipsec_site_connection, keys, dpd): self.assertEqual( keys, dict((k, v) for k, v in ipsec_site_connection.items() if k in keys)) self.assertEqual( dpd, dict((k, v) for k, v in ipsec_site_connection['dpd'].items() if k in dpd)) def _set_active(self, model, resource_id): service_plugin = directory.get_plugin(nconstants.VPN) adminContext = context.get_admin_context() with db_api.CONTEXT_WRITER.using(adminContext): resource_db = service_plugin._get_resource( adminContext, model, resource_id) resource_db.status = lib_constants.ACTIVE class VPNPluginDbTestCase(VPNTestMixin, test_l3_plugin.L3NatTestCaseMixin, base.NeutronDbPluginV2TestCase): def setUp(self, core_plugin=None, vpnaas_plugin=DB_VPN_PLUGIN_KLASS, vpnaas_provider=None): if not vpnaas_provider: vpnaas_provider = ( nconstants.VPN + ':vpnaas:neutron_vpnaas.services.vpn.' 'service_drivers.ipsec.IPsecVPNDriver:default') bits = vpnaas_provider.split(':') vpnaas_provider = { 'service_type': bits[0], 'name': bits[1], 'driver': bits[2] } if len(bits) == 4: vpnaas_provider['default'] = True # override the default service provider self.service_providers = ( mock.patch.object(sdb.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = [vpnaas_provider] # force service type manager to reload configuration: sdb.ServiceTypeManager._instance = None service_plugins = { 'vpnaas_plugin': vpnaas_plugin, 'flavors_plugin': FLAVOR_PLUGIN_KLASS} plugin_str = ('neutron_vpnaas.tests.unit.db.vpn.' 'test_vpn_db.TestVpnCorePlugin') super(VPNPluginDbTestCase, self).setUp( plugin_str, service_plugins=service_plugins ) self._subnet_id = _uuid() self.core_plugin = TestVpnCorePlugin() self.plugin = vpn_plugin.VPNPlugin() ext_mgr = api_extensions.PluginAwareExtensionManager( extensions_path, {nconstants.CORE: self.core_plugin, nconstants.VPN: self.plugin} ) app = config.load_paste_app('extensions_test_app') self.ext_api = api_extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) class TestVpnaas(VPNPluginDbTestCase): def setUp(self, **kwargs): # TODO(armax): this is far from being a unit test case, as it tests # that multiple parties (core + vpn) are integrated properly and # should be replaced by API test that do not rely on so much mocking. # NOTE(armax): make sure that the callbacks needed by this test are # registered, as they may get wiped out depending by the order in # which imports, subscriptions and mocks occur. super(TestVpnaas, self).setUp(**kwargs) vpn_db.subscribe() def _check_policy(self, policy, keys, lifetime): for k, v in keys: self.assertEqual(policy[k], v) for k, v in lifetime.items(): self.assertEqual(policy['lifetime'][k], v) def test_create_ikepolicy(self): """Test case to create an ikepolicy.""" name = "ikepolicy1" description = 'ipsec-ikepolicy' keys = [('name', name), ('description', 'ipsec-ikepolicy'), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ikepolicy(name=name, description=description) as ikepolicy: self._check_policy(ikepolicy['ikepolicy'], keys, lifetime) def test_create_ikepolicy_with_aggressive_mode(self): """Test case to create an ikepolicy with aggressive mode.""" name = "ikepolicy1" description = 'ipsec-ikepolicy' mode = 'aggressive' keys = [('name', name), ('description', 'ipsec-ikepolicy'), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'aggressive'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ikepolicy(name=name, description=description, phase1_negotiation_mode=mode) as ikepolicy: self._check_policy(ikepolicy['ikepolicy'], keys, lifetime) def test_delete_ikepolicy(self): """Test case to delete an ikepolicy.""" with self.ikepolicy(do_delete=False) as ikepolicy: req = self.new_delete_request('ikepolicies', ikepolicy['ikepolicy']['id']) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, 204) def test_show_ikepolicy(self): """Test case to show or get an ikepolicy.""" name = "ikepolicy1" description = 'ipsec-ikepolicy' keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ikepolicy(name=name, description=description) as ikepolicy: req = self.new_show_request('ikepolicies', ikepolicy['ikepolicy']['id'], fmt=self.fmt) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self._check_policy(res['ikepolicy'], keys, lifetime) def test_list_ikepolicies(self): """Test case to list all ikepolicies.""" name = "ikepolicy_list" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ikepolicy(name=name) as ikepolicy: keys.append(('id', ikepolicy['ikepolicy']['id'])) req = self.new_list_request('ikepolicies') res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(len(res), 1) for k, v in keys: self.assertEqual(res['ikepolicies'][0][k], v) for k, v in lifetime.items(): self.assertEqual(res['ikepolicies'][0]['lifetime'][k], v) def test_list_ikepolicies_with_sort_emulated(self): """Test case to list all ikepolicies.""" with self.ikepolicy(name='ikepolicy1') as ikepolicy1, \ self.ikepolicy(name='ikepolicy2') as ikepolicy2, \ self.ikepolicy(name='ikepolicy3') as ikepolicy3: self._test_list_with_sort('ikepolicy', (ikepolicy3, ikepolicy2, ikepolicy1), [('name', 'desc')], 'ikepolicies') def test_list_ikepolicies_with_pagination_emulated(self): """Test case to list all ikepolicies with pagination.""" with self.ikepolicy(name='ikepolicy1') as ikepolicy1, \ self.ikepolicy(name='ikepolicy2') as ikepolicy2, \ self.ikepolicy(name='ikepolicy3') as ikepolicy3: self._test_list_with_pagination('ikepolicy', (ikepolicy1, ikepolicy2, ikepolicy3), ('name', 'asc'), 2, 2, 'ikepolicies') def test_list_ikepolicies_with_pagination_reverse_emulated(self): """Test case to list all ikepolicies with reverse pagination.""" with self.ikepolicy(name='ikepolicy1') as ikepolicy1, \ self.ikepolicy(name='ikepolicy2') as ikepolicy2, \ self.ikepolicy(name='ikepolicy3') as ikepolicy3: self._test_list_with_pagination_reverse('ikepolicy', (ikepolicy1, ikepolicy2, ikepolicy3), ('name', 'asc'), 2, 2, 'ikepolicies') def test_update_ikepolicy(self): """Test case to update an ikepolicy.""" name = "new_ikepolicy1" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'main'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id), ('lifetime', {'units': 'seconds', 'value': 60})] with self.ikepolicy(name=name) as ikepolicy: data = {'ikepolicy': {'name': name, 'lifetime': {'units': 'seconds', 'value': 60}}} req = self.new_update_request("ikepolicies", data, ikepolicy['ikepolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['ikepolicy'][k], v) def test_update_ikepolicy_with_aggressive_mode(self): """Test case to update an ikepolicy with aggressive mode.""" name = "new_ikepolicy1" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('phase1_negotiation_mode', 'aggressive'), ('ike_version', 'v1'), ('pfs', 'group5'), ('tenant_id', self._tenant_id), ('lifetime', {'units': 'seconds', 'value': 60})] with self.ikepolicy(name=name) as ikepolicy: data = {'ikepolicy': {'name': name, 'phase1_negotiation_mode': 'aggressive', 'lifetime': {'units': 'seconds', 'value': 60}}} req = self.new_update_request("ikepolicies", data, ikepolicy['ikepolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['ikepolicy'][k], v) def test_create_ikepolicy_with_invalid_values(self): """Test case to test invalid values.""" name = 'ikepolicy1' self._create_ikepolicy(name=name, fmt=self.fmt, auth_algorithm='md5', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, auth_algorithm=200, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, encryption_algorithm='des', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, encryption_algorithm=100, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, phase1_negotiation_mode='unsupported', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, phase1_negotiation_mode=-100, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, ike_version='v6', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, ike_version=500, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, pfs='group1', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, pfs=120, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_units='Megabytes', expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_units=20000, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_value=-20, expected_res_status=400) self._create_ikepolicy(name=name, fmt=self.fmt, lifetime_value='Megabytes', expected_res_status=400) def test_create_ipsecpolicy(self): """Test case to create an ipsecpolicy.""" name = "ipsecpolicy1" description = 'my-ipsecpolicy' keys = [('name', name), ('description', 'my-ipsecpolicy'), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ipsecpolicy(name=name, description=description) as ipsecpolicy: self._check_policy(ipsecpolicy['ipsecpolicy'], keys, lifetime) def test_delete_ipsecpolicy(self): """Test case to delete an ipsecpolicy.""" with self.ipsecpolicy(do_delete=False) as ipsecpolicy: req = self.new_delete_request('ipsecpolicies', ipsecpolicy['ipsecpolicy']['id']) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, 204) def test_show_ipsecpolicy(self): """Test case to show or get an ipsecpolicy.""" name = "ipsecpolicy1" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ipsecpolicy(name=name) as ipsecpolicy: req = self.new_show_request('ipsecpolicies', ipsecpolicy['ipsecpolicy']['id'], fmt=self.fmt) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self._check_policy(res['ipsecpolicy'], keys, lifetime) def test_list_ipsecpolicies(self): """Test case to list all ipsecpolicies.""" name = "ipsecpolicy_list" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id)] lifetime = { 'units': 'seconds', 'value': 3600} with self.ipsecpolicy(name=name) as ipsecpolicy: keys.append(('id', ipsecpolicy['ipsecpolicy']['id'])) req = self.new_list_request('ipsecpolicies') res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(len(res), 1) self._check_policy(res['ipsecpolicies'][0], keys, lifetime) def test_list_ipsecpolicies_with_sort_emulated(self): """Test case to list all ipsecpolicies.""" with self.ipsecpolicy(name='ipsecpolicy1') as ipsecpolicy1, \ self.ipsecpolicy(name='ipsecpolicy2') as ipsecpolicy2, \ self.ipsecpolicy(name='ipsecpolicy3') as ipsecpolicy3: self._test_list_with_sort('ipsecpolicy', (ipsecpolicy3, ipsecpolicy2, ipsecpolicy1), [('name', 'desc')], 'ipsecpolicies') def test_list_ipsecpolicies_with_pagination_emulated(self): """Test case to list all ipsecpolicies with pagination.""" with self.ipsecpolicy(name='ipsecpolicy1') as ipsecpolicy1, \ self.ipsecpolicy(name='ipsecpolicy2') as ipsecpolicy2, \ self.ipsecpolicy(name='ipsecpolicy3') as ipsecpolicy3: self._test_list_with_pagination('ipsecpolicy', (ipsecpolicy1, ipsecpolicy2, ipsecpolicy3), ('name', 'asc'), 2, 2, 'ipsecpolicies') def test_list_ipsecpolicies_with_pagination_reverse_emulated(self): """Test case to list all ipsecpolicies with reverse pagination.""" with self.ipsecpolicy(name='ipsecpolicy1') as ipsecpolicy1, \ self.ipsecpolicy(name='ipsecpolicy2') as ipsecpolicy2, \ self.ipsecpolicy(name='ipsecpolicy3') as ipsecpolicy3: self._test_list_with_pagination_reverse('ipsecpolicy', (ipsecpolicy1, ipsecpolicy2, ipsecpolicy3), ('name', 'asc'), 2, 2, 'ipsecpolicies') def test_update_ipsecpolicy(self): """Test case to update an ipsecpolicy.""" name = "new_ipsecpolicy1" keys = [('name', name), ('auth_algorithm', 'sha1'), ('encryption_algorithm', 'aes-128'), ('encapsulation_mode', 'tunnel'), ('transform_protocol', 'esp'), ('pfs', 'group5'), ('tenant_id', self._tenant_id), ('lifetime', {'units': 'seconds', 'value': 60})] with self.ipsecpolicy(name=name) as ipsecpolicy: data = {'ipsecpolicy': {'name': name, 'lifetime': {'units': 'seconds', 'value': 60}}} req = self.new_update_request("ipsecpolicies", data, ipsecpolicy['ipsecpolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['ipsecpolicy'][k], v) def test_update_ipsecpolicy_lifetime(self): with self.ipsecpolicy() as ipsecpolicy: data = {'ipsecpolicy': {'lifetime': {'units': 'seconds'}}} req = self.new_update_request("ipsecpolicies", data, ipsecpolicy['ipsecpolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(res['ipsecpolicy']['lifetime']['units'], 'seconds') data = {'ipsecpolicy': {'lifetime': {'value': 60}}} req = self.new_update_request("ipsecpolicies", data, ipsecpolicy['ipsecpolicy']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(res['ipsecpolicy']['lifetime']['value'], 60) def test_create_ipsecpolicy_with_invalid_values(self): """Test case to test invalid values.""" name = 'ipsecpolicy1' self._create_ipsecpolicy( fmt=self.fmt, name=name, auth_algorithm='md5', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, auth_algorithm=100, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, encryption_algorithm='des', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, encryption_algorithm=200, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, transform_protocol='abcd', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, transform_protocol=500, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, encapsulation_mode='unsupported', expected_res_status=400) self._create_ipsecpolicy(name=name, fmt=self.fmt, encapsulation_mode=100, expected_res_status=400) self._create_ipsecpolicy(name=name, fmt=self.fmt, pfs='group9', expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, pfs=-1, expected_res_status=400) self._create_ipsecpolicy( fmt=self.fmt, name=name, lifetime_units='minutes', expected_res_status=400) self._create_ipsecpolicy(fmt=self.fmt, name=name, lifetime_units=100, expected_res_status=400) self._create_ipsecpolicy(fmt=self.fmt, name=name, lifetime_value=-800, expected_res_status=400) self._create_ipsecpolicy(fmt=self.fmt, name=name, lifetime_value='Megabytes', expected_res_status=400) def test_create_vpnservice(self, **extras): """Test case to create a vpnservice.""" description = 'my-vpn-service' expected = {'name': 'vpnservice1', 'description': 'my-vpn-service', 'admin_state_up': True, 'status': 'PENDING_CREATE', 'tenant_id': self._tenant_id, } expected.update(extras) with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: expected['router_id'] = router['router']['id'] expected['subnet_id'] = subnet['subnet']['id'] name = expected['name'] with self.vpnservice(name=name, subnet=subnet, router=router, description=description, **extras) as vpnservice: self.assertEqual(dict((k, v) for k, v in vpnservice['vpnservice'].items() if k in expected), expected) def test_delete_router_interface_in_use_by_vpnservice(self): """Test delete router interface in use by vpn service.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.vpnservice(subnet=subnet, router=router): self._router_interface_action('remove', router['router']['id'], subnet['subnet']['id'], None, expected_code=webob.exc. HTTPConflict.code) def test_delete_router_interface_not_in_use_by_vpnservice(self): """Test delete router interface not in use by vpn service.""" with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router1, self.router() as router2, \ self.vpnservice(subnet=subnet, router=router1), \ self.port(subnet=subnet) as port: self._router_interface_action('add', router2['router']['id'], None, port['port']['id'], expected_code=webob.exc. HTTPOk.code) self._router_interface_action('remove', router1['router']['id'], subnet['subnet']['id'], None, expected_code=webob.exc. HTTPConflict.code) self._router_interface_action('remove', router2['router']['id'], None, port['port']['id'], expected_code=webob.exc. HTTPOk.code) def test_delete_external_gateway_interface_in_use_by_vpnservice(self): """Test delete external gateway interface in use by vpn service.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.subnet(cidr='11.0.0.0/24') as public_sub: self._set_net_external( public_sub['subnet']['network_id']) self._add_external_gateway_to_router( router['router']['id'], public_sub['subnet']['network_id']) with self.vpnservice(subnet=subnet, router=router): self._remove_external_gateway_from_router( router['router']['id'], public_sub['subnet']['network_id'], expected_code=webob.exc.HTTPConflict.code) def test_router_update_after_ipsec_site_connection(self): """Test case to update router after vpn connection.""" rname1 = "router_one" rname2 = "router_two" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router(name=rname1) as r: with self.vpnservice(subnet=subnet, router=r ) as vpnservice: self.ipsec_site_connection( name='connection1', vpnservice=vpnservice ) body = self._show('routers', r['router']['id']) self.assertEqual(body['router']['name'], rname1) body = self._update('routers', r['router']['id'], {'router': {'name': rname2}}) body = self._show('routers', r['router']['id']) self.assertEqual(body['router']['name'], rname2) def test_update_vpnservice(self): """Test case to update a vpnservice.""" name = 'new_vpnservice1' keys = [('name', name)] with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(name=name, subnet=subnet, router=router) as vpnservice: keys.append(('subnet_id', vpnservice['vpnservice']['subnet_id'])) keys.append(('router_id', vpnservice['vpnservice']['router_id'])) data = {'vpnservice': {'name': name}} self._set_active(vpn_models.VPNService, vpnservice['vpnservice']['id']) req = self.new_update_request( 'vpnservices', data, vpnservice['vpnservice']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['vpnservice'][k], v) def test_update_vpnservice_with_invalid_state(self): """Test case to update a vpnservice in invalid state .""" name = 'new_vpnservice1' keys = [('name', name)] with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(name=name, subnet=subnet, router=router) as vpnservice: keys.append(('subnet_id', vpnservice['vpnservice']['subnet_id'])) keys.append(('router_id', vpnservice['vpnservice']['router_id'])) data = {'vpnservice': {'name': name}} req = self.new_update_request( 'vpnservices', data, vpnservice['vpnservice']['id']) res = req.get_response(self.ext_api) self.assertEqual(400, res.status_int) res = self.deserialize(self.fmt, res) self.assertIn(vpnservice['vpnservice']['id'], res['NeutronError']['message']) def test_delete_vpnservice(self): """Test case to delete a vpnservice.""" with self.vpnservice(name='vpnserver', do_delete=False) as vpnservice: req = self.new_delete_request('vpnservices', vpnservice['vpnservice']['id']) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, 204) def test_show_vpnservice(self): """Test case to show or get a vpnservice.""" name = "vpnservice1" keys = [('name', name), ('description', ''), ('admin_state_up', True), ('status', 'PENDING_CREATE')] with self.vpnservice(name=name) as vpnservice: req = self.new_show_request('vpnservices', vpnservice['vpnservice']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) for k, v in keys: self.assertEqual(res['vpnservice'][k], v) def test_list_vpnservices(self): """Test case to list all vpnservices.""" name = "vpnservice_list" keys = [('name', name), ('description', ''), ('admin_state_up', True), ('status', 'PENDING_CREATE')] with self.vpnservice(name=name) as vpnservice: keys.append(('subnet_id', vpnservice['vpnservice']['subnet_id'])) keys.append(('router_id', vpnservice['vpnservice']['router_id'])) req = self.new_list_request('vpnservices') res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(len(res), 1) for k, v in keys: self.assertEqual(res['vpnservices'][0][k], v) def test_list_vpnservices_with_sort_emulated(self): """Test case to list all vpnservices with sorting.""" with self.subnet() as subnet: with self.router() as router: with self.vpnservice(name='vpnservice1', subnet=subnet, router=router, external_subnet_cidr='192.168.10.0/24' ) as vpnservice1, \ self.vpnservice(name='vpnservice2', subnet=subnet, router=router, plug_subnet=False, external_router=False, external_subnet_cidr='192.168.11.0/24' ) as vpnservice2, \ self.vpnservice(name='vpnservice3', subnet=subnet, router=router, plug_subnet=False, external_router=False, external_subnet_cidr='192.168.13.0/24' ) as vpnservice3: self._test_list_with_sort('vpnservice', (vpnservice3, vpnservice2, vpnservice1), [('name', 'desc')]) def test_list_vpnservice_with_pagination_emulated(self): """Test case to list all vpnservices with pagination.""" with self.subnet() as subnet: with self.router() as router: with self.vpnservice(name='vpnservice1', subnet=subnet, router=router, external_subnet_cidr='192.168.10.0/24' ) as vpnservice1, \ self.vpnservice(name='vpnservice2', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.20.0/24', external_router=False ) as vpnservice2, \ self.vpnservice(name='vpnservice3', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.30.0/24', external_router=False ) as vpnservice3: self._test_list_with_pagination('vpnservice', (vpnservice1, vpnservice2, vpnservice3), ('name', 'asc'), 2, 2) def test_list_vpnservice_with_pagination_reverse_emulated(self): """Test case to list all vpnservices with reverse pagination.""" with self.subnet() as subnet: with self.router() as router: with self.vpnservice(name='vpnservice1', subnet=subnet, router=router, external_subnet_cidr='192.168.10.0/24' ) as vpnservice1, \ self.vpnservice(name='vpnservice2', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.11.0/24', external_router=False ) as vpnservice2, \ self.vpnservice(name='vpnservice3', subnet=subnet, router=router, plug_subnet=False, external_subnet_cidr='192.168.12.0/24', external_router=False ) as vpnservice3: self._test_list_with_pagination_reverse('vpnservice', (vpnservice1, vpnservice2, vpnservice3), ('name', 'asc'), 2, 2) def test_create_ipsec_site_connection_with_invalid_values(self): """Test case to create an ipsec_site_connection with invalid values.""" name = 'connection1' self._create_ipsec_site_connection( fmt=self.fmt, name=name, peer_cidrs='myname', expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, mtu=-100, expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, dpd_action='unsupported', expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, dpd_interval=-1, expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, dpd_timeout=-200, expected_status_int=400) self._create_ipsec_site_connection( fmt=self.fmt, name=name, initiator='unsupported', expected_status_int=400) def _test_create_ipsec_site_connection(self, key_overrides=None, setup_overrides=None, expected_status_int=200): """Create ipsec_site_connection and check results.""" params = {'ikename': 'ikepolicy1', 'ipsecname': 'ipsecpolicy1', 'vpnsname': 'vpnservice1', 'subnet_cidr': '10.2.0.0/24', 'subnet_version': 4} if setup_overrides is not None: params.update(setup_overrides) keys = {'name': 'connection1', 'description': 'my-ipsec-connection', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'initiator': 'bi-directional', 'mtu': 1500, 'tenant_id': self._tenant_id, 'psk': 'abcd', 'status': 'PENDING_CREATE', 'admin_state_up': True} if key_overrides is not None: keys.update(key_overrides) dpd = {'action': 'hold', 'interval': 40, 'timeout': 120} with self.ikepolicy(name=params['ikename']) as ikepolicy, \ self.ipsecpolicy(name=params['ipsecname']) as ipsecpolicy, \ self.subnet(cidr=params['subnet_cidr'], ip_version=params['subnet_version']) as subnet, \ self.router() as router: with self.vpnservice(name=params['vpnsname'], subnet=subnet, router=router) as vpnservice1: keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id'] keys['vpnservice_id'] = vpnservice1['vpnservice']['id'] try: with self.ipsec_site_connection( self.fmt, keys['name'], keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], dpd['action'], dpd['interval'], dpd['timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=keys['description'] ) as ipsec_site_connection: if expected_status_int != 200: self.fail("Expected failure on create") self._check_ipsec_site_connection( ipsec_site_connection['ipsec_site_connection'], keys, dpd) except webob.exc.HTTPClientError as ce: self.assertEqual(ce.code, expected_status_int) self._delete('subnets', subnet['subnet']['id']) def test_create_ipsec_site_connection(self, **extras): """Test case to create an ipsec_site_connection.""" self._test_create_ipsec_site_connection(key_overrides=extras) def test_delete_ipsec_site_connection(self): """Test case to delete a ipsec_site_connection.""" with self.ipsec_site_connection( do_delete=False) as ipsec_site_connection: req = self.new_delete_request( 'ipsec-site-connections', ipsec_site_connection['ipsec_site_connection']['id'] ) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, 204) def test_update_ipsec_site_connection(self): """Test case for valid updates to IPSec site connection.""" dpd = {'action': 'hold', 'interval': 40, 'timeout': 120} self._test_update_ipsec_site_connection(update={'dpd': dpd}) self._test_update_ipsec_site_connection(update={'mtu': 2000}) ipv6_settings = { 'peer_address': 'fe80::c0a8:10a', 'peer_id': 'fe80::c0a8:10a', 'peer_cidrs': ['fe80::c0a8:200/120', 'fe80::c0a8:300/120'], 'subnet_cidr': 'fe80::a02:0/120', 'subnet_version': 6} self._test_update_ipsec_site_connection(update={'mtu': 2000}, overrides=ipv6_settings) def test_update_ipsec_site_connection_with_invalid_state(self): """Test updating an ipsec_site_connection in invalid state.""" self._test_update_ipsec_site_connection( overrides={'make_active': False}, expected_status_int=400) def test_update_ipsec_site_connection_peer_cidrs(self): """Test updating an ipsec_site_connection for peer_cidrs.""" new_peers = {'peer_cidrs': ['192.168.4.0/24', '192.168.5.0/24']} self._test_update_ipsec_site_connection( update=new_peers) def _test_update_ipsec_site_connection(self, update={'name': 'new name'}, overrides=None, expected_status_int=200): """Creates and then updates ipsec_site_connection.""" keys = {'name': 'new_ipsec_site_connection', 'ikename': 'ikepolicy1', 'ipsecname': 'ipsecpolicy1', 'vpnsname': 'vpnservice1', 'description': 'my-ipsec-connection', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'initiator': 'bi-directional', 'mtu': 1500, 'tenant_id': self._tenant_id, 'psk': 'abcd', 'status': 'ACTIVE', 'admin_state_up': True, 'action': 'hold', 'interval': 40, 'timeout': 120, 'subnet_cidr': '10.2.0.0/24', 'subnet_version': 4, 'make_active': True} if overrides is not None: keys.update(overrides) with self.ikepolicy(name=keys['ikename']) as ikepolicy, \ self.ipsecpolicy(name=keys['ipsecname']) as ipsecpolicy, \ self.subnet(cidr=keys['subnet_cidr'], ip_version=keys['subnet_version']) as subnet, \ self.router() as router: with self.vpnservice(name=keys['vpnsname'], subnet=subnet, router=router) as vpnservice1: ext_gw = router['router']['external_gateway_info'] if ext_gw: self._create_subnet(self.fmt, net_id=ext_gw['network_id'], ip_version=6, cidr='2001:db8::/32') keys['vpnservice_id'] = vpnservice1['vpnservice']['id'] keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id'] with self.ipsec_site_connection( self.fmt, keys['name'], keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], keys['action'], keys['interval'], keys['timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=keys['description'] ) as ipsec_site_connection: data = {'ipsec_site_connection': update} if keys.get('make_active', None): self._set_active( vpn_models.IPsecSiteConnection, (ipsec_site_connection['ipsec_site_connection'] ['id'])) req = self.new_update_request( 'ipsec-site-connections', data, ipsec_site_connection['ipsec_site_connection']['id']) res = req.get_response(self.ext_api) self.assertEqual(expected_status_int, res.status_int) if expected_status_int == 200: res_dict = self.deserialize(self.fmt, res) actual = res_dict['ipsec_site_connection'] for k, v in update.items(): # Sort lists before checking equality if isinstance(actual[k], list): self.assertEqual(v, sorted(actual[k])) else: self.assertEqual(v, actual[k]) self._delete('networks', subnet['subnet']['network_id']) def test_show_ipsec_site_connection(self): """Test case to show a ipsec_site_connection.""" ikename = "ikepolicy1" ipsecname = "ipsecpolicy1" vpnsname = "vpnservice1" name = "connection1" description = "my-ipsec-connection" keys = {'name': name, 'description': "my-ipsec-connection", 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'initiator': 'bi-directional', 'mtu': 1500, 'tenant_id': self._tenant_id, 'psk': 'abcd', 'status': 'PENDING_CREATE', 'admin_state_up': True} dpd = {'action': 'hold', 'interval': 40, 'timeout': 120} with self.ikepolicy(name=ikename) as ikepolicy, \ self.ipsecpolicy(name=ipsecname) as ipsecpolicy, \ self.subnet() as subnet, \ self.router() as router: with self.vpnservice(name=vpnsname, subnet=subnet, router=router) as vpnservice1: keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id'] keys['vpnservice_id'] = vpnservice1['vpnservice']['id'] with self.ipsec_site_connection( self.fmt, name, keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], dpd['action'], dpd['interval'], dpd['timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=description, ) as ipsec_site_connection: req = self.new_show_request( 'ipsec-site-connections', ipsec_site_connection[ 'ipsec_site_connection']['id'], fmt=self.fmt ) res = self.deserialize( self.fmt, req.get_response(self.ext_api) ) self._check_ipsec_site_connection( res['ipsec_site_connection'], keys, dpd) def test_list_ipsec_site_connections_with_sort_emulated(self): """Test case to list all ipsec_site_connections with sort.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.vpnservice(subnet=subnet, router=router ) as vpnservice: with self.ipsec_site_connection(name='connection1', vpnservice=vpnservice ) as conn1, \ self.ipsec_site_connection(name='connection2', vpnservice=vpnservice ) as conn2, \ self.ipsec_site_connection(name='connection3', vpnservice=vpnservice ) as conn3: self._test_list_with_sort('ipsec-site-connection', (conn3, conn2, conn1), [('name', 'desc')]) def test_list_ipsec_site_connections_with_pagination_emulated(self): """Test case to list all ipsec_site_connections with pagination.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.vpnservice(subnet=subnet, router=router ) as vpnservice: with self.ipsec_site_connection( name='ipsec_site_connection1', vpnservice=vpnservice) as conn1, \ self.ipsec_site_connection( name='ipsec_site_connection1', vpnservice=vpnservice) as conn2, \ self.ipsec_site_connection( name='ipsec_site_connection1', vpnservice=vpnservice) as conn3: self._test_list_with_pagination( 'ipsec-site-connection', (conn1, conn2, conn3), ('name', 'asc'), 2, 2) def test_list_ipsec_site_conns_with_pagination_reverse_emulated(self): """Test to list all ipsec_site_connections with reverse pagination.""" with self.subnet(cidr='10.2.0.0/24') as subnet: with self.router() as router: with self.vpnservice(subnet=subnet, router=router ) as vpnservice: with self.ipsec_site_connection(name='connection1', vpnservice=vpnservice ) as conn1, \ self.ipsec_site_connection(name='connection2', vpnservice=vpnservice ) as conn2, \ self.ipsec_site_connection(name='connection3', vpnservice=vpnservice ) as conn3: self._test_list_with_pagination_reverse( 'ipsec-site-connection', (conn1, conn2, conn3), ('name', 'asc'), 2, 2 ) def test_create_vpn(self): """Test case to create a vpn.""" vpns_name = "vpnservice1" ike_name = "ikepolicy1" ipsec_name = "ipsecpolicy1" name1 = "ipsec_site_connection1" with self.ikepolicy(name=ike_name) as ikepolicy, \ self.ipsecpolicy(name=ipsec_name) as ipsecpolicy, \ self.vpnservice(name=vpns_name) as vpnservice: vpnservice_id = vpnservice['vpnservice']['id'] ikepolicy_id = ikepolicy['ikepolicy']['id'] ipsecpolicy_id = ipsecpolicy['ipsecpolicy']['id'] with self.ipsec_site_connection( self.fmt, name1, '192.168.1.10', '192.168.1.10', ['192.168.2.0/24', '192.168.3.0/24'], 1500, 'abcdef', 'bi-directional', 'hold', 30, 120, vpnservice, ikepolicy, ipsecpolicy, True ) as vpnconn1: vpnservice_req = self.new_show_request( 'vpnservices', vpnservice_id, fmt=self.fmt) vpnservice_updated = self.deserialize( self.fmt, vpnservice_req.get_response(self.ext_api) ) self.assertEqual( vpnservice_updated['vpnservice']['id'], vpnconn1['ipsec_site_connection']['vpnservice_id'] ) ikepolicy_req = self.new_show_request('ikepolicies', ikepolicy_id, fmt=self.fmt) ikepolicy_res = self.deserialize( self.fmt, ikepolicy_req.get_response(self.ext_api) ) self.assertEqual( ikepolicy_res['ikepolicy']['id'], vpnconn1['ipsec_site_connection']['ikepolicy_id']) ipsecpolicy_req = self.new_show_request( 'ipsecpolicies', ipsecpolicy_id, fmt=self.fmt) ipsecpolicy_res = self.deserialize( self.fmt, ipsecpolicy_req.get_response(self.ext_api) ) self.assertEqual( ipsecpolicy_res['ipsecpolicy']['id'], vpnconn1['ipsec_site_connection']['ipsecpolicy_id'] ) def test_delete_ikepolicy_inuse(self): """Test case to delete an ikepolicy, that is in use.""" vpns_name = "vpnservice1" ike_name = "ikepolicy1" ipsec_name = "ipsecpolicy1" name1 = "ipsec_site_connection1" with self.ikepolicy(name=ike_name) as ikepolicy: with self.ipsecpolicy(name=ipsec_name) as ipsecpolicy: with self.vpnservice(name=vpns_name) as vpnservice: with self.ipsec_site_connection( self.fmt, name1, '192.168.1.10', '192.168.1.10', ['192.168.2.0/24', '192.168.3.0/24'], 1500, 'abcdef', 'bi-directional', 'hold', 30, 120, vpnservice, ikepolicy, ipsecpolicy, True ): delete_req = self.new_delete_request( 'ikepolicies', ikepolicy['ikepolicy']['id'] ) delete_res = delete_req.get_response(self.ext_api) self.assertEqual(409, delete_res.status_int) def test_delete_ipsecpolicy_inuse(self): """Test case to delete an ipsecpolicy, that is in use.""" vpns_name = "vpnservice1" ike_name = "ikepolicy1" ipsec_name = "ipsecpolicy1" name1 = "ipsec_site_connection1" with self.ikepolicy(name=ike_name) as ikepolicy: with self.ipsecpolicy(name=ipsec_name) as ipsecpolicy: with self.vpnservice(name=vpns_name) as vpnservice: with self.ipsec_site_connection( self.fmt, name1, '192.168.1.10', '192.168.1.10', ['192.168.2.0/24', '192.168.3.0/24'], 1500, 'abcdef', 'bi-directional', 'hold', 30, 120, vpnservice, ikepolicy, ipsecpolicy, True ): delete_req = self.new_delete_request( 'ipsecpolicies', ipsecpolicy['ipsecpolicy']['id'] ) delete_res = delete_req.get_response(self.ext_api) self.assertEqual(409, delete_res.status_int) def test_router_in_use_by_vpnaas(self): """Check that exception raised, if router in use by VPNaaS.""" with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(subnet=subnet, router=router): self.assertRaises(l3_exception.RouterInUse, self.plugin.check_router_in_use, context.get_admin_context(), router['router']['id']) def test_subnet_in_use_by_vpnaas(self): """Check that exception raised, if subnet in use by VPNaaS.""" with self.subnet(cidr='10.2.0.0/24') as subnet, \ self.router() as router: with self.vpnservice(subnet=subnet, router=router): self.assertRaises(vpn_exception.SubnetInUseByVPNService, self.plugin.check_subnet_in_use, context.get_admin_context(), subnet['subnet']['id'], router['router']['id']) def test_check_router_has_no_vpn(self): vpn_plugin = mock.Mock() directory.add_plugin('VPN', vpn_plugin) payload = events.DBEventPayload( context=mock.ANY, states=({'id': 'foo_id'},)) self.assertTrue(vpn_db.migration_callback( mock.ANY, mock.ANY, mock.ANY, payload)) vpn_plugin.check_router_in_use.assert_called_once_with( mock.ANY, 'foo_id') # Note: Below are new database related tests that only exercise the database # instead of going through the client API. The intent here is to (eventually) # convert all the database tests to this method, for faster, more granular # tests. # TODO(pcm): Put helpers in another module for sharing class NeutronResourcesMixin(object): def create_network(self, overrides=None): """Create database entry for network.""" network_info = {'network': {'name': 'my-net', 'tenant_id': self.tenant_id, 'admin_state_up': True, 'shared': False}} if overrides: network_info['network'].update(overrides) return self.core_plugin.create_network(self.context, network_info) def create_subnet(self, overrides=None): """Create database entry for subnet.""" subnet_info = {'subnet': {'name': 'my-subnet', 'tenant_id': self.tenant_id, 'ip_version': 4, 'enable_dhcp': True, 'dns_nameservers': None, 'host_routes': None, 'allocation_pools': None}} if overrides: subnet_info['subnet'].update(overrides) return self.core_plugin.create_subnet(self.context, subnet_info) def create_router(self, overrides=None, gw=None): """Create database entry for router with optional gateway.""" router_info = { 'router': { 'name': 'my-router', 'tenant_id': self.tenant_id, 'admin_state_up': True, } } if overrides: router_info['router'].update(overrides) if gw: gw_info = { 'external_gateway_info': { 'network_id': gw['net_id'], 'external_fixed_ips': [{'subnet_id': gw['subnet_id'], 'ip_address': gw['ip']}], } } router_info['router'].update(gw_info) return self.l3_plugin.create_router(self.context, router_info) def create_router_port_for_subnet(self, router, subnet): """Creates port on router for subnet specified.""" port = {'port': { 'tenant_id': self.tenant_id, 'network_id': subnet['network_id'], 'fixed_ips': [ {'ip_address': subnet['gateway_ip'], 'subnet_id': subnet['id']} ], 'mac_address': lib_constants.ATTR_NOT_SPECIFIED, 'admin_state_up': True, 'device_id': router['id'], 'device_owner': lib_constants.DEVICE_OWNER_ROUTER_INTF, 'name': '' }} return self.core_plugin.create_port(self.context, port) def create_basic_topology(self, create_router_port=True): """Setup networks, subnets, and a router for testing VPN.""" public_net = self.create_network(overrides={'name': 'public', 'router:external': True}) private_net = self.create_network(overrides={'name': 'private'}) overrides = {'name': 'private-subnet', 'cidr': '10.2.0.0/24', 'gateway_ip': '10.2.0.1', 'network_id': private_net['id']} private_subnet = self.create_subnet(overrides=overrides) overrides = {'name': 'public-subnet', 'cidr': '192.168.100.0/24', 'gateway_ip': '192.168.100.1', 'allocation_pools': [{'start': '192.168.100.2', 'end': '192.168.100.254'}], 'network_id': public_net['id']} public_subnet = self.create_subnet(overrides=overrides) gw_info = {'net_id': public_net['id'], 'subnet_id': public_subnet['id'], 'ip': '192.168.100.5'} router = self.create_router(gw=gw_info) if create_router_port: self.create_router_port_for_subnet(router, private_subnet) return (private_subnet, router) class TestVpnDatabase(base.NeutronDbPluginV2TestCase, NeutronResourcesMixin): def setUp(self): # Setup the core plugin self.plugin_str = ('neutron_vpnaas.tests.unit.db.vpn.' 'test_vpn_db.TestVpnCorePlugin') super(TestVpnDatabase, self).setUp(self.plugin_str) # Get the plugins self.core_plugin = directory.get_plugin() self.l3_plugin = directory.get_plugin(nconstants.L3) # Create VPN database instance self.plugin = vpn_db.VPNPluginDb() self.tenant_id = _uuid() self.context = context.get_admin_context() def prepare_service_info(self, private_subnet, router): subnet_id = private_subnet['id'] if private_subnet else None return {'vpnservice': {'tenant_id': self.tenant_id, 'name': 'my-service', 'description': 'new service', 'subnet_id': subnet_id, 'router_id': router['id'], 'flavor_id': None, 'admin_state_up': True}} def test_create_vpnservice(self): private_subnet, router = self.create_basic_topology() info = self.prepare_service_info(private_subnet, router) expected = {'admin_state_up': True, 'external_v4_ip': None, 'external_v6_ip': None, 'status': 'PENDING_CREATE'} expected.update(info['vpnservice']) new_service = self.plugin.create_vpnservice(self.context, info) self.assertDictSupersetOf(expected, new_service) def test_create_vpn_service_without_subnet(self): """Create service w/o subnet (will use endpoint groups for conn).""" private_subnet, router = self.create_basic_topology() info = self.prepare_service_info(private_subnet=None, router=router) expected = {'admin_state_up': True, 'external_v4_ip': None, 'external_v6_ip': None, 'status': 'PENDING_CREATE'} expected.update(info['vpnservice']) new_service = self.plugin.create_vpnservice(self.context, info) self.assertDictSupersetOf(expected, new_service) def test_update_external_tunnel_ips(self): """Verify that external tunnel IPs can be set.""" private_subnet, router = self.create_basic_topology() info = self.prepare_service_info(private_subnet, router) expected = {'admin_state_up': True, 'external_v4_ip': None, 'external_v6_ip': None, 'status': 'PENDING_CREATE'} expected.update(info['vpnservice']) new_service = self.plugin.create_vpnservice(self.context, info) self.assertDictSupersetOf(expected, new_service) external_v4_ip = '192.168.100.5' external_v6_ip = 'fd00:1000::4' expected.update({'external_v4_ip': external_v4_ip, 'external_v6_ip': external_v6_ip}) mod_service = self.plugin.set_external_tunnel_ips(self.context, new_service['id'], v4_ip=external_v4_ip, v6_ip=external_v6_ip) self.assertDictSupersetOf(expected, mod_service) def prepare_endpoint_info(self, group_type, endpoints): return {'endpoint_group': {'tenant_id': self.tenant_id, 'name': 'my endpoint group', 'description': 'my description', 'type': group_type, 'endpoints': endpoints}} def test_endpoint_group_create_with_cidrs(self): """Verify create endpoint group using CIDRs.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] new_endpoint_group = self.plugin.create_endpoint_group(self.context, info) self._compare_groups(expected, new_endpoint_group) def test_endpoint_group_create_with_subnets(self): """Verify create endpoint group using subnets.""" # Skip validation for subnets, as validation is checked in other tests mock.patch.object(self.l3_plugin, "get_subnet").start() private_subnet, router = self.create_basic_topology() private_net2 = self.create_network(overrides={'name': 'private2'}) overrides = {'name': 'private-subnet2', 'cidr': '10.1.0.0/24', 'gateway_ip': '10.1.0.1', 'network_id': private_net2['id']} private_subnet2 = self.create_subnet(overrides=overrides) self.create_router_port_for_subnet(router, private_subnet2) info = self.prepare_endpoint_info(constants.SUBNET_ENDPOINT, [private_subnet['id'], private_subnet2['id']]) expected = info['endpoint_group'] new_endpoint_group = self.plugin.create_endpoint_group(self.context, info) self._compare_groups(expected, new_endpoint_group) def test_endpoint_group_create_with_vlans(self): """Verify endpoint group using VLANs.""" info = self.prepare_endpoint_info(constants.VLAN_ENDPOINT, ['100', '200', '300']) expected = info['endpoint_group'] new_endpoint_group = self.plugin.create_endpoint_group(self.context, info) self._compare_groups(expected, new_endpoint_group) def _compare_groups(self, expected_group, actual_group): # Callers may want to reuse passed dicts later expected_group = copy.deepcopy(expected_group) actual_group = copy.deepcopy(actual_group) # We need to compare endpoints separately because their order is # not defined check_endpoints = 'endpoints' in expected_group expected_endpoints = set(expected_group.pop('endpoints', [])) actual_endpoints = set(actual_group.pop('endpoints', [])) self.assertDictSupersetOf(expected_group, actual_group) if check_endpoints: self.assertEqual(expected_endpoints, actual_endpoints) def helper_create_endpoint_group(self, info): """Create endpoint group database entry and verify OK.""" group = info['endpoint_group'] try: actual = self.plugin.create_endpoint_group(self.context, info) except db_exc.DBError as e: self.fail("Endpoint create in prep for test failed: %s" % e) self._compare_groups(group, actual) self.assertIn('id', actual) return actual['id'] def check_endpoint_group_entry(self, endpoint_group_id, expected_info, should_exist=True): try: endpoint_group = self.plugin.get_endpoint_group(self.context, endpoint_group_id) is_found = True except vpn_exception.VPNEndpointGroupNotFound: is_found = False except Exception as e: self.fail("Unexpected exception getting endpoint group: %s" % e) if should_exist != is_found: self.fail("Endpoint group should%(expected)s exist, but " "did%(actual)s exist" % {'expected': '' if should_exist else ' not', 'actual': '' if is_found else ' not'}) if is_found: self._compare_groups(expected_info, endpoint_group) def test_delete_endpoint_group(self): """Test that endpoint group is deleted.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] group_id = self.helper_create_endpoint_group(info) self.check_endpoint_group_entry(group_id, expected, should_exist=True) self.plugin.delete_endpoint_group(self.context, group_id) self.check_endpoint_group_entry(group_id, expected, should_exist=False) self.assertRaises(vpn_exception.VPNEndpointGroupNotFound, self.plugin.delete_endpoint_group, self.context, group_id) def test_show_endpoint_group(self): """Test showing a single endpoint group.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] group_id = self.helper_create_endpoint_group(info) self.check_endpoint_group_entry(group_id, expected, should_exist=True) actual = self.plugin.get_endpoint_group(self.context, group_id) self._compare_groups(expected, actual) def test_fail_showing_non_existent_endpoint_group(self): """Test failure to show non-existent endpoint group.""" self.assertRaises(vpn_exception.VPNEndpointGroupNotFound, self.plugin.get_endpoint_group, self.context, uuidutils.generate_uuid()) def test_list_endpoint_groups(self): """Test listing multiple endpoint groups.""" # Skip validation for subnets, as validation is checked in other tests mock.patch.object(self.l3_plugin, "get_subnet").start() info1 = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected1 = info1['endpoint_group'] group_id1 = self.helper_create_endpoint_group(info1) self.check_endpoint_group_entry(group_id1, expected1, should_exist=True) info2 = self.prepare_endpoint_info(constants.SUBNET_ENDPOINT, [uuidutils.generate_uuid(), uuidutils.generate_uuid()]) expected2 = info2['endpoint_group'] group_id2 = self.helper_create_endpoint_group(info2) self.check_endpoint_group_entry(group_id2, expected2, should_exist=True) expected1.update({'id': group_id1}) expected2.update({'id': group_id2}) expected_groups = [expected1, expected2] actual_groups = self.plugin.get_endpoint_groups(self.context, fields=('type', 'tenant_id', 'endpoints', 'name', 'description', 'id')) for expected_group, actual_group in zip(expected_groups, actual_groups): self._compare_groups(expected_group, actual_group) def test_update_endpoint_group(self): """Test updating endpoint group information.""" info = self.prepare_endpoint_info(constants.CIDR_ENDPOINT, ['10.10.10.0/24', '20.20.20.0/24']) expected = info['endpoint_group'] group_id = self.helper_create_endpoint_group(info) self.check_endpoint_group_entry(group_id, expected, should_exist=True) group_updates = {'endpoint_group': {'name': 'new name', 'description': 'new description'}} updated_group = self.plugin.update_endpoint_group(self.context, group_id, group_updates) # Check what was returned, and what is stored in database self._compare_groups(group_updates['endpoint_group'], updated_group) expected.update(group_updates['endpoint_group']) self.check_endpoint_group_entry(group_id, expected, should_exist=True) def test_fail_updating_non_existent_group(self): """Test fail updating a non-existent group.""" group_updates = {'endpoint_group': {'name': 'new name'}} self.assertRaises( vpn_exception.VPNEndpointGroupNotFound, self.plugin.update_endpoint_group, self.context, _uuid(), group_updates) def prepare_ike_policy_info(self): return {'ikepolicy': {'tenant_id': self.tenant_id, 'name': 'ike policy', 'description': 'my ike policy', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'main', 'lifetime': {'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5'}} def test_create_ike_policy(self): """Create IKE policy with all settings specified.""" info = self.prepare_ike_policy_info() expected = info['ikepolicy'] new_ike_policy = self.plugin.create_ikepolicy(self.context, info) self.assertDictSupersetOf(expected, new_ike_policy) def prepare_ipsec_policy_info(self): return {'ipsecpolicy': {'tenant_id': self.tenant_id, 'name': 'ipsec policy', 'description': 'my ipsec policy', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'transform_protocol': 'esp', 'lifetime': {'units': 'seconds', 'value': 3600}, 'pfs': 'group5'}} def test_create_ipsec_policy(self): """Create IPsec policy with all settings specified.""" info = self.prepare_ipsec_policy_info() expected = info['ipsecpolicy'] new_ipsec_policy = self.plugin.create_ipsecpolicy(self.context, info) self.assertDictSupersetOf(expected, new_ipsec_policy) def create_vpn_service(self, with_subnet=True): private_subnet, router = self.create_basic_topology() if not with_subnet: private_subnet = None info = self.prepare_service_info(private_subnet, router) return self.plugin.create_vpnservice(self.context, info) def create_ike_policy(self): info = self.prepare_ike_policy_info() return self.plugin.create_ikepolicy(self.context, info) def create_ipsec_policy(self): info = self.prepare_ipsec_policy_info() return self.plugin.create_ipsecpolicy(self.context, info) def create_endpoint_group(self, group_type, endpoints): info = self.prepare_endpoint_info(group_type=group_type, endpoints=endpoints) return self.plugin.create_endpoint_group(self.context, info) def prepare_connection_info(self, service_id, ike_policy_id, ipsec_policy_id, local_id=''): """Creates connection request dictionary. The peer_cidrs, local_ep_group_id, and peer_ep_group_id are set to defaults. Caller must then fill in either CIDRs or endpoints, before creating a connection. """ return {'ipsec_site_connection': {'name': 'my connection', 'description': 'my description', 'peer_id': '192.168.1.10', 'peer_address': '192.168.1.10', 'peer_cidrs': [], 'local_id': local_id, 'mtu': 1500, 'psk': 'shhhh!!!', 'initiator': 'bi-directional', 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120, 'vpnservice_id': service_id, 'ikepolicy_id': ike_policy_id, 'ipsecpolicy_id': ipsec_policy_id, 'admin_state_up': True, 'tenant_id': self._tenant_id, 'local_ep_group_id': None, 'peer_ep_group_id': None}} def build_expected_connection_result(self, info): """Create the expected IPsec connection dict from the request info. The DPD information is stored and converted to a nested dict, instead of individual fields. """ expected = copy.copy(info['ipsec_site_connection']) expected['dpd'] = {'action': expected['dpd_action'], 'interval': expected['dpd_interval'], 'timeout': expected['dpd_timeout']} del expected['dpd_action'] del expected['dpd_interval'] del expected['dpd_timeout'] expected['status'] = 'PENDING_CREATE' return expected def prepare_for_ipsec_connection_create(self, with_subnet=True): service = self.create_vpn_service(with_subnet) ike_policy = self.create_ike_policy() ipsec_policy = self.create_ipsec_policy() return self.prepare_connection_info(service['id'], ike_policy['id'], ipsec_policy['id']) def test_create_ipsec_site_connection_with_peer_cidrs(self): """Create connection using old API with peer CIDRs specified.""" info = self.prepare_for_ipsec_connection_create() info['ipsec_site_connection']['peer_cidrs'] = ['10.1.0.0/24', '10.2.0.0/24'] expected = self.build_expected_connection_result(info) new_conn = self.plugin.create_ipsec_site_connection(self.context, info) self.assertDictSupersetOf(expected, new_conn) def test_create_ipsec_site_connection_with_endpoint_groups(self): """Create connection using new API with endpoint groups.""" # Skip validation, which is tested separately mock.patch.object(self.plugin, '_get_validator').start() local_net = self.create_network(overrides={'name': 'local'}) overrides = {'name': 'local-subnet', 'cidr': '30.0.0.0/24', 'gateway_ip': '30.0.0.1', 'network_id': local_net['id']} local_subnet = self.create_subnet(overrides=overrides) info = self.prepare_for_ipsec_connection_create(with_subnet=False) local_ep_group = self.create_endpoint_group( group_type='subnet', endpoints=[local_subnet['id']]) peer_ep_group = self.create_endpoint_group( group_type='cidr', endpoints=['20.1.0.0/24', '20.2.0.0/24']) info['ipsec_site_connection'].update( {'local_ep_group_id': local_ep_group['id'], 'peer_ep_group_id': peer_ep_group['id']}) expected = self.build_expected_connection_result(info) new_conn = self.plugin.create_ipsec_site_connection(self.context, info) self.assertDictSupersetOf(expected, new_conn) def test_fail_endpoint_group_delete_when_in_use_by_ipsec_conn(self): """Ensure endpoint group is not deleted from under IPSec connection.""" # Skip validation, which is tested separately mock.patch.object(self.plugin, '_get_validator').start() local_net = self.create_network(overrides={'name': 'local'}) overrides = {'name': 'local-subnet', 'cidr': '30.0.0.0/24', 'gateway_ip': '30.0.0.1', 'network_id': local_net['id']} local_subnet = self.create_subnet(overrides=overrides) info = self.prepare_for_ipsec_connection_create(with_subnet=False) local_ep_group = self.create_endpoint_group( group_type='subnet', endpoints=[local_subnet['id']]) peer_ep_group = self.create_endpoint_group( group_type='cidr', endpoints=['20.1.0.0/24', '20.2.0.0/24']) info['ipsec_site_connection'].update( {'local_ep_group_id': local_ep_group['id'], 'peer_ep_group_id': peer_ep_group['id']}) self.plugin.create_ipsec_site_connection(self.context, info) self.assertRaises(vpn_exception.EndpointGroupInUse, self.plugin.delete_endpoint_group, self.context, local_ep_group['id']) self.assertRaises(vpn_exception.EndpointGroupInUse, self.plugin.delete_endpoint_group, self.context, peer_ep_group['id']) unused_ep_group = self.create_endpoint_group( group_type=constants.CIDR_ENDPOINT, endpoints=['30.0.0.0/24']) self.plugin.delete_endpoint_group(self.context, unused_ep_group['id']) def test_fail_subnet_delete_when_in_use_by_endpoint_group(self): """Ensure don't delete subnet from under endpoint group.""" # mock.patch.object(self.plugin, '_get_validator').start() local_net = self.create_network(overrides={'name': 'local'}) overrides = {'name': 'local-subnet', 'cidr': '30.0.0.0/24', 'gateway_ip': '30.0.0.1', 'network_id': local_net['id']} local_subnet = self.create_subnet(overrides=overrides) self.create_endpoint_group(group_type='subnet', endpoints=[local_subnet['id']]) self.assertRaises(vpn_exception.SubnetInUseByEndpointGroup, self.plugin.check_subnet_in_use_by_endpoint_group, self.context, local_subnet['id']) def test_subnet_in_use_by_ipsec_site_connection(self): mock.patch.object(self.plugin, '_get_validator').start() private_subnet, router = self.create_basic_topology( create_router_port=False) self.l3_plugin.add_router_interface( self.context, router['id'], {'subnet_id': private_subnet['id']}) vpn_service_info = self.prepare_service_info(private_subnet=None, router=router) vpn_service = self.plugin.create_vpnservice(self.context, vpn_service_info) ike_policy = self.create_ike_policy() ipsec_policy = self.create_ipsec_policy() ipsec_site_connection = self.prepare_connection_info( vpn_service['id'], ike_policy['id'], ipsec_policy['id']) local_ep_group = self.create_endpoint_group( group_type='subnet', endpoints=[private_subnet['id']]) peer_ep_group = self.create_endpoint_group( group_type='cidr', endpoints=['20.1.0.0/24', '20.2.0.0/24']) ipsec_site_connection['ipsec_site_connection'].update( {'local_ep_group_id': local_ep_group['id'], 'peer_ep_group_id': peer_ep_group['id']}) self.plugin.create_ipsec_site_connection(self.context, ipsec_site_connection) self.assertRaises(vpn_exception.SubnetInUseByIPsecSiteConnection, self.plugin.check_subnet_in_use, self.context, private_subnet['id'], router['id']) def _setup_ipsec_site_connections_with_ep_groups(self, peer_cidr_lists): private_subnet, router = self.create_basic_topology() vpn_service_info = self.prepare_service_info(private_subnet=None, router=router) vpn_service = self.plugin.create_vpnservice(self.context, vpn_service_info) ike_policy = self.create_ike_policy() ipsec_policy = self.create_ipsec_policy() ipsec_site_connection = self.prepare_connection_info( vpn_service['id'], ike_policy['id'], ipsec_policy['id']) local_ep_group = self.create_endpoint_group( group_type='subnet', endpoints=[private_subnet['id']]) for peer_cidrs in peer_cidr_lists: peer_ep_group = self.create_endpoint_group( group_type='cidr', endpoints=peer_cidrs) ipsec_site_connection['ipsec_site_connection'].update( {'local_ep_group_id': local_ep_group['id'], 'peer_ep_group_id': peer_ep_group['id']}) self.plugin.create_ipsec_site_connection(self.context, ipsec_site_connection) return private_subnet, router def _setup_ipsec_site_connections_without_ep_groups(self, peer_cidr_lists): private_subnet, router = self.create_basic_topology() vpn_service_info = \ self.prepare_service_info(private_subnet=private_subnet, router=router) vpn_service = self.plugin.create_vpnservice(self.context, vpn_service_info) ike_policy = self.create_ike_policy() ipsec_policy = self.create_ipsec_policy() ipsec_site_connection = self.prepare_connection_info( vpn_service['id'], ike_policy['id'], ipsec_policy['id']) for peer_cidrs in peer_cidr_lists: ipsec_site_connection['ipsec_site_connection'].update( {'peer_cidrs': peer_cidrs}) self.plugin.create_ipsec_site_connection(self.context, ipsec_site_connection) return private_subnet, router def _test_get_peer_cidrs_for_router(self, setup_func): mock.patch.object(self.plugin, '_get_validator').start() # create 1st setup with two connections peer_cidrs = [ ['20.1.0.0/24', '20.2.0.0/24'], ['20.3.0.0/24'] ] private_subnet, router = setup_func(peer_cidrs) # create a 2nd setup for a different router setup_func([['10.1.0.0/24', '10.2.0.0/24']]) returned_cidrs = self.plugin.get_peer_cidrs_for_router(self.context, router['id']) expected = ['20.1.0.0/24', '20.2.0.0/24', '20.3.0.0/24'] self.assertEqual(sorted(expected), sorted(returned_cidrs)) def test_get_peer_cidrs_for_router_with_ep_groups(self): self._test_get_peer_cidrs_for_router( self._setup_ipsec_site_connections_with_ep_groups) def test_get_peer_cidrs_for_router_without_ep_groups(self): self._test_get_peer_cidrs_for_router( self._setup_ipsec_site_connections_without_ep_groups) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/vpn/test_vpn_ext_gw_db.py0000664000175000017500000002305200000000000027414 0ustar00zuulzuul00000000000000# Copyright 2023 SysEleven GmbH # # 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.api import extensions from neutron.tests.unit.api import test_extensions from neutron.tests.unit.extensions import test_l3 as test_l3_plugin from neutron_lib.callbacks import events from neutron_lib.callbacks import exceptions as cb_exc from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as lib_constants from neutron_lib import context from neutron_lib.plugins import constants as nconstants from neutron_lib.plugins import directory from neutron_vpnaas.db.vpn.vpn_ext_gw_db import VPNExtGWPlugin_db from neutron_vpnaas.services.vpn.common import constants as v_constants from neutron_vpnaas.tests import base from neutron_vpnaas.tests.unit.db.vpn import test_vpn_db OVN_VPN_PLUGIN_KLASS = "neutron_vpnaas.services.vpn.ovn_plugin.VPNOVNPlugin" class VPNOVNPluginDbTestCase(test_l3_plugin.L3NatTestCaseMixin, base.NeutronDbPluginV2TestCase): def setUp(self, core_plugin=None, vpnaas_plugin=OVN_VPN_PLUGIN_KLASS, vpnaas_provider=None): service_plugins = {'vpnaas_plugin': vpnaas_plugin} plugin_str = 'neutron.tests.unit.extensions.test_l3.TestL3NatIntPlugin' super().setUp(plugin_str, service_plugins=service_plugins) ext_mgr = extensions.PluginAwareExtensionManager.get_instance() self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr) self.core_plugin = directory.get_plugin() self.tenant_id = 'tenant1' class TestVPNExtGw(VPNOVNPluginDbTestCase): def _pre_port_delete(self, admin_context, port_id): registry.publish( resources.PORT, events.BEFORE_DELETE, self, payload=events.DBEventPayload( admin_context, metadata={'port_check': True}, resource_id=port_id)) def _pre_subnet_delete(self, admin_context, subnet_id): registry.publish(resources.SUBNET, events.BEFORE_DELETE, self, payload=events.DBEventPayload(admin_context, resource_id=subnet_id)) def _pre_network_delete(self, admin_context, network_id): registry.publish(resources.NETWORK, events.BEFORE_DELETE, self, payload=events.DBEventPayload(admin_context, resource_id=network_id)) def _test_prevent_vpn_port_deletion(self, device_owner, gw_key): plugin = directory.get_plugin(nconstants.VPN) with self.router() as router, \ self.port(device_owner=device_owner) as port: gateway = {'gateway': { 'router_id': router['router']['id'], gw_key: port['port']['id'], 'tenant_id': self.tenant_id }} admin_context = context.get_admin_context() plugin.create_gateway(admin_context, gateway) self.assertRaises( cb_exc.CallbackFailure, self._pre_port_delete, admin_context, port['port']['id']) def test_prevent_vpn_port_deletion_gw_port(self): self._test_prevent_vpn_port_deletion( v_constants.DEVICE_OWNER_VPN_ROUTER_GW, 'gw_port_id') def test_prevent_vpn_port_deletion_transit_port(self): self._test_prevent_vpn_port_deletion( v_constants.DEVICE_OWNER_TRANSIT_NETWORK, 'transit_port_id') def test_prevent_vpn_port_deletion_other_device_owner(self): plugin = directory.get_plugin(nconstants.VPN) device_owner = v_constants.DEVICE_OWNER_TRANSIT_NETWORK with self.router() as router, \ self.port(device_owner=device_owner) as transit_port, \ self.port(device_owner='other-device-owner') as other_port: gateway = {'gateway': { 'router_id': router['router']['id'], 'transit_port_id': transit_port['port']['id'], 'tenant_id': self.tenant_id }} admin_context = context.get_admin_context() plugin.create_gateway(admin_context, gateway) # BEFORE_DELETE event for other_port should not raise an exception self._pre_port_delete(admin_context, other_port['port']['id']) def test_prevent_vpn_subnet_deletion(self): plugin = directory.get_plugin(nconstants.VPN) with self.router() as router, self.subnet() as subnet: gateway = {'gateway': { 'router_id': router['router']['id'], 'transit_subnet_id': subnet['subnet']['id'], 'tenant_id': self.tenant_id }} admin_context = context.get_admin_context() plugin.create_gateway(admin_context, gateway) self.assertRaises( cb_exc.CallbackFailure, self._pre_subnet_delete, admin_context, subnet['subnet']['id']) # should not raise an exception for other subnet id self._pre_subnet_delete(admin_context, "other-id") def test_prevent_vpn_network_deletion(self): plugin = directory.get_plugin(nconstants.VPN) with self.router() as router, self.network() as network: gateway = {'gateway': { 'router_id': router['router']['id'], 'transit_network_id': network['network']['id'], 'tenant_id': self.tenant_id }} admin_context = context.get_admin_context() plugin.create_gateway(admin_context, gateway) self.assertRaises( cb_exc.CallbackFailure, self._pre_network_delete, admin_context, network['network']['id']) # should not raise an exception for other network id self._pre_network_delete(admin_context, "other-id") class TestVPNExtGwDB(base.NeutronDbPluginV2TestCase, test_vpn_db.NeutronResourcesMixin): def setUp(self): plugin_str = 'neutron.tests.unit.extensions.test_l3.TestL3NatIntPlugin' super().setUp(plugin_str) self.core_plugin = directory.get_plugin() self.l3_plugin = directory.get_plugin(nconstants.L3) self.tenant_id = 'tenant1' self.context = context.get_admin_context() def _create_gw_port(self, router): port = {'port': { 'tenant_id': self.tenant_id, 'network_id': router['external_gateway_info']['network_id'], 'fixed_ips': lib_constants.ATTR_NOT_SPECIFIED, 'mac_address': lib_constants.ATTR_NOT_SPECIFIED, 'admin_state_up': True, 'device_id': router['id'], 'device_owner': v_constants.DEVICE_OWNER_VPN_ROUTER_GW, 'name': '' }} return self.core_plugin.create_port(self.context, port) def test_create_gateway(self): private_subnet, router = self.create_basic_topology() gateway = {'gateway': { 'router_id': router['id'], 'tenant_id': self.tenant_id }} gwdb = VPNExtGWPlugin_db() new_gateway = gwdb.create_gateway(self.context, gateway) expected = {**gateway['gateway'], 'status': lib_constants.PENDING_CREATE} self.assertDictSupersetOf(expected, new_gateway) def test_update_gateway_with_external_port(self): private_subnet, router = self.create_basic_topology() gwdb = VPNExtGWPlugin_db() # create gateway gateway = {'gateway': { 'router_id': router['id'], 'tenant_id': self.tenant_id }} new_gateway = gwdb.create_gateway(self.context, gateway) # create external port and update gateway with the port id gw_port = self._create_gw_port(router) gateway_update = {'gateway': { 'gw_port_id': gw_port['id'] }} gwdb.update_gateway(self.context, new_gateway['id'], gateway_update) # check that get_vpn_gw_dict_by_router_id includes external_fixed_ips found_gateway = gwdb.get_vpn_gw_dict_by_router_id(self.context, router['id']) self.assertIn('external_fixed_ips', found_gateway) expected = sorted(gw_port['fixed_ips']) returned = sorted(found_gateway['external_fixed_ips']) self.assertEqual(returned, expected) def test_delete_gateway(self): private_subnet, router = self.create_basic_topology() gwdb = VPNExtGWPlugin_db() # create gateway gateway = {'gateway': { 'router_id': router['id'], 'tenant_id': self.tenant_id }} new_gateway = gwdb.create_gateway(self.context, gateway) self.assertIsNotNone(new_gateway) deleted = gwdb.delete_gateway(self.context, new_gateway['id']) self.assertEqual(deleted, 1) deleted = gwdb.delete_gateway(self.context, new_gateway['id']) self.assertEqual(deleted, 0) found_gateway = gwdb.get_vpn_gw_dict_by_router_id(self.context, router['id']) self.assertIsNone(found_gateway) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/db/vpn/test_vpn_validator.py0000664000175000017500000006065200000000000027446 0ustar00zuulzuul00000000000000# Copyright 2015, Cisco Systems Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import socket from unittest import mock from neutron.db import l3_db from neutron_lib import context as n_ctx from neutron_lib import exceptions as nexception from neutron_lib.exceptions import vpn as vpn_exception from neutron_lib.plugins import constants as nconstants from neutron_lib.plugins import directory from oslo_utils import uuidutils from sqlalchemy.orm import query from neutron_vpnaas.db.vpn import vpn_validator from neutron_vpnaas.services.vpn.common import constants as v_constants from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_ROUTER_ID = _uuid() FAKE_ROUTER = {l3_db.EXTERNAL_GW_INFO: FAKE_ROUTER_ID} FAKE_SUBNET_ID = _uuid() IPV4 = 4 IPV6 = 6 class TestVpnValidation(base.BaseTestCase): def setUp(self): super(TestVpnValidation, self).setUp() self.l3_plugin = mock.Mock() self.core_plugin = mock.Mock() directory.add_plugin(nconstants.CORE, self.core_plugin) directory.add_plugin(nconstants.L3, self.l3_plugin) self.context = n_ctx.Context('some_user', 'some_tenant') self.validator = vpn_validator.VpnReferenceValidator() self.router = mock.Mock() self.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}]} def test_non_public_router_for_vpn_service(self): """Failure test of service validate, when router missing ext. I/F.""" self.l3_plugin.get_router.return_value = {} # No external gateway vpnservice = {'router_id': 123, 'subnet_id': 456} self.assertRaises(vpn_exception.RouterIsNotExternal, self.validator.validate_vpnservice, self.context, vpnservice) def test_subnet_not_connected_for_vpn_service(self): """Failure test of service validate, when subnet not on router.""" self.l3_plugin.get_router.return_value = FAKE_ROUTER self.core_plugin.get_ports.return_value = None vpnservice = {'router_id': FAKE_ROUTER_ID, 'subnet_id': FAKE_SUBNET_ID} self.assertRaises(vpn_exception.SubnetIsNotConnectedToRouter, self.validator.validate_vpnservice, self.context, vpnservice) def test_defaults_for_ipsec_site_connections_on_create(self): """Check that defaults are applied correctly. MTU has a default and will always be present on create. However, the DPD settings do not have a default, so database create method will assign default values for any missing. In addition, the DPD dict will be flattened for storage into the database, so we'll do it as part of assigning defaults. """ ipsec_sitecon = {} self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon) expected = { 'dpd_action': 'hold', 'dpd_timeout': 120, 'dpd_interval': 30 } self.assertEqual(expected, ipsec_sitecon) ipsec_sitecon = {'dpd': {'interval': 50}} self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon) expected = { 'dpd': {'interval': 50}, 'dpd_action': 'hold', 'dpd_timeout': 120, 'dpd_interval': 50 } self.assertEqual(expected, ipsec_sitecon) def test_resolve_peer_address_with_ipaddress(self): ipsec_sitecon = {'peer_address': '10.0.0.9'} self.validator._validate_peer_address = mock.Mock() self.validator.resolve_peer_address(ipsec_sitecon, self.router) self.assertEqual('10.0.0.9', ipsec_sitecon['peer_address']) self.validator._validate_peer_address.assert_called_once_with( IPV4, self.router) def test_resolve_peer_address_with_fqdn(self): with mock.patch.object(socket, 'getaddrinfo') as mock_getaddr_info: mock_getaddr_info.return_value = [(2, 1, 6, '', ('10.0.0.9', 0))] ipsec_sitecon = {'peer_address': 'fqdn.peer.addr'} self.validator._validate_peer_address = mock.Mock() self.validator.resolve_peer_address(ipsec_sitecon, self.router) self.assertEqual('10.0.0.9', ipsec_sitecon['peer_address']) self.validator._validate_peer_address.assert_called_once_with( IPV4, self.router) def test_resolve_peer_address_with_invalid_fqdn(self): with mock.patch.object(socket, 'getaddrinfo') as mock_getaddr_info: def getaddr_info_failer(*args, **kwargs): raise socket.gaierror() mock_getaddr_info.side_effect = getaddr_info_failer ipsec_sitecon = {'peer_address': 'fqdn.invalid'} self.assertRaises(vpn_exception.VPNPeerAddressNotResolved, self.validator.resolve_peer_address, ipsec_sitecon, self.router) def helper_validate_peer_address(self, fixed_ips, ip_version, expected_exception=False): self.router.id = FAKE_ROUTER_ID self.router.gw_port = {'fixed_ips': fixed_ips} try: self.validator._validate_peer_address(ip_version, self.router) if expected_exception: self.fail("No exception raised for invalid peer address") except vpn_exception.ExternalNetworkHasNoSubnet: if not expected_exception: self.fail("exception for valid peer address raised") def test_validate_peer_address(self): # validate ipv4 peer_address with ipv4 gateway fixed_ips = [{'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV4) # validate ipv6 peer_address with ipv6 gateway fixed_ips = [{'ip_address': '2001::1'}] self.helper_validate_peer_address(fixed_ips, IPV6) # validate ipv6 peer_address with both ipv4 and ipv6 gateways fixed_ips = [{'ip_address': '2001::1'}, {'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV6) # validate ipv4 peer_address with both ipv4 and ipv6 gateways fixed_ips = [{'ip_address': '2001::1'}, {'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV4) # validate ipv4 peer_address with ipv6 gateway fixed_ips = [{'ip_address': '2001::1'}] self.helper_validate_peer_address(fixed_ips, IPV4, expected_exception=True) # validate ipv6 peer_address with ipv4 gateway fixed_ips = [{'ip_address': '10.0.0.99'}] self.helper_validate_peer_address(fixed_ips, IPV6, expected_exception=True) def test_defaults_for_ipsec_site_connections_on_update(self): """Check that defaults are used for any values not specified.""" ipsec_sitecon = {} prev_connection = {'peer_cidrs': [{'cidr': '10.0.0.0/24'}, {'cidr': '20.0.0.0/24'}], 'local_ep_group_id': None, 'peer_ep_group_id': None, 'dpd_action': 'clear', 'dpd_timeout': 500, 'dpd_interval': 250} self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, prev_connection) expected = { 'peer_cidrs': ['10.0.0.0/24', '20.0.0.0/24'], 'local_ep_group_id': None, 'peer_ep_group_id': None, 'dpd_action': 'clear', 'dpd_timeout': 500, 'dpd_interval': 250 } self.assertEqual(expected, ipsec_sitecon) ipsec_sitecon = {'dpd': {'timeout': 200}} local_epg_id = _uuid() peer_epg_id = _uuid() prev_connection = {'peer_cidrs': [], 'local_ep_group_id': local_epg_id, 'peer_ep_group_id': peer_epg_id, 'dpd_action': 'clear', 'dpd_timeout': 500, 'dpd_interval': 100} self.validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon, prev_connection) expected = { 'peer_cidrs': [], 'local_ep_group_id': local_epg_id, 'peer_ep_group_id': peer_epg_id, 'dpd': {'timeout': 200}, 'dpd_action': 'clear', 'dpd_timeout': 200, 'dpd_interval': 100 } self.assertEqual(expected, ipsec_sitecon) def test_bad_dpd_settings_on_create(self): """Failure tests of DPD settings for IPSec conn during create.""" ipsec_sitecon = {'dpd_action': 'hold', 'dpd_interval': 100, 'dpd_timeout': 100} self.assertRaises( vpn_exception.IPsecSiteConnectionDpdIntervalValueError, self.validator._check_dpd, ipsec_sitecon) ipsec_sitecon = {'dpd_action': 'hold', 'dpd_interval': 100, 'dpd_timeout': 99} self.assertRaises( vpn_exception.IPsecSiteConnectionDpdIntervalValueError, self.validator._check_dpd, ipsec_sitecon) def test_bad_mtu_for_ipsec_connection(self): """Failure test of invalid MTU values for IPSec conn create/update.""" ip_version_limits = self.validator.IP_MIN_MTU for version, limit in ip_version_limits.items(): ipsec_sitecon = {'mtu': limit - 1} self.assertRaises( vpn_exception.IPsecSiteConnectionMtuError, self.validator._check_mtu, self.context, ipsec_sitecon['mtu'], version) def test_endpoints_all_cidrs_in_endpoint_group(self): """All endpoints in the endpoint group are valid CIDRs.""" endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24', '20.20.20.0/24']} try: self.validator.validate_endpoint_group(self.context, endpoint_group) except Exception: self.fail("All CIDRs in endpoint_group should be valid") def test_endpoints_all_subnets_in_endpoint_group(self): """All endpoints in the endpoint group are valid subnets.""" endpoint_group = {'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), _uuid()]} try: self.validator.validate_endpoint_group(self.context, endpoint_group) except Exception: self.fail("All subnets in endpoint_group should be valid") def test_mixed_endpoint_types_in_endpoint_group(self): """Fail when mixing types of endpoints in endpoint group.""" endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24', _uuid()]} self.assertRaises(vpn_exception.InvalidEndpointInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) endpoint_group = {'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), '10.10.10.0/24']} self.assertRaises(vpn_exception.InvalidEndpointInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_missing_endpoints_for_endpoint_group(self): endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': []} self.assertRaises(vpn_exception.MissingEndpointForEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_fail_bad_cidr_in_endpoint_group(self): """Testing catches bad CIDR. Just check one case, as CIDR validator used has good test coverage. """ endpoint_group = {'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.10/24', '20.20.20.1']} self.assertRaises(vpn_exception.InvalidEndpointInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_unknown_subnet_in_endpoint_group(self): subnet_id = _uuid() self.core_plugin.get_subnet.side_effect = nexception.SubnetNotFound( subnet_id=subnet_id) endpoint_group = {'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [subnet_id]} self.assertRaises(vpn_exception.NonExistingSubnetInEndpointGroup, self.validator.validate_endpoint_group, self.context, endpoint_group) def test_fail_subnets_not_on_same_router_for_endpoint_group(self): """Detect when local endpoints not on the same router.""" subnet1 = {'id': _uuid(), 'ip_version': 4} subnet2 = {'id': _uuid(), 'ip_version': 4} router = _uuid() multiple_subnets = [subnet1, subnet2] port_mock = mock.patch.object(self.core_plugin, "get_ports").start() port_mock.side_effect = ['dummy info', None] self.assertRaises(vpn_exception.SubnetIsNotConnectedToRouter, self.validator._check_local_subnets_on_router, self.context, router, multiple_subnets) def test_ipsec_conn_local_endpoints_same_ip_version(self): """Check local endpoint subnets all have same IP version.""" endpoint_group_id = _uuid() subnet1 = {'ip_version': 4} subnet2 = {'ip_version': 4} single_subnet = [subnet1] version = self.validator._check_local_endpoint_ip_versions( endpoint_group_id, single_subnet) self.assertEqual(4, version) multiple_subnets = [subnet1, subnet2] version = self.validator._check_local_endpoint_ip_versions( endpoint_group_id, multiple_subnets) self.assertEqual(4, version) def test_fail_ipsec_conn_local_endpoints_mixed_ip_version(self): """Fail when mixed IP versions in local endpoints.""" endpoint_group_id = _uuid() subnet1 = {'ip_version': 6} subnet2 = {'ip_version': 4} mixed_subnets = [subnet1, subnet2] self.assertRaises(vpn_exception.MixedIPVersionsForIPSecEndpoints, self.validator._check_local_endpoint_ip_versions, endpoint_group_id, mixed_subnets) def test_ipsec_conn_peer_endpoints_same_ip_version(self): """Check all CIDRs have the same IP version.""" endpoint_group_id = _uuid() one_cidr = ['2002:0a00::/48'] version = self.validator._check_peer_endpoint_ip_versions( endpoint_group_id, one_cidr) self.assertEqual(6, version) multiple_cidr = ['10.10.10.0/24', '20.20.20.0/24'] version = self.validator._check_peer_endpoint_ip_versions( endpoint_group_id, multiple_cidr) self.assertEqual(4, version) def test_fail_ipsec_conn_peer_endpoints_mixed_ip_version(self): """Fail when mixed IP versions in peer endpoints.""" endpoint_group_id = _uuid() mixed_cidrs = ['10.10.10.0/24', '2002:1400::/48'] self.assertRaises(vpn_exception.MixedIPVersionsForIPSecEndpoints, self.validator._check_peer_endpoint_ip_versions, endpoint_group_id, mixed_cidrs) def test_fail_ipsec_conn_locals_and_peers_different_ip_version(self): """Ensure catch when local and peer IP versions are not the same.""" self.assertRaises(vpn_exception.MixedIPVersionsForIPSecConnection, self.validator._validate_compatible_ip_versions, 4, 6) def test_fail_ipsec_conn_no_subnet_requiring_endpoint_groups(self): """When no subnet, connection must use endpoints. This means both endpoint groups must be present, and peer_cidrs cannot be used. """ subnet = None ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpn_exception.PeerCidrsInvalid, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': None, 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpn_exception.MissingRequiredEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': None} self.assertRaises(vpn_exception.MissingRequiredEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': None, 'peer_ep_group_id': None} self.assertRaises(vpn_exception.MissingRequiredEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) def test_fail_ipsec_conn_subnet_requiring_peer_cidrs(self): """When legacy mode, no endpoint groups. This means neither endpoint group can be specified, and the peer_cidrs must be present. """ subnet = {'id': FAKE_SUBNET_ID} ipsec_sitecon = {'peer_cidrs': [], 'local_ep_group_id': None, 'peer_ep_group_id': None} self.assertRaises(vpn_exception.MissingPeerCidrs, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': None} self.assertRaises(vpn_exception.InvalidEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': None, 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpn_exception.InvalidEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) ipsec_sitecon = {'peer_cidrs': ['10.0.0.0/24'], 'local_ep_group_id': 'local-epg-id', 'peer_ep_group_id': 'peer-epg-id'} self.assertRaises(vpn_exception.InvalidEndpointGroup, self.validator.validate_ipsec_conn_optional_args, ipsec_sitecon, subnet) def test_ipsec_conn_get_local_subnets(self): subnet1 = _uuid() subnet2 = _uuid() expected_subnets = [subnet1, subnet2] local_epg = {'id': _uuid(), 'type': v_constants.SUBNET_ENDPOINT, 'endpoints': expected_subnets} query_mock = mock.patch.object(query.Query, 'all').start() query_mock.return_value = expected_subnets subnets = self.validator._get_local_subnets(self.context, local_epg) self.assertEqual(expected_subnets, subnets) def test_ipsec_conn_get_peer_cidrs(self): expected_cidrs = ['10.10.10.10/24', '20.20.20.20/24'] peer_epg = {'id': 'should-be-cidrs', 'type': v_constants.CIDR_ENDPOINT, 'endpoints': expected_cidrs} cidrs = self.validator._get_peer_cidrs(peer_epg) self.assertEqual(expected_cidrs, cidrs) def test_ipsec_conn_check_peer_cidrs(self): peer_cidrs = ['10.10.10.0/24', '20.20.20.0/24'] try: self.validator._check_peer_cidrs(peer_cidrs) except Exception: self.fail("All peer cidrs format should be valid") def test_fail_ipsec_conn_peer_cidrs_with_invalid_format(self): peer_cidrs = ['invalid_cidr'] self.assertRaises(vpn_exception.IPsecSiteConnectionPeerCidrError, self.validator._check_peer_cidrs, peer_cidrs) peer_cidrs = ['192/24'] self.assertRaises(vpn_exception.IPsecSiteConnectionPeerCidrError, self.validator._check_peer_cidrs, peer_cidrs) def test_fail_ipsec_conn_endpoint_group_types(self): local_epg = {'id': 'should-be-subnets', 'type': v_constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.10/24', '20.20.20.20/24']} self.assertRaises(vpn_exception.WrongEndpointGroupType, self.validator._get_local_subnets, self.context, local_epg) peer_epg = {'id': 'should-be-cidrs', 'type': v_constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), _uuid()]} self.assertRaises(vpn_exception.WrongEndpointGroupType, self.validator._get_peer_cidrs, peer_epg) def test_validate_ipsec_conn_for_endpoints(self): """Check upper-level validation method for endpoint groups. Tests the happy path for doing general validation of the IPSec connection, calling all the sub-checks for an endpoint group case. """ subnet1 = {'id': _uuid(), 'ip_version': 4} subnet2 = {'id': _uuid(), 'ip_version': 4} local_subnets = [subnet1, subnet2] local_epg_id = _uuid() local_epg = {'id': local_epg_id, 'type': v_constants.SUBNET_ENDPOINT, 'endpoints': local_subnets} # Mock getting the subnets from the IDs query_mock = mock.patch.object(query.Query, 'all').start() query_mock.return_value = local_subnets # Mock that subnet is on router port_mock = mock.patch.object(self.core_plugin, "get_ports").start() port_mock.side_effect = ['dummy info', 'more dummy info'] peer_epg_id = _uuid() peer_cidrs = ['10.10.10.10/24', '20.20.20.20/24'] peer_epg = {'id': peer_epg_id, 'type': v_constants.CIDR_ENDPOINT, 'endpoints': peer_cidrs} ipsec_sitecon = {'local_ep_group_id': local_epg_id, 'local_epg_subnets': local_epg, 'peer_ep_group_id': peer_epg_id, 'peer_epg_cidrs': peer_epg, 'mtu': 2000, 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120} local_version = None vpnservice = {'router_id': _uuid()} self.validator.validate_ipsec_site_connection( self.context, ipsec_sitecon, local_version, vpnservice) # NOTE: Following are tests for the older API, providing some additional # coverage. def test_ipsec_conn_peer_cidrs_same_ip_version(self): """Check legacy peer_cidrs have same IP version.""" one_cidr = ['2002:0a00::/48'] version = self.validator._check_peer_cidrs_ip_versions(one_cidr) self.assertEqual(6, version) multiple_cidrs = ['10.10.10.0/24', '20.20.20.0/24'] version = self.validator._check_peer_cidrs_ip_versions(multiple_cidrs) self.assertEqual(4, version) def test_fail_ipsec_conn_peer_cidrs_mixed_ip_version(self): mixed_cidrs = ['2002:0a00::/48', '20.20.20.0/24'] self.assertRaises(vpn_exception.MixedIPVersionsForPeerCidrs, self.validator._check_peer_cidrs_ip_versions, mixed_cidrs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/dummy_ipsec.py0000664000175000017500000000277500000000000024667 0ustar00zuulzuul00000000000000# Copyright 2018, Fujitsu Vietnam Ltd. # 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 import rpc as n_rpc from neutron_vpnaas.services.vpn.service_drivers import base_ipsec from neutron_vpnaas.services.vpn.service_drivers import ipsec_validator IPSEC = 'ipsec' BASE_IPSEC_VERSION = '1.0' class DummyIPsecVPNDriver(base_ipsec.BaseIPsecVPNDriver): """Dummy VPN Service Driver class for IPsec.""" def __init__(self, service_plugin): super(DummyIPsecVPNDriver, self).__init__( service_plugin, ipsec_validator.IpsecVpnValidator(self)) def create_rpc_conn(self): self.endpoints = [base_ipsec.IPsecVpnDriverCallBack(self)] self.conn = n_rpc.Connection() self.conn.create_consumer( 'dummy_ipsec_driver', self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = base_ipsec.IPsecVpnAgentApi( 'dummy_ipsec_agent', BASE_IPSEC_VERSION, self) ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/extensions/0000775000175000017500000000000000000000000024163 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/extensions/__init__.py0000664000175000017500000000000000000000000026262 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/extensions/test_vpn_endpoint_groups.py0000664000175000017500000002037100000000000031701 0ustar00zuulzuul00000000000000# (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 copy from unittest import mock from oslo_utils import uuidutils from webob import exc from neutron.tests.unit.api.v2 import test_base as test_api_v2 from neutron_lib.plugins import constants as nconstants from neutron_vpnaas.extensions import vpn_endpoint_groups from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.services.vpn.common import constants from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid _get_path = test_api_v2._get_path class VpnEndpointGroupsTestPlugin( vpnaas.VPNPluginBase, vpn_endpoint_groups.VPNEndpointGroupsPluginBase): pass class VpnEndpointGroupsTestCase(base.ExtensionTestCase): fmt = 'json' def setUp(self): super(VpnEndpointGroupsTestCase, self).setUp() plural_mappings = {'endpoint_group': 'endpoint-groups'} self.setup_extension( 'neutron_vpnaas.tests.unit.extensions.test_vpn_endpoint_groups.' 'VpnEndpointGroupsTestPlugin', nconstants.VPN, vpn_endpoint_groups.Vpn_endpoint_groups, 'vpn', plural_mappings=plural_mappings, use_quota=True) def helper_test_endpoint_group_create(self, data): """Check that the endpoint_group_create works. Uses passed in endpoint group information, which specifies an endpoint type and values. """ data['endpoint_group'].update({'tenant_id': _uuid(), 'name': 'my endpoint group', 'description': 'my description'}) return_value = copy.copy(data['endpoint_group']) return_value.update({'id': _uuid()}) instance = self.plugin.return_value instance.create_endpoint_group.return_value = return_value res = self.api.post(_get_path('vpn/endpoint-groups', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_endpoint_group.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('endpoint_group', res) self.assertDictSupersetOf(return_value, res['endpoint_group']) def test_create_cidr_endpoint_group_create(self): """Test creation of CIDR type endpoint group.""" data = {'endpoint_group': {'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24', '20.20.20.0/24']}} self.helper_test_endpoint_group_create(data) def test_create_subnet_endpoint_group_create(self): """Test creation of subnet type endpoint group.""" data = {'endpoint_group': {'type': constants.SUBNET_ENDPOINT, 'endpoints': [_uuid(), _uuid()]}} self.helper_test_endpoint_group_create(data) def test_create_vlan_endpoint_group_create(self): """Test creation of VLAN type endpoint group.""" data = {'endpoint_group': {'type': constants.VLAN_ENDPOINT, 'endpoints': ['100', '200', '300', '400']}} self.helper_test_endpoint_group_create(data) def test_get_endpoint_group(self): """Test show for endpoint group.""" endpoint_group_id = _uuid() return_value = {'id': endpoint_group_id, 'tenant_id': _uuid(), 'name': 'my-endpoint-group', 'description': 'my endpoint group', 'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24']} instance = self.plugin.return_value instance.get_endpoint_group.return_value = return_value res = self.api.get(_get_path('vpn/endpoint-groups', id=endpoint_group_id, fmt=self.fmt)) instance.get_endpoint_group.assert_called_with(mock.ANY, endpoint_group_id, fields=mock.ANY) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('endpoint_group', res) self.assertEqual(res['endpoint_group'], return_value) def test_endpoint_group_list(self): """Test listing all endpoint groups.""" return_value = [{'id': _uuid(), 'tenant_id': _uuid(), 'name': 'my-endpoint-group', 'description': 'my endpoint group', 'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24']}, {'id': _uuid(), 'tenant_id': _uuid(), 'name': 'another-endpoint-group', 'description': 'second endpoint group', 'type': constants.VLAN_ENDPOINT, 'endpoints': ['100', '200', '300']}] instance = self.plugin.return_value instance.get_endpoint_groups.return_value = return_value res = self.api.get(_get_path('vpn/endpoint-groups', fmt=self.fmt)) instance.get_endpoint_groups.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(res.status_int, exc.HTTPOk.code) def test_endpoint_group_delete(self): """Test deleting an endpoint group.""" self._test_entity_delete('endpoint_group') def test_endpoint_group_update(self): """Test updating endpoint_group.""" endpoint_group_id = _uuid() update_data = {'endpoint_group': {'description': 'new description'}} return_value = {'id': endpoint_group_id, 'tenant_id': _uuid(), 'name': 'my-endpoint-group', 'description': 'new_description', 'type': constants.CIDR_ENDPOINT, 'endpoints': ['10.10.10.0/24']} instance = self.plugin.return_value instance.update_endpoint_group.return_value = return_value res = self.api.put(_get_path('vpn/endpoint-groups', id=endpoint_group_id, fmt=self.fmt), self.serialize(update_data)) instance.update_endpoint_group.assert_called_with( mock.ANY, endpoint_group_id, endpoint_group=update_data) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('endpoint_group', res) self.assertEqual(res['endpoint_group'], return_value) def test_fail_updating_endpoints_in_endpoint_group(self): """Test fails to update the endpoints in an endpoint group. This documents that we are not allowing endpoints to be updated (currently), as doing so, implies that the connection using the enclosing endpoint group would also need to be updated. For now, a new endpoint group can be created, and the connection can be updated to point to the new endpoint group. """ endpoint_group_id = _uuid() update_data = {'endpoint_group': {'endpoints': ['10.10.10.0/24']}} res = self.api.put(_get_path('vpn/endpoint-groups', id=endpoint_group_id, fmt=self.fmt), params=self.serialize(update_data), expect_errors=True) self.assertEqual(exc.HTTPBadRequest.code, res.status_int) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/extensions/test_vpnaas.py0000664000175000017500000007236000000000000027074 0ustar00zuulzuul00000000000000# (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 copy from unittest import mock from neutron.tests.unit.api.v2 import test_base as test_api_v2 from neutron_lib.plugins import constants as nconstants from oslo_utils import uuidutils from webob import exc from neutron_vpnaas.extensions import vpnaas from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid _get_path = test_api_v2._get_path class VpnaasExtensionTestCase(base.ExtensionTestCase): fmt = 'json' def setUp(self): super(VpnaasExtensionTestCase, self).setUp() plural_mappings = {'ipsecpolicy': 'ipsecpolicies', 'ikepolicy': 'ikepolicies', 'ipsec_site_connection': 'ipsec-site-connections', 'endpoint_group': 'endpoint-groups'} self.setup_extension( 'neutron_vpnaas.extensions.vpnaas.VPNPluginBase', nconstants.VPN, vpnaas.Vpnaas, 'vpn', plural_mappings=plural_mappings, use_quota=True) def test_ikepolicy_create(self): """Test case to create an ikepolicy.""" ikepolicy_id = _uuid() data = {'ikepolicy': {'name': 'ikepolicy1', 'description': 'myikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'main', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid()}} return_value = copy.copy(data['ikepolicy']) return_value.update({'id': ikepolicy_id}) instance = self.plugin.return_value instance.create_ikepolicy.return_value = return_value res = self.api.post(_get_path('vpn/ikepolicies', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_ikepolicy.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertDictSupersetOf(return_value, res['ikepolicy']) def test_ikepolicy_create_with_aggressive_mode(self): """Test case to create an ikepolicy with agressive mode.""" ikepolicy_id = _uuid() data = {'ikepolicy': {'name': 'ikepolicy1', 'description': 'myikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'aggressive', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid()}} return_value = copy.copy(data['ikepolicy']) return_value.update({'id': ikepolicy_id}) instance = self.plugin.return_value instance.create_ikepolicy.return_value = return_value res = self.api.post(_get_path('vpn/ikepolicies', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_ikepolicy.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertDictSupersetOf(return_value, res['ikepolicy']) def test_ikepolicy_list(self): """Test case to list all ikepolicies.""" ikepolicy_id = _uuid() return_value = [{'name': 'ikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'ike_version': 'v1', 'id': ikepolicy_id}] instance = self.plugin.return_value instance.get_ikepolicies.return_value = return_value res = self.api.get(_get_path('vpn/ikepolicies', fmt=self.fmt)) instance.get_ikepolicies.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_ikepolicy_update(self): """Test case to update an ikepolicy.""" ikepolicy_id = _uuid() update_data = {'ikepolicy': {'name': 'ikepolicy1', 'encryption_algorithm': 'aes-256'}} return_value = {'name': 'ikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-256', 'phase1_negotiation_mode': 'main', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ikepolicy_id} instance = self.plugin.return_value instance.update_ikepolicy.return_value = return_value res = self.api.put(_get_path('vpn/ikepolicies', id=ikepolicy_id, fmt=self.fmt), self.serialize(update_data)) instance.update_ikepolicy.assert_called_with(mock.ANY, ikepolicy_id, ikepolicy=update_data) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertEqual(return_value, res['ikepolicy']) def test_ikepolicy_update_with_aggressive_mode(self): """Test case to update an ikepolicy with aggressive mode.""" ikepolicy_id = _uuid() update_data = {'ikepolicy': {'name': 'ikepolicy1', 'phase1_negotiation_mode': 'aggressive', 'encryption_algorithm': 'aes-256'}} return_value = {'name': 'ikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-256', 'phase1_negotiation_mode': 'aggressive', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ikepolicy_id} instance = self.plugin.return_value instance.update_ikepolicy.return_value = return_value res = self.api.put(_get_path('vpn/ikepolicies', id=ikepolicy_id, fmt=self.fmt), self.serialize(update_data)) instance.update_ikepolicy.assert_called_with(mock.ANY, ikepolicy_id, ikepolicy=update_data) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertEqual(return_value, res['ikepolicy']) def test_ikepolicy_get(self): """Test case to get or show an ikepolicy.""" ikepolicy_id = _uuid() return_value = {'name': 'ikepolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'phase1_negotiation_mode': 'main', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'ike_version': 'v1', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ikepolicy_id} instance = self.plugin.return_value instance.get_ikepolicy.return_value = return_value res = self.api.get(_get_path('vpn/ikepolicies', id=ikepolicy_id, fmt=self.fmt)) instance.get_ikepolicy.assert_called_with(mock.ANY, ikepolicy_id, fields=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ikepolicy', res) self.assertEqual(return_value, res['ikepolicy']) def test_ikepolicy_delete(self): """Test case to delete an ikepolicy.""" self._test_entity_delete('ikepolicy') def test_ipsecpolicy_create(self): """Test case to create an ipsecpolicy.""" ipsecpolicy_id = _uuid() data = {'ipsecpolicy': {'name': 'ipsecpolicy1', 'description': 'myipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'transform_protocol': 'esp', 'pfs': 'group5', 'tenant_id': _uuid()}} return_value = copy.copy(data['ipsecpolicy']) return_value.update({'id': ipsecpolicy_id}) instance = self.plugin.return_value instance.create_ipsecpolicy.return_value = return_value res = self.api.post(_get_path('vpn/ipsecpolicies', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_ipsecpolicy.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsecpolicy', res) self.assertDictSupersetOf(return_value, res['ipsecpolicy']) def test_ipsecpolicy_list(self): """Test case to list an ipsecpolicy.""" ipsecpolicy_id = _uuid() return_value = [{'name': 'ipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'pfs': 'group5', 'id': ipsecpolicy_id}] instance = self.plugin.return_value instance.get_ipsecpolicies.return_value = return_value res = self.api.get(_get_path('vpn/ipsecpolicies', fmt=self.fmt)) instance.get_ipsecpolicies.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_ipsecpolicy_update(self): """Test case to update an ipsecpolicy.""" ipsecpolicy_id = _uuid() update_data = {'ipsecpolicy': {'name': 'ipsecpolicy1', 'encryption_algorithm': 'aes-256'}} return_value = {'name': 'ipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'transform_protocol': 'esp', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ipsecpolicy_id} instance = self.plugin.return_value instance.update_ipsecpolicy.return_value = return_value res = self.api.put(_get_path('vpn/ipsecpolicies', id=ipsecpolicy_id, fmt=self.fmt), self.serialize(update_data)) instance.update_ipsecpolicy.assert_called_with(mock.ANY, ipsecpolicy_id, ipsecpolicy=update_data) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsecpolicy', res) self.assertEqual(return_value, res['ipsecpolicy']) def test_ipsecpolicy_get(self): """Test case to get or show an ipsecpolicy.""" ipsecpolicy_id = _uuid() return_value = {'name': 'ipsecpolicy1', 'auth_algorithm': 'sha1', 'encryption_algorithm': 'aes-128', 'encapsulation_mode': 'tunnel', 'lifetime': { 'units': 'seconds', 'value': 3600}, 'transform_protocol': 'esp', 'pfs': 'group5', 'tenant_id': _uuid(), 'id': ipsecpolicy_id} instance = self.plugin.return_value instance.get_ipsecpolicy.return_value = return_value res = self.api.get(_get_path('vpn/ipsecpolicies', id=ipsecpolicy_id, fmt=self.fmt)) instance.get_ipsecpolicy.assert_called_with(mock.ANY, ipsecpolicy_id, fields=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsecpolicy', res) self.assertEqual(return_value, res['ipsecpolicy']) def test_ipsecpolicy_delete(self): """Test case to delete an ipsecpolicy.""" self._test_entity_delete('ipsecpolicy') def _test_vpnservice_create(self, more_args, defaulted_args): """Helper to test VPN service creation. Allows additional args to be specified for different test cases. Includes expected args, for case where an optional args are not specified and API applies defaults. """ data = {'vpnservice': {'name': 'vpnservice1', 'description': 'descr_vpn1', 'router_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid()}} data['vpnservice'].update(more_args) # Add in any default values for args that were not provided actual_args = copy.copy(data) actual_args['vpnservice'].update(defaulted_args) return_value = copy.copy(data['vpnservice']) return_value.update({'status': "ACTIVE", 'id': _uuid()}) return_value.update(defaulted_args) instance = self.plugin.return_value instance.create_vpnservice.return_value = return_value res = self.api.post(_get_path('vpn/vpnservices', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_vpnservice.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('vpnservice', res) self.assertDictSupersetOf(return_value, res['vpnservice']) def test_vpnservice_create(self): """Create VPN service using subnet (older API).""" subnet = {'subnet_id': _uuid()} self._test_vpnservice_create(more_args=subnet, defaulted_args={}) def test_vpnservice_create_no_subnet(self): """Test case to create a vpnservice w/o subnet (newer API).""" no_subnet = {'subnet_id': None} self._test_vpnservice_create(more_args={}, defaulted_args=no_subnet) def test_vpnservice_list(self): """Test case to list all vpnservices.""" vpnservice_id = _uuid() return_value = [{'name': 'vpnservice1', 'tenant_id': _uuid(), 'status': 'ACTIVE', 'id': vpnservice_id}] instance = self.plugin.return_value instance.get_vpnservice.return_value = return_value res = self.api.get(_get_path('vpn/vpnservices', fmt=self.fmt)) instance.get_vpnservices.assert_called_with(mock.ANY, fields=mock.ANY, filters=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_vpnservice_update(self): """Test case to update a vpnservice.""" vpnservice_id = _uuid() update_data = {'vpnservice': {'admin_state_up': False}} return_value = {'name': 'vpnservice1', 'admin_state_up': False, 'subnet_id': _uuid(), 'router_id': _uuid(), 'tenant_id': _uuid(), 'status': "ACTIVE", 'id': vpnservice_id} instance = self.plugin.return_value instance.update_vpnservice.return_value = return_value res = self.api.put(_get_path('vpn/vpnservices', id=vpnservice_id, fmt=self.fmt), self.serialize(update_data)) instance.update_vpnservice.assert_called_with(mock.ANY, vpnservice_id, vpnservice=update_data) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('vpnservice', res) self.assertEqual(return_value, res['vpnservice']) def test_vpnservice_get(self): """Test case to get or show a vpnservice.""" vpnservice_id = _uuid() return_value = {'name': 'vpnservice1', 'admin_state_up': True, 'subnet_id': _uuid(), 'router_id': _uuid(), 'tenant_id': _uuid(), 'status': "ACTIVE", 'id': vpnservice_id} instance = self.plugin.return_value instance.get_vpnservice.return_value = return_value res = self.api.get(_get_path('vpn/vpnservices', id=vpnservice_id, fmt=self.fmt)) instance.get_vpnservice.assert_called_with(mock.ANY, vpnservice_id, fields=mock.ANY) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('vpnservice', res) self.assertEqual(return_value, res['vpnservice']) def test_vpnservice_delete(self): """Test case to delete a vpnservice.""" self._test_entity_delete('vpnservice') def _test_ipsec_site_connection_create(self, more_args, defaulted_args): """Helper to test creating IPSec connection.""" ipsecsite_con_id = _uuid() ikepolicy_id = _uuid() ipsecpolicy_id = _uuid() data = { 'ipsec_site_connection': {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': ikepolicy_id, 'ipsecpolicy_id': ipsecpolicy_id, 'vpnservice_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid()} } data['ipsec_site_connection'].update(more_args) # Add in any default values for args that were not provided actual_args = copy.copy(data) actual_args['ipsec_site_connection'].update(defaulted_args) return_value = copy.copy(data['ipsec_site_connection']) return_value.update({'status': "ACTIVE", 'id': ipsecsite_con_id}) return_value.update(defaulted_args) instance = self.plugin.return_value instance.create_ipsec_site_connection.return_value = return_value res = self.api.post(_get_path('vpn/ipsec-site-connections', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertEqual(1, instance.create_ipsec_site_connection.call_count) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsec_site_connection', res) self.assertDictSupersetOf(return_value, res['ipsec_site_connection']) def test_ipsec_site_connection_create(self): """Create an IPSec connection with peer CIDRs (old API).""" more_args = {'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'local_id': ''} no_endpoint_groups = {'local_ep_group_id': None, 'peer_ep_group_id': None} self._test_ipsec_site_connection_create( more_args=more_args, defaulted_args=no_endpoint_groups) def test_ipsec_site_connection_create_with_endpoints(self): """Create an IPSec connection with endpoint groups (new API).""" more_args = {'local_ep_group_id': _uuid(), 'peer_ep_group_id': _uuid(), 'local_id': ''} no_peer_cidrs = {'peer_cidrs': []} self._test_ipsec_site_connection_create(more_args=more_args, defaulted_args=no_peer_cidrs) def test_ipsec_site_connection_create_with_invalid_cidr_format(self): peer_cidrs = ['192.168.2.0/24', '10/8'] data = { 'ipsec_site_connection': {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': peer_cidrs, 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': _uuid(), 'ipsecpolicy_id': _uuid(), 'vpnservice_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid()} } res = self.api.post(_get_path('vpn/ipsec-site-connections', fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertEqual(exc.HTTPBadRequest.code, res.status_int) def test_ipsec_site_connection_list(self): """Test case to list all ipsec_site_connections.""" ipsecsite_con_id = _uuid() return_value = [{'name': 'connection1', 'peer_address': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'route_mode': 'static', 'auth_mode': 'psk', 'local_ep_group_id': None, 'peer_ep_group_id': None, 'tenant_id': _uuid(), 'status': 'ACTIVE', 'id': ipsecsite_con_id}] instance = self.plugin.return_value instance.get_ipsec_site_connections.return_value = return_value res = self.api.get( _get_path('vpn/ipsec-site-connections', fmt=self.fmt)) instance.get_ipsec_site_connections.assert_called_with( mock.ANY, fields=mock.ANY, filters=mock.ANY ) self.assertEqual(exc.HTTPOk.code, res.status_int) def test_ipsec_site_connection_update(self): """Test case to update a ipsec_site_connection.""" ipsecsite_con_id = _uuid() update_data = {'ipsec_site_connection': {'admin_state_up': False}} return_value = {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': _uuid(), 'ipsecpolicy_id': _uuid(), 'vpnservice_id': _uuid(), 'admin_state_up': False, 'local_ep_group_id': None, 'peer_ep_group_id': None, 'tenant_id': _uuid(), 'status': 'ACTIVE', 'id': ipsecsite_con_id} instance = self.plugin.return_value instance.update_ipsec_site_connection.return_value = return_value res = self.api.put(_get_path('vpn/ipsec-site-connections', id=ipsecsite_con_id, fmt=self.fmt), self.serialize(update_data)) instance.update_ipsec_site_connection.assert_called_with( mock.ANY, ipsecsite_con_id, ipsec_site_connection=update_data ) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsec_site_connection', res) self.assertEqual(return_value, res['ipsec_site_connection']) def test_ipsec_site_connection_get(self): """Test case to get or show a ipsec_site_connection.""" ipsecsite_con_id = _uuid() return_value = {'name': 'connection1', 'description': 'Remote-connection1', 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'mtu': 1500, 'psk': 'abcd', 'initiator': 'bi-directional', 'dpd': { 'action': 'hold', 'interval': 30, 'timeout': 120}, 'ikepolicy_id': _uuid(), 'ipsecpolicy_id': _uuid(), 'vpnservice_id': _uuid(), 'admin_state_up': True, 'tenant_id': _uuid(), 'local_ep_group_id': None, 'peer_ep_group_id': None, 'status': 'ACTIVE', 'id': ipsecsite_con_id} instance = self.plugin.return_value instance.get_ipsec_site_connection.return_value = return_value res = self.api.get(_get_path('vpn/ipsec-site-connections', id=ipsecsite_con_id, fmt=self.fmt)) instance.get_ipsec_site_connection.assert_called_with( mock.ANY, ipsecsite_con_id, fields=mock.ANY ) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('ipsec_site_connection', res) self.assertEqual(return_value, res['ipsec_site_connection']) def test_ipsec_site_connection_delete(self): """Test case to delete a ipsec_site_connection.""" self._test_entity_delete('ipsec_site_connection') ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/0000775000175000017500000000000000000000000023607 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/__init__.py0000664000175000017500000000000000000000000025706 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/0000775000175000017500000000000000000000000024412 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/__init__.py0000664000175000017500000000000000000000000026511 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1712152350.91135 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/common/0000775000175000017500000000000000000000000025702 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/common/__init__.py0000664000175000017500000000000000000000000030001 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/common/test_netns_wrapper.py0000664000175000017500000000606100000000000032205 0ustar00zuulzuul00000000000000# 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. from unittest import mock from neutron.tests import base from neutron_vpnaas.services.vpn.common import netns_wrapper as nswrap class TestNetnsWrapper(base.BaseTestCase): def setUp(self): super(TestNetnsWrapper, self).setUp() patch_methods = ['filter_command', 'execute', 'setup_conf'] for method in patch_methods: self.patch_obj(nswrap, method) patch_classes = ['neutron.common.config.setup_logging', 'os.path.isdir', 'os.path.samefile', 'sys.exit'] for cls in patch_classes: self.patch_cls(cls) self.filter_command.return_value = False self.execute.return_value = 0 self.conf = mock.Mock() self.conf.cmd = 'ls,-al' self.conf.mount_paths = {'/foo': '/dir/foo', '/var': '/dir/var'} self.setup_conf.return_value = self.conf self.conf.rootwrap_config = 'conf' self.isdir.return_value = True self.samefile.return_value = False def patch_obj(self, obj, method): _m = mock.patch.object(obj, method) _mock = _m.start() setattr(self, method, _mock) def patch_cls(self, patch_class): _m = mock.patch(patch_class) mock_name = patch_class.split('.')[-1] _mock = _m.start() setattr(self, mock_name, _mock) def test_netns_wrap_fail_without_netns(self): self.samefile.return_value = True return_val = nswrap.execute_with_mount() self.assertTrue(return_val) def test_netns_wrap(self): self.conf.cmd = 'ls,-al' return_val = nswrap.execute_with_mount() exp_calls = [mock.call(['mount', '--bind', '/dir/foo', '/foo']), mock.call(['mount', '--bind', '/dir/var', '/var']), mock.call('ls,-al')] self.execute.assert_has_calls(exp_calls, any_order=True) self.assertFalse(return_val) def test_netns_wrap_fail_without_cmd(self): self.conf.cmd = None return_val = nswrap.execute_with_mount() self.assertFalse(self.execute.called) self.assertTrue(return_val) def test_netns_wrap_fail_without_mount_paths(self): self.conf.mount_paths = None return_val = nswrap.execute_with_mount() self.assertFalse(self.execute.called) self.assertTrue(return_val) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9153502 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/0000775000175000017500000000000000000000000027407 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/__init__.py0000664000175000017500000000000000000000000031506 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py0000664000175000017500000021243100000000000032126 0ustar00zuulzuul00000000000000# 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 copy import difflib import io import os import socket from unittest import mock import netaddr from neutron.agent.l3 import dvr_edge_router from neutron.agent.l3 import dvr_snat_ns from neutron.agent.l3 import legacy_router from neutron.agent.linux import iptables_manager from neutron.conf.agent.l3 import config as l3_config from neutron_lib import constants from neutron_lib.exceptions import vpn as vpn_exception from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.services.vpn.device_drivers import ipsec as openswan_ipsec from neutron_vpnaas.services.vpn.device_drivers import libreswan_ipsec from neutron_vpnaas.services.vpn.device_drivers import strongswan_ipsec from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_HOST = 'fake_host' FAKE_ROUTER_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION1_ID = _uuid() FAKE_IPSEC_SITE_CONNECTION2_ID = _uuid() FAKE_VPNSERVICE_ID = _uuid() FAKE_IKE_POLICY = { 'ike_version': 'v1', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'lifetime_value': 3600 } FAKE_IPSEC_POLICY = { 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'transform_protocol': 'esp', 'lifetime_value': 3600, 'encapsulation_mode': 'tunnel' } FAKE_VPN_SERVICE = { 'id': FAKE_VPNSERVICE_ID, 'router_id': FAKE_ROUTER_ID, 'name': 'myvpn', 'admin_state_up': True, 'status': constants.PENDING_CREATE, 'external_ip': '60.0.0.4', 'ipsec_site_connections': [ {'peer_cidrs': ['20.0.0.0/24', '30.0.0.0/24'], 'local_cidrs': ['10.0.0.0/24'], 'local_ip_vers': 4, 'admin_state_up': True, 'id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'external_ip': '60.0.0.4', 'local_id': '60.0.0.4', 'peer_address': '60.0.0.5', 'mtu': 1500, 'peer_id': '60.0.0.5', 'psk': 'password', 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120, 'status': constants.PENDING_CREATE}, {'peer_cidrs': ['40.0.0.0/24', '50.0.0.0/24'], 'local_cidrs': ['11.0.0.0/24'], 'local_ip_vers': 4, 'admin_state_up': True, 'external_ip': '60.0.0.4', 'local_id': '60.0.0.4', 'peer_address': '60.0.0.6', 'peer_id': '60.0.0.6', 'mtu': 1500, 'psk': 'password', 'id': FAKE_IPSEC_SITE_CONNECTION2_ID, 'initiator': 'bi-directional', 'ikepolicy': FAKE_IKE_POLICY, 'ipsecpolicy': FAKE_IPSEC_POLICY, 'dpd_action': 'hold', 'dpd_interval': 30, 'dpd_timeout': 120, 'status': constants.PENDING_CREATE}] } AUTH_ESP = '''esp # [encryption_algorithm]-[auth_algorithm]-[pfs] phase2alg=aes128-sha1;modp1536''' AUTH_AH = '''ah # AH protocol does not support encryption # [auth_algorithm]-[pfs] phase2alg=sha1;modp1536''' OPENSWAN_CONNECTION_DETAILS = '''# rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) # [mtu] mtu=1500 # [dpd_action] dpdaction=%(dpd_action)s # [dpd_interval] dpddelay=%(dpd_delay)s # [dpd_timeout] dpdtimeout=%(dpd_timeout)s # [auth_mode] authby=secret ###################### # IKEPolicy params ###################### #ike version ikev2=never # [encryption_algorithm]-[auth_algorithm]-[pfs] ike=aes128-sha1;modp1536 # [lifetime_value] ikelifetime=%(ike_lifetime)ss # NOTE: it looks lifetime_units=kilobytes can't be enforced \ (could be seconds, hours, days...) ########################## # IPsecPolicys params ########################## # [transform_protocol] phase2=%(auth_mode)s # [encapsulation_mode] type=%(encapsulation_mode)s # [lifetime_value] lifetime=%(life_time)ss # lifebytes=100000 if lifetime_units=kilobytes (IKEv2 only) ''' IPV4_NEXT_HOP = '''# NOTE: a default route is required for %defaultroute to work... leftnexthop=%defaultroute rightnexthop=%defaultroute''' IPV6_NEXT_HOP = '''# To recognize the given IP addresses in this config # as IPv6 addresses by pluto whack. Default is ipv4 connaddrfamily=ipv6 # openswan can't process defaultroute for ipv6. # Assign gateway address as leftnexthop leftnexthop=%s # rightnexthop is not mandatory for ipsec, so no need in ipv6.''' EXPECTED_OPENSWAN_CONF = """ # Configuration for %(vpnservice_id)s config setup nat_traversal=yes virtual_private=%(virtual_privates)s conn %%default keylife=60m keyingtries=%%forever conn %(conn1_id)s %(next_hop)s left=%(left)s leftid=%(leftid)s auto=start # NOTE:REQUIRED # [subnet] leftsubnet%(local_cidrs1)s # [updown] # What "updown" script to run to adjust routing and/or firewalling when # the status of the connection changes (default "ipsec _updown"). # "--route yes" allows to specify such routing options as mtu and metric. leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ###################### # [peer_address] right=%(right1)s # [peer_id] rightid=%(right1)s # [peer_cidrs] rightsubnets={ %(peer_cidrs1)s } %(conn_details)sconn %(conn2_id)s %(next_hop)s left=%(left)s leftid=%(leftid)s auto=start # NOTE:REQUIRED # [subnet] leftsubnet%(local_cidrs2)s # [updown] # What "updown" script to run to adjust routing and/or firewalling when # the status of the connection changes (default "ipsec _updown"). # "--route yes" allows to specify such routing options as mtu and metric. leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ###################### # [peer_address] right=%(right2)s # [peer_id] rightid=%(right2)s # [peer_cidrs] rightsubnets={ %(peer_cidrs2)s } %(conn_details)s """ STRONGSWAN_AUTH_ESP = 'esp=aes128-sha1-modp1536' STRONGSWAN_AUTH_AH = 'ah=sha1-modp1536' EXPECTED_IPSEC_OPENSWAN_SECRET_CONF = ''' # Configuration for %s 60.0.0.4 60.0.0.5 : PSK 0scGFzc3dvcmQ= 60.0.0.4 60.0.0.6 : PSK 0scGFzc3dvcmQ=''' % FAKE_VPNSERVICE_ID EXPECTED_IPSEC_STRONGSWAN_CONF = ''' # Configuration for %(vpnservice_id)s config setup conn %%default keylife=20m rekeymargin=3m keyingtries=1 authby=psk mobike=no conn %(conn1_id)s keyexchange=ikev1 left=%(left)s leftsubnet=%(local_cidrs1)s leftid=%(leftid)s leftfirewall=yes right=%(right1)s rightsubnet=%(peer_cidrs1)s rightid=%(right1)s auto=route dpdaction=%(dpd_action)s dpddelay=%(dpd_delay)ss dpdtimeout=%(dpd_timeout)ss ike=%(ike_encryption_algorithm)s-%(ike_auth_algorithm)s-%(ike_pfs)s ikelifetime=%(ike_lifetime)ss %(auth_mode)s lifetime=%(life_time)ss type=%(encapsulation_mode)s conn %(conn2_id)s keyexchange=ikev1 left=%(left)s leftsubnet=%(local_cidrs2)s leftid=%(leftid)s leftfirewall=yes right=%(right2)s rightsubnet=%(peer_cidrs2)s rightid=%(right2)s auto=route dpdaction=%(dpd_action)s dpddelay=%(dpd_delay)ss dpdtimeout=%(dpd_timeout)ss ike=%(ike_encryption_algorithm)s-%(ike_auth_algorithm)s-%(ike_pfs)s ikelifetime=%(ike_lifetime)ss %(auth_mode)s lifetime=%(life_time)ss type=%(encapsulation_mode)s ''' EXPECTED_STRONGSWAN_DEFAULT_CONF = ''' charon { load_modular = yes plugins { include strongswan.d/charon/*.conf } } include strongswan.d/*.conf ''' EXPECTED_IPSEC_STRONGSWAN_SECRET_CONF = ''' # Configuration for %s 60.0.0.4 60.0.0.5 : PSK 0scGFzc3dvcmQ= 60.0.0.4 60.0.0.6 : PSK 0scGFzc3dvcmQ= ''' % FAKE_VPNSERVICE_ID PLUTO_ACTIVE_STATUS = """000 "%(conn_id)s/0x1": erouted;\n 000 #4: "%(conn_id)s/0x1":500 STATE_QUICK_R2 (IPsec SA established); \ newest IPSEC;""" % { 'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_ACTIVE_STATUS_IKEV2 = """000 "%(conn_id)s/0x1": erouted;\n 000 #4: "%(conn_id)s/0x1":500 STATE_PARENT_R2 (PARENT SA established); \ newest IPSEC;""" % { 'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_MULTIPLE_SUBNETS_ESTABLISHED_STATUS = """000 "%(conn_id1)s/1x1": erouted;\n 000 #4: "%(conn_id1)s/1x1":500 STATE_QUICK_R2 (IPsec SA established); \ newest IPSEC;\n 000 "%(conn_id2)s/2x1": erouted;\n 000 #4: "%(conn_id2)s/2x1":500 STATE_QUICK_R2 (IPsec SA established); \ newest IPSEC;\n""" % { 'conn_id1': FAKE_IPSEC_SITE_CONNECTION1_ID, 'conn_id2': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_ACTIVE_NO_IPSEC_SA_STATUS = """000 "%(conn_id)s/0x1": erouted;\n 000 #258: "%(conn_id)s/0x1":500 STATE_MAIN_R2 (sent MR2, expecting MI3);""" % { 'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} PLUTO_DOWN_STATUS = "000 \"%(conn_id)s/0x1\": unrouted;" % {'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} CHARON_ACTIVE_STATUS = "%(conn_id)s{1}: INSTALLED, TUNNEL" % {'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} CHARON_DOWN_STATUS = "%(conn_id)s{1}: ROUTED, TUNNEL" % {'conn_id': FAKE_IPSEC_SITE_CONNECTION2_ID} NOT_RUNNING_STATUS = "Command: ['ipsec', 'status'] Exit code: 3 Stdout:" class BaseIPsecDeviceDriver(base.BaseTestCase): def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess, vpnservice=FAKE_VPN_SERVICE): super(BaseIPsecDeviceDriver, self).setUp() for klass in [ 'neutron_lib.rpc.Connection', 'oslo_service.loopingcall.FixedIntervalLoopingCall' ]: mock.patch(klass).start() self._execute = mock.patch.object(ipsec_process, '_execute').start() self.agent = mock.Mock() self.conf = cfg.CONF l3_config.register_l3_agent_config_opts(l3_config.OPTS, self.conf) self.agent.conf = self.conf self.driver = driver( self.agent, FAKE_HOST) self.driver.agent_rpc = mock.Mock() self.ri_kwargs = {'router': {'id': FAKE_ROUTER_ID, 'ha': False}, 'agent_conf': self.conf, 'interface_driver': mock.sentinel.interface_driver} self.iptables = mock.Mock() self.apply_mock = mock.Mock() self.vpnservice = copy.deepcopy(vpnservice) ipsec_process._get_strongswan_piddir = mock.Mock( return_value="/var/run") @staticmethod def generate_diff(a, b): """Generates unified diff of a and b.""" a, b = list(a.splitlines(True)), list(b.splitlines(True)) diff = difflib.unified_diff(a, b, fromfile="expected", tofile="actual") return diff def modify_config_for_test(self, overrides): """Revise service/connection settings to test variations. Must update service, so that dialect mappings occur for any changes that are made. """ ipsec_auth_protocol = overrides.get('ipsec_auth') if ipsec_auth_protocol: auth_proto = {'transform_protocol': ipsec_auth_protocol} for conn in self.vpnservice['ipsec_site_connections']: conn['ipsecpolicy'].update(auth_proto) local_cidrs = overrides.get('local_cidrs') if local_cidrs: for i, conn in enumerate( self.vpnservice['ipsec_site_connections']): conn['local_cidrs'] = local_cidrs[i] local_ip_version = overrides.get('local_ip_vers', 4) for conn in self.vpnservice['ipsec_site_connections']: conn['local_ip_vers'] = local_ip_version peer_cidrs = overrides.get('peer_cidrs') if peer_cidrs: for i, conn in enumerate( self.vpnservice['ipsec_site_connections']): conn['peer_cidrs'] = peer_cidrs[i] peers = overrides.get('peers') if peers: for i, conn in enumerate( self.vpnservice['ipsec_site_connections']): conn['peer_id'] = peers[i] conn['peer_address'] = peers[i] local_ip = overrides.get('local') local_id = overrides.get('local_id') if local_ip: for conn in self.vpnservice['ipsec_site_connections']: conn['external_ip'] = local_ip conn['local_id'] = local_ip if local_id: conn['local_id'] = local_id def check_config_file(self, expected, actual): expected = expected.strip() actual = actual.strip() res_diff = self.generate_diff(expected, actual) self.assertEqual(expected, actual, message=''.join(res_diff)) def _test_ipsec_connection_config(self, info): """Check config file string for service/connection. Calls test specific method to create (and override as needed) the expected config file string, generates the config using the test's IPSec template, and then compares the results. """ expected = self.build_ipsec_expected_config_for_test(info) actual = self.process._gen_config_content(self.ipsec_template, self.vpnservice) self.check_config_file(expected, actual) class IPSecDeviceLegacy(BaseIPsecDeviceDriver): def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(IPSecDeviceLegacy, self).setUp(driver, ipsec_process) self._make_router_info_for_test() def _make_router_info_for_test(self): self.router = legacy_router.LegacyRouter(router_id=FAKE_ROUTER_ID, agent=self.agent, **self.ri_kwargs) self.router.router['distributed'] = False self.router.iptables_manager.ipv4['nat'] = self.iptables self.router.iptables_manager.apply = self.apply_mock self.driver.routers[FAKE_ROUTER_ID] = self.router def _test_vpnservice_updated(self, expected_param, **kwargs): with mock.patch.object(self.driver, 'sync') as sync: context = mock.Mock() self.driver.vpnservice_updated(context, **kwargs) sync.assert_called_once_with(context, expected_param) def test_vpnservice_updated(self): self._test_vpnservice_updated([]) def test_vpnservice_updated_with_router_info(self): router_info = {'id': FAKE_ROUTER_ID, 'ha': False} kwargs = {'router': router_info} self._test_vpnservice_updated([router_info], **kwargs) def test_create_router(self): process = mock.Mock(openswan_ipsec.OpenSwanProcess) process.vpnservice = self.vpnservice self.driver.processes = { FAKE_ROUTER_ID: process} self.driver.create_router(self.router) self._test_add_nat_rule() process.enable.assert_called_once_with() def test_destroy_router(self): process_id = _uuid() process = mock.Mock() process.vpnservice = self.vpnservice self.driver.processes = { process_id: process} self.driver.destroy_router(process_id) process.disable.assert_called_once_with() self.assertNotIn(process_id, self.driver.processes) def _test_add_nat_rule(self): self.router.iptables_manager.ipv4['nat'].assert_has_calls([ mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 20.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 30.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 40.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 50.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True) ]) self.router.iptables_manager.apply.assert_called_once_with() def _test_add_nat_rule_with_multiple_locals(self): self.router.iptables_manager.ipv4['nat'].assert_has_calls([ mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 20.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 10.0.0.0/24 -d 30.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 20.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 11.0.0.0/24 -d 30.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 12.0.0.0/24 -d 40.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 12.0.0.0/24 -d 50.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 13.0.0.0/24 -d 40.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True), mock.call.add_rule( 'POSTROUTING', '-s 13.0.0.0/24 -d 50.0.0.0/24 -m policy ' '--dir out --pol ipsec -j ACCEPT ', top=True) ]) self.router.iptables_manager.apply.assert_called_once_with() def test_sync(self): fake_vpn_service = FAKE_VPN_SERVICE self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ fake_vpn_service] context = mock.Mock() self.driver._sync_vpn_processes = mock.Mock() self.driver._delete_vpn_processes = mock.Mock() self.driver._cleanup_stale_vpn_processes = mock.Mock() sync_routers = [{'id': fake_vpn_service['router_id']}] sync_router_ids = [fake_vpn_service['router_id']] self.driver.sync(context, sync_routers) self.driver._sync_vpn_processes.assert_called_once_with( [fake_vpn_service], sync_router_ids) self.driver._delete_vpn_processes.assert_called_once_with( sync_router_ids, sync_router_ids) self.driver._cleanup_stale_vpn_processes.assert_called_once_with( sync_router_ids) def test__sync_vpn_processes_new_vpn_service(self): new_vpnservice = self.vpnservice router_id = new_vpnservice['router_id'] self.driver.processes = {} with mock.patch.object(self.driver, 'ensure_process') as ensure_p: ensure_p.side_effect = self.fake_ensure_process self.driver._sync_vpn_processes([new_vpnservice], router_id) self._test_add_nat_rule() self.driver.processes[router_id].update.assert_called_once_with() def test_add_nat_rules_with_multiple_local_subnets(self): """Ensure that add nat rule combinations are correct.""" overrides = {'local_cidrs': [['10.0.0.0/24', '11.0.0.0/24'], ['12.0.0.0/24', '13.0.0.0/24']]} self.modify_config_for_test(overrides) self.driver._update_nat(self.vpnservice, self.driver.add_nat_rule) self._test_add_nat_rule_with_multiple_locals() def test__sync_vpn_processes_router_with_no_vpn(self): """Test _sync_vpn_processes with a router not hosting vpnservice. This test case tests that when a router which doesn't host vpn services is updated, sync_vpn_processes doesn't restart/update the existing vpnservice processes. """ process = mock.Mock() process.vpnservice = self.vpnservice process.connection_status = {} self.driver.processes = { self.vpnservice['router_id']: process} router_id_no_vpn = _uuid() with mock.patch.object(self.driver, 'ensure_process') as ensure_p: self.driver._sync_vpn_processes([self.vpnservice], [router_id_no_vpn]) self.assertEqual(0, ensure_p.call_count) def test__sync_vpn_processes_router_with_no_vpn_and_no_vpn_services(self): """No vpn services running and router not hosting vpn svc.""" router_id_no_vpn = _uuid() self.driver.process_status_cache = {} self.driver.processes = {} with mock.patch.object(self.driver, 'ensure_process') as ensure_p: ensure_p.side_effect = self.fake_ensure_process self.driver._sync_vpn_processes([], [router_id_no_vpn]) self.assertEqual(0, ensure_p.call_count) def test__sync_vpn_processes_router_with_no_vpn_agent_restarted(self): """Test for the router not hosting vpnservice and agent restarted. This test case tests that when a non vpnservice hosted router is updated, _sync_vpn_processes restart/update the existing vpnservices which are not yet stored in driver.processes. """ router_id = FAKE_ROUTER_ID self.driver.process_status_cache = {} self.driver.processes = {} with mock.patch.object(self.driver, 'ensure_process') as ensure_p: ensure_p.side_effect = self.fake_ensure_process self.driver._sync_vpn_processes([self.vpnservice], [router_id]) self._test_add_nat_rule() self.driver.processes[router_id].update.assert_called_once_with() def test_delete_vpn_processes(self): router_id_no_vpn = _uuid() vpn_service_router_id = _uuid() with mock.patch.object(self.driver, 'destroy_process') as (fake_destroy_process): self.driver._delete_vpn_processes([router_id_no_vpn], [vpn_service_router_id]) fake_destroy_process.assert_has_calls( [mock.call(router_id_no_vpn)]) # test that _delete_vpn_processes doesn't delete the # the valid vpn processes with mock.patch.object(self.driver, 'destroy_process') as fake_destroy_process: self.driver._delete_vpn_processes([vpn_service_router_id], [vpn_service_router_id]) self.assertFalse(fake_destroy_process.called) def test_cleanup_stale_vpn_processes(self): stale_vpn_service = {'router_id': _uuid()} active_vpn_service = {'router_id': _uuid()} self.driver.processes = { stale_vpn_service['router_id']: stale_vpn_service, active_vpn_service['router_id']: active_vpn_service} with mock.patch.object(self.driver, 'destroy_process') as destroy_p: self.driver._cleanup_stale_vpn_processes( [active_vpn_service['router_id']]) destroy_p.assert_has_calls( [mock.call(stale_vpn_service['router_id'])]) def fake_ensure_process(self, process_id, vpnservice=None): process = self.driver.processes.get(process_id) if not process: process = mock.Mock() process.vpnservice = self.vpnservice process.connection_status = {} process.status = constants.ACTIVE process.updated_pending_status = True self.driver.processes[process_id] = process elif vpnservice: process.vpnservice = vpnservice process.update_vpnservice(vpnservice) return process def fake_destroy_router(self, process_id): process = self.driver.processes.get(process_id) if process: del self.driver.processes[process_id] def test_sync_update_vpnservice(self): with mock.patch.object(self.driver, 'ensure_process') as ensure_process: ensure_process.side_effect = self.fake_ensure_process new_vpn_service = self.vpnservice updated_vpn_service = copy.deepcopy(new_vpn_service) updated_vpn_service['ipsec_site_connections'][1].update( {'peer_cidrs': ['60.0.0.0/24', '70.0.0.0/24']}) context = mock.Mock() self.driver.process_status_cache = {} self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ new_vpn_service] self.driver.sync(context, [{'id': FAKE_ROUTER_ID}]) process = self.driver.processes[FAKE_ROUTER_ID] self.assertEqual(new_vpn_service, process.vpnservice) self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ updated_vpn_service] self.driver.sync(context, [{'id': FAKE_ROUTER_ID}]) process = self.driver.processes[FAKE_ROUTER_ID] process.update_vpnservice.assert_called_once_with( updated_vpn_service) self.assertEqual(updated_vpn_service, process.vpnservice) def test_sync_removed(self): self.driver.agent_rpc.get_vpn_services_on_host.return_value = [] context = mock.Mock() process_id = _uuid() process = mock.Mock() process.vpnservice = self.vpnservice self.driver.processes = { process_id: process} self.driver.sync(context, []) process.disable.assert_called_once_with() self.assertNotIn(process_id, self.driver.processes) def test_sync_removed_router(self): self.driver.agent_rpc.get_vpn_services_on_host.return_value = [] context = mock.Mock() process_id = _uuid() self.driver.sync(context, [{'id': process_id}]) self.assertNotIn(process_id, self.driver.processes) def test_status_updated_on_connection_admin_down(self): self.driver.process_status_cache = { '1': { 'status': constants.ACTIVE, 'id': 123, 'updated_pending_status': False, 'ipsec_site_connections': { '10': { 'status': constants.ACTIVE, 'updated_pending_status': False, }, '20': { 'status': constants.ACTIVE, 'updated_pending_status': False, } } } } # Simulate that there is no longer status for connection '20' # e.g. connection admin down new_status = { 'ipsec_site_connections': { '10': { 'status': constants.ACTIVE, 'updated_pending_status': False } } } self.driver.update_downed_connections('1', new_status) existing_conn = new_status['ipsec_site_connections'].get('10') self.assertIsNotNone(existing_conn) self.assertEqual(constants.ACTIVE, existing_conn['status']) missing_conn = new_status['ipsec_site_connections'].get('20') self.assertIsNotNone(missing_conn) self.assertEqual(constants.DOWN, missing_conn['status']) def test_status_updated_on_service_admin_down(self): self.driver.process_status_cache = { '1': { 'status': constants.ACTIVE, 'id': 123, 'updated_pending_status': False, 'ipsec_site_connections': { '10': { 'status': constants.ACTIVE, 'updated_pending_status': False, }, '20': { 'status': constants.ACTIVE, 'updated_pending_status': False, } } } } # Simulate that there are no connections now new_status = { 'ipsec_site_connections': {} } self.driver.update_downed_connections('1', new_status) missing_conn = new_status['ipsec_site_connections'].get('10') self.assertIsNotNone(missing_conn) self.assertEqual(constants.DOWN, missing_conn['status']) missing_conn = new_status['ipsec_site_connections'].get('20') self.assertIsNotNone(missing_conn) self.assertEqual(constants.DOWN, missing_conn['status']) def _test_status_handling_for_downed_connection(self, down_status): """Test status handling for downed connection.""" router_id = self.router.router_id connection_id = FAKE_IPSEC_SITE_CONNECTION2_ID self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = down_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.ACTIVE, process_status['status']) self.assertEqual(constants.DOWN, ipsec_site_conn[connection_id]['status']) def _test_status_handling_for_active_connection(self, active_status): """Test status handling for active connection.""" router_id = self.router.router_id connection_id = FAKE_IPSEC_SITE_CONNECTION2_ID self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = active_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[ router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.ACTIVE, process_status['status']) self.assertEqual(constants.ACTIVE, ipsec_site_conn[connection_id]['status']) def _test_status_handling_for_ike_v2_active_connection(self, active_status): """Test status handling for active connection.""" router_id = self.router.router_id connection_id = FAKE_IPSEC_SITE_CONNECTION2_ID ike_policy = {'ike_version': 'v2', 'encryption_algorithm': 'aes-128', 'auth_algorithm': 'sha1', 'pfs': 'group5', 'lifetime_value': 3600} vpn_service = FAKE_VPN_SERVICE for isc in vpn_service["ipsec_site_connections"]: isc['ikepolicy'] = ike_policy self.driver.ensure_process(router_id, vpn_service) self._execute.return_value = active_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[ router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.ACTIVE, process_status['status']) self.assertEqual(constants.ACTIVE, ipsec_site_conn[connection_id]['status']) def _test_connection_names_handling_for_multiple_subnets(self, active_status): """Test connection names handling for multiple subnets.""" router_id = self.router.router_id process = self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = active_status names = process.get_established_connections() self.assertEqual(2, len(names)) def _test_status_handling_for_deleted_connection(self, not_running_status): """Test status handling for deleted connection.""" router_id = self.router.router_id self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = not_running_status self.driver.report_status(mock.Mock()) process_status = self.driver.process_status_cache[router_id] ipsec_site_conn = process_status['ipsec_site_connections'] self.assertEqual(constants.DOWN, process_status['status']) self.assertFalse(ipsec_site_conn) def _test_parse_connection_status(self, not_running_status, active_status, down_status): """Test the status of ipsec-site-connection is parsed correctly.""" router_id = self.router.router_id process = self.driver.ensure_process(router_id, self.vpnservice) self._execute.return_value = not_running_status self.assertFalse(process.active) # An empty return value to simulate that the process # does not have any status to report. self._execute.return_value = '' self.assertFalse(process.active) self._execute.return_value = active_status self.assertTrue(process.active) self._execute.return_value = down_status self.assertTrue(process.active) def test_get_namespace_for_router(self): namespace = self.driver.get_namespace(FAKE_ROUTER_ID) self.assertEqual('qrouter-' + FAKE_ROUTER_ID, namespace) def test_fail_getting_namespace_for_unknown_router(self): self.assertFalse(self.driver.get_namespace('bogus_id')) def test_add_nat_rule(self): self.driver.add_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.add_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) def test_add_nat_rule_with_no_router(self): self.driver.add_nat_rule( 'bogus_router_id', 'fake_chain', 'fake_rule', True) self.assertFalse(self.iptables.add_rule.called) def test_remove_rule(self): self.driver.remove_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.remove_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) def test_remove_rule_with_no_router(self): self.driver.remove_nat_rule( 'bogus_router_id', 'fake_chain', 'fake_rule') self.assertFalse(self.iptables.remove_rule.called) def test_iptables_apply(self): self.driver.iptables_apply(FAKE_ROUTER_ID) self.apply_mock.assert_called_once_with() def test_iptables_apply_with_no_router(self): self.driver.iptables_apply('bogus_router_id') self.assertFalse(self.apply_mock.called) class IPSecDeviceDVR(BaseIPsecDeviceDriver): def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(IPSecDeviceDVR, self).setUp(driver, ipsec_process) mock.patch.object(dvr_snat_ns.SnatNamespace, 'create').start() self._make_dvr_edge_router_info_for_test() def _make_dvr_edge_router_info_for_test(self): router = dvr_edge_router.DvrEdgeRouter(mock.sentinel.agent, mock.sentinel.myhost, FAKE_ROUTER_ID, **self.ri_kwargs) router.router['distributed'] = True router.snat_namespace = dvr_snat_ns.SnatNamespace(router.router['id'], mock.sentinel.agent, self.driver, mock.ANY) router.snat_namespace.create() router.snat_iptables_manager = iptables_manager.IptablesManager( namespace='snat-' + FAKE_ROUTER_ID, use_ipv6=mock.ANY) router.snat_iptables_manager.ipv4['nat'] = self.iptables router.snat_iptables_manager.apply = self.apply_mock self.driver.routers[FAKE_ROUTER_ID] = router def test_sync_dvr(self): fake_vpn_service = FAKE_VPN_SERVICE self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ fake_vpn_service] context = mock.Mock() self.driver._sync_vpn_processes = mock.Mock() self.driver._delete_vpn_processes = mock.Mock() self.driver._cleanup_stale_vpn_processes = mock.Mock() sync_routers = [{'id': fake_vpn_service['router_id']}] sync_router_ids = [fake_vpn_service['router_id']] with mock.patch.object(self.driver, 'get_process_status_cache') as process_status: self.driver.sync(context, sync_routers) self.driver._sync_vpn_processes.assert_called_once_with( [fake_vpn_service], sync_router_ids) self.driver._delete_vpn_processes.assert_called_once_with( sync_router_ids, sync_router_ids) self.driver._cleanup_stale_vpn_processes.assert_called_once_with( sync_router_ids) self.assertEqual(0, process_status.call_count) def test_get_namespace_for_dvr_edge_router(self): namespace = self.driver.get_namespace(FAKE_ROUTER_ID) self.assertEqual('snat-' + FAKE_ROUTER_ID, namespace) def test_add_nat_rule_with_dvr_edge_router(self): self.driver.add_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.add_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) def test_iptables_apply_with_dvr_edge_router(self): self.driver.iptables_apply(FAKE_ROUTER_ID) self.apply_mock.assert_called_once_with() def test_remove_rule_with_dvr_edge_router(self): self.driver.remove_nat_rule(FAKE_ROUTER_ID, 'fake_chain', 'fake_rule', True) self.iptables.remove_rule.assert_called_once_with( 'fake_chain', 'fake_rule', top=True) class TestOpenSwanConfigGeneration(BaseIPsecDeviceDriver): """Verify that configuration files are generated correctly. Besides the normal translation of some settings, when creating the config file, the generated file can also vary based on the following special conditions: - IPv6 versus IPv4 - Multiple left subnets versus a single left subnet - IPSec policy using AH transform The tests will focus on these variations. """ def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(TestOpenSwanConfigGeneration, self).setUp( driver, ipsec_process, vpnservice=FAKE_VPN_SERVICE) self.conf.register_opts(openswan_ipsec.openswan_opts, 'openswan') self.conf.set_override('state_path', '/tmp') self.ipsec_template = self.conf.openswan.ipsec_config_template self.process = openswan_ipsec.OpenSwanProcess(self.conf, 'foo-process-id', self.vpnservice, mock.ANY) def build_ipsec_expected_config_for_test(self, info): """Modify OpenSwan ipsec expected config files for test variations.""" auth_mode = info.get('ipsec_auth', AUTH_ESP) conn_details = OPENSWAN_CONNECTION_DETAILS % {'auth_mode': auth_mode, 'dpd_action': 'hold', 'dpd_delay': 30, 'dpd_timeout': 120, 'ike_lifetime': 3600, 'life_time': 3600, 'encapsulation_mode': 'tunnel'} virtual_privates = [] # Convert local CIDRs into assignment strings. IF more than one, # pluralize the attribute name and enclose in brackets. cidrs = info.get('local_cidrs', [['10.0.0.0/24'], ['11.0.0.0/24']]) local_cidrs = [] for cidr in cidrs: if len(cidr) == 2: local_cidrs.append("s={ %s }" % ' '.join(cidr)) else: local_cidrs.append("=%s" % cidr[0]) for net in cidr: version = netaddr.IPNetwork(net).version virtual_privates.append('%%v%s:%s' % (version, net)) # Convert peer CIDRs into space separated strings cidrs = info.get('peer_cidrs', [['20.0.0.0/24', '30.0.0.0/24'], ['40.0.0.0/24', '50.0.0.0/24']]) for cidr in cidrs: for net in cidr: version = netaddr.IPNetwork(net).version virtual_privates.append('%%v%s:%s' % (version, net)) peer_cidrs = [' '.join(cidr) for cidr in cidrs] local_ip = info.get('local', '60.0.0.4') local_id = info.get('local_id') leftid = local_ip if local_id: leftid = local_id version = info.get('local_ip_vers', 4) next_hop = IPV4_NEXT_HOP if version == 4 else IPV6_NEXT_HOP % local_ip peer_ips = info.get('peers', ['60.0.0.5', '60.0.0.6']) virtual_privates.sort() return EXPECTED_OPENSWAN_CONF % { 'vpnservice_id': FAKE_VPNSERVICE_ID, 'virtual_privates': ','.join(virtual_privates), 'next_hop': next_hop, 'local_cidrs1': local_cidrs[0], 'local_cidrs2': local_cidrs[1], 'local_ver': version, 'peer_cidrs1': peer_cidrs[0], 'peer_cidrs2': peer_cidrs[1], 'left': local_ip, 'leftid': leftid, 'right1': peer_ips[0], 'right2': peer_ips[1], 'conn1_id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'conn2_id': FAKE_IPSEC_SITE_CONNECTION2_ID, 'conn_details': conn_details} def test_connections_with_esp_transform_protocol(self): """Test config file with IPSec policy using ESP.""" self._test_ipsec_connection_config({}) def test_connections_with_ah_transform_protocol(self): """Test config file with IPSec policy using ESP.""" overrides = {'ipsec_auth': 'ah'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) info = {'ipsec_auth': AUTH_AH} self._test_ipsec_connection_config(info) def test_connections_with_multiple_left_subnets(self): """Test multiple local subnets. The configure uses the 'leftsubnets' attribute, instead of the 'leftsubnet' attribute. """ overrides = {'local_cidrs': [['10.0.0.0/24', '11.0.0.0/24'], ['12.0.0.0/24', '13.0.0.0/24']]} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_config_files_with_ipv6_addresses(self): """Test creating config files using IPv6 addressing.""" overrides = {'local_cidrs': [['2002:0a00::/48'], ['2002:0b00::/48']], 'local_ip_vers': 6, 'peer_cidrs': [['2002:1400::/48', '2002:1e00::/48'], ['2002:2800::/48', '2002:3200::/48']], 'local': '2002:3c00:0004::', 'peers': ['2002:3c00:0005::', '2002:3c00:0006::'], 'local_id': '2002:3c00:0004::'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_config_files_with_ipv6_addresses_without_local_id(self): """Test creating config files using IPv6 addressing.""" overrides = {'local_cidrs': [['2002:0a00::/48'], ['2002:0b00::/48']], 'local_ip_vers': 6, 'peer_cidrs': [['2002:1400::/48', '2002:1e00::/48'], ['2002:2800::/48', '2002:3200::/48']], 'local': '2002:3c00:0004::', 'peers': ['2002:3c00:0005::', '2002:3c00:0006::']} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_secrets_config_file(self): expected = EXPECTED_IPSEC_OPENSWAN_SECRET_CONF actual = self.process._gen_config_content( self.conf.openswan.ipsec_secret_template, self.vpnservice) self.check_config_file(expected, actual) class IPsecStrongswanConfigGeneration(BaseIPsecDeviceDriver): def setUp(self, driver=strongswan_ipsec.StrongSwanDriver, ipsec_process=strongswan_ipsec.StrongSwanProcess): super(IPsecStrongswanConfigGeneration, self).setUp( driver, ipsec_process, vpnservice=FAKE_VPN_SERVICE) self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') self.conf.set_override('state_path', '/tmp') self.ipsec_template = self.conf.strongswan.ipsec_config_template self.process = strongswan_ipsec.StrongSwanProcess(self.conf, 'foo-process-id', self.vpnservice, mock.ANY) def build_ipsec_expected_config_for_test(self, info): cidrs = info.get('local_cidrs', [['10.0.0.0/24'], ['11.0.0.0/24']]) local_cidrs = [','.join(cidr) for cidr in cidrs] cidrs = info.get('peer_cidrs', [['20.0.0.0/24', '30.0.0.0/24'], ['40.0.0.0/24', '50.0.0.0/24']]) peer_cidrs = [','.join(cidr) for cidr in cidrs] local_ip = info.get('local', '60.0.0.4') local_id = info.get('local_id') leftid = local_ip if local_id: leftid = local_id peer_ips = info.get('peers', ['60.0.0.5', '60.0.0.6']) auth_mode = info.get('ipsec_auth', STRONGSWAN_AUTH_ESP) return EXPECTED_IPSEC_STRONGSWAN_CONF % { 'vpnservice_id': FAKE_VPNSERVICE_ID, 'local_cidrs1': local_cidrs[0], 'local_cidrs2': local_cidrs[1], 'peer_cidrs1': peer_cidrs[0], 'peer_cidrs2': peer_cidrs[1], 'left': local_ip, 'leftid': leftid, 'right1': peer_ips[0], 'right2': peer_ips[1], 'dpd_action': 'hold', 'dpd_delay': 30, 'dpd_timeout': 120, 'ike_encryption_algorithm': 'aes128', 'ike_auth_algorithm': 'sha1', 'ike_pfs': 'modp1536', 'ike_lifetime': 3600, 'life_time': 3600, 'auth_mode': auth_mode, 'encapsulation_mode': 'tunnel', 'conn1_id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'conn2_id': FAKE_IPSEC_SITE_CONNECTION2_ID} def test_ipsec_config_file_with_esp(self): self._test_ipsec_connection_config({}) def test_ipsec_config_file_with_ah(self): overrides = {'ipsec_auth': 'ah'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) info = {'ipsec_auth': STRONGSWAN_AUTH_AH} self._test_ipsec_connection_config(info) def test_ipsec_config_file_for_v6(self): overrides = {'local_cidrs': [['2002:0a00::/48'], ['2002:0b00::/48']], 'peer_cidrs': [['2002:1400::/48', '2002:1e00::/48'], ['2002:2800::/48', '2002:3200::/48']], 'local': '2002:3c00:0004::', 'peers': ['2002:3c00:0005::', '2002:3c00:0006::'], 'local_id': '2002:3c00:0004::'} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_ipsec_config_file_for_v6_without_local_id(self): overrides = {'local_cidrs': [['2002:0a00::/48'], ['2002:0b00::/48']], 'peer_cidrs': [['2002:1400::/48', '2002:1e00::/48'], ['2002:2800::/48', '2002:3200::/48']], 'local': '2002:3c00:0004::', 'peers': ['2002:3c00:0005::', '2002:3c00:0006::']} self.modify_config_for_test(overrides) self.process.update_vpnservice(self.vpnservice) self._test_ipsec_connection_config(overrides) def test_strongswan_default_config_file(self): expected = EXPECTED_STRONGSWAN_DEFAULT_CONF actual = self.process._gen_config_content( self.conf.strongswan.strongswan_config_template, self.vpnservice) self.check_config_file(expected, actual) def test_secrets_config_file(self): expected = EXPECTED_IPSEC_STRONGSWAN_SECRET_CONF actual = self.process._gen_config_content( self.conf.strongswan.ipsec_secret_template, self.vpnservice) self.check_config_file(expected, actual) class TestOpenSwanProcess(IPSecDeviceLegacy): _test_timeout = 1 _test_backoff = 2 _test_retries = 5 def setUp(self, driver=openswan_ipsec.OpenSwanDriver, ipsec_process=openswan_ipsec.OpenSwanProcess): super(TestOpenSwanProcess, self).setUp(driver, ipsec_process) self.conf.register_opts(openswan_ipsec.openswan_opts, 'openswan') self.conf.set_override('state_path', '/tmp') cfg.CONF.register_opts(openswan_ipsec.pluto_opts, 'pluto') cfg.CONF.set_override('shutdown_check_timeout', self._test_timeout, group='pluto') cfg.CONF.set_override('shutdown_check_back_off', self._test_backoff, group='pluto') cfg.CONF.set_override('shutdown_check_retries', self._test_retries, group='pluto') self.addCleanup(cfg.CONF.reset) self.os_remove = mock.patch('os.remove').start() self.process = openswan_ipsec.OpenSwanProcess(self.conf, 'foo-process-id', self.vpnservice, mock.ANY) def test__resolve_fqdn(self): with mock.patch.object(socket, 'getaddrinfo') as mock_getaddr_info: mock_getaddr_info.return_value = [(2, 1, 6, '', ('172.168.1.2', 0))] resolved_ip_addr = self.process._resolve_fqdn('fqdn.foo.addr') self.assertEqual('172.168.1.2', resolved_ip_addr) def _test_get_nexthop_helper(self, address, _resolve_fqdn_side_effect, expected_ip_cmd, expected_nexthop): with mock.patch.object(self.process, '_resolve_fqdn') as fake_resolve_fqdn: fake_resolve_fqdn.side_effect = _resolve_fqdn_side_effect returned_next_hop = self.process._get_nexthop(address, 'fake-conn-id') _resolve_fqdn_expected_call_count = ( 1 if _resolve_fqdn_side_effect else 0) self.assertEqual(_resolve_fqdn_expected_call_count, fake_resolve_fqdn.call_count) self._execute.assert_called_once_with(expected_ip_cmd) self.assertEqual(expected_nexthop, returned_next_hop) def test__get_nexthop_peer_addr_is_ipaddr(self): gw_addr = '10.0.0.1' self._execute.return_value = '172.168.1.2 via %s' % gw_addr peer_address = '172.168.1.2' expected_ip_cmd = ['ip', 'route', 'get', peer_address] self._test_get_nexthop_helper(peer_address, None, expected_ip_cmd, gw_addr) def test__get_nexthop_peer_addr_is_valid_fqdn(self): peer_address = 'foo.peer.addr' expected_ip_cmd = ['ip', 'route', 'get', '172.168.1.2'] gw_addr = '10.0.0.1' self._execute.return_value = '172.168.1.2 via %s' % gw_addr def _fake_resolve_fqdn(address): return '172.168.1.2' self._test_get_nexthop_helper(peer_address, _fake_resolve_fqdn, expected_ip_cmd, gw_addr) def test__get_nexthop_gw_not_present(self): peer_address = '172.168.1.2' expected_ip_cmd = ['ip', 'route', 'get', '172.168.1.2'] self._execute.return_value = ' ' self._test_get_nexthop_helper(peer_address, None, expected_ip_cmd, peer_address) def test__get_nexthop_fqdn_peer_addr_is_not_resolved(self): self.process.connection_status = {} expected_connection_status_dict = ( {'fake-conn-id': {'status': constants.ERROR, 'updated_pending_status': True}}) self.assertRaises(vpn_exception.VPNPeerAddressNotResolved, self.process._get_nexthop, 'foo.peer.addr', 'fake-conn-id') self.assertEqual(expected_connection_status_dict, self.process.connection_status) self.process.connection_status = ( {'fake-conn-id': {'status': constants.PENDING_CREATE, 'updated_pending_status': False}}) self.assertRaises(vpn_exception.VPNPeerAddressNotResolved, self.process._get_nexthop, 'foo.peer.addr', 'fake-conn-id') self.assertEqual(expected_connection_status_dict, self.process.connection_status) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') def test_no_cleanups(self, cleanup_mock, hop_mock): # Not an "awesome test" but more of a check box item. Basically, # what happens if we didn't need to clean up any files. with mock.patch.object(self.process, '_process_running', return_value=True) as query_mock: self.process.start() self.assertEqual(1, query_mock.call_count) # This is really what is being tested here. If process is # running, we shouldn't attempt a cleanup. self.assertFalse(cleanup_mock.called) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('os.path.exists', return_value=True) def test_cleanup_files(self, exists_mock, hop_mock): # Tests the 'bones' of things really and kind of check-box-item-bogus # test - this really needs exercising through a higher level test. with mock.patch.object(self.process, '_process_running', return_value=False) as query_mock: fake_path = '/fake/path/run' self.process.pid_path = fake_path self.process.pid_file = '%s.pid' % fake_path self.process.start() self.assertEqual(1, query_mock.call_count) self.assertEqual(2, self.os_remove.call_count) self.os_remove.assert_has_calls([mock.call('%s.pid' % fake_path), mock.call('%s.ctl' % fake_path)]) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._process_running', return_value=False) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') @mock.patch('eventlet.sleep') def test_restart_process_not_running(self, sleep_mock, cleanup_mock, query_mock, hop_mock): self.process.restart() # Really what is being tested - retry configuration exists and that # we do the right things when process check is false. self.assertTrue(query_mock.called) self.assertTrue(cleanup_mock.called) self.assertFalse(sleep_mock.called) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._process_running', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') @mock.patch('eventlet.sleep') def test_restart_process_doesnt_stop(self, sleep_mock, cleanup_mock, query_mock, hop_mock): self.process.restart() # Really what is being tested - retry configuration exists and that # we do the right things when process check is True. self.assertEqual(self._test_retries + 1, query_mock.call_count) self.assertFalse(cleanup_mock.called) self.assertEqual(self._test_retries, sleep_mock.call_count) calls = [mock.call(1), mock.call(2), mock.call(4), mock.call(8), mock.call(16)] sleep_mock.assert_has_calls(calls) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._get_nexthop', return_value='172.168.1.2') @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._process_running', side_effect=[True, True, False, False]) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.' 'ipsec.OpenSwanProcess._cleanup_control_files') @mock.patch('eventlet.sleep') def test_restart_process_retry_until_stop(self, sleep_mock, cleanup_mock, query_mock, hop_mock): self.process.restart() # Really what is being tested - retry configuration exists and that # we do the right things when process check is True a few times and # then returns False. self.assertEqual(4, query_mock.call_count) self.assertTrue(cleanup_mock.called) self.assertEqual(2, sleep_mock.call_count) def test_process_running_no_pid(self): with mock.patch('os.path.exists', return_value=False): self.assertFalse( self.process._process_running()) # open() is used elsewhere, so we need to inject a mocked open into the # module to be tested. @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=IOError) def test_process_running_open_failure(self, mock_open, mock_exists): self.assertFalse(self.process._process_running()) self.assertTrue(mock_exists.called) self.assertTrue(mock_open.called) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'invalid'), IOError]) def test_process_running_bogus_pid(self, mock_open, mock_exists): with mock.patch.object(openswan_ipsec.LOG, 'error'): self.assertFalse(self.process._process_running()) self.assertTrue(mock_exists.called) self.assertEqual(2, mock_open.call_count) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'134'), io.StringIO(u'')]) def test_process_running_no_cmdline(self, mock_open, mock_exists): with mock.patch.object(openswan_ipsec.LOG, 'error') as log_mock: self.assertFalse(self.process._process_running()) self.assertFalse(log_mock.called) self.assertEqual(2, mock_open.call_count) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'134'), io.StringIO(u'ps ax')]) def test_process_running_cmdline_mismatch(self, mock_open, mock_exists): with mock.patch.object(openswan_ipsec.LOG, 'error') as log_mock: self.assertFalse(self.process._process_running()) self.assertFalse(log_mock.called) self.assertEqual(2, mock_open.call_count) @mock.patch('os.path.exists', return_value=True) @mock.patch('neutron_vpnaas.services.vpn.device_drivers.ipsec.open', create=True, side_effect=[io.StringIO(u'134'), io.StringIO(u'/usr/libexec/ipsec/pluto -ctlbase' '/some/foo/path')]) def test_process_running_cmdline_match(self, mock_open, mock_exists): self.process.pid_path = '/some/foo/path' with mock.patch.object(openswan_ipsec.LOG, 'error') as log_mock: self.assertTrue(self.process._process_running()) self.assertTrue(log_mock.called) def test_status_handling_for_downed_connection(self): """Test status handling for downed connection.""" self._test_status_handling_for_downed_connection(PLUTO_DOWN_STATUS) def test_status_handling_for_connection_with_no_ipsec_sa(self): """Test status handling for downed connection.""" self._test_status_handling_for_downed_connection( PLUTO_ACTIVE_NO_IPSEC_SA_STATUS) def test_status_handling_for_active_connection(self): """Test status handling for active connection.""" self._test_status_handling_for_active_connection(PLUTO_ACTIVE_STATUS) def test_status_handling_for_ike_v2_active_connection(self): """Test status handling for active connection.""" self._test_status_handling_for_ike_v2_active_connection( PLUTO_ACTIVE_STATUS_IKEV2) def test_status_handling_for_deleted_connection(self): """Test status handling for deleted connection.""" self._test_status_handling_for_deleted_connection(NOT_RUNNING_STATUS) def test_connection_names_handling_for_multiple_subnets(self): """Test connection names handling for multiple subnets.""" self._test_connection_names_handling_for_multiple_subnets( PLUTO_MULTIPLE_SUBNETS_ESTABLISHED_STATUS) def test_parse_connection_status(self): """Test the status of ipsec-site-connection parsed correctly.""" self._test_parse_connection_status(NOT_RUNNING_STATUS, PLUTO_ACTIVE_STATUS, PLUTO_DOWN_STATUS) class TestLibreSwanProcess(base.BaseTestCase): def setUp(self): super(TestLibreSwanProcess, self).setUp() self.vpnservice = copy.deepcopy(FAKE_VPN_SERVICE) self.ipsec_process = libreswan_ipsec.LibreSwanProcess(cfg.CONF, 'foo-process-id', self.vpnservice, mock.ANY) @mock.patch('os.path.exists', return_value=True) def test_ensure_configs_on_restart(self, exists_mock): openswan_ipsec.OpenSwanProcess.ensure_configs = mock.Mock() with mock.patch.object( self.ipsec_process, '_execute' ) as fake_execute, mock.patch.object( self.ipsec_process, '_ipsec_execute' ) as fake_ipsec_execute, mock.patch.object( self.ipsec_process, '_ensure_needed_files' ) as fake_ensure_needed_files: self.ipsec_process.ensure_configs() expected = [mock.call(['rm', '-f', self.ipsec_process._get_config_filename( 'ipsec.secrets')]), mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process._get_config_filename( 'ipsec.secrets')]), mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process.log_dir])] fake_execute.assert_has_calls(expected) self.assertEqual(3, fake_execute.call_count) expected = [mock.call(['_stackmanager', 'start']), mock.call(['checknss'])] fake_ipsec_execute.assert_has_calls(expected) self.assertEqual(2, fake_ipsec_execute.call_count) self.assertTrue(fake_ensure_needed_files.called) self.assertTrue(exists_mock.called) @mock.patch('os.path.exists', return_value=False) def test_ensure_configs(self, exists_mock): openswan_ipsec.OpenSwanProcess.ensure_configs = mock.Mock() with mock.patch.object( self.ipsec_process, '_execute' ) as fake_execute, mock.patch.object( self.ipsec_process, '_ipsec_execute' ) as fake_ipsec_execute, mock.patch.object( self.ipsec_process, '_ensure_needed_files' ) as fake_ensure_needed_files: self.ipsec_process.ensure_configs() expected = [mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process._get_config_filename( 'ipsec.secrets')]), mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process.log_dir])] fake_execute.assert_has_calls(expected) self.assertEqual(2, fake_execute.call_count) expected = [mock.call(['_stackmanager', 'start']), mock.call(['checknss'])] fake_ipsec_execute.assert_has_calls(expected) self.assertEqual(2, fake_ipsec_execute.call_count) self.assertTrue(fake_ensure_needed_files.called) self.assertTrue(exists_mock.called) exists_mock.reset_mock() with mock.patch.object( self.ipsec_process, '_execute' ) as fake_execute, mock.patch.object( self.ipsec_process, '_ipsec_execute' ) as fake_ipsec_execute, mock.patch.object( self.ipsec_process, '_ensure_needed_files' ) as fake_ensure_needed_files: fake_ipsec_execute.side_effect = [None, RuntimeError, None] self.ipsec_process.ensure_configs() expected = [mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process._get_config_filename( 'ipsec.secrets')]), mock.call(['chown', '--from=%s' % os.getuid(), 'root:root', self.ipsec_process.log_dir])] fake_execute.assert_has_calls(expected) self.assertEqual(2, fake_execute.call_count) expected = [mock.call(['_stackmanager', 'start']), mock.call(['checknss']), mock.call(['initnss'])] self.assertEqual(3, fake_ipsec_execute.call_count) fake_ipsec_execute.assert_has_calls(expected) self.assertTrue(fake_ensure_needed_files.called) self.assertTrue(exists_mock.called) class IPsecStrongswanDeviceDriverLegacy(IPSecDeviceLegacy): def setUp(self, driver=strongswan_ipsec.StrongSwanDriver, ipsec_process=strongswan_ipsec.StrongSwanProcess): super(IPsecStrongswanDeviceDriverLegacy, self).setUp(driver, ipsec_process) self.conf.register_opts(strongswan_ipsec.strongswan_opts, 'strongswan') self.conf.set_override('state_path', '/tmp') self.driver.agent_rpc.get_vpn_services_on_host.return_value = [ self.vpnservice] def test_status_handling_for_downed_connection(self): """Test status handling for downed connection.""" self._test_status_handling_for_downed_connection(CHARON_DOWN_STATUS) def test_status_handling_for_active_connection(self): """Test status handling for active connection.""" self._test_status_handling_for_active_connection(CHARON_ACTIVE_STATUS) def test_status_handling_for_deleted_connection(self): """Test status handling for deleted connection.""" self._test_status_handling_for_deleted_connection(NOT_RUNNING_STATUS) def test_parse_connection_status(self): """Test the status of ipsec-site-connection parsed correctly.""" self._test_parse_connection_status(NOT_RUNNING_STATUS, CHARON_ACTIVE_STATUS, CHARON_DOWN_STATUS) class IPsecStrongswanDeviceDriverDVR(IPSecDeviceDVR): def setUp(self, driver=strongswan_ipsec.StrongSwanDriver, ipsec_process=strongswan_ipsec.StrongSwanProcess): super(IPsecStrongswanDeviceDriverDVR, self).setUp(driver, ipsec_process) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ovn_ipsec.py0000664000175000017500000002242000000000000033005 0ustar00zuulzuul00000000000000# Copyright 2023 SysEleven GmbH. # # 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 unittest import mock from neutron.agent.linux import ip_lib from neutron.conf.agent import common as agent_config from neutron.conf import common as common_config from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.services.vpn.device_drivers import ovn_ipsec from neutron_vpnaas.tests import base from neutron_vpnaas.tests.unit.services.vpn.device_drivers import test_ipsec _uuid = uuidutils.generate_uuid FAKE_PROCESS_ID = "c5b52e50-e678-491e-98dd-34e2676a6f81" FAKE_NAMESPACE_NAME = "qvpn-c5b52e50-e678-491e-98dd-34e2676a6f81" FAKE_GW_PORT_ID = "e95d89fb-1723-4865-876f-a1c8efed4b55" FAKE_GW_PORT_INTERFACE_NAME = "vge95d89fb-172" FAKE_GW_PORT_IP_ADDRESS = "20.20.20.20" FAKE_GW_PORT_MAC_ADDRESS = "11:22:33:44:55:66" FAKE_GW_PORT_SUBNET_ID = _uuid() FAKE_GW_PORT_SUBNET_INFO = { 'id': FAKE_GW_PORT_SUBNET_ID, 'cidr': '20.20.20.0/24', 'ip_version': 4 } FAKE_GW_PORT = { 'id': FAKE_GW_PORT_ID, 'mac_address': FAKE_GW_PORT_MAC_ADDRESS, 'fixed_ips': [{ 'ip_address': FAKE_GW_PORT_IP_ADDRESS, 'subnet_id': FAKE_GW_PORT_SUBNET_ID }] } FAKE_TRANSIT_PORT_ID = "0eb4bdb3-fe2e-4724-bb04-f84b6a5974f8" FAKE_TRANSIT_PORT_INTERFACE_NAME = "vr0eb4bdb3-fe2" FAKE_TRANSIT_PORT_MAC_ADDRESS = "22:33:44:55:66:77" FAKE_TRANSIT_PORT_IP_ADDRESS = "169.254.0.2" FAKE_TRANSIT_PORT_SUBNET_ID = _uuid() FAKE_TRANSIT_PORT = { 'id': FAKE_TRANSIT_PORT_ID, 'mac_address': FAKE_TRANSIT_PORT_MAC_ADDRESS, 'fixed_ips': [{ 'ip_address': FAKE_TRANSIT_PORT_IP_ADDRESS, 'subnet_id': FAKE_TRANSIT_PORT_SUBNET_ID }] } def fake_interface_driver(*args, **kwargs): driver = mock.Mock() driver.DEV_NAME_LEN = 14 return driver class TestDeviceManager(base.BaseTestCase): def setUp(self): super().setUp() self.conf = cfg.CONF self.conf.register_opts(common_config.core_opts) self.conf.register_opts(agent_config.INTERFACE_DRIVER_OPTS) self.conf.set_override('interface_driver', 'neutron_vpnaas.tests.unit.services.vpn.device_drivers' '.test_ovn_ipsec.fake_interface_driver') self.host = "some-hostname" self.plugin = mock.Mock() self.plugin.get_subnet_info.return_value = FAKE_GW_PORT_SUBNET_INFO self.context = mock.Mock() def test_names(self): mgr = ovn_ipsec.DeviceManager(self.conf, self.host, self.plugin, self.context) port = {'id': "0df5beb8-4794-4217-acde-e6ce4875a59f"} name = mgr.get_interface_name(port, "internal") self.assertEqual(name, "vr0df5beb8-479") name = mgr.get_interface_name(port, "external") self.assertEqual(name, "vg0df5beb8-479") name = mgr.get_namespace_name("0df5beb8-4794-4217-acde-e6ce4875a59f") self.assertEqual(name, "qvpn-0df5beb8-4794-4217-acde-e6ce4875a59f") def test_setup_external(self): ext_net_id = _uuid() network_details = { 'gw_port': FAKE_GW_PORT, 'external_network': { 'id': ext_net_id } } mgr = ovn_ipsec.DeviceManager(self.conf, self.host, self.plugin, self.context) with mock.patch.object(ip_lib, 'ensure_device_is_ready') as dev_ready: with mock.patch.object(mgr, 'set_default_route') as set_def_route: dev_ready.return_value = False mgr.setup_external(FAKE_PROCESS_ID, network_details) dev_ready.assert_called_once() self.plugin.get_subnet_info.assert_called_once_with( FAKE_GW_PORT_SUBNET_ID ) set_def_route.assert_called_once_with( FAKE_NAMESPACE_NAME, FAKE_GW_PORT_SUBNET_INFO, FAKE_GW_PORT_INTERFACE_NAME ) mgr.driver.init_l3.assert_called_once() mgr.driver.plug.assert_called_once() def test_setup_internal(self): network_details = {'transit_port': FAKE_TRANSIT_PORT} mgr = ovn_ipsec.DeviceManager(self.conf, self.host, self.plugin, self.context) with mock.patch.object(ip_lib, 'ensure_device_is_ready') as dev_ready: dev_ready.return_value = False mgr.setup_internal(FAKE_PROCESS_ID, network_details) dev_ready.assert_called_once() mgr.driver.init_l3.assert_called_once() mgr.driver.plug.assert_called_once() def test_list_routes(self): mgr = ovn_ipsec.DeviceManager(self.conf, self.host, self.plugin, self.context) mock_ipdev = mock.Mock() routes = [ {'cidr': '192.168.111.0/24', 'via': FAKE_TRANSIT_PORT_IP_ADDRESS} ] with mock.patch.object(ip_lib, 'IPDevice') as ipdev: ipdev.return_value = mock_ipdev mock_ipdev.route.list_routes.return_value = routes returned = mgr.list_routes(FAKE_NAMESPACE_NAME) self.assertEqual(returned, routes) def test_del_static_routes(self): mgr = ovn_ipsec.DeviceManager(self.conf, self.host, self.plugin, self.context) mock_ipdev = mock.Mock() routes = [ {'cidr': '192.168.111.0/24', 'via': FAKE_TRANSIT_PORT_IP_ADDRESS}, {'cidr': '192.168.112.0/24', 'via': FAKE_TRANSIT_PORT_IP_ADDRESS} ] with mock.patch.object(ip_lib, 'IPDevice') as ipdev: ipdev.return_value = mock_ipdev mock_ipdev.route.list_routes.return_value = routes mgr.del_static_routes(FAKE_NAMESPACE_NAME) mock_ipdev.route.delete_route.assert_has_calls([ mock.call(routes[0]['cidr'], via=FAKE_TRANSIT_PORT_IP_ADDRESS), mock.call(routes[1]['cidr'], via=FAKE_TRANSIT_PORT_IP_ADDRESS), ], any_order=True) class TestOvnStrongSwanDriver(test_ipsec.IPSecDeviceLegacy): def setUp(self, driver=ovn_ipsec.OvnStrongSwanDriver, ipsec_process=ovn_ipsec.OvnStrongSwanProcess): conf = cfg.CONF conf.register_opts(common_config.core_opts) conf.register_opts(agent_config.INTERFACE_DRIVER_OPTS) conf.set_override('interface_driver', 'neutron_vpnaas.tests.unit.services.vpn.device_drivers' '.test_ovn_ipsec.fake_interface_driver') super().setUp(driver, ipsec_process) self.driver.nsmgr = mock.Mock() self.driver.nsmgr.exists.return_value = False self.driver.devmgr = mock.Mock() self.driver.devmgr.get_namespace_name.return_value = \ FAKE_NAMESPACE_NAME self.driver.devmgr.list_routes.return_value = [] self.driver.devmgr.get_existing_process_ids.return_value = [] self.driver.agent_rpc.get_vpn_transit_network_details.return_value = { 'transit_gateway_ip': '192.168.1.1', } def test_iptables_apply(self): """Not applicable for OvnIPsecDriver""" pass def test_get_namespace_for_router(self): """Different for OvnIPsecDriver""" namespace = self.driver.get_namespace(FAKE_PROCESS_ID) self.assertEqual(FAKE_NAMESPACE_NAME, namespace) def test_fail_getting_namespace_for_unknown_router(self): """Not applicable for OvnIPsecDriver""" pass def test_create_router(self): """Not applicable for OvnIPsecDriver""" pass def test_destroy_router(self): """Not applicable for OvnIPsecDriver""" pass def test_remove_rule(self): """Not applicable for OvnIPsecDriver""" pass def test_add_nat_rules_with_multiple_local_subnets(self): """Not applicable for OvnIPsecDriver""" pass def _test_add_nat_rule(self): """Not applicable for OvnIPsecDriver""" pass def test_add_nat_rule(self): """Not applicable for OvnIPsecDriver""" pass def test_stale_cleanup(self): process = self.fake_ensure_process(FAKE_PROCESS_ID) self.driver.devmgr.get_existing_process_ids.return_value = [ FAKE_PROCESS_ID] self.driver.agent_rpc.get_vpn_services_on_host.return_value = [] context = mock.Mock() with mock.patch.object(self.driver, 'ensure_process') as ensure: ensure.return_value = process self.driver.sync(context, []) process.disable.assert_called() class TestOvnOpenSwanDriver(TestOvnStrongSwanDriver): def setUp(self): super().setUp(driver=ovn_ipsec.OvnOpenSwanDriver, ipsec_process=ovn_ipsec.OvnOpenSwanProcess) class TestOvnLibreSwanDriver(TestOvnStrongSwanDriver): def setUp(self): super().setUp(driver=ovn_ipsec.OvnLibreSwanDriver, ipsec_process=ovn_ipsec.OvnLibreSwanProcess) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9153502 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/0000775000175000017500000000000000000000000027610 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/__init__.py0000664000175000017500000000000000000000000031707 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py0000664000175000017500000005227000000000000032332 0ustar00zuulzuul00000000000000# 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 unittest import mock from neutron.db import servicetype_db as st_db from neutron_lib import context as n_ctx from neutron_lib.plugins import constants from neutron_lib.plugins import directory from oslo_utils import uuidutils from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.services.vpn.service_drivers import ipsec as ipsec_driver from neutron_vpnaas.services.vpn.service_drivers import ipsec_validator from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_SERVICE_ID = _uuid() FAKE_VPN_CONNECTION = { 'vpnservice_id': FAKE_SERVICE_ID } FAKE_ROUTER_ID = _uuid() FAKE_VPN_SERVICE = { 'router_id': FAKE_ROUTER_ID } FAKE_HOST = 'fake_host' FAKE_CONN_ID = _uuid() IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.' 'ipsec.IPsecVPNDriver') class FakeSqlQueryObject(dict): """To fake SqlAlchemy query object and access keys as attributes.""" def __init__(self, **entries): self.__dict__.update(entries) super(FakeSqlQueryObject, self).__init__(**entries) class TestValidatorSelection(base.BaseTestCase): def setUp(self): super(TestValidatorSelection, self).setUp() vpnaas_provider = [{ 'service_type': constants.VPN, 'name': 'vpnaas', 'driver': IPSEC_SERVICE_DRIVER, 'default': True }] # override the default service provider self.service_providers = ( mock.patch.object(st_db.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = vpnaas_provider mock.patch('neutron_lib.rpc.Connection').start() stm = st_db.ServiceTypeManager() stm.get_provider_names_by_resource_ids = mock.Mock( return_value={}) mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=stm).start() mock.patch('neutron_vpnaas.db.vpn.vpn_db.VPNPluginDb.get_vpnservices', return_value=[]).start() self.vpn_plugin = vpn_plugin.VPNDriverPlugin() def test_reference_driver_used(self): default_provider = self.vpn_plugin.default_provider default_driver = self.vpn_plugin.drivers[default_provider] self.assertIsInstance(default_driver.validator, ipsec_validator.IpsecVpnValidator) class TestIPsecDriver(base.BaseTestCase): def setUp(self): super(TestIPsecDriver, self).setUp() mock.patch('neutron_lib.rpc.Connection').start() l3_agent = mock.Mock() l3_agent.host = FAKE_HOST plugin = mock.Mock() plugin.get_l3_agents_hosting_routers.return_value = [l3_agent] directory.add_plugin(constants.CORE, plugin) directory.add_plugin(constants.L3, plugin) self.svc_plugin = mock.Mock() self.svc_plugin.get_l3_agents_hosting_routers.return_value = [l3_agent] self._fake_vpn_router_id = _uuid() self.svc_plugin.get_vpnservice_router_id.return_value = \ self._fake_vpn_router_id self.driver = ipsec_driver.IPsecVPNDriver(self.svc_plugin) self.validator = ipsec_validator.IpsecVpnValidator(self.driver) self.context = n_ctx.get_admin_context() def _test_update(self, func, args, additional_info=None): ctxt = n_ctx.Context('', 'somebody') with mock.patch.object(self.driver.agent_rpc.client, 'cast' ) as rpc_mock, \ mock.patch.object(self.driver.agent_rpc.client, 'prepare' ) as prepare_mock: prepare_mock.return_value = self.driver.agent_rpc.client func(ctxt, *args) prepare_args = {'server': 'fake_host', 'version': '1.0'} prepare_mock.assert_called_once_with(**prepare_args) rpc_mock.assert_called_once_with(ctxt, 'vpnservice_updated', **additional_info) def test_create_ipsec_site_connection(self): self._test_update(self.driver.create_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_update_ipsec_site_connection(self): self._test_update(self.driver.update_ipsec_site_connection, [FAKE_VPN_CONNECTION, FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_delete_ipsec_site_connection(self): self._test_update(self.driver.delete_ipsec_site_connection, [FAKE_VPN_CONNECTION], {'router': {'id': self._fake_vpn_router_id}}) def test_update_vpnservice(self): self._test_update(self.driver.update_vpnservice, [FAKE_VPN_SERVICE, FAKE_VPN_SERVICE], {'router': {'id': FAKE_VPN_SERVICE['router_id']}}) def test_delete_vpnservice(self): self._test_update(self.driver.delete_vpnservice, [FAKE_VPN_SERVICE], {'router': {'id': FAKE_VPN_SERVICE['router_id']}}) def prepare_dummy_query_objects(self, info): """Create fake query objects to test dict creation for sync oper.""" external_ip = '10.0.0.99' peer_address = '10.0.0.2' peer_endpoints = info.get('peer_endpoints', []) local_endpoints = info.get('local_endpoints', []) peer_cidrs = info.get('peer_cidrs', ['40.4.0.0/24', '50.5.0.0/24']) peer_id = info.get('peer_id', '30.30.0.0') local_id = info.get('local_id', '') fake_ikepolicy = FakeSqlQueryObject(id='foo-ike', name='ike-name') fake_ipsecpolicy = FakeSqlQueryObject(id='foo-ipsec') fake_peer_cidrs_list = [ FakeSqlQueryObject(cidr=cidr, ipsec_site_connection_id='conn-id') for cidr in peer_cidrs] peer_epg_id = 'peer-epg-id' if peer_endpoints else None local_epg_id = 'local-epg-id' if local_endpoints else None fake_ipsec_conn = FakeSqlQueryObject(id='conn-id', peer_id=peer_id, peer_address=peer_address, local_id=local_id, ikepolicy=fake_ikepolicy, ipsecpolicy=fake_ipsecpolicy, peer_ep_group_id=peer_epg_id, local_ep_group_id=local_epg_id, peer_cidrs=fake_peer_cidrs_list) if peer_endpoints: fake_peer_ep_group = FakeSqlQueryObject(id=peer_epg_id) fake_peer_ep_group.endpoints = [ FakeSqlQueryObject(endpoint=ep, endpoint_group_id=peer_epg_id) for ep in peer_endpoints] fake_ipsec_conn.peer_ep_group = fake_peer_ep_group if local_endpoints: fake_local_ep_group = FakeSqlQueryObject(id=local_epg_id) fake_local_ep_group.endpoints = [ FakeSqlQueryObject(endpoint=ep, endpoint_group_id=local_epg_id) for ep in local_endpoints] fake_ipsec_conn.local_ep_group = fake_local_ep_group subnet_id = None else: subnet_id = 'foo-subnet-id' fake_gw_port = {'fixed_ips': [{'ip_address': external_ip}]} fake_router = FakeSqlQueryObject(gw_port=fake_gw_port) fake_vpnservice = FakeSqlQueryObject(id='foo-vpn-id', name='foo-vpn', description='foo-vpn-service', admin_state_up=True, status='active', external_v4_ip=external_ip, external_v6_ip=None, subnet_id=subnet_id, router_id='foo-router-id', project_id='foo-project-id') if local_endpoints: fake_vpnservice.subnet = None else: fake_subnet = FakeSqlQueryObject(id=subnet_id, name='foo-subnet', cidr='9.0.0.0/16', network_id='foo-net-id') fake_vpnservice.subnet = fake_subnet fake_vpnservice.router = fake_router fake_vpnservice.ipsec_site_connections = [fake_ipsec_conn] return fake_vpnservice def build_expected_dict(self, info): """Create the expected dict used in sync operations. The default is to use non-endpoint groups, where the peer CIDRs come from the peer_cidrs arguments, the local CIDRs come from the (sole) subnet CIDR, and there is subnet info. Tests will customize the peer ID and peer CIDRs. """ external_ip = '10.0.0.99' peer_id = info.get('peer_id', '30.30.0.0') peer_cidrs = info.get('peer_cidrs', ['40.4.0.0/24', '50.5.0.0/24']) local_id = info.get('local_id', '') return {'name': 'foo-vpn', 'id': 'foo-vpn-id', 'description': 'foo-vpn-service', 'admin_state_up': True, 'status': 'active', 'external_v4_ip': external_ip, 'external_v6_ip': None, 'router_id': 'foo-router-id', 'subnet': {'cidr': '9.0.0.0/16', 'id': 'foo-subnet-id', 'name': 'foo-subnet', 'network_id': 'foo-net-id'}, 'subnet_id': 'foo-subnet-id', 'external_ip': external_ip, 'project_id': 'foo-project-id', 'tenant_id': 'foo-project-id', 'ipsec_site_connections': [ {'id': 'conn-id', 'peer_id': peer_id, 'external_ip': external_ip, 'peer_address': '10.0.0.2', 'local_id': local_id, 'ikepolicy': {'id': 'foo-ike', 'name': 'ike-name'}, 'ipsecpolicy': {'id': 'foo-ipsec'}, 'peer_ep_group_id': None, 'local_ep_group_id': None, 'peer_cidrs': peer_cidrs, 'local_cidrs': ['9.0.0.0/16'], 'local_ip_vers': 4} ]} def build_expected_dict_for_endpoints(self, info): """Create the expected dict used in sync operations for endpoints. The local and peer CIDRs come from the endpoint groups (with the local CIDR translated from the corresponding subnets specified). Tests will customize CIDRs, and the subnet, which is needed for backward compatibility with agents, during rolling upgrades. """ external_ip = '10.0.0.99' peer_id = '30.30.0.0' local_id = info.get('local_id', '') return {'name': 'foo-vpn', 'id': 'foo-vpn-id', 'description': 'foo-vpn-service', 'admin_state_up': True, 'status': 'active', 'external_v4_ip': external_ip, 'external_v6_ip': None, 'router_id': 'foo-router-id', 'subnet': None, 'subnet_id': None, 'external_ip': external_ip, 'project_id': 'foo-project-id', 'tenant_id': 'foo-project-id', 'ipsec_site_connections': [ {'id': 'conn-id', 'peer_id': peer_id, 'external_ip': external_ip, 'peer_address': '10.0.0.2', 'local_id': local_id, 'ikepolicy': {'id': 'foo-ike', 'name': 'ike-name'}, 'ipsecpolicy': {'id': 'foo-ipsec'}, 'peer_ep_group_id': 'peer-epg-id', 'local_ep_group_id': 'local-epg-id', 'peer_cidrs': info['peers'], 'local_cidrs': info['locals'], 'local_ip_vers': info['vers']} ]} def test_make_vpnservice_dict_peer_id_is_ipaddr(self): """Peer ID as IP should be copied as-is, when creating dict.""" subnet_cidr_map = {} peer_id_as_ip = {'peer_id': '10.0.0.2'} fake_service = self.prepare_dummy_query_objects(peer_id_as_ip) expected_dict = self.build_expected_dict(peer_id_as_ip) actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) # make sure that ipsec_site_conn peer_id is not updated by # _make_vpnservice_dict (bug #1423244) self.assertEqual(peer_id_as_ip['peer_id'], fake_service.ipsec_site_connections[0].peer_id) def test_make_vpnservice_dict_peer_id_is_string(self): """Peer ID as string should have '@' prepended, when creating dict.""" subnet_cidr_map = {} peer_id_as_name = {'peer_id': 'foo.peer.id'} fake_service = self.prepare_dummy_query_objects(peer_id_as_name) expected_peer_id = {'peer_id': '@foo.peer.id'} expected_dict = self.build_expected_dict(expected_peer_id) actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) # make sure that ipsec_site_conn peer_id is not updated by # _make_vpnservice_dict (bug #1423244) self.assertEqual(peer_id_as_name['peer_id'], fake_service.ipsec_site_connections[0].peer_id) def test_make_vpnservice_dict_peer_cidrs_from_peer_cidr_table(self): """Peer CIDRs list populated from peer_cidr table. User provides peer CIDRs as parameters to IPSec site-to-site connection API, and they are stored in the peercidrs table. """ subnet_cidr_map = {} peer_cidrs = {'peer_cidrs': ['80.0.0.0/24', '90.0.0.0/24']} fake_service = self.prepare_dummy_query_objects(peer_cidrs) expected_dict = self.build_expected_dict(peer_cidrs) actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) def test_make_vpnservice_dict_cidrs_from_endpoints(self): """CIDRs list populated from local and peer endpoints. User provides peer and local endpoint group IDs in the IPSec site-to-site connection API. The endpoint groups contains peer CIDRs and local subnets (which will be mapped to CIDRs). """ # Cannot have peer CIDRs specified, when using endpoint group subnet_cidr_map = {'local-sn1': '5.0.0.0/16', 'local-sn2': '5.1.0.0/16'} endpoint_groups = {'peer_cidrs': [], 'peer_endpoints': ['80.0.0.0/24', '90.0.0.0/24'], 'local_endpoints': ['local-sn1', 'local-sn2']} expected_cidrs = {'peers': ['80.0.0.0/24', '90.0.0.0/24'], 'locals': ['5.0.0.0/16', '5.1.0.0/16'], 'vers': 4} fake_service = self.prepare_dummy_query_objects(endpoint_groups) expected_dict = self.build_expected_dict_for_endpoints(expected_cidrs) expected_dict['subnet'] = {'cidr': '5.0.0.0/16'} actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) def test_make_vpnservice_dict_v6_cidrs_from_endpoints(self): """IPv6 CIDRs list populated from local and peer endpoints.""" # Cannot have peer CIDRs specified, when using endpoint group subnet_cidr_map = {'local-sn1': '2002:0a00:0000::/48', 'local-sn2': '2002:1400:0000::/48'} endpoint_groups = {'peer_cidrs': [], 'peer_endpoints': ['2002:5000:0000::/48', '2002:5a00:0000::/48'], 'local_endpoints': ['local-sn1', 'local-sn2']} expected_cidrs = {'peers': ['2002:5000:0000::/48', '2002:5a00:0000::/48'], 'locals': ['2002:0a00:0000::/48', '2002:1400:0000::/48'], 'vers': 6} fake_service = self.prepare_dummy_query_objects(endpoint_groups) expected_dict = self.build_expected_dict_for_endpoints(expected_cidrs) expected_dict['subnet'] = {'cidr': '2002:0a00:0000::/48'} actual_dict = self.driver.make_vpnservice_dict(fake_service, subnet_cidr_map) self.assertEqual(expected_dict, actual_dict) def test_get_external_ip_based_on_ipv4_peer(self): vpnservice = mock.Mock() vpnservice.external_v4_ip = '10.0.0.99' vpnservice.external_v6_ip = '2001::1' ipsec_sitecon = {'id': FAKE_CONN_ID, 'peer_address': '10.0.0.9'} ip_to_use = self.driver.get_external_ip_based_on_peer(vpnservice, ipsec_sitecon) self.assertEqual('10.0.0.99', ip_to_use) def test_get_external_ip_based_on_ipv6_peer(self): vpnservice = mock.Mock() vpnservice.external_v4_ip = '10.0.0.99' vpnservice.external_v6_ip = '2001::1' ipsec_sitecon = {'id': FAKE_CONN_ID, 'peer_address': '2001::5'} ip_to_use = self.driver.get_external_ip_based_on_peer(vpnservice, ipsec_sitecon) self.assertEqual('2001::1', ip_to_use) def test_get_ipv4_gw_ip(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertEqual('10.0.0.99', v4_ip) self.assertIsNone(v6_ip) def test_get_ipv6_gw_ip(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '2001::1'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertIsNone(v4_ip) self.assertEqual('2001::1', v6_ip) def test_get_both_gw_ips(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}, {'ip_address': '2001::1'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertEqual('10.0.0.99', v4_ip) self.assertEqual('2001::1', v6_ip) def test_use_first_gw_ips_when_multiples(self): vpnservice = mock.Mock() vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}, {'ip_address': '20.0.0.99'}, {'ip_address': '2001::1'}, {'ip_address': 'fd00::4'}]} v4_ip, v6_ip = self.driver._get_gateway_ips(vpnservice.router) self.assertEqual('10.0.0.99', v4_ip) self.assertEqual('2001::1', v6_ip) def test_store_gw_ips_on_service_create(self): vpnservice = mock.Mock() self.svc_plugin._get_vpnservice.return_value = vpnservice vpnservice.router.gw_port = {'fixed_ips': [{'ip_address': '10.0.0.99'}, {'ip_address': '2001::1'}]} ctxt = n_ctx.Context('', 'somebody') vpnservice_dict = {'id': FAKE_SERVICE_ID, 'router_id': FAKE_ROUTER_ID} self.driver.create_vpnservice(ctxt, vpnservice_dict) self.svc_plugin.set_external_tunnel_ips.assert_called_once_with( ctxt, FAKE_SERVICE_ID, v4_ip='10.0.0.99', v6_ip='2001::1') def test_validate_ipsec_policy(self): # Validate IPsec Policy transform_protocol ipsec_policy = {'transform_protocol': 'ah-esp'} self.assertRaises(ipsec_validator.IpsecValidationFailure, self.validator.validate_ipsec_policy, self.context, ipsec_policy) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ovn_ipsec.py0000664000175000017500000002756700000000000033227 0ustar00zuulzuul00000000000000# Copyright 2020, SysEleven GbmH # 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 unittest import mock from neutron_lib import context as n_ctx from neutron_lib.plugins import constants from neutron_lib.plugins import directory from oslo_utils import uuidutils from neutron_vpnaas.services.vpn.service_drivers import ipsec_validator from neutron_vpnaas.services.vpn.service_drivers \ import ovn_ipsec as ipsec_driver from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid FAKE_HOST = 'fake_host' FAKE_TENANT_ID = 'tenant1' FAKE_ROUTER_ID = _uuid() FAKE_TRANSIT_IP_ADDRESS = '169.254.0.2' FAKE_VPNSERVICE_1 = { 'id': _uuid(), 'router_id': FAKE_ROUTER_ID, 'tenant_id': FAKE_TENANT_ID } FAKE_VPNSERVICE_2 = { 'id': _uuid(), 'router_id': FAKE_ROUTER_ID, 'tenant_id': FAKE_TENANT_ID } FAKE_VPN_CONNECTION_1 = { 'vpnservice_id': FAKE_VPNSERVICE_1['id'] } class FakeSqlQueryObject(dict): """To fake SqlAlchemy query object and access keys as attributes.""" def __init__(self, **entries): self.__dict__.update(entries) super(FakeSqlQueryObject, self).__init__(**entries) class FakeGatewayDB(object): def __init__(self): self.gateways_by_router = {} self.gateways_by_id = {} def create_gateway(self, context, gateway): info = gateway['gateway'] fake_gw = { 'id': _uuid(), 'status': 'PENDING_CREATE', 'external_fixed_ips': [{'subnet_id': '1', 'ip_address': '10.2.3.4'}], **info } self.gateways_by_router[info['router_id']] = fake_gw self.gateways_by_id[fake_gw['id']] = fake_gw return fake_gw def update_gateway(self, context, gateway_id, gateway): self.gateways_by_id[gateway_id].update(**gateway['gateway']) def delete_gateway(self, context, gateway_id): fake_gw = self.gateways_by_id.pop(gateway_id, None) if fake_gw: self.gateways_by_router.pop(fake_gw['router_id']) return 1 if fake_gw else 0 def get_vpn_gw_dict_by_router_id(self, context, router_id, refresh=False): return self.gateways_by_router.get(router_id) class TestOvnIPsecDriver(base.BaseTestCase): def setUp(self): super().setUp() mock.patch('neutron_lib.rpc.Connection').start() self.create_port = \ mock.patch('neutron_lib.plugins.utils.create_port').start() self.create_network = \ mock.patch('neutron_lib.plugins.utils.create_network').start() self.create_subnet = \ mock.patch('neutron_lib.plugins.utils.create_subnet').start() self.create_port.side_effect = lambda pl, c, p: { 'id': _uuid(), 'fixed_ips': [{'subnet_id': '1', 'ip_address': '10.1.1.2'}]} self.create_network.side_effect = lambda pl, c, n: {'id': _uuid()} self.create_subnet.side_effect = lambda pl, c, s: {'id': _uuid()} vpn_agent = {'host': FAKE_HOST} self.core_plugin = mock.Mock() self.core_plugin.get_vpn_agents_hosting_routers.return_value = \ [vpn_agent] directory.add_plugin(constants.CORE, self.core_plugin) self._fake_router = FakeSqlQueryObject( id=FAKE_ROUTER_ID, gw_port=FakeSqlQueryObject(network_id=_uuid()) ) self.l3_plugin = mock.Mock() self.l3_plugin.get_router.return_value = self._fake_router directory.add_plugin(constants.L3, self.l3_plugin) self.svc_plugin = mock.Mock() self.svc_plugin.get_vpn_agents_hosting_routers.return_value = \ [vpn_agent] self.svc_plugin.schedule_router.return_value = vpn_agent self.svc_plugin._get_vpnservice.return_value = FakeSqlQueryObject( router_id=FAKE_ROUTER_ID, router=self._fake_router ) self.svc_plugin.get_vpnservice.return_value = FAKE_VPNSERVICE_1 self.svc_plugin.get_vpnservice_router_id.return_value = FAKE_ROUTER_ID self.driver = ipsec_driver.IPsecOvnVPNDriver(self.svc_plugin) self.validator = ipsec_validator.IpsecVpnValidator(self.driver) self.context = n_ctx.get_admin_context() def test_create_vpnservice(self): mock.patch.object(self.driver.agent_rpc.client, 'cast') mock.patch.object(self.driver.agent_rpc.client, 'prepare') fake_gw_db = FakeGatewayDB() self.svc_plugin.get_vpn_gw_dict_by_router_id.side_effect = \ fake_gw_db.get_vpn_gw_dict_by_router_id self.svc_plugin.create_gateway.side_effect = fake_gw_db.create_gateway self.svc_plugin.update_gateway.side_effect = fake_gw_db.update_gateway self.driver.create_vpnservice(self.context, FAKE_VPNSERVICE_1) self.svc_plugin.create_gateway.assert_called_once() # check that the plugin utils create functions were called self.create_port.assert_called() self.create_network.assert_called_once() self.create_subnet.assert_called_once() # check that the core plugin create functions were not called directly self.core_plugin.create_port.assert_not_called() self.core_plugin.create_network.assert_not_called() self.core_plugin.create_subnet.assert_not_called() self.svc_plugin.reset_mock() self.driver.create_vpnservice(self.context, FAKE_VPNSERVICE_2) self.svc_plugin.create_gateway.assert_not_called() def test_delete_vpnservice(self): mock.patch.object(self.driver.agent_rpc.client, 'cast') mock.patch.object(self.driver.agent_rpc.client, 'prepare') fake_gw_db = FakeGatewayDB() self.svc_plugin.get_vpn_gw_dict_by_router_id.side_effect = \ fake_gw_db.get_vpn_gw_dict_by_router_id self.svc_plugin.create_gateway.side_effect = fake_gw_db.create_gateway self.svc_plugin.update_gateway.side_effect = fake_gw_db.update_gateway self.svc_plugin.delete_gateway.side_effect = fake_gw_db.delete_gateway # create 2 VPN services on same router self.driver.create_vpnservice(self.context, FAKE_VPNSERVICE_1) self.driver.create_vpnservice(self.context, FAKE_VPNSERVICE_2) self.svc_plugin.reset_mock() # deleting one VPN service must not delete the VPN gateway self.svc_plugin.get_vpnservices.return_value = [FAKE_VPNSERVICE_2] self.driver.delete_vpnservice(self.context, FAKE_VPNSERVICE_1) self.core_plugin.delete_port.assert_not_called() self.core_plugin.delete_network.assert_not_called() self.core_plugin.delete_subnet.assert_not_called() self.svc_plugin.create_gateway.assert_not_called() self.svc_plugin.delete_gateway.assert_not_called() # deleting last VPN service shall delete the VPN gateway self.svc_plugin.get_vpnservices.return_value = [] self.driver.delete_vpnservice(self.context, FAKE_VPNSERVICE_1) self.core_plugin.delete_port.assert_called() self.core_plugin.delete_network.assert_called_once() self.core_plugin.delete_subnet.assert_called_once() self.svc_plugin.create_gateway.assert_not_called() self.svc_plugin.delete_gateway.assert_called_once() def _test_ipsec_site_connection(self, old_peers, new_peers, func, args, expected_add, expected_remove): self._fake_router['routes'] = [ {'destination': peer, 'nexthop': FAKE_TRANSIT_IP_ADDRESS} for peer in old_peers ] transit_port = FakeSqlQueryObject( id=_uuid(), fixed_ips=[ {'subnet_id': _uuid(), 'ip_address': FAKE_TRANSIT_IP_ADDRESS} ] ) self.svc_plugin.get_vpn_gw_by_router_id.return_value = \ FakeSqlQueryObject(id=_uuid(), router_id=FAKE_ROUTER_ID, transit_port_id=transit_port.id, transit_port=transit_port) self.svc_plugin.get_peer_cidrs_for_router.return_value = new_peers # create/update/delete_ipsec_site_connection with mock.patch.object(self.driver.agent_rpc.client, 'cast' ) as rpc_mock, \ mock.patch.object(self.driver.agent_rpc.client, 'prepare' ) as prepare_mock: prepare_mock.return_value = self.driver.agent_rpc.client func(self.context, *args) prepare_args = {'server': 'fake_host', 'version': '1.0'} prepare_mock.assert_called_once_with(**prepare_args) # check that agent RPC vpnservice_updated is called rpc_mock.assert_called_once_with(self.context, 'vpnservice_updated', router={'id': FAKE_ROUTER_ID}) # check that routes were updated if expected_add: expected_router = {'router': {'routes': [ {'destination': peer, 'nexthop': FAKE_TRANSIT_IP_ADDRESS} for peer in expected_add ]}} self.l3_plugin.add_extraroutes.assert_called_once_with( self.context, FAKE_ROUTER_ID, expected_router) else: self.l3_plugin.add_extraroutes.assert_not_called() if expected_remove: expected_router = {'router': {'routes': [ {'destination': peer, 'nexthop': FAKE_TRANSIT_IP_ADDRESS} for peer in expected_remove ]}} self.l3_plugin.remove_extraroutes.assert_called_once_with( self.context, FAKE_ROUTER_ID, expected_router) else: self.l3_plugin.remove_extraroutes.assert_not_called() def test_create_ipsec_site_connection_1(self): old_peers = [] new_peers = ['192.168.1.0/24'] expected_add = new_peers expected_remove = [] self._test_ipsec_site_connection( old_peers, new_peers, self.driver.create_ipsec_site_connection, [FAKE_VPN_CONNECTION_1], expected_add, expected_remove ) def test_create_ipsec_site_connection_2(self): """Test creating a 2nd site connection.""" old_peers = ['192.168.1.0/24'] new_peers = ['192.168.1.0/24', '192.168.2.0/24'] expected_add = ['192.168.2.0/24'] expected_remove = [] self._test_ipsec_site_connection( old_peers, new_peers, self.driver.create_ipsec_site_connection, [FAKE_VPN_CONNECTION_1], expected_add, expected_remove ) def test_update_ipsec_site_connection(self): old_peers = ['192.168.1.0/24'] new_peers = ['192.168.2.0/24'] expected_add = new_peers expected_remove = old_peers self._test_ipsec_site_connection( old_peers, new_peers, self.driver.update_ipsec_site_connection, [FAKE_VPN_CONNECTION_1, FAKE_VPN_CONNECTION_1], expected_add, expected_remove ) def test_delete_ipsec_site_connection(self): old_peers = ['192.168.1.0/24', '192.168.2.0/24'] new_peers = ['192.168.2.0/24'] expected_add = [] expected_remove = ['192.168.1.0/24'] self._test_ipsec_site_connection( old_peers, new_peers, self.driver.delete_ipsec_site_connection, [FAKE_VPN_CONNECTION_1], expected_add, expected_remove ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/test_plugin.py0000664000175000017500000003754700000000000027341 0ustar00zuulzuul00000000000000# 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 contextlib from unittest import mock from neutron.db import servicetype_db as st_db from neutron.services.flavors import flavors_plugin from neutron.tests.unit.db import test_agentschedulers_db from neutron.tests.unit.extensions import test_agent as test_agent_ext_plugin from neutron_lib import constants as lib_constants from neutron_lib import context from neutron_lib import exceptions as lib_exc from neutron_lib.exceptions import flavors as flav_exc from neutron_lib.plugins import constants as p_constants from neutron_lib.plugins import directory from oslo_utils import uuidutils from neutron_vpnaas.extensions import vpn_flavors from neutron_vpnaas.services.vpn import plugin as vpn_plugin from neutron_vpnaas.services.vpn.service_drivers import driver_validator from neutron_vpnaas.services.vpn.service_drivers import ipsec as ipsec_driver from neutron_vpnaas.tests import base from neutron_vpnaas.tests.unit.db.vpn import test_vpn_db as test_db_vpnaas FAKE_HOST = test_agent_ext_plugin.L3_HOSTA VPN_DRIVER_CLASS = 'neutron_vpnaas.services.vpn.plugin.VPNDriverPlugin' IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.services.vpn.service_drivers.' 'ipsec.IPsecVPNDriver') DUMMY_IPSEC_SERVICE_DRIVER = ('neutron_vpnaas.tests.unit.dummy_ipsec.' 'DummyIPsecVPNDriver') _uuid = uuidutils.generate_uuid class TestVPNDriverPlugin(test_db_vpnaas.TestVpnaas, test_agentschedulers_db.AgentSchedulerTestMixIn, test_agent_ext_plugin.AgentDBTestMixIn): def setUp(self): driver_cls_p = mock.patch( 'neutron_vpnaas.services.vpn.' 'service_drivers.ipsec.IPsecVPNDriver') driver_cls = driver_cls_p.start() self.driver = mock.Mock() self.driver.service_type = ipsec_driver.IPSEC self.driver.validator = driver_validator.VpnDriverValidator( self.driver) driver_cls.return_value = self.driver super(TestVPNDriverPlugin, self).setUp( vpnaas_plugin=VPN_DRIVER_CLASS) # Note: Context must be created after BaseTestCase.setUp() so that # config for policy is set. self.adminContext = context.get_admin_context() def test_create_ipsec_site_connection(self, **extras): super(TestVPNDriverPlugin, self).test_create_ipsec_site_connection() self.driver.create_ipsec_site_connection.assert_called_once_with( mock.ANY, mock.ANY) self.driver.delete_ipsec_site_connection.assert_called_once_with( mock.ANY, mock.ANY) def test_create_vpnservice(self): mock.patch('neutron_vpnaas.services.vpn.plugin.' 'VPNDriverPlugin._get_driver_for_vpnservice', return_value=self.driver).start() stm = directory.get_plugin(p_constants.VPN).service_type_manager stm.add_resource_association = mock.Mock() super(TestVPNDriverPlugin, self).test_create_vpnservice() self.driver.create_vpnservice.assert_called_once_with( mock.ANY, mock.ANY) stm.add_resource_association.assert_called_once_with( mock.ANY, p_constants.VPN, 'vpnaas', mock.ANY) def test_delete_vpnservice(self, **extras): stm = directory.get_plugin(p_constants.VPN).service_type_manager stm.del_resource_associations = mock.Mock() super(TestVPNDriverPlugin, self).test_delete_vpnservice() self.driver.delete_vpnservice.assert_called_once_with( mock.ANY, mock.ANY) stm.del_resource_associations.assert_called_once_with( mock.ANY, [mock.ANY]) def test_update_vpnservice(self, **extras): super(TestVPNDriverPlugin, self).test_update_vpnservice() self.driver.update_vpnservice.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY) @contextlib.contextmanager def vpnservice_set(self): """Test case to create a ipsec_site_connection.""" vpnservice_name = "vpn1" ipsec_site_connection_name = "ipsec_site_connection" ikename = "ikepolicy1" ipsecname = "ipsecpolicy1" description = "my-vpn-connection" keys = {'name': vpnservice_name, 'description': "my-vpn-connection", 'peer_address': '192.168.1.10', 'peer_id': '192.168.1.10', 'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'], 'initiator': 'bi-directional', 'mtu': 1500, 'dpd_action': 'hold', 'dpd_interval': 40, 'dpd_timeout': 120, 'tenant_id': self._tenant_id, 'psk': 'abcd', 'status': 'PENDING_CREATE', 'admin_state_up': True} with self.ikepolicy(name=ikename) as ikepolicy: with self.ipsecpolicy(name=ipsecname) as ipsecpolicy: with self.subnet() as subnet: with self.router() as router: plugin = directory.get_plugin() agent = {'host': FAKE_HOST, 'agent_type': lib_constants.AGENT_TYPE_L3, 'binary': 'fake-binary', 'topic': 'fake-topic'} plugin.create_or_update_agent(self.adminContext, agent) plugin.schedule_router( self.adminContext, router['router']['id']) with self.vpnservice(name=vpnservice_name, subnet=subnet, router=router) as vpnservice1: keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id'] keys['ipsecpolicy_id'] = ( ipsecpolicy['ipsecpolicy']['id'] ) keys['vpnservice_id'] = ( vpnservice1['vpnservice']['id'] ) with self.ipsec_site_connection( self.fmt, ipsec_site_connection_name, keys['peer_address'], keys['peer_id'], keys['peer_cidrs'], keys['mtu'], keys['psk'], keys['initiator'], keys['dpd_action'], keys['dpd_interval'], keys['dpd_timeout'], vpnservice1, ikepolicy, ipsecpolicy, keys['admin_state_up'], description=description, ): yield vpnservice1['vpnservice'] def test_update_status(self): with self.vpnservice_set() as vpnservice: self._register_agent_states() service_plugin = directory.get_plugin(p_constants.VPN) service_plugin.update_status_by_agent( self.adminContext, [{'status': 'ACTIVE', 'ipsec_site_connections': {}, 'updated_pending_status': True, 'id': vpnservice['id']}]) vpnservice = service_plugin.get_vpnservice( self.adminContext, vpnservice['id']) self.assertEqual(lib_constants.ACTIVE, vpnservice['status']) class TestVPNDriverPluginMultipleDrivers(base.BaseTestCase): def setUp(self): super(TestVPNDriverPluginMultipleDrivers, self).setUp() vpnaas_providers = [ {'service_type': p_constants.VPN, 'name': 'ipsec', 'driver': IPSEC_SERVICE_DRIVER, 'default': True}, {'service_type': p_constants.VPN, 'name': 'dummy', 'driver': DUMMY_IPSEC_SERVICE_DRIVER, 'default': False}] self.service_providers = ( mock.patch.object(st_db.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = vpnaas_providers self.adminContext = context.get_admin_context() @contextlib.contextmanager def vpnservices_providers_set(self, vpnservices=None, provider_names=None): if not vpnservices: vpnservices = [] if not provider_names: provider_names = {} stm = st_db.ServiceTypeManager() stm.get_provider_names_by_resource_ids = mock.Mock( return_value=provider_names) mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=stm).start() mock.patch('neutron_vpnaas.db.vpn.vpn_db.VPNPluginDb.get_vpnservices', return_value=vpnservices).start() yield stm def test_multiple_drivers_loaded(self): with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual(2, len(driver_plugin.drivers)) self.assertEqual('ipsec', driver_plugin.default_provider) self.assertIn('ipsec', driver_plugin.drivers) self.assertEqual('ipsec', driver_plugin.drivers['ipsec'].name) self.assertIn('dummy', driver_plugin.drivers) self.assertEqual('dummy', driver_plugin.drivers['dummy'].name) def test_provider_lost(self): LOST_SERVICE_ID = _uuid() LOST_PROVIDER_SERVICE = {'id': LOST_SERVICE_ID} with self.vpnservices_providers_set( vpnservices=[LOST_PROVIDER_SERVICE], provider_names={LOST_SERVICE_ID: 'LOST_PROVIDER'} ): self.assertRaises(SystemExit, vpn_plugin.VPNDriverPlugin) def test_unasso_vpnservices(self): UNASSO_SERVICE_ID = _uuid() with self.vpnservices_providers_set( vpnservices=[{'id': UNASSO_SERVICE_ID}] ) as stm: stm.add_resource_association = mock.Mock() vpn_plugin.VPNDriverPlugin() stm.add_resource_association.assert_called_once_with( mock.ANY, p_constants.VPN, 'ipsec', UNASSO_SERVICE_ID) def test_get_driver_for_vpnservice(self): DUMMY_VPNSERVICE_ID = _uuid() DUMMY_VPNSERVICE = {'id': DUMMY_VPNSERVICE_ID} provider_names = {DUMMY_VPNSERVICE_ID: 'dummy'} with self.vpnservices_providers_set(provider_names=provider_names): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual( driver_plugin.drivers['dummy'], driver_plugin._get_driver_for_vpnservice( self.adminContext, DUMMY_VPNSERVICE)) def test_get_driver_for_ipsec_site_connection(self): IPSEC_VPNSERVICE_ID = _uuid() IPSEC_SITE_CONNECTION = {'vpnservice_id': IPSEC_VPNSERVICE_ID} provider_names = {IPSEC_VPNSERVICE_ID: 'ipsec'} with self.vpnservices_providers_set(provider_names=provider_names): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual( driver_plugin.drivers['ipsec'], driver_plugin._get_driver_for_ipsec_site_connection( self.adminContext, IPSEC_SITE_CONNECTION)) def test_get_provider_for_none_flavor_id(self): with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() provider = driver_plugin._get_provider_for_flavor( self.adminContext, None) self.assertEqual( driver_plugin.default_provider, provider) def test_get_provider_for_flavor_id_plugin_not_loaded(self): with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( vpn_flavors.FlavorsPluginNotLoaded, driver_plugin._get_provider_for_flavor, self.adminContext, _uuid()) def test_get_provider_for_flavor_id_invalid_type(self): FAKE_FLAVOR = {'service_type': 'NOT_VPN'} directory.add_plugin(p_constants.FLAVORS, flavors_plugin.FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( lib_exc.InvalidServiceType, driver_plugin._get_provider_for_flavor, self.adminContext, _uuid()) def test_get_provider_for_flavor_id_flavor_disabled(self): FAKE_FLAVOR = {'service_type': p_constants.VPN, 'enabled': False} directory.add_plugin(p_constants.FLAVORS, flavors_plugin.FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( flav_exc.FlavorDisabled, driver_plugin._get_provider_for_flavor, self.adminContext, _uuid()) def test_get_provider_for_flavor_id_provider_not_found(self): FLAVOR_ID = _uuid() FAKE_FLAVOR = {'id': FLAVOR_ID, 'service_type': p_constants.VPN, 'enabled': True} PROVIDERS = [{'provider': 'SOME_PROVIDER'}] directory.add_plugin(p_constants.FLAVORS, flavors_plugin.FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() mock.patch( 'neutron.services.flavors.flavors_plugin.' 'FlavorsPlugin.get_flavor_next_provider', return_value=PROVIDERS).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertRaises( vpn_flavors.NoProviderFoundForFlavor, driver_plugin._get_provider_for_flavor, self.adminContext, FLAVOR_ID) def test_get_provider_for_flavor_id(self): FLAVOR_ID = _uuid() FAKE_FLAVOR = {'id': FLAVOR_ID, 'service_type': p_constants.VPN, 'enabled': True} PROVIDERS = [{'provider': 'dummy'}] directory.add_plugin(p_constants.FLAVORS, flavors_plugin.FlavorsPlugin()) mock.patch( 'neutron.services.flavors.flavors_plugin.FlavorsPlugin.get_flavor', return_value=FAKE_FLAVOR).start() mock.patch( 'neutron.services.flavors.flavors_plugin.' 'FlavorsPlugin.get_flavor_next_provider', return_value=PROVIDERS).start() with self.vpnservices_providers_set(): driver_plugin = vpn_plugin.VPNDriverPlugin() self.assertEqual( 'dummy', driver_plugin._get_provider_for_flavor( self.adminContext, FLAVOR_ID)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py0000664000175000017500000000671400000000000030356 0ustar00zuulzuul00000000000000# Copyright 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. from unittest import mock from neutron_lib.callbacks import registry from neutron_lib.exceptions import vpn as vpn_exception from oslo_config import cfg from oslo_utils import uuidutils from neutron_vpnaas.services.vpn import agent as vpn_agent from neutron_vpnaas.services.vpn import device_drivers from neutron_vpnaas.services.vpn import vpn_service from neutron_vpnaas.tests import base _uuid = uuidutils.generate_uuid VPNAAS_NOP_DEVICE = ('neutron_vpnaas.tests.unit.services.' 'vpn.test_vpn_service.NoopDeviceDriver') VPNAAS_DEFAULT_DEVICE = ('neutron_vpnaas.services.vpn.' 'device_drivers.ipsec.OpenSwanDriver') FAKE_ROUTER_ID = _uuid() class NoopDeviceDriver(device_drivers.DeviceDriver): def sync(self, context, processes): pass def create_router(self, router_info): pass def destroy_router(self, process_id): pass class VPNBaseTestCase(base.BaseTestCase): def setUp(self): super(VPNBaseTestCase, self).setUp() self.conf = cfg.CONF self.ri_kwargs = {'router': {'id': FAKE_ROUTER_ID, 'ha': False}, 'agent_conf': self.conf, 'interface_driver': mock.sentinel.interface_driver} class TestVirtualPrivateNetworkDeviceDriverLoading(VPNBaseTestCase): def setUp(self): super(TestVirtualPrivateNetworkDeviceDriverLoading, self).setUp() cfg.CONF.register_opts(vpn_agent.vpn_agent_opts, 'vpnagent') self.agent = mock.Mock() self.agent.conf = cfg.CONF mock.patch.object(registry, 'subscribe').start() self.service = vpn_service.VPNService(self.agent) def test_loading_vpn_device_drivers(self): """Get two device drivers (in a list) for VPNaaS.""" cfg.CONF.set_override('vpn_device_driver', [VPNAAS_NOP_DEVICE, VPNAAS_NOP_DEVICE], 'vpnagent') drivers = self.service.load_device_drivers('host') self.assertEqual(2, len(drivers)) self.assertIn(drivers[0].__class__.__name__, VPNAAS_NOP_DEVICE) self.assertIn(drivers[1].__class__.__name__, VPNAAS_NOP_DEVICE) def test_use_default_for_vpn_device_driver(self): """When no VPNaaS device drivers specified, we get the default.""" drivers = self.service.load_device_drivers('host') self.assertEqual(1, len(drivers)) self.assertIn(drivers[0].__class__.__name__, VPNAAS_DEFAULT_DEVICE) def test_fail_no_such_vpn_device_driver(self): """Failure test of import error for VPNaaS device driver.""" cfg.CONF.set_override('vpn_device_driver', ['no.such.class'], 'vpnagent') self.assertRaises(vpn_exception.DeviceDriverImportError, self.service.load_device_drivers, 'host') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/neutron_vpnaas/version.py0000664000175000017500000000126500000000000021706 0ustar00zuulzuul00000000000000# Copyright 2011 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. import pbr.version version_info = pbr.version.VersionInfo('neutron-vpnaas') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8953495 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/0000775000175000017500000000000000000000000021335 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/PKG-INFO0000664000175000017500000000374300000000000022441 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: neutron-vpnaas Version: 24.0.1 Summary: OpenStack Networking VPN as a Service Home-page: https://docs.openstack.org/neutron-vpnaas/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: 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 opendev.org at: https://opendev.org/openstack/neutron-vpnaas. Please refer to Neutron documentation for more information: `Neutron README.rst `_ Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/neutron-vpnaas.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/SOURCES.txt0000664000175000017500000002750300000000000023230 0ustar00zuulzuul00000000000000.coveragerc .mailmap .pylintrc .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst TESTING.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini devstack/README.md devstack/local.conf.sample devstack/local_AIO.conf.sample devstack/ovn-local.conf.sample devstack/plugin.sh devstack/settings devstack/lib/l3_agent devstack/upgrade/resources.sh devstack/upgrade/settings devstack/upgrade/shutdown.sh devstack/upgrade/upgrade.sh doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/_static/.placeholder doc/source/admin/index.rst doc/source/configuration/index.rst doc/source/configuration/l3_agent.rst doc/source/configuration/neutron_ovn_vpn_agent.rst doc/source/configuration/neutron_vpnaas.rst doc/source/configuration/policy-sample.rst doc/source/configuration/policy.rst doc/source/configuration/samples/neutron_vpnaas.rst doc/source/configuration/samples/vpn_agent.rst doc/source/contributor/devstack.rst doc/source/contributor/index.rst doc/source/contributor/multiple-local-subnets.rst doc/source/contributor/team.rst doc/source/contributor/testing-with-devstack.rst doc/source/contributor/vpnaas-rally-test.rst doc/source/contributor/vpnaas-tempest-test.rst doc/source/install/index.rst doc/source/user/index.rst etc/README.txt etc/neutron/rootwrap.d/vpnaas.filters etc/oslo-config-generator/neutron_ovn_vpn_agent.ini etc/oslo-config-generator/neutron_vpnaas.conf etc/oslo-config-generator/vpn_agent.ini etc/oslo-policy-generator/policy.conf neutron_vpnaas/__init__.py neutron_vpnaas/_i18n.py neutron_vpnaas/opts.py neutron_vpnaas/version.py neutron_vpnaas.egg-info/PKG-INFO neutron_vpnaas.egg-info/SOURCES.txt neutron_vpnaas.egg-info/dependency_links.txt neutron_vpnaas.egg-info/entry_points.txt neutron_vpnaas.egg-info/not-zip-safe neutron_vpnaas.egg-info/pbr.json neutron_vpnaas.egg-info/requires.txt neutron_vpnaas.egg-info/top_level.txt neutron_vpnaas/agent/__init__.py neutron_vpnaas/agent/ovn/__init__.py neutron_vpnaas/agent/ovn/vpn/__init__.py neutron_vpnaas/agent/ovn/vpn/agent.py neutron_vpnaas/agent/ovn/vpn/ovsdb.py neutron_vpnaas/api/rpc/agentnotifiers/vpn_rpc_agent_api.py neutron_vpnaas/cmd/eventlet/__init__.py neutron_vpnaas/cmd/eventlet/ovn_agent.py neutron_vpnaas/db/__init__.py neutron_vpnaas/db/migration/__init__.py neutron_vpnaas/db/migration/alembic_migrations/README neutron_vpnaas/db/migration/alembic_migrations/__init__.py neutron_vpnaas/db/migration/alembic_migrations/env.py neutron_vpnaas/db/migration/alembic_migrations/script.py.mako neutron_vpnaas/db/migration/alembic_migrations/versions/3ea02b2a773e_add_index_tenant_id.py neutron_vpnaas/db/migration/alembic_migrations/versions/CONTRACT_HEAD neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD neutron_vpnaas/db/migration/alembic_migrations/versions/kilo_release.py neutron_vpnaas/db/migration/alembic_migrations/versions/start_neutron_vpnaas.py neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/expand/22e0145ac80b_add_vpn_gateway_port.py neutron_vpnaas/db/migration/alembic_migrations/versions/2023.2/expand/3b739d6906cf_vpn_scheduler.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/2c82e782d734_drop_tenant_id_in_cisco_csr_identifier_.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/333dfd6afaa2_populate_vpn_service_table_fields.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/contract/56893333aa52_fix_identifier_map_fk.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/24f28869838b_add_fields_to_vpn_service_table.py neutron_vpnaas/db/migration/alembic_migrations/versions/liberty/expand/30018084ed99_initial.py neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/contract/2cb4ee992b41_multiple_local_subnets.py neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/28ee739a7e4b_multiple_local_subnets.py neutron_vpnaas/db/migration/alembic_migrations/versions/mitaka/expand/41b509d10b5e_vpnaas_endpoint_groups.py neutron_vpnaas/db/migration/alembic_migrations/versions/newton/contract/b6a2519ab7dc_rename_tenant_to_project.py neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/52783a36bd67_support_local_id.py neutron_vpnaas/db/migration/alembic_migrations/versions/newton/expand/fe637dc3f042_support_sha256.py neutron_vpnaas/db/migration/alembic_migrations/versions/ocata/expand/38893903cbde_add_auth_algorithm_sha384_and_sha512.py neutron_vpnaas/db/migration/alembic_migrations/versions/pike/expand/95601446dbcc_add_flavor_id_to_vpnservices.py neutron_vpnaas/db/migration/alembic_migrations/versions/rocky/contract/e50641731f1a_drop_cisco_csr_identifier_map_table.py neutron_vpnaas/db/migration/alembic_migrations/versions/victoria/expand/5f884db48ba9_add_aggressive_negotiation_modes.py neutron_vpnaas/db/models/__init__.py neutron_vpnaas/db/models/head.py neutron_vpnaas/db/vpn/__init__.py neutron_vpnaas/db/vpn/vpn_agentschedulers_db.py neutron_vpnaas/db/vpn/vpn_db.py neutron_vpnaas/db/vpn/vpn_ext_gw_db.py neutron_vpnaas/db/vpn/vpn_models.py neutron_vpnaas/db/vpn/vpn_validator.py neutron_vpnaas/extensions/__init__.py neutron_vpnaas/extensions/vpn_agentschedulers.py neutron_vpnaas/extensions/vpn_endpoint_groups.py neutron_vpnaas/extensions/vpn_flavors.py neutron_vpnaas/extensions/vpnaas.py neutron_vpnaas/policies/__init__.py neutron_vpnaas/policies/endpoint_group.py neutron_vpnaas/policies/ike_policy.py neutron_vpnaas/policies/ipsec_policy.py neutron_vpnaas/policies/ipsec_site_connection.py neutron_vpnaas/policies/vpnservice.py neutron_vpnaas/scheduler/vpn_agent_scheduler.py neutron_vpnaas/services/__init__.py neutron_vpnaas/services/vpn/__init__.py neutron_vpnaas/services/vpn/agent.py neutron_vpnaas/services/vpn/ovn_agent.py neutron_vpnaas/services/vpn/ovn_plugin.py neutron_vpnaas/services/vpn/plugin.py neutron_vpnaas/services/vpn/vpn_service.py neutron_vpnaas/services/vpn/common/__init__.py neutron_vpnaas/services/vpn/common/constants.py neutron_vpnaas/services/vpn/common/netns_wrapper.py neutron_vpnaas/services/vpn/common/topics.py neutron_vpnaas/services/vpn/device_drivers/__init__.py neutron_vpnaas/services/vpn/device_drivers/ipsec.py neutron_vpnaas/services/vpn/device_drivers/libreswan_ipsec.py neutron_vpnaas/services/vpn/device_drivers/ovn_ipsec.py neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.secret.template neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.template neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.secret.template neutron_vpnaas/services/vpn/device_drivers/template/strongswan/strongswan.conf.template neutron_vpnaas/services/vpn/ovn/__init__.py neutron_vpnaas/services/vpn/ovn/agent_monitor.py neutron_vpnaas/services/vpn/service_drivers/__init__.py neutron_vpnaas/services/vpn/service_drivers/base_ipsec.py neutron_vpnaas/services/vpn/service_drivers/driver_validator.py neutron_vpnaas/services/vpn/service_drivers/ipsec.py neutron_vpnaas/services/vpn/service_drivers/ipsec_validator.py neutron_vpnaas/services/vpn/service_drivers/ovn_ipsec.py neutron_vpnaas/tests/__init__.py neutron_vpnaas/tests/base.py neutron_vpnaas/tests/contrib/README neutron_vpnaas/tests/contrib/filters.template neutron_vpnaas/tests/contrib/functional-test-rootwrap.conf neutron_vpnaas/tests/contrib/functional-testing.filters neutron_vpnaas/tests/contrib/gate_hook.sh neutron_vpnaas/tests/contrib/post_test_hook.sh neutron_vpnaas/tests/functional/__init__.py neutron_vpnaas/tests/functional/requirements.txt neutron_vpnaas/tests/functional/common/README neutron_vpnaas/tests/functional/common/__init__.py neutron_vpnaas/tests/functional/common/ovn_base.py neutron_vpnaas/tests/functional/common/test_migrations_sync.py neutron_vpnaas/tests/functional/common/test_scenario.py neutron_vpnaas/tests/functional/openswan/README neutron_vpnaas/tests/functional/openswan/__init__.py neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py neutron_vpnaas/tests/functional/openswan/test_ovn_openswan.py neutron_vpnaas/tests/functional/strongswan/README neutron_vpnaas/tests/functional/strongswan/__init__.py neutron_vpnaas/tests/functional/strongswan/test_netns_wrapper.py neutron_vpnaas/tests/functional/strongswan/test_ovn_strongswan.py neutron_vpnaas/tests/functional/strongswan/test_strongswan_driver.py neutron_vpnaas/tests/unit/__init__.py neutron_vpnaas/tests/unit/dummy_ipsec.py neutron_vpnaas/tests/unit/db/__init__.py neutron_vpnaas/tests/unit/db/vpn/__init__.py neutron_vpnaas/tests/unit/db/vpn/test_vpn_agentschedulers_db.py neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py neutron_vpnaas/tests/unit/db/vpn/test_vpn_ext_gw_db.py neutron_vpnaas/tests/unit/db/vpn/test_vpn_validator.py neutron_vpnaas/tests/unit/extensions/__init__.py neutron_vpnaas/tests/unit/extensions/test_vpn_endpoint_groups.py neutron_vpnaas/tests/unit/extensions/test_vpnaas.py neutron_vpnaas/tests/unit/services/__init__.py neutron_vpnaas/tests/unit/services/vpn/__init__.py neutron_vpnaas/tests/unit/services/vpn/test_plugin.py neutron_vpnaas/tests/unit/services/vpn/test_vpn_service.py neutron_vpnaas/tests/unit/services/vpn/common/__init__.py neutron_vpnaas/tests/unit/services/vpn/common/test_netns_wrapper.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/__init__.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ovn_ipsec.py neutron_vpnaas/tests/unit/services/vpn/service_drivers/__init__.py neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ipsec.py neutron_vpnaas/tests/unit/services/vpn/service_drivers/test_ovn_ipsec.py playbooks/configure_functional_job.yaml rally-jobs/__init__.py rally-jobs/plugins/__init__.py rally-jobs/plugins/test_vpn_connectivity.py rally-jobs/plugins/test_vpn_status.py rally-jobs/plugins/test_vpn_tenant_scenario.py rally-jobs/plugins/vpn_base.py rally-jobs/plugins/vpn_utils.py rally-jobs/rally-configs/args_template.json rally-jobs/rally-configs/rally_config_dvr.yaml rally-jobs/rally-configs/rally_config_non_dvr.yaml releasenotes/notes/.placeholder releasenotes/notes/Enable-sha384-and-sha512-auth-algorithms-for-Swan-drivers-9897b96f90737a20.yaml releasenotes/notes/add-aggressive-negotiation-mode-3d8fbcd843d003b7.yaml releasenotes/notes/config-file-generation-0dcf19f5d8baaf5d.yaml releasenotes/notes/drivers-removal-944ce5e75d55b449.yaml releasenotes/notes/drop-py27-support-d83cd4daa0e37383.yaml releasenotes/notes/drop-python-3-6-and-3-7-a4a72c6c8775e0b7.yaml releasenotes/notes/flavor-framework-integration-f68d28bd35ce2643.yaml releasenotes/notes/libreswan-driver-works-with-3.19+-7e1fc79ac6c7efe5.yaml releasenotes/notes/oslo-reports-3059c2e10e1b35b5.yaml releasenotes/notes/vpnaas-for-ovn-a487c62b877e3201.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po roles/configure_functional_tests/README.rst roles/configure_functional_tests/defaults/main.yaml roles/configure_functional_tests/tasks/main.yaml tools/check_i18n.py tools/check_i18n_test_case.txt tools/check_unit_test_structure.sh tools/clean.sh tools/configure_for_vpn_func_testing.sh tools/deploy_rootwrap.sh tools/generate_config_file_samples.sh tools/i18n_cfg.py tools/test_script.sh tools/with_venv.sh././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/dependency_links.txt0000664000175000017500000000000100000000000025403 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/entry_points.txt0000664000175000017500000000210100000000000024625 0ustar00zuulzuul00000000000000[console_scripts] neutron-ovn-vpn-agent = neutron_vpnaas.cmd.eventlet.ovn_agent:main neutron-vpn-netns-wrapper = neutron_vpnaas.services.vpn.common.netns_wrapper:main [device_drivers] neutron.services.vpn.device_drivers.ipsec.OpenSwanDriver = neutron_vpnaas.services.vpn.device_drivers.ipsec:OpenSwanDriver [neutron.agent.l3.extensions] vpnaas = neutron_vpnaas.services.vpn.agent:L3WithVPNaaS [neutron.db.alembic_migrations] neutron-vpnaas = neutron_vpnaas.db.migration:alembic_migrations [neutron.policies] neutron-vpnaas = neutron_vpnaas.policies:list_rules [neutron.service_plugins] neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin ovn-vpnaas = neutron_vpnaas.services.vpn.ovn_plugin:VPNOVNDriverPlugin vpnaas = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin [oslo.config.opts] neutron.vpnaas = neutron_vpnaas.opts:list_opts neutron.vpnaas.agent = neutron_vpnaas.opts:list_agent_opts neutron.vpnaas.ovn_agent = neutron_vpnaas.opts:list_ovn_agent_opts [oslo.policy.policies] neutron-vpnaas = neutron_vpnaas.policies:list_rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/not-zip-safe0000664000175000017500000000000100000000000023563 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/pbr.json0000664000175000017500000000006000000000000023007 0ustar00zuulzuul00000000000000{"git_version": "297855d0c", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/requires.txt0000664000175000017500000000044500000000000023740 0ustar00zuulzuul00000000000000Jinja2>=2.10 SQLAlchemy>=1.3.0 alembic>=1.6.5 netaddr>=0.7.18 neutron-lib>=2.6.0 neutron>=17.0.0 oslo.concurrency>=3.26.0 oslo.config>=8.0.0 oslo.db>=4.44.0 oslo.log>=4.5.0 oslo.messaging>=7.0.0 oslo.reports>=1.18.0 oslo.serialization>=2.25.0 oslo.service>=1.31.0 oslo.utils>=4.5.0 pbr>=4.0.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152350.0 neutron-vpnaas-24.0.1/neutron_vpnaas.egg-info/top_level.txt0000664000175000017500000000001700000000000024065 0ustar00zuulzuul00000000000000neutron_vpnaas ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9153502 neutron-vpnaas-24.0.1/playbooks/0000775000175000017500000000000000000000000016604 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/playbooks/configure_functional_job.yaml0000664000175000017500000000011200000000000024517 0ustar00zuulzuul00000000000000- hosts: all roles: - setup_logdir - configure_functional_tests ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9153502 neutron-vpnaas-24.0.1/rally-jobs/0000775000175000017500000000000000000000000016657 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/__init__.py0000664000175000017500000000000000000000000020756 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9153502 neutron-vpnaas-24.0.1/rally-jobs/plugins/0000775000175000017500000000000000000000000020340 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/plugins/__init__.py0000664000175000017500000000000000000000000022437 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/plugins/test_vpn_connectivity.py0000664000175000017500000000557100000000000025362 0ustar00zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 rally.common import logging from rally.task import scenario from rally.task import types import vpn_base LOG = logging.getLogger(__name__) class TestVpnBasicScenario(vpn_base.VpnBase): """Rally scenarios for VPNaaS""" @types.convert(image={"type": "glance_image"}, flavor={"type": "nova_flavor"}) @scenario.configure() def create_and_delete_vpn_connection(self, **kwargs): """Basic VPN connectivity scenario. 1. Create 2 private networks, subnets and routers 2. Create public network, subnets and GW IPs on routers, if not present 3. Execute ip netns command and get the snat and qrouter namespaces (assuming we use DVR) 4. Verify that there is a route between the router gateways by pinging each other from their snat namespaces 5. Add security group rules for SSH and ICMP 6. Start a nova instance in each of the private networks 7. Create IKE and IPSEC policies 8. Create VPN service at each of the routers 9. Create IPSEC site connections at both endpoints 10. Verify that the ipsec-site-connection is ACTIVE (takes upto 30secs) 11. To verify the vpn connectivity, get into the peer router's snat namespace and start a tcpdump at the qg-xxxx interface 12. SSH into the nova instance from the local qrouter namespace and try to ping the nova instance on the peer network. 14. Verify that the captured packets are encapsulated and encrypted. 15. Verify the connectivity in the reverse direction following the steps 11 through 13 16. Submit a request to delete all the resources """ try: self.setup(**kwargs) self.create_networks(**kwargs) self.create_servers(**kwargs) self.check_route() self.ike_policy = self._create_ike_policy(**kwargs) self.ipsec_policy = self._create_ipsec_policy(**kwargs) self.create_vpn_services() self.create_ipsec_site_connections(**kwargs) self.assert_statuses(final_status='ACTIVE', **kwargs) self.verify_vpn_connectivity(**kwargs) LOG.info("VPN CONNECTIVITY TEST PASSED!") finally: self.cleanup() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/plugins/test_vpn_status.py0000664000175000017500000000564600000000000024172 0ustar00zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 rally.common import logging from rally.task import scenario from rally.task import types import vpn_base LOG = logging.getLogger(__name__) class TestVpnStatusScenario(vpn_base.VpnBase): @types.convert(image={"type": "glance_image"}, flavor={"type": "nova_flavor"}) @scenario.configure() def check_vpn_status(self, **kwargs): """Test VPN's status correctly after bringing router's status to DOWN and back to ACTIVE state 1. Create 2 private networks, subnets and routers 2. Create public network, subnets and GW IPs on routers, if not present 3. Execute ip netns command and get the snat and qrouter namespaces (assuming we use DVR) 4. Verify that there is a route between the router gateways by pinging each other from their snat namespaces 5. Add security group rules for SSH and ICMP 6. Start a nova instance in each of the private networks 7. Create IKE and IPSEC policies 8. Create VPN service at each of the routers 9. Create IPSEC site connections at both endpoints 10. Bring both the private router's status to DOWN state 11. Verify that vpn-service and ipsec-site-connection is DOWN 12. Bring back the router's status to ACTIVE state 13. Verify the vpn-service and ipsec-site-connection is back to ACTIVE 14. Perform resource cleanup """ try: self.setup(**kwargs) self.create_networks(**kwargs) self.check_route() self.ike_policy = self._create_ike_policy(**kwargs) self.ipsec_policy = self._create_ipsec_policy(**kwargs) self.create_vpn_services() self.create_ipsec_site_connections(**kwargs) self.assert_statuses(final_status='ACTIVE', **kwargs) self.update_router(self.router_ids[0], admin_state_up=False) self.update_router(self.router_ids[1], admin_state_up=False) self.assert_statuses(final_status='DOWN', **kwargs) self.update_router(self.router_ids[0], admin_state_up=True) self.update_router(self.router_ids[1], admin_state_up=True) self.assert_statuses(final_status='ACTIVE', **kwargs) LOG.info("VPN STATUS TEST PASSED!") finally: self.cleanup() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/plugins/test_vpn_tenant_scenario.py0000664000175000017500000000461100000000000026012 0ustar00zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 rally.common import logging from rally.task import scenario from rally.task import types import vpn_base LOG = logging.getLogger(__name__) class TestVpnTenantScenario(vpn_base.VpnBase): """Rally scenarios for VPNaaS""" @types.convert(image={"type": "glance_image"}, flavor={"type": "nova_flavor"}) @scenario.configure() def multitenants_vpn_test(self, **kwargs): """Test VPN connectivity under two different tenants. 1. Create 2 private networks with 2 different tenants, subnets, routers 2. Create public network, subnets and GW IPs on routers, if not present 3. Execute ip netns command and get the snat and qrouter namespaces (assuming we use DVR) 4. Verify that there is a route between the router gateways by pinging each other from their snat namespaces 5. Add security group rules for SSH and ICMP 6. Start a nova instance in each of the private networks 7. Create IKE and IPSEC policies 8. Create VPN service at each of the routers 9. Create IPSEC site connections at both endpoints 10. Verify that the vpn-service and ipsec-site-connection are ACTIVE 11. Cleanup the resources that are setup for this test """ try: self.setup(**kwargs) self.create_tenants() self.create_networks(**kwargs) self.check_route() self.ike_policy = self._create_ike_policy(**kwargs) self.ipsec_policy = self._create_ipsec_policy(**kwargs) self.create_vpn_services() self.create_ipsec_site_connections(**kwargs) self.assert_statuses(final_status='ACTIVE', **kwargs) LOG.info("VPN TENANT TEST PASSED!") finally: self.cleanup() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/plugins/vpn_base.py0000664000175000017500000005223500000000000022516 0ustar00zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 concurrent.futures import re import threading import time from oslo_utils import uuidutils from rally.common import logging from rally.plugins.openstack import scenario as rally_base from rally.task import atomic import vpn_utils LOG = logging.getLogger(__name__) LOCK = threading.RLock() MAX_RESOURCES = 2 class VpnBase(rally_base.OpenStackScenario): def setup(self, **kwargs): """Create and initialize data structures to hold various resources""" with LOCK: LOG.debug('SETUP RESOURCES') self.neutron_admin_client = self.admin_clients("neutron") if kwargs['use_admin_client']: self.neutron_client = self.neutron_admin_client self.keystone_client = self.admin_clients("keystone") self.nova_client = self.admin_clients("nova") else: self.neutron_client = self.clients("neutron") self.nova_client = self.clients("nova") self.suffixes = [uuidutils.generate_uuid(), uuidutils.generate_uuid()] self.remote_key_files = ['rally_keypair_' + x for x in self.suffixes] self.local_key_files = ['/tmp/' + x for x in self.remote_key_files] self.private_key_file = kwargs["private_key"] self.keypairs = [] self.tenant_ids = [] self.ns_controller_tuples = [] self.qrouterns_compute_tuples = [] self.router_ids = [] self.rally_router_gw_ips = [] self.rally_routers = [] self.rally_networks = [] self.rally_subnets = [] self.rally_cidrs = [] self.ike_policy = None self.ipsec_policy = None self.vpn_services = [] self.ipsec_site_connections = [] self.servers = [] self.server_private_ips = [] self.server_fips = [] def create_tenants(self): """Create tenants""" for x in range(MAX_RESOURCES): tenant_id = vpn_utils.create_tenant( self.keystone_client, self.suffixes[x]) with LOCK: self.tenant_ids.append(tenant_id) def create_networks(self, **kwargs): """Create networks to test vpn connectivity""" for x in range(MAX_RESOURCES): if self.tenant_ids: router, network, subnet, cidr = vpn_utils.create_network( self.neutron_client, self.neutron_admin_client, self.suffixes[x], tenant_id=self.tenant_ids[x], DVR_flag=kwargs["DVR_flag"], ext_net_name=kwargs["ext-net"]) else: router, network, subnet, cidr = vpn_utils.create_network( self.neutron_client, self.neutron_admin_client, self.suffixes[x], DVR_flag=kwargs["DVR_flag"], ext_net_name=kwargs["ext-net"]) with LOCK: self.rally_cidrs.append(cidr) self.rally_subnets.append(subnet) self.rally_networks.append(network) self.rally_routers.append(router) self.router_ids.append(router["router"]['id']) self.rally_router_gw_ips.append( router["router"]["external_gateway_info"] ["external_fixed_ips"][0]["ip_address"]) if(kwargs["DVR_flag"]): ns, controller = vpn_utils.wait_for_namespace_creation( "snat-", router["router"]['id'], kwargs['controller_creds'], self.private_key_file, kwargs['namespace_creation_timeout']) else: ns, controller = vpn_utils.wait_for_namespace_creation( "qrouter-", router["router"]['id'], kwargs['controller_creds'], self.private_key_file, kwargs['namespace_creation_timeout']) with LOCK: self.ns_controller_tuples.append((ns, controller)) def create_servers(self, **kwargs): """Create servers""" for x in range(MAX_RESOURCES): kwargs.update({ "nics": [{"net-id": self.rally_networks[x]["network"]["id"]}], "sec_group_suffix": self.suffixes[x], "server_suffix": self.suffixes[x] }) keypair = vpn_utils.create_keypair( self.nova_client, self.suffixes[x]) server = vpn_utils.create_server( self.nova_client, keypair, **kwargs) vpn_utils.assert_server_status(server, **kwargs) with LOCK: self.servers.append(server) self.keypairs.append(keypair) self.server_private_ips.append(vpn_utils.get_server_ip( self.nova_client, server.id, self.suffixes[x])) if(kwargs["DVR_flag"]): qrouter, compute = vpn_utils.wait_for_namespace_creation( "qrouter-", self.router_ids[x], kwargs['compute_creds'], self.private_key_file, kwargs['namespace_creation_timeout']) vpn_utils.write_key_to_compute_node( keypair, self.local_key_files[x], self.remote_key_files[x], compute, self.private_key_file) with LOCK: self.qrouterns_compute_tuples.append((qrouter, compute)) else: vpn_utils.write_key_to_local_path(self.keypairs[x], self.local_key_files[x]) fip = vpn_utils.add_floating_ip(self.nova_client, server) with LOCK: self.server_fips.append(fip) def check_route(self): """Verify route exists between the router gateways""" LOG.debug("VERIFY ROUTE EXISTS BETWEEN THE ROUTER GATEWAYS") for tuple in self.ns_controller_tuples: for ip in self.rally_router_gw_ips: assert(vpn_utils.ping_router_gateway( tuple, ip, self.private_key_file)), ( "PING TO IP " + ip + " FAILED") @atomic.action_timer("_create_ike_policy") def _create_ike_policy(self, **kwargs): """Create IKE policy :return: IKE policy """ LOG.debug('CREATING IKE_POLICY') ike_policy = self.neutron_client.create_ikepolicy({ "ikepolicy": { "phase1_negotiation_mode": kwargs.get("phase1_negotiation_mode", "main"), "auth_algorithm": kwargs.get("auth_algorithm", "sha1"), "encryption_algorithm": kwargs.get("encryption_algorithm", "aes-128"), "pfs": kwargs.get("pfs", "group5"), "lifetime": { "units": "seconds", "value": kwargs.get("value", 7200)}, "ike_version": kwargs.get("ike_version", "v1"), "name": "rally_ikepolicy" } }) return ike_policy @atomic.action_timer("_create_ipsec_policy") def _create_ipsec_policy(self, **kwargs): """Create IPSEC policy :return: IPSEC policy """ LOG.debug('CREATING IPSEC_POLICY') ipsec_policy = self.neutron_client.create_ipsecpolicy({ "ipsecpolicy": { "name": "rally_ipsecpolicy", "transform_protocol": kwargs.get("transform_protocol", "esp"), "auth_algorithm": kwargs.get("auth_algorithm", "sha1"), "encapsulation_mode": kwargs.get("encapsulation_mode", "tunnel"), "encryption_algorithm": kwargs.get("encryption_algorithm", "aes-128"), "pfs": kwargs.get("pfs", "group5"), "lifetime": { "units": "seconds", "value": kwargs.get("value", 7200) } } }) return ipsec_policy @atomic.action_timer("_create_vpn_service") def _create_vpn_service(self, rally_subnet, rally_router, vpn_suffix=None): """Create VPN service endpoints :param rally_subnet: local subnet :param rally_router: router endpoint :param vpn_suffix: suffix name for vpn service :return: VPN service """ LOG.debug('CREATING VPN_SERVICE') vpn_service = self.neutron_client.create_vpnservice({ "vpnservice": { "subnet_id": rally_subnet["subnet"]["id"], "router_id": rally_router["router"]["id"], "name": "rally_vpn_service_" + vpn_suffix, "admin_state_up": True } }) return vpn_service def create_vpn_services(self): """Create VPN services""" for x in range(MAX_RESOURCES): vpn_service = self._create_vpn_service( self.rally_subnets[x], self.rally_routers[x], self.suffixes[x]) with LOCK: self.vpn_services.append(vpn_service) @atomic.action_timer("_create_ipsec_site_connection") def _create_ipsec_site_connection(self, local_index, peer_index, **kwargs): """Create IPSEC site connection :param local_index: parameter to point to the local end-point :param peer_index: parameter to point to the peer end-point :return: IPSEC site connection """ LOG.debug('CREATING IPSEC_SITE_CONNECTION') ipsec_site_conn = self.neutron_client.create_ipsec_site_connection({ "ipsec_site_connection": { "psk": kwargs.get("secret", "secret"), "initiator": "bi-directional", "ipsecpolicy_id": self.ipsec_policy["ipsecpolicy"]["id"], "admin_state_up": True, "peer_cidrs": self.rally_cidrs[peer_index], "mtu": kwargs.get("mtu", "1500"), "ikepolicy_id": self.ike_policy["ikepolicy"]["id"], "dpd": { "action": "disabled", "interval": 60, "timeout": 240 }, "vpnservice_id": self.vpn_services[local_index]["vpnservice"]["id"], "peer_address": self.rally_router_gw_ips[peer_index], "peer_id": self.rally_router_gw_ips[peer_index], "name": "rally_ipsec_site_connection_" + self.suffixes[local_index] } }) return ipsec_site_conn def create_ipsec_site_connections(self, **kwargs): """Create IPSEC site connections""" a = self._create_ipsec_site_connection(0, 1, **kwargs) b = self._create_ipsec_site_connection(1, 0, **kwargs) with LOCK: self.ipsec_site_connections = [a, b] def _get_resource(self, resource_tag, resource_id): """Get the resource(vpn_service or ipsec_site_connection) :param resource_tag: "vpnservice" or "ipsec_site_connection" :param resource_id: id of the resource :return: resource (vpn_service or ipsec_site_connection) """ if resource_tag == "vpnservice": vpn_service = self.neutron_client.show_vpnservice(resource_id) if vpn_service: return vpn_service elif resource_tag == 'ipsec_site_connection': ipsec_site_conn = self.neutron_client.show_ipsec_site_connection( resource_id) if ipsec_site_conn: return ipsec_site_conn def _wait_for_status_change(self, resource, resource_tag, final_status, wait_timeout=60, check_interval=1): """Wait for resource's status change Wait till the status of the resource changes to final state or till the time exceeds the wait_timeout value. :param resource: resource whose status has to be checked :param final_status: desired final status of the resource :param resource_tag: to identify the resource as vpnservice or ipsec_site_connection :param wait_timeout: timeout value in seconds :param check_interval: time to sleep before each check for the status change :return: resource """ LOG.debug('WAIT_FOR_%s_STATUS_CHANGE ', resource[resource_tag]['id']) start_time = time.time() while True: resource = self._get_resource( resource_tag, resource[resource_tag]['id']) current_status = resource[resource_tag]['status'] if current_status == final_status: return resource time.sleep(check_interval) if time.time() - start_time > wait_timeout: raise Exception( "Timeout waiting for resource {} to change to {} status". format(resource[resource_tag]['name'], final_status)) @atomic.action_timer("wait_time_for_status_change") def _assert_statuses(self, ipsec_site_conn, vpn_service, final_status, **kwargs): """Assert statuses of vpn_service and ipsec_site_connection :param ipsec_site_conn: ipsec_site_connection object :param vpn_service: vpn_service object :param final_status: status of vpn and ipsec_site_connection object """ vpn_service = self._wait_for_status_change( vpn_service, resource_tag="vpnservice", final_status=final_status, wait_timeout=kwargs.get("vpn_service_creation_timeout"), check_interval=5) ipsec_site_conn = self._wait_for_status_change( ipsec_site_conn, resource_tag="ipsec_site_connection", final_status=final_status, wait_timeout=kwargs.get("ipsec_site_connection_creation_timeout"), check_interval=5) LOG.debug("VPN SERVICE STATUS %s", vpn_service['vpnservice']['status']) LOG.debug("IPSEC_SITE_CONNECTION STATUS %s", ipsec_site_conn['ipsec_site_connection']['status']) def assert_statuses(self, final_status, **kwargs): """Assert active statuses for VPN services and VPN connections :param final_status: the final status you expect the resource to be in """ LOG.debug("ASSERTING ACTIVE STATUSES FOR VPN-SERVICES AND " "IPSEC-SITE-CONNECTIONS") for x in range(MAX_RESOURCES): self._assert_statuses( self.ipsec_site_connections[x], self.vpn_services[x], final_status, **kwargs) def _get_qg_interface(self, peer_index): """Get the qg- interface :param peer_index: parameter to point to the local end-point :return: qg-interface """ qg = vpn_utils.get_interfaces( self.ns_controller_tuples[peer_index], self.private_key_file) p = re.compile(r"qg-\w+-\w+") for line in qg: m = p.search(line) if m: return m.group() return None @atomic.action_timer("_verify_vpn_connection") def _verify_vpn_connectivity(self, local_index, peer_index, **kwargs): """Verify the vpn connectivity between the endpoints Get the qg- interface from the snat namespace corresponding to the peer router and start a tcp dump. Concurrently, SSH into the nova instance on the local subnet from the qrouter namespace and try to ping the nova instance on the peer subnet. Inspect the captured packets to see if they are encrypted. :param local_index: parameter to point to the local end-point :param peer_index: parameter to point to the peer end-point :return: True if vpn connectivity test passes False if the test fails """ qg_interface = self._get_qg_interface(peer_index) if qg_interface: with concurrent.futures.ThreadPoolExecutor(max_workers=2) as e: tcpdump_future = e.submit(vpn_utils.start_tcpdump, self.ns_controller_tuples[peer_index], qg_interface, self.private_key_file) if(kwargs["DVR_flag"]): ssh_future = e.submit( vpn_utils.ssh_and_ping_server, self.server_private_ips[local_index], self.server_private_ips[peer_index], self.qrouterns_compute_tuples[local_index], self.remote_key_files[local_index], self.private_key_file) else: ssh_future = e.submit( vpn_utils.ssh_and_ping_server_with_fip, self.server_fips[local_index], self.server_private_ips[peer_index], self.local_key_files[local_index], self.private_key_file) assert(ssh_future.result()), "SSH/Ping failed" for line in tcpdump_future.result(): if 'ESP' in line: return True return False def verify_vpn_connectivity(self, **kwargs): """Verify VPN connectivity""" LOG.debug("VERIFY THE VPN CONNECTIVITY") with LOCK: assert(self._verify_vpn_connectivity( 0, 1, **kwargs)), "VPN CONNECTION FAILED" with LOCK: assert(self._verify_vpn_connectivity( 1, 0, **kwargs)), "VPN CONNECTION FAILED" def update_router(self, router_id, admin_state_up=False): """Update router's admin_state_up field :param router_id: uuid of the router :param admin_state_up: True or False """ LOG.debug('UPDATE ROUTER') router_args = {'router': {'admin_state_up': admin_state_up}} self.neutron_client.update_router(router_id, router_args) @atomic.action_timer("_delete_ipsec_site_connection") def _delete_ipsec_site_connections(self): """Delete IPSEC site connections""" for site_conn in self.ipsec_site_connections: LOG.debug("DELETING IPSEC_SITE_CONNECTION %s", site_conn['ipsec_site_connection']['id']) self.neutron_client.delete_ipsec_site_connection( site_conn['ipsec_site_connection']['id']) @atomic.action_timer("_delete_vpn_service") def _delete_vpn_services(self): """Delete VPN service endpoints""" for vpn_service in self.vpn_services: LOG.debug("DELETING VPN_SERVICE %s", vpn_service['vpnservice']['id']) self.neutron_client.delete_vpnservice( vpn_service['vpnservice']['id']) @atomic.action_timer("_delete_ipsec_policy") def _delete_ipsec_policy(self): """Delete IPSEC policy""" LOG.debug("DELETING IPSEC POLICY") if self.ipsec_policy: self.neutron_client.delete_ipsecpolicy( self.ipsec_policy['ipsecpolicy']['id']) @atomic.action_timer("_delete_ike_policy") def _delete_ike_policy(self): """Delete IKE policy""" LOG.debug('DELETING IKE POLICY') if self.ike_policy: self.neutron_client.delete_ikepolicy( self.ike_policy['ikepolicy']['id']) @atomic.action_timer("cleanup") def cleanup(self): """Clean the resources""" vpn_utils.delete_servers(self.nova_client, self.servers) if self.server_fips: vpn_utils.delete_floating_ips(self.nova_client, self.server_fips) vpn_utils.delete_keypairs(self.nova_client, self.keypairs) if self.qrouterns_compute_tuples: vpn_utils.delete_hosts_from_knownhosts_file( self.server_private_ips, self.qrouterns_compute_tuples, self.private_key_file) vpn_utils.delete_keyfiles( self.local_key_files, self.remote_key_files, self.qrouterns_compute_tuples, self.private_key_file) else: vpn_utils.delete_hosts_from_knownhosts_file( self.server_private_ips) vpn_utils.delete_keyfiles(self.local_key_files) self._delete_ipsec_site_connections() self._delete_vpn_services() self._delete_ipsec_policy() self._delete_ike_policy() vpn_utils.delete_networks( self.neutron_client, self.neutron_admin_client, self.rally_routers, self.rally_networks, self.rally_subnets) if self.tenant_ids: vpn_utils.delete_tenants(self.keystone_client, self.tenant_ids) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/plugins/vpn_utils.py0000664000175000017500000006051600000000000022745 0ustar00zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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 socket import stat import time import paramiko from rally.common import logging from rally.plugins.openstack.wrappers import network as network_wrapper from rally.task import utils as task_utils LOG = logging.getLogger(__name__) SUBNET_IP_VERSION = 4 START_CIDR = "10.2.0.0/24" EXT_NET_CIDR = "172.16.1.0/24" def execute_cmd_over_ssh(host, cmd, private_key): """Run the given command over ssh Using paramiko package, it creates a connection to the given host; executes the required command on it and returns the output. :param host: Dictionary of ip, username and password :param cmd: Command to be run over ssh :param private_key: path to private key file :return: Output of the executed command """ LOG.debug('EXECUTE COMMAND <%s> OVER SSH', cmd) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) k = paramiko.RSAKey.from_private_key_file(private_key) try: client.connect(host["ip"], username=host["username"], pkey=k) except paramiko.BadHostKeyException as e: raise Exception( "BADHOSTKEY EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.AuthenticationException as e: raise Exception( "AUTHENTICATION EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.SSHException as e: raise Exception("SSH EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except socket.error as e: raise Exception("SOCKET ERROR WHEN CONNECTING TO %s", host["ip"], e) LOG.debug("CONNECTED TO HOST <%s>", host["ip"]) try: stdin, stdout, stderr = client.exec_command(cmd) return stdout.read().splitlines() except paramiko.SSHException as e: raise Exception("SSHEXCEPTION WHEN CONNECTING TO %s", host["ip"], e) finally: client.close() def create_tenant(keystone_client, tenant_suffix): """Creates keystone tenant with a random name. :param keystone_client: keystone client :param tenant_suffix: suffix name for the tenant :returns: uuid of the new tenant """ tenant_name = "rally_tenant_" + tenant_suffix LOG.debug("CREATING NEW TENANT %s", tenant_name) return keystone_client.tenants.create(tenant_name).id def create_network(neutron_client, neutron_admin_client, network_suffix, tenant_id=None, DVR_flag=True, ext_net_name=None): """Create neutron network, subnet, router :param neutron_client: neutron client :param neutron_admin_client: neutron client with admin credentials :param network_suffix: str, suffix name of the new network :param tenant_id: uuid of the tenant :param DVR_flag: True - creates a DVR router False - creates a non DVR router :param ext_net_name: external network that is to be used :return: router, subnet, network, subnet_cidr """ subnet_cidr = network_wrapper.generate_cidr(start_cidr=START_CIDR) def _create_network(neutron_client, network_suffix, is_external=False): """Creates neutron network""" network_name = "rally_network_" + network_suffix network_args = {"name": network_name, "router:external": is_external } if tenant_id: network_args["tenant_id"] = tenant_id LOG.debug("ADDING NEW NETWORK %s", network_name) return neutron_client.create_network({"network": network_args}) def _create_subnet(neutron_client, rally_network, network_suffix, cidr): """Create neutron subnet""" network_id = rally_network["network"]["id"] subnet_name = "rally_subnet_" + network_suffix subnet_args = {"name": subnet_name, "cidr": cidr, "network_id": network_id, "ip_version": SUBNET_IP_VERSION } if tenant_id: subnet_args["tenant_id"] = tenant_id LOG.debug("ADDING SUBNET %s", subnet_name) return neutron_client.create_subnet({"subnet": subnet_args}) def _create_router(neutron_client, ext_network_id, rally_subnet, dvr_flag): """Create router, set the external gateway and add router interface :param neutron_client: neutron_client :param ext_network_id: uuid of the external network :param rally_subnet: subnet to add router interface :param dvr_flag: True - creates a DVR router False - creates a non DVR router :return: router """ router_name = "rally_router_" + network_suffix gw_info = {"network_id": ext_network_id} router_args = {"name": router_name, "external_gateway_info": gw_info } if not dvr_flag: router_args["distributed"] = dvr_flag if tenant_id: router_args["tenant_id"] = 'tenant_id' LOG.debug("ADDING ROUTER %s", router_name) rally_router = neutron_client.create_router({"router": router_args}) LOG.debug("[%s]: ADDING ROUTER INTERFACE") neutron_client.add_interface_router( rally_router['router']["id"], {"subnet_id": rally_subnet["subnet"]["id"]}) return rally_router def _get_external_network_id(ext_net_name): """Fetch the network id for the given external network, if it exists. Else fetch the first external network present. """ ext_nets = neutron_client.list_networks( **{'router:external': True})['networks'] ext_nets_searched = [n for n in ext_nets if n['name'] == ext_net_name] if ext_nets_searched: return ext_nets_searched[0]['id'] elif ext_nets: return ext_nets[0]['id'] else: return None def _create_external_network(): """Creat external network and subnet""" ext_net = _create_network(neutron_admin_client, "public", True) _create_subnet(neutron_admin_client, ext_net, "public", EXT_NET_CIDR) return ext_net['network']['id'] ext_network_id = _get_external_network_id(ext_net_name) if not ext_network_id: ext_network_id = _create_external_network() rally_network = _create_network(neutron_client, network_suffix) rally_subnet = _create_subnet(neutron_client, rally_network, network_suffix, subnet_cidr) rally_router = _create_router(neutron_client, ext_network_id, rally_subnet, DVR_flag) return rally_router, rally_network, rally_subnet, subnet_cidr def create_keypair(nova_client, keypair_suffix): """Create keypair :param nova_client: nova_client :param keypair_suffix: sufix name for the keypair :return: keypair """ keypair_name = "rally_keypair_" + keypair_suffix LOG.debug("CREATING A KEYPAIR %s", keypair_name) keypair = nova_client.keypairs.create(keypair_name) return keypair def write_key_to_local_path(keypair, local_key_file): """Write the private key of the nova instance to a temp file :param keypair: nova keypair :param local_key_file: path to private key file :return: """ with open(local_key_file, 'w') as f: os.chmod(local_key_file, stat.S_IREAD | stat.S_IWRITE) f.write(keypair.private_key) def write_key_to_compute_node(keypair, local_path, remote_path, host, private_key): """Write the private key of the nova instance to the compute node First fetches the private key from the keypair and writes it to a temporary file in the local machine. It then sftp's the file to the compute host. :param keypair: nova keypair :param local_path: path to private key file of the nova instance in the local machine :param remote_path: path where the private key file has to be placed in the remote machine :param host: compute host credentials :param private_key: path to your private key file :return: """ LOG.debug("WRITING PRIVATE KEY TO COMPUTE NODE") k = paramiko.RSAKey.from_private_key_file(private_key) write_key_to_local_path(keypair, local_path) try: transport = paramiko.Transport(host['ip'], host['port']) except paramiko.SSHException as e: raise Exception( "PARAMIKO TRANSPORT FAILED. CHECK IF THE HOST IP %s AND PORT %s " "ARE CORRECT %s", host['ip'], host['port'], e) try: transport.connect( username=host['username'], pkey=k) except paramiko.BadHostKeyException as e: transport.close() raise Exception( "BADHOSTKEY EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.AuthenticationException as e: transport.close() raise Exception("AUTHENTICATION EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) except paramiko.SSHException as e: transport.close() raise Exception("SSH EXCEPTION WHEN CONNECTING TO %s", host["ip"], e) LOG.debug("CONNECTED TO HOST <%s>", host["ip"]) try: sftp_client = paramiko.SFTPClient.from_transport(transport) sftp_client.put(local_path, remote_path) except IOError as e: raise Exception("FILE PATH DOESN'T EXIST", e) finally: transport.close() def create_server(nova_client, keypair, **kwargs): """Create nova instance :param nova_client: nova client :param keypair: key-pair to allow ssh :return: new nova instance """ # add sec-group sec_group_name = "rally_secgroup_" + kwargs["sec_group_suffix"] LOG.debug("ADDING NEW SECURITY GROUP %s", sec_group_name) secgroup = nova_client.security_groups.create(sec_group_name, sec_group_name) # add security rules for SSH and ICMP nova_client.security_group_rules.create(secgroup.id, from_port=22, to_port=22, ip_protocol="tcp", cidr="0.0.0.0/0") nova_client.security_group_rules.create(secgroup.id, from_port=-1, to_port=-1, ip_protocol="icmp", cidr="0.0.0.0/0") # boot new nova instance server_name = "rally_server_" + (kwargs["server_suffix"]) LOG.debug("BOOTING NEW INSTANCE: %s", server_name) LOG.debug("%s", kwargs["image"]) server = nova_client.servers.create(server_name, image=kwargs["image"], flavor=kwargs["flavor"], key_name=keypair.name, security_groups=[secgroup.id], nics=kwargs["nics"]) return server def assert_server_status(server, **kwargs): """Assert server status :param server: nova server """ LOG.debug('WAITING FOR SERVER TO GO ACTIVE') server = task_utils.wait_for( server, is_ready=task_utils.resource_is("ACTIVE"), update_resource=task_utils.get_from_manager(), timeout=kwargs["nova_server_boot_timeout"], check_interval=5) LOG.debug("SERVER STATUS: %s", server.status) assert('ACTIVE' == server.status), ("THE INSTANCE IS NOT IN ACTIVE STATE") def get_server_ip(nova_client, server_id, network_suffix): """Get the ip associated with the nova instance :param nova_client: nova client :param server_id: uuid of the nova instance whose ip is required :param network_suffix: suffix name of the network :return: ip address of the instance """ network_name = "rally_network_" + network_suffix server_details = nova_client.servers.get(server_id) server_ip = server_details.addresses[network_name][0]["addr"] return server_ip def add_floating_ip(nova_client, server): """Associates floating-ip to a server :param nova_client: nova client :param server: nova instance :return: associated floating ip """ fip_list = nova_client.floating_ips.list() for fip in fip_list: if fip.instance_id is None: floating_ip = fip break else: LOG.debug("CREATING NEW FLOATING IP") floating_ip = nova_client.floating_ips.create() LOG.debug("ASSOCIATING FLOATING IP %s", floating_ip.ip) nova_client.servers.add_floating_ip(server.id, floating_ip.ip) return floating_ip def get_namespace(host, private_key): """SSH into the host and get the namespaces :param host : dictionary of controller/compute node credentials {ip:x.x.x.x, username:xxx, password:xxx} :param private_key: path to private key file :return: namespaces """ LOG.debug("GET NAMESPACES") cmd = "sudo ip netns" namespaces = execute_cmd_over_ssh(host, cmd, private_key) LOG.debug("NAMESPACES %s", namespaces) return namespaces def wait_for_namespace_creation(namespace_tag, router_id, hosts, private_key, timeout=60): """Wait for the namespace creation Get into each of the controllers/compute nodes and check which one contains the snat/qrouter namespace corresponding to rally_router. Sleep for a sec and repeat until either the namespace is found or the namespace_creation_ time exceeded. :param namespace_tag: which namespace ("snat_" or "qrouter_") :param router_id: uuid of the rally_router :param hosts: controllers or compute hosts :param private_key: path to private key file :param timeout: namespace creation time :return: """ start_time = time.time() while True: for host in hosts: namespaces = get_namespace(host, private_key) for line in namespaces: if line == (namespace_tag + router_id): namespace_tag = line return namespace_tag, host time.sleep(1) if time.time() - start_time > timeout: raise Exception("TIMEOUT WHILE WAITING FOR" " NAMESPACES TO BE CREATED") def ping(host, cmd, private_key): """Execute ping command over ssh""" ping_result = execute_cmd_over_ssh(host, cmd, private_key) if ping_result: LOG.debug("PING RESULT %s", ping_result) return True else: return False def ping_router_gateway(namespace_controller_tuple, router_gw_ip, private_key): """Ping the ip address from network namespace Get into controller's snat-namespaces and ping the peer router gateway ip. :param namespace_controller_tuple: namespace, controller tuple. (It's the controller that contains the namespace ) :param router_gw_ip: ip address to be pinged :param private_key: path to private key file :return: True if ping succeeds False if ping fails """ namespace, controller = namespace_controller_tuple LOG.debug("PING %s FROM THE NAMESPACE %s", router_gw_ip, namespace) count = 4 cmd = "sudo ip netns exec {} ping -w {} -c {} {}".format( namespace, 2 * count, count, router_gw_ip) return ping(controller, cmd, private_key) def get_interfaces(namespace_controller_tuple, private_key): """Get the interfaces Get into the controller's snat namespace and list the interfaces. :param namespace_controller_tuple: namespace, controller tuple(the controller that contains the namespace). :param private_key: path to private key file :return: interfaces """ namespace, controller = namespace_controller_tuple LOG.debug("GET THE INTERFACES BY USING 'ip a' FROM THE NAMESPACE %s", namespace) cmd = "sudo ip netns exec {} ip a".format(namespace) interfaces = execute_cmd_over_ssh(controller, cmd, private_key) LOG.debug("INTERFACES %s", interfaces) return interfaces def start_tcpdump(namespace_controller_tuple, interface, private_key): """Start the tcpdump at the given interface Get into the controller's snat namespace and start a tcp dump at the qg-interface. :param namespace_controller_tuple: namespace, controller tuple. (It's the controller that contains the namespace ) :param interface: interface in which tcpdump has to be run :param private_key: path to private key file :return: tcpdump output """ namespace, controller = namespace_controller_tuple LOG.debug("START THE TCPDUMP USING 'tcpdump -i %s FROM THE NAMESPACE" " %s", interface, namespace) cmd = ("sudo ip netns exec {} timeout 15 tcpdump -n -i {}" .format(namespace, interface)) tcpdump = execute_cmd_over_ssh(controller, cmd, private_key) LOG.debug("TCPDUMP %s", tcpdump) return tcpdump def ssh_and_ping_server(local_server, peer_server, ns_compute_tuple, keyfile, private_key): """SSH and ping the nova instance from the namespace Get into the compute node's qrouter namespace and then ssh into the local nova instance & ping the peer nova instance. :param local_server: private ip of the server to ssh into :param peer_server: private ip of the server to ping to :param ns_compute_tuple: namespace, compute tuple. (It's the compute node that contains the namespace ) :param keyfile: path to private key file of the nova instance :param private_key: path to private key file :return: True if ping succeeds False if ping fails """ namespace, compute_host = ns_compute_tuple LOG.debug("SSH INTO SERVER %s AND PING THE PEER SERVER %s FROM THE" " NAMESPACE %s", local_server, peer_server, namespace) host = "cirros@" + local_server count = 20 cmd = ("sudo ip netns exec {} ssh -v -o StrictHostKeyChecking=no -o" "HashKnownHosts=no -i {} {} ping -w {} -c {} {}" .format(namespace, keyfile, host, 2 * count, count, peer_server)) return ping(compute_host, cmd, private_key) def ssh_and_ping_server_with_fip(local_server, peer_server, keyfile, private_key): """SSH into the local nova instance and ping the peer instance using fips :param local_server: fip of the server to ssh into :param peer_server: private ip of the server to ping to :param keyfile: path to private key file of the nova instance :param private_key: path to private key file :return: True if ping succeeds False if ping fails """ LOG.debug("SSH INTO LOCAL SERVER %s AND PING THE PEER SERVER %s", local_server.ip, peer_server) count = 20 local_host = {"ip": "127.0.0.1", "username": None} host = "cirros@" + local_server.ip cmd = ("ssh -v -o StrictHostKeyChecking=no -o" "HashKnownHosts=no -i {} {} ping -w {} -c {} {}" .format(keyfile, host, 2 * count, count, peer_server)) return ping(local_host, cmd, private_key) def delete_servers(nova_client, servers): """Delete nova servers It deletes the nova servers, associated security groups. :param nova_client: nova client :param servers: nova instances to be deleted :return: """ for server in servers: LOG.debug("DELETING NOVA INSTANCE: %s", server.id) sec_group_id = server.security_groups[0]['name'] nova_client.servers.delete(server.id) LOG.debug("WAITING FOR INSTANCE TO GET DELETED") task_utils.wait_for_delete( server, update_resource=task_utils.get_from_manager()) for secgroup in nova_client.security_groups.list(): if secgroup.id == sec_group_id: LOG.debug("DELETING SEC_GROUP: %s", sec_group_id) nova_client.security_groups.delete(secgroup.id) def delete_floating_ips(nova_client, fips): """Delete floating ips :param nova_client: nova client :param fips: list of floating ips :return: """ for fip in fips: nova_client.floating_ips.delete(fip.id) def delete_keypairs(nova_client, keypairs): """Delete key pairs :param nova_client: nova client :param keypairs: list of keypairs :return """ for key_pair in keypairs: LOG.debug("DELETING KEY_PAIR %s", key_pair.name) nova_client.keypairs.delete(key_pair.id) def delete_networks(neutron_client, neutron_admin_client, routers, networks, subnets): """Delete neutron network, subnets amd routers :param neutron_client: neutron client :param neutron_admin_client: neutron_admin_client :param routers: list of routers to be deleted :param networks: list of networks to be deleted :param subnets: list of subnets to be deleted :return """ LOG.debug("DELETING RALLY ROUTER INTERFACES & GATEWAYS") for router in routers: neutron_client.remove_gateway_router(router['router']['id']) router_name = router['router']['name'] subnet_name = ("rally_subnet_" + router_name[13:len(router_name)]) for subnet in subnets: if subnet_name == subnet['subnet']['name']: neutron_client.remove_interface_router( router['router']['id'], {"subnet_id": subnet['subnet']['id']}) LOG.debug("DELETING RALLY ROUTERS") for router in routers: neutron_client.delete_router(router['router']['id']) LOG.debug("DELETING RALLY NETWORKS") for network in networks: if (network['network']['router:external'] and network['network']['name'] == "rally_network_public"): external_network = network neutron_admin_client.delete_network( external_network['network']["id"]) elif network['network']['router:external']: pass else: neutron_client.delete_network(network['network']['id']) def delete_tenants(keystone_client, tenant_ids): """Delete keystone tenant :param keystone_client: keystone client :param tenant_ids: list of tenants' uuids :returns: delete keystone tenant instance """ LOG.debug('DELETE TENANTS') for id in tenant_ids: keystone_client.tenants.delete(id) def delete_keyfiles(local_key_files, remote_key_files=None, ns_compute_tuples=None, private_key=None): """Delete the SSH keyfiles from the compute and the local nodes :param local_key_files: paths to ssh key files in local node :param remote_key_files: paths to ssh key files in compute nodes :param ns_compute_tuples: namespace, compute tuple. (It's the compute node that contains the namespace ) :param private_key: path to private key file :return: """ LOG.debug("DELETING RALLY KEY FILES FROM LOCAL MACHINE") for key in local_key_files: if os.path.exists(key): os.remove(key) if ns_compute_tuples: LOG.debug("DELETING RALLY KEY FILES FROM COMPUTE HOSTS") for key, ns_comp in zip(remote_key_files, ns_compute_tuples): cmd = "sudo rm -f {}".format(key) host = ns_comp[1] execute_cmd_over_ssh(host, cmd, private_key) def delete_hosts_from_knownhosts_file(hosts, ns_compute_tuples=None, private_key=None): """Remove the hosts from the knownhosts file :param hosts: host ips to be removed from /root/.ssh/knownhosts :param ns_compute_tuples: namespace, compute tuple. (It's the compute node that contains the namespace ) :param private_key: path to private key file :return: """ if ns_compute_tuples: LOG.debug("DELETES HOSTS FROM THE KNOWNHOSTS FILE") for host, ns_comp in zip(hosts, ns_compute_tuples): compute_host = ns_comp[1] cmd = ("sudo ssh-keygen -f /root/.ssh/known_hosts -R" " {}".format(host)) execute_cmd_over_ssh(compute_host, cmd, private_key) else: for host in hosts: os.system("sudo ssh-keygen -f /root/.ssh/known_hosts -R" " {}".format(host)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9153502 neutron-vpnaas-24.0.1/rally-jobs/rally-configs/0000775000175000017500000000000000000000000021430 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/rally-configs/args_template.json0000664000175000017500000000046200000000000025154 0ustar00zuulzuul00000000000000{ "image_name": "^cirros.*-disk$", "private_key": "", "controller_creds": [ { "ip": "x.x.x.x", "username": "xxx" } ], "compute_creds": [ { "ip": "x.x.x.x", "username": "xxx", "port": 22 } ] } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/rally-configs/rally_config_dvr.yaml0000664000175000017500000000477200000000000025651 0ustar00zuulzuul00000000000000--- TestVpnBasicScenario.create_and_delete_vpn_connection: - args: flavor: name: "m1.tiny" image: name: {{image_name}} phase1_negotiation_mode: "main" auth_algorithm: "sha1" encryption_algorithm: "aes-128" pfs: "group5" value: 7200 ike_version: "v1" transform_protocol: "esp" encapsulation_mode: "tunnel" mtu: 1500 secret: "secret" nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: True use_admin_client: False ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnStatusScenario.check_vpn_status: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 400 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: True use_admin_client: False ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnTenantScenario.multitenants_vpn_test: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: True use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/rally-jobs/rally-configs/rally_config_non_dvr.yaml0000664000175000017500000000477300000000000026524 0ustar00zuulzuul00000000000000--- TestVpnBasicScenario.create_and_delete_vpn_connection: - args: flavor: name: "m1.tiny" image: name: {{image_name}} phase1_negotiation_mode: "main" auth_algorithm: "sha1" encryption_algorithm: "aes-128" pfs: "group5" value: 7200 ike_version: "v1" transform_protocol: "esp" encapsulation_mode: "tunnel" mtu: 1500 secret: "secret" nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: False use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnStatusScenario.check_vpn_status: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 400 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: False use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 TestVpnTenantScenario.multitenants_vpn_test: - args: flavor: name: "m1.tiny" image: name: {{image_name}} nova_server_boot_timeout: 60 * 6 vpn_service_creation_timeout: 100 ipsec_site_connection_creation_timeout: 180 namespace_creation_timeout: 60 private_key: {{private_key}} controller_creds: {{controller_creds}} compute_creds: {{compute_creds}} DVR_flag: False use_admin_client: True ext-net: "ext-net" runner: type: "constant" times: 1 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 sla: failure_rate: max: 0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/releasenotes/0000775000175000017500000000000000000000000017272 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9153502 neutron-vpnaas-24.0.1/releasenotes/notes/0000775000175000017500000000000000000000000020422 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000022673 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000023000000000000011450 xustar0000000000000000130 path=neutron-vpnaas-24.0.1/releasenotes/notes/Enable-sha384-and-sha512-auth-algorithms-for-Swan-drivers-9897b96f90737a20.yaml 22 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/Enable-sha384-and-sha512-auth-algorithms-for-Swan-drivers-90000664000175000017500000000034600000000000032731 0ustar00zuulzuul00000000000000--- prelude: > Enable sha384 and sha512 auth algorithms for \*Swan drivers features: - Users can now specify sha384 and sha512 as the auth algorithm for both IKE policy and IPsec policy, when using \*Swan IPsec drivers. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/add-aggressive-negotiation-mode-3d8fbcd843d003b7.yaml0000664000175000017500000000020300000000000031720 0ustar00zuulzuul00000000000000--- features: - | The phase1 negotiation mode can use ``aggressive`` mode in VPNaaS ikepolicy when using \*Swan drivers. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/config-file-generation-0dcf19f5d8baaf5d.yaml0000664000175000017500000000043700000000000030343 0ustar00zuulzuul00000000000000--- prelude: > Generation of sample Neutron VPNaaS configuration files. features: - Neutron VPNaaS no longer includes static example configuration files. Instead, use tools/generate_config_file_samples.sh to generate them. The files are generated with a .sample extension. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/drivers-removal-944ce5e75d55b449.yaml0000664000175000017500000000051500000000000026605 0ustar00zuulzuul00000000000000--- upgrade: - The following drivers are removed due to the lack of maintainers of the drivers ``CiscoCsrIPsecDriver``, ``FedoraStrongSwanDriver``, ``VyattaIPsecDriver``. Please refer the following `mailing list post `_ for more detail. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/drop-py27-support-d83cd4daa0e37383.yaml0000664000175000017500000000033200000000000027053 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of neutron-vpnaas to support python 2.7 is OpenStack Train. The minimum version of Python now supported by neutron-vpnaas is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/drop-python-3-6-and-3-7-a4a72c6c8775e0b7.yaml0000664000175000017500000000020100000000000027352 0ustar00zuulzuul00000000000000--- upgrade: - | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now supported is Python 3.8. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/flavor-framework-integration-f68d28bd35ce2643.yaml0000664000175000017500000000053100000000000031336 0ustar00zuulzuul00000000000000--- prelude: > Flavor framework integration. features: - Neutron VPNaaS is now integrated with Neutron flavor framework. Multiple VPN service providers might be configured at the same time. A flavor of service type VPN associated with a profile containing a driver is used to find the provider for a newly created VPN service. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/libreswan-driver-works-with-3.19+-7e1fc79ac6c7efe5.yaml0000664000175000017500000000023100000000000032014 0ustar00zuulzuul00000000000000--- fixes: - The libreswan driver of neutron-vpnaas can now also work with Libreswan 3.19+ (bug `#1711456 `_). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/oslo-reports-3059c2e10e1b35b5.yaml0000664000175000017500000000034100000000000026074 0ustar00zuulzuul00000000000000--- prelude: > Neutron VPNaaS is integrated with Guru Meditation Reports library. features: - Neutron VPNaaS services should respond to SIGUSR2 signal by dumping valuable debug information to standard error output. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/notes/vpnaas-for-ovn-a487c62b877e3201.yaml0000664000175000017500000000035400000000000026243 0ustar00zuulzuul00000000000000--- prelude: > VPNaaS support for ML2/OVN features: - | Neutron VPNaaS now supports OVN networking. There is a new stand-alone VPN agent to support ML2/OVN+VPN. OVN-specific service and device drivers have been added. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/releasenotes/source/0000775000175000017500000000000000000000000020572 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/2023.1.rst0000664000175000017500000000020200000000000022043 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022044 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/releasenotes/source/_static/0000775000175000017500000000000000000000000022220 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000024471 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/releasenotes/source/_templates/0000775000175000017500000000000000000000000022727 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000025200 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/conf.py0000664000175000017500000002161200000000000022073 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # 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. # Neutron VPNaaS Release Notes documentation build configuration file, created # by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # 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. # 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. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Neutron VPNaaS Release Notes' copyright = '2015, Neutron VPNaaS Developers' # Release notes are version independent. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # 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 patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used 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 = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. 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 = [] # The name for this set of Sphinx documents. If None, it defaults to # " v 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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # 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_domain_indices = 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, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'NeutronVPNaaSReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'NeutronVPNaaSReleaseNotes.tex', 'Neutron VPNaaS Release Notes Documentation', 'Neutron VPNaaS Developers', '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 # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'neutronvpnaasreleasenotes', 'Neutron VPNaaS Release Notes ' 'Documentation', ['Neutron VPNaaS Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'NeutronVPNaaSReleaseNotes', 'Neutron VPNaaS Release Notes ' 'Documentation', 'Neutron VPNaaS Developers', 'NeutronVPNaaSReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/neutron-vpnaas' openstackdocs_auto_name = False openstackdocs_bug_project = 'neutron' openstackdocs_bug_tag = 'doc' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/index.rst0000664000175000017500000000045200000000000022434 0ustar00zuulzuul00000000000000============================== Neutron VPNaaS Release Notes ============================== .. toctree:: :maxdepth: 1 unreleased 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata newton mitaka liberty ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/liberty.rst0000664000175000017500000000022200000000000022772 0ustar00zuulzuul00000000000000============================== Liberty Series Release Notes ============================== .. release-notes:: :branch: origin/stable/liberty ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/releasenotes/source/locale/0000775000175000017500000000000000000000000022031 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/releasenotes/source/locale/fr/0000775000175000017500000000000000000000000022440 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000000000000000024225 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000175000017500000000235200000000000027260 0ustar00zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: Neutron VPNaaS Release Notes 9.0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-10-24 16:01+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 05:51+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.7.3\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "7.0.2" msgstr "7.0.2" msgid "8.0.0" msgstr "8.0.0" msgid "9.0.0" msgstr "9.0.0" msgid "9.0.0.0rc1" msgstr "9.0.0.0rc1" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "Liberty Series Release Notes" msgstr "Note de release pour Liberty" msgid "Mitaka Series Release Notes" msgstr "Note de release pour Mitaka" msgid "Neutron VPNaaS Release Notes" msgstr "Note de release de Neutron VPNaaS" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Other Notes" msgstr "Autres notes" msgid "Start using reno to manage release notes." msgstr "Commence à utiliser reno pour la gestion des notes de release" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/mitaka.rst0000664000175000017500000000023200000000000022567 0ustar00zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/newton.rst0000664000175000017500000000023200000000000022633 0ustar00zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/ocata.rst0000664000175000017500000000023000000000000022406 0ustar00zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/pike.rst0000664000175000017500000000021700000000000022254 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/queens.rst0000664000175000017500000000022300000000000022621 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000022446 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/stein.rst0000664000175000017500000000022100000000000022441 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/train.rst0000664000175000017500000000017600000000000022445 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/unreleased.rst0000664000175000017500000000016000000000000023450 0ustar00zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000022650 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000023136 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000022754 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/xena.rst0000664000175000017500000000020000000000000022247 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000022253 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/releasenotes/source/zed.rst0000664000175000017500000000016600000000000022111 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: stable/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/requirements.txt0000664000175000017500000000232700000000000020071 0ustar00zuulzuul00000000000000# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=4.0.0 # Apache-2.0 Jinja2>=2.10 # BSD License (3 clause) netaddr>=0.7.18 # BSD SQLAlchemy>=1.3.0 # MIT alembic>=1.6.5 # MIT neutron-lib>=2.6.0 # Apache-2.0 oslo.concurrency>=3.26.0 # Apache-2.0 oslo.config>=8.0.0 # Apache-2.0 oslo.db>=4.44.0 # Apache-2.0 oslo.log>=4.5.0 # Apache-2.0 oslo.messaging>=7.0.0 # Apache-2.0 oslo.reports>=1.18.0 # Apache-2.0 oslo.serialization>=2.25.0 # Apache-2.0 oslo.service>=1.31.0 # Apache-2.0 oslo.utils>=4.5.0 # Apache-2.0 neutron>=17.0.0 # Apache-2.0 # The comment below indicates this project repo is current with neutron-lib # and should receive neutron-lib consumption patches as they are released # in neutron-lib. It also implies the project will stay current with TC # and infra initiatives ensuring consumption patches can land. # neutron-lib-current ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.8873491 neutron-vpnaas-24.0.1/roles/0000775000175000017500000000000000000000000015725 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/roles/configure_functional_tests/0000775000175000017500000000000000000000000023352 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/roles/configure_functional_tests/README.rst0000664000175000017500000000111100000000000025033 0ustar00zuulzuul00000000000000Configure host to run on it Neutron functional/fullstack tests **Role Variables** .. zuul:rolevar:: tests_venv :default: {{ tox_envlist }} .. zuul:rolevar:: project_name :default: neutron .. zuul:rolevar:: base_dir :default: {{ ansible_user_dir }}/src/opendev.org .. zuul:rolevar:: gate_dest_dir :default: {{ base_dir }}/openstack .. zuul:rolevar:: devstack_dir :default: {{ base_dir }}/openstack/devstack .. zuul:rolevar:: neutron_dir :default: {{ gate_dest_dir }}/neutron .. zuul:rolevar:: neutron_vpnaas_dir :default: {{ gate_dest_dir }}/neutron-vpnaas ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/roles/configure_functional_tests/defaults/0000775000175000017500000000000000000000000025161 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/roles/configure_functional_tests/defaults/main.yaml0000664000175000017500000000045300000000000026773 0ustar00zuulzuul00000000000000tests_venv: "{{ tox_envlist }}" project_name: "neutron" base_dir: "{{ ansible_user_dir }}/src/opendev.org" gate_dest_dir: "{{ base_dir }}/openstack" devstack_dir: "{{ base_dir }}/openstack/devstack" neutron_dir: "{{ gate_dest_dir }}/neutron" neutron_vpnaas_dir: "{{ gate_dest_dir }}/neutron-vpnaas" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/roles/configure_functional_tests/tasks/0000775000175000017500000000000000000000000024477 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/roles/configure_functional_tests/tasks/main.yaml0000664000175000017500000000117500000000000026313 0ustar00zuulzuul00000000000000- shell: cmd: | set -e set -x GATE_STACK_USER={{ ansible_user }} IS_GATE=True BASE_DIR={{ base_dir }} GATE_DEST={{ gate_dest_dir }} PROJECT_NAME={{ project_name }} NEUTRON_PATH={{ neutron_dir }} NEUTRON_VPNAAS_PATH={{ neutron_vpnaas_dir }} DEVSTACK_PATH={{ devstack_dir }} VENV={{ tests_venv }} source $DEVSTACK_PATH/functions source $DEVSTACK_PATH/lib/neutron_plugins/ovs_source source $NEUTRON_VPNAAS_PATH/tools/configure_for_vpn_func_testing.sh configure_host_for_vpn_func_testing executable: /bin/bash ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9233503 neutron-vpnaas-24.0.1/setup.cfg0000664000175000017500000000405600000000000016427 0ustar00zuulzuul00000000000000[metadata] name = neutron-vpnaas summary = OpenStack Networking VPN as a Service description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/neutron-vpnaas/latest/ python_requires = >=3.8 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 [files] packages = neutron_vpnaas data_files = etc/neutron/rootwrap.d = etc/neutron/rootwrap.d/vpnaas.filters [entry_points] console_scripts = neutron-vpn-netns-wrapper = neutron_vpnaas.services.vpn.common.netns_wrapper:main neutron-ovn-vpn-agent = neutron_vpnaas.cmd.eventlet.ovn_agent:main neutron.agent.l3.extensions = vpnaas = neutron_vpnaas.services.vpn.agent:L3WithVPNaaS device_drivers = neutron.services.vpn.device_drivers.ipsec.OpenSwanDriver = neutron_vpnaas.services.vpn.device_drivers.ipsec:OpenSwanDriver neutron.db.alembic_migrations = neutron-vpnaas = neutron_vpnaas.db.migration:alembic_migrations neutron.service_plugins = vpnaas = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin ovn-vpnaas = neutron_vpnaas.services.vpn.ovn_plugin:VPNOVNDriverPlugin neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin oslo.config.opts = neutron.vpnaas = neutron_vpnaas.opts:list_opts neutron.vpnaas.agent = neutron_vpnaas.opts:list_agent_opts neutron.vpnaas.ovn_agent = neutron_vpnaas.opts:list_ovn_agent_opts oslo.policy.policies = neutron-vpnaas = neutron_vpnaas.policies:list_rules neutron.policies = neutron-vpnaas = neutron_vpnaas.policies:list_rules [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/setup.py0000664000175000017500000000127100000000000016314 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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 setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/test-requirements.txt0000664000175000017500000000071600000000000021046 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. coverage!=4.4,>=4.0 # Apache-2.0 testtools>=2.2.0 # MIT oslotest>=3.2.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 # This is necessary as pecan dropped this dependency # see https://review.opendev.org/c/openstack/neutron/+/848706 WebTest>=2.0.27 # MIT ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1712152350.9193504 neutron-vpnaas-24.0.1/tools/0000775000175000017500000000000000000000000015741 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/check_i18n.py0000664000175000017500000001274600000000000020241 0ustar00zuulzuul00000000000000# Copyright 2012 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. import compiler import importlib.util import os.path import sys def is_localized(node): """Check message wrapped by _()""" if isinstance(node.parent, compiler.ast.CallFunc): if isinstance(node.parent.node, compiler.ast.Name): if node.parent.node.name == '_': return True return False class ASTWalker(compiler.visitor.ASTVisitor): def default(self, node, *args): for child in node.getChildNodes(): child.parent = node compiler.visitor.ASTVisitor.default(self, node, *args) class Visitor(object): def __init__(self, filename, i18n_msg_predicates, msg_format_checkers, debug): self.filename = filename self.debug = debug self.error = 0 self.i18n_msg_predicates = i18n_msg_predicates self.msg_format_checkers = msg_format_checkers with open(filename) as f: self.lines = f.readlines() def visitConst(self, node): if not isinstance(node.value, str): return if is_localized(node): for (checker, msg) in self.msg_format_checkers: if checker(node): print('%s:%d %s: %s Error: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], checker.__name__, msg), file=sys.stderr) self.error = 1 return if debug: print('%s:%d %s: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], "Pass")) else: for (predicate, action, msg) in self.i18n_msg_predicates: if predicate(node): if action == 'skip': if debug: print('%s:%d %s: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], "Pass")) return elif action == 'error': print('%s:%d %s: %s Error: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], predicate.__name__, msg), file=sys.stderr) self.error = 1 return elif action == 'warn': print('%s:%d %s: %s' % (self.filename, node.lineno, self.lines[node.lineno - 1][:-1], "Warn: %s" % msg)) return print('Predicate with wrong action!', file=sys.stderr) def is_file_in_black_list(black_list, f): for f in black_list: if os.path.abspath(input_file).startswith( os.path.abspath(f)): return True return False def check_i18n(input_file, i18n_msg_predicates, msg_format_checkers, debug): input_mod = compiler.parseFile(input_file) v = compiler.visitor.walk(input_mod, Visitor(input_file, i18n_msg_predicates, msg_format_checkers, debug), ASTWalker()) return v.error def load_module(name, path): module_spec = importlib.util.spec_from_file_location( name, path ) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) return module if __name__ == '__main__': input_path = sys.argv[1] cfg_path = sys.argv[2] try: cfg_mod = load_module('', cfg_path) except Exception: print("Load cfg module failed", file=sys.stderr) sys.exit(1) i18n_msg_predicates = cfg_mod.i18n_msg_predicates msg_format_checkers = cfg_mod.msg_format_checkers black_list = cfg_mod.file_black_list debug = False if len(sys.argv) > 3: if sys.argv[3] == '-d': debug = True if os.path.isfile(input_path): sys.exit(check_i18n(input_path, i18n_msg_predicates, msg_format_checkers, debug)) error = 0 for dirpath, dirs, files in os.walk(input_path): for f in files: if not f.endswith('.py'): continue input_file = os.path.join(dirpath, f) if is_file_in_black_list(black_list, input_file): continue if check_i18n(input_file, i18n_msg_predicates, msg_format_checkers, debug): error = 1 sys.exit(error) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/check_i18n_test_case.txt0000664000175000017500000000264500000000000022457 0ustar00zuulzuul00000000000000# test-case for check_i18n.py # python check_i18n.py check_i18n.txt -d # message format checking # capital checking msg = _("hello world, error") msg = _("hello world_var, error") msg = _('file_list xyz, pass') msg = _("Hello world, pass") # format specifier checking msg = _("Hello %s world %d, error") msg = _("Hello %s world, pass") msg = _("Hello %(var1)s world %(var2)s, pass") # message has been localized # is_localized msg = _("Hello world, pass") msg = _("Hello world, pass") % var LOG.debug(_('Hello world, pass')) LOG.info(_('Hello world, pass')) raise x.y.Exception(_('Hello world, pass')) raise Exception(_('Hello world, pass')) # message need be localized # is_log_callfunc LOG.debug('hello world, error') LOG.debug('hello world, error' % xyz) sys.append('hello world, warn') # is_log_i18n_msg_with_mod LOG.debug(_('Hello world, error') % xyz) # default warn msg = 'hello world, warn' msg = 'hello world, warn' % var # message needn't be localized # skip only one word msg = '' msg = "hello,pass" # skip dict msg = {'hello world, pass': 1} # skip list msg = ["hello world, pass"] # skip subscript msg['hello world, pass'] # skip xml marker msg = ", pass" # skip sql statement msg = "SELECT * FROM xyz WHERE hello=1, pass" msg = "select * from xyz, pass" # skip add statement msg = 'hello world' + e + 'world hello, pass' # skip doc string """ Hello world, pass """ class Msg: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/check_unit_test_structure.sh0000775000175000017500000000307700000000000023602 0ustar00zuulzuul00000000000000#!/usr/bin/env bash # This script identifies the unit test modules that do not correspond # directly with a module in the code tree. See TESTING.rst for the # intended structure. neutron_path=$(cd "$(dirname "$0")/.." && pwd) base_test_path=neutron_vpnaas/tests/unit test_path=$neutron_path/$base_test_path test_files=$(find ${test_path} -iname 'test_*.py') ignore_regexes=( "^plugins.*$" ) error_count=0 ignore_count=0 total_count=0 for test_file in ${test_files[@]}; do relative_path=${test_file#$test_path/} expected_path=$(dirname $neutron_path/neutron_vpnaas/$relative_path) test_filename=$(basename "$test_file") expected_filename=${test_filename#test_} # Module filename (e.g. foo/bar.py -> foo/test_bar.py) filename=$expected_path/$expected_filename # Package dir (e.g. foo/ -> test_foo.py) package_dir=${filename%.py} if [ ! -f "$filename" ] && [ ! -d "$package_dir" ]; then for ignore_regex in ${ignore_regexes[@]}; do if [[ "$relative_path" =~ $ignore_regex ]]; then ((ignore_count++)) continue 2 fi done echo "Unexpected test file: $base_test_path/$relative_path" ((error_count++)) fi ((total_count++)) done if [ "$ignore_count" -ne 0 ]; then echo "$ignore_count unmatched test modules were ignored" fi if [ "$error_count" -eq 0 ]; then echo 'Success! All test modules match targets in the code tree.' exit 0 else echo "Failure! $error_count of $total_count test modules do not match targets in the code tree." exit 1 fi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/clean.sh0000775000175000017500000000030400000000000017357 0ustar00zuulzuul00000000000000#!/usr/bin/env bash rm -rf ./*.deb ./*.tar.gz ./*.dsc ./*.changes rm -rf */*.deb rm -rf ./plugins/**/build/ ./plugins/**/dist rm -rf ./plugins/**/lib/neutron_*_plugin.egg-info ./plugins/neutron-* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/configure_for_vpn_func_testing.sh0000775000175000017500000000331600000000000024565 0ustar00zuulzuul00000000000000#!/usr/bin/env 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. set -e IS_GATE=${IS_GATE:-False} USE_CONSTRAINT_ENV=${USE_CONSTRAINT_ENV:-False} PROJECT_NAME=${PROJECT_NAME:-neutron-vpnaas} REPO_BASE=${GATE_DEST:-$(cd $(dirname "$BASH_SOURCE")/../.. && pwd)} NEUTRON_DIR=$REPO_BASE/neutron source $REPO_BASE/neutron/tools/configure_for_func_testing.sh source $REPO_BASE/neutron-vpnaas/devstack/settings source $NEUTRON_VPNAAS_DIR/devstack/plugin.sh function _install_vpn_package { case $VENV in dsvm-functional-sswan*) IPSEC_PACKAGE=strongswan ;; *) IPSEC_PACKAGE=openswan ;; esac echo_summary "Installing $IPSEC_PACKAGE for $VENV" neutron_agent_vpnaas_install_agent_packages } function configure_host_for_vpn_func_testing { echo_summary "Configuring for VPN functional testing" if [ "$IS_GATE" == "True" ]; then configure_host_for_func_testing fi # Note(pc_m): Need to ensure this is installed so we have # oslo-config-generator present (as this script runs before tox.ini). sudo pip3 install --force oslo.config _install_vpn_package } if [ "$IS_GATE" != "True" ]; then configure_host_for_vpn_func_testing fi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/deploy_rootwrap.sh0000775000175000017500000000361000000000000021531 0ustar00zuulzuul00000000000000#!/usr/bin/env 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. set -eu if [ $# -ne 2 ]; then >&2 echo "Usage: $0 /path/to/repo /path/to/virtual-env Deploy rootwrap configuration and filters. Warning: Any existing rootwrap files at the specified etc path will be removed by this script. Optional: set OS_SUDO_TESTING=1 to deploy the filters required by Neutron's functional testing suite." exit 1 fi OS_SUDO_TESTING=${OS_SUDO_TESTING:-0} repo_path=$1 venv_path=$2 src_conf_path=${repo_path}/neutron_vpnaas/tests/contrib src_conf=${src_conf_path}/functional-test-rootwrap.conf src_rootwrap_path=${repo_path}/etc/neutron/rootwrap.d dst_conf_path=${venv_path}/etc/neutron dst_conf=${dst_conf_path}/rootwrap.conf dst_rootwrap_path=${dst_conf_path}/rootwrap.d # Clear any existing filters in virtual env if [[ -d "$dst_rootwrap_path" ]]; then rm -rf ${dst_rootwrap_path} fi mkdir -p -m 755 ${dst_rootwrap_path} # Get all needed filters cp -p ${src_rootwrap_path}/* ${dst_rootwrap_path}/ if [[ "$OS_SUDO_TESTING" = "1" ]]; then cp -p ${repo_path}/neutron_vpnaas/tests/contrib/functional-testing.filters \ ${dst_rootwrap_path}/ fi # Get config file and modify for this repo cp -p ${src_conf} ${dst_conf} sed -i "s:^filters_path=.*$:filters_path=${dst_rootwrap_path}:" ${dst_conf} sed -i "s:^\(exec_dirs=.*\)$:\1,${venv_path}/bin:" ${dst_conf} sudo mkdir -p /etc/neutron/ sudo cp ${dst_conf} /etc/neutron/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/generate_config_file_samples.sh0000775000175000017500000000143300000000000024143 0ustar00zuulzuul00000000000000#!/bin/sh # # 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. set -e GEN_CMD=oslo-config-generator if ! type "$GEN_CMD" > /dev/null; then echo "ERROR: $GEN_CMD not installed on the system." exit 1 fi for file in etc/oslo-config-generator/*; do $GEN_CMD --config-file=$file done set -x ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/i18n_cfg.py0000664000175000017500000000664300000000000017722 0ustar00zuulzuul00000000000000import compiler import re def is_log_callfunc(n): """LOG.xxx('hello %s' % xyz) and LOG('hello')""" if isinstance(n.parent, compiler.ast.Mod): n = n.parent if isinstance(n.parent, compiler.ast.CallFunc): if isinstance(n.parent.node, compiler.ast.Getattr): if isinstance(n.parent.node.getChildNodes()[0], compiler.ast.Name): if n.parent.node.getChildNodes()[0].name == 'LOG': return True return False def is_log_i18n_msg_with_mod(n): """LOG.xxx("Hello %s" % xyz) should be LOG.xxx("Hello %s", xyz)""" if not isinstance(n.parent.parent, compiler.ast.Mod): return False n = n.parent.parent if isinstance(n.parent, compiler.ast.CallFunc): if isinstance(n.parent.node, compiler.ast.Getattr): if isinstance(n.parent.node.getChildNodes()[0], compiler.ast.Name): if n.parent.node.getChildNodes()[0].name == 'LOG': return True return False def is_wrong_i18n_format(n): """Check _('hello %s' % xyz)""" if isinstance(n.parent, compiler.ast.Mod): n = n.parent if isinstance(n.parent, compiler.ast.CallFunc): if isinstance(n.parent.node, compiler.ast.Name): if n.parent.node.name == '_': return True return False """ Used for check message need be localized or not. (predicate_func, action, message) """ i18n_msg_predicates = [ # Skip ['hello world', 1] (lambda n: isinstance(n.parent, compiler.ast.List), 'skip', ''), # Skip {'hellow world', 1} (lambda n: isinstance(n.parent, compiler.ast.Dict), 'skip', ''), # Skip msg['hello world'] (lambda n: isinstance(n.parent, compiler.ast.Subscript), 'skip', ''), # Skip doc string (lambda n: isinstance(n.parent, compiler.ast.Discard), 'skip', ''), # Skip msg = "hello", in normal, message should more than one word (lambda n: len(n.value.strip().split(' ')) <= 1, 'skip', ''), # Skip msg = 'hello world' + vars + 'world hello' (lambda n: isinstance(n.parent, compiler.ast.Add), 'skip', ''), # Skip xml markers msg = "" (lambda n: len(re.compile("").findall(n.value)) > 0, 'skip', ''), # Skip sql statement (lambda n: len( re.compile("^SELECT.*FROM", flags=re.I).findall(n.value)) > 0, 'skip', ''), # LOG.xxx() (is_log_callfunc, 'error', 'Message must be localized'), # _('hello %s' % xyz) should be _('hello %s') % xyz (is_wrong_i18n_format, 'error', ("Message format was wrong, _('hello %s' % xyz) " "should be _('hello %s') % xyz")), # default (lambda n: True, 'warn', 'Message might need localized') ] """ Used for checking message format. (checker_func, message) """ msg_format_checkers = [ # If message contain more than on format specifier, it should use # mapping key (lambda n: len(re.compile("%[bcdeEfFgGnosxX]").findall(n.value)) > 1, "The message shouldn't contain more than one format specifier"), # Check capital (lambda n: n.value.split(' ')[0].count('_') == 0 and n.value[0].isalpha() and n.value[0].islower(), "First letter must be capital"), (is_log_i18n_msg_with_mod, 'LOG.xxx("Hello %s" % xyz) should be LOG.xxx("Hello %s", xyz)') ] file_black_list = ["./neutron/tests/unit", "./neutron/openstack", "./neutron/plugins/bigswitch/tests"] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/test_script.sh0000775000175000017500000000402100000000000020640 0ustar00zuulzuul00000000000000#!/usr/bin/env bash EXT_NW_ID=`openstack network show public -c id -f value` EXTERNAL_SUBNET_IP_VERSION='v4' WEST_SUBNET='192.168.1.0/24' EAST_SUBNET='192.168.2.0/24' function setup_site(){ local site_name=$1 local cidr=$2 openstack network create net_$site_name openstack subnet create --network net_$site_name --subnet-range $2 subnet_$site_name openstack router create router_$site_name openstack router add subnet router_$site_name subnet_$site_name openstack router set --external-gateway $EXT_NW_ID router_$site_name openstack vpn service create --subnet subnet_$site_name --router router_$site_name vpn_$site_name } function get_external_ip(){ echo `openstack vpn service show $1 -c external_${EXTERNAL_SUBNET_IP_VERSION}_ip -f value` } function clean_site(){ local site_name=$1 openstack vpn ipsec site connection delete conn_$site_name openstack vpn service delete vpn_$site_name openstack router unset --external-gateway router_$site_name openstack router remove subnet router_$site_name subnet_$site_name openstack router delete router_$site_name openstack subnet delete subnet_$site_name openstack network delete net_$site_name } function setup(){ openstack vpn ike policy create ikepolicy1 openstack vpn ipsec policy create ipsecpolicy1 setup_site west $WEST_SUBNET WEST_IP=$(get_external_ip vpn_west) setup_site east $EAST_SUBNET EAST_IP=$(get_external_ip vpn_east) openstack vpn ipsec site connection create \ --vpnservice vpn_east --ikepolicy ikepolicy1 --ipsecpolicy ipsecpolicy1 \ --peer-address $WEST_IP --peer-id $WEST_IP --peer-cidr $WEST_SUBNET \ --psk secret conn_east openstack vpn ipsec site connection create \ --vpnservice vpn_west --ikepolicy ikepolicy1 --ipsecpolicy ipsecpolicy1 \ --peer-address $EAST_IP --peer-id $EAST_IP --peer-cidr $EAST_SUBNET \ --psk secret conn_west } function cleanup(){ clean_site west clean_site east openstack vpn ike policy delete ikepolicy1 openstack vpn ipsec policy delete ipsecpolicy1 } cleanup setup ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tools/with_venv.sh0000775000175000017500000000133300000000000020311 0ustar00zuulzuul00000000000000#!/usr/bin/env bash # 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. TOOLS=`dirname $0` VENV=$TOOLS/../.venv source $VENV/bin/activate && "$@" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1712152304.0 neutron-vpnaas-24.0.1/tox.ini0000664000175000017500000001200100000000000016106 0ustar00zuulzuul00000000000000[tox] envlist = py39,py38,pep8,docs minversion = 3.18.0 [testenv] setenv = VIRTUAL_ENV={envdir} OS_LOG_CAPTURE={env:OS_LOG_CAPTURE:true} OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:true} OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:true} PYTHONWARNINGS=default::DeprecationWarning usedevelop = True deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.1} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt allowlist_externals = bash commands = stestr run {posargs} # there is also secret magic in stestr which lets you run in a fail only # mode. To do this define the TRACE_FAILONLY environmental variable. [testenv:functional] deps = {[testenv]deps} -r{toxinidir}/neutron_vpnaas/tests/functional/requirements.txt setenv = OS_SUDO_TESTING=1 OS_LOG_PATH={env:OS_LOG_PATH:/opt/stack/logs} OS_ROOTWRAP_CMD=sudo {envdir}/bin/neutron-rootwrap {envdir}/etc/neutron/rootwrap.conf OS_ROOTWRAP_DAEMON_CMD=sudo {envdir}/bin/neutron-rootwrap-daemon {envdir}/etc/neutron/rootwrap.conf OS_FAIL_ON_MISSING_DEPS=1 allowlist_externals = bash cp sudo [testenv:dev] # run locally (not in the gate) using editable mode # https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs # note that order is important to ensure dependencies don't override commands = pip install -q -e "git+https://opendev.org/openstack/neutron#egg=neutron" [testenv:py-dev] commands = {[testenv:dev]commands} {[testenv]commands} [testenv:dsvm-functional] setenv = OS_TEST_PATH=./neutron_vpnaas/tests/functional/openswan {[testenv:functional]setenv} deps = {[testenv:functional]deps} allowlist_externals = {[testenv:functional]allowlist_externals} commands = bash {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir} stestr run {posargs} [testenv:dsvm-functional-sswan] setenv = OS_TEST_PATH=./neutron_vpnaas/tests/functional/strongswan {[testenv:functional]setenv} deps = {[testenv:functional]deps} allowlist_externals = {[testenv:functional]allowlist_externals} commands = {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir} stestr run {posargs} [testenv:releasenotes] deps = {[testenv:docs]deps} commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:pep8] deps = {[testenv]deps} hacking>=4.0.0,<4.1 # Apache-2.0 flake8-import-order==0.18.1 # LGPLv3 pylint==2.5.3 # GPLv2 isort==4.3.21 # MIT commands = flake8 pylint --rcfile=.pylintrc --output-format=colorized {posargs:neutron_vpnaas} bash {toxinidir}/tools/check_unit_test_structure.sh neutron-db-manage --subproject neutron-vpnaas --database-connection sqlite:// check_migration {[testenv:genconfig]commands} {[testenv:genpolicy]commands} allowlist_externals = bash [testenv:pep8-dev] deps = {[testenv:pep8]deps} commands = {[testenv:dev]commands} {[testenv:pep8]commands} [testenv:i18n] commands = python ./tools/check_i18n.py ./neutron-vpnaas ./tools/i18n_cfg.py [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source neutron_vpnaas --parallel-mode commands = stestr run --no-subunit-trace {posargs} coverage combine coverage report --skip-covered coverage html -d cover coverage xml -o cover/coverage.xml [testenv:venv] commands = {posargs} [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.1} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -a -b html doc/source doc/build/html [testenv:pdf-docs] deps = {[testenv:docs]deps} allowlist_externals = make commands = sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [flake8] # E125 continuation line does not distinguish itself from next logical line # E126 continuation line over-indented for hanging indent # E128 continuation line under-indented for visual indent # E129 visually indented line with same indent as next logical line # E265 block comment should start with ‘# ‘ # W504 line break after binary operator # I202 Additional newline in a group of imports # H404 multi line docstring should start with a summary # H405 multi line docstring summary not separated with an empty line # TODO(dougwig) -- uncomment this to test for remaining linkages # N530 direct neutron imports not allowed # N531 Log messages require translation hints ignore = E125,E126,E128,E129,E265,W504,I202,H404,H405,N530,N531 # H106 Don't put vim configuration in source files # H203 Use assertIs(Not)None to check for None # H904 Delay string interpolations at logging calls enable-extensions=H106,H203,H904 exclude = .venv,.git,.tox,dist,doc,.tmp,*lib/python*,*egg,build,tools,.ropeproject,rally-scenarios import-order-style = pep8 [hacking] import_exceptions = neutron_vpnaas._i18n local-check-factory = neutron_lib.hacking.checks.factory [testenv:genconfig] commands = bash {toxinidir}/tools/generate_config_file_samples.sh [testenv:genpolicy] commands = oslopolicy-sample-generator --config-file=etc/oslo-policy-generator/policy.conf