././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/0000775000175000017500000000000014572557757016240 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/.coveragerc0000664000175000017500000000020714572557755020356 0ustar00jamespagejamespage[run] branch = True source = networking_l2gw omit = networking_l2gw/tests/*,networking_l2gw/openstack/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/.stestr.conf0000664000175000017500000000011514572557755020504 0ustar00jamespagejamespage[DEFAULT] test_path=${OS_TEST_PATH:-./networking_l2gw/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/.zuul.yaml0000664000175000017500000000453314572557755020204 0ustar00jamespagejamespage- job: name: openstack-tox-py310-with-sqlalchemy-main-l2gw parent: openstack-tox-py310-with-oslo-master 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 - project: templates: - check-requirements - openstack-python3-jobs-neutron - build-openstack-docs-pti check: jobs: - networking-l2gw-tempest-dummy # Currently, we cannot specify that we want neutron # checked out from master using tox-siblings for this # job, so this always fails. # TODO: make this voting again - openstack-tox-lower-constraints: voting: false - openstack-tox-pep8: required-projects: - x/networking-l2gw - openstack-tox-py38: required-projects: &l2gw_required_projects - openstack/neutron - x/networking-l2gw - openstack-tox-py39: required-projects: *l2gw_required_projects - openstack-tox-py310: required-projects: *l2gw_required_projects - openstack-tox-py310: required-projects: *l2gw_required_projects gate: jobs: - networking-l2gw-tempest-dummy - openstack-tox-pep8: required-projects: - x/networking-l2gw - openstack-tox-py38: required-projects: *l2gw_required_projects - openstack-tox-py39: required-projects: *l2gw_required_projects - openstack-tox-py310: required-projects: *l2gw_required_projects - openstack-tox-py310: required-projects: *l2gw_required_projects periodic-weekly: jobs: - openstack-tox-py311: required-projects: *l2gw_required_projects - openstack-tox-py310-with-oslo-master: required-projects: *l2gw_required_projects - openstack-tox-py310-with-sqlalchemy-main-l2gw - networking-l2gw-tempest-dummy experimental: jobs: - openstack-tox-py310-with-oslo-master: required-projects: *l2gw_required_projects - openstack-tox-py310-with-sqlalchemy-main-l2gw: required-projects: *l2gw_required_projects ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/AUTHORS0000664000175000017500000000572514572557756017320 0ustar00jamespagejamespageAaron Rosen Abhishek Raut Adit Sarfaty Akihiro Motoki Akihiro Motoki Alok Maurya Andreas Jaeger Anusree Armando Migliaccio Ashish Gupta Ashish Gupta Boden R Brian Haley Chandan Kumar Chuck Carlino Corey Bryant Daniel Mellado Dariusz Smigiel Dong Jun Doug Hellmann Elod Illes Emilien Macchi Gal Sagie Gary Kotton Hangdong Zhang Henry Gessau Henry Gessau Henry Gessau Hiroyuki Ito HoboK James E. Blair James Page Jeremy Stanley Koteswara Rao Kelam Kyle Mestery Lajos Katona Le Hou LiuYong Luigi Toscano Manjunath Patil Mariusz Karpiarz Maruti Michael Micucci Monty Taylor Nguyen Hung Phuong Nguyen Phuong An Ofer Ben-Yacov Pavlo Shchelokovskyy Peng Liu Phani Pawan Ricardo Noriega Roey Chen SDN Developer Saverio Proto Sean McGinnis Selvakumar Selvakumar S Sukhdev Kapur Takashi Kajinami Thomas Goirand Victor Pickard Vieri <15050873171@163.com> YAMAMOTO Takashi YAMAMOTO Takashi ZhaoBo armando-migliaccio elajkat fanghuilin ghanshyam ghanshyam huang.zhiping jmehta judy-yu melissaml pengyuesheng preeti mirji preeti-mirji qinchunhua selvakumar vikas vikas wangfaxin wangqi xuqihou ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/CONTRIBUTING.rst0000664000175000017500000000103314572557755020674 0ustar00jamespagejamespageIf you would like to contribute to the development of OpenStack, you must follow the steps in this page: 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, not GitHub: https://bugs.launchpad.net/networking-l2gw ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/ChangeLog0000664000175000017500000003555114572557756020022 0ustar00jamespagejamespageCHANGES ======= * Bump hacking * tox: Drop envdir * py311: add required projects to py311 job and add it to weekly * Fix bindep.txt for python 3.11 job(Debian Bookworm) * CI: Change l2gw to use stestr 20.0.0 ------ * [sqlalchemy-20] The Session.begin(subtransactions) flag is deprecated * Add missing db context to \_get\_l2\_gateway\_connections * Update python runtime to be larger or equal than 3.8 * Remove the old CLI code from networking-l2gw * CI: Fix dependencies of SQLAlchemy main job * Fix job name for periodic * CI: Add periodic weekly job with sqlalchemy master * Fix tox.ini for tox4 * Update requirements for python 3.11 * CI: Add periodic weekly jobs * Add context for all SQL transactions * Tests: fix requirements for unit tests * Fix missing common options * Add missing "Getting started" shell commands 18.0.0 ------ * .zuul.yaml to the project and change location * Remove .zuul.yaml * Retire networking-l2gw: remove zuul jobs * Cleanup py27 support * Fix cleaning vlan\_bindings in neutron db when vlan is 0 * Switch to newer openstackdocstheme version * Add Python3 victoria unit tests * Use unittest.mock instead of third party mock 16.0.0 ------ * Monkey patch original current\_thread \_active * Make gateway update work without device info * Remove networking-l2gw python2 jobs * Switch to Ussuri jobs * Fix misspell word 15.0.0 ------ * Update the constraints url * PDF documentation build * Blacklist sphinx 2.1.0 (autodoc bug) * use standard\_attr db from neutron-lib * py3: encode messages on py3 * Bump the openstackdocstheme extension to 1.20 * Add Python 3 Train unit tests * Add local bindep.txt * Start agent looping task anyway * Use opendev repository * Import and update jobs * bump lower-constraints * Fix sphinx requirements * OpenDev Migration Patch 14.0.0 ------ * Change mailing list to openstack-discuss in README * Fix parameters when raising L2GatewayConnectionNotFound * Change openstack-dev to openstack-discuss * stop using \_apply\_filters\_to\_query * Update min tox version to 2.0 * opt in for neutron-lib consumption patches * use common rpc and exceptions from neutron-lib * add local tox targets for pep8 and py3 * Fix test\_list\_physical\_switches() 13.0.0 ------ * The Python 3.5 is added and delete Python 3.4 * update requirements for neutron-lib 1.18.0 * Add networking-l2gw OSC commands * fix tox python3 overrides * Use constant from neutron-lib * use rpc Connection rather than create\_connection * Make use of mock instead of mox * Fix pep8 new warnings * add lower-constraints job * Testing if Sphinx 1.6.5 can be a lower requirement * Support quotas * Update code to deal with mox removal from neutron client * Updated from global requirements * Avoid tox-install.sh * use common agent topics from neutron-lib * Change neutron service name for devstack * queens: disable tripleo jobs for now * Replace Chinese quotes to English quotes * Updated from global requirements * Add standard attributes to l2gateway * Fix l2gw devstack plugin for Queens * Updated from global requirements * Zuul: Remove project name * Updated from global requirements * use callbacks from neutron-lib * Remove bundled in-tree Tempest plugin * Adding tempest job to verify gate * Migrating tempest job to Zuulv3 * Add UT cases for L2GW Update * Enable L2GW Update with existing connections * Replace the fixed path prefix with $TEMPEST\_DIR * Tempest: update to follow code depcreation * zuul: run TripleO jobs with new zuulv3 layout * Updated from global requirements * Updated from global requirements * use FAULT\_MAP from neutron-lib * Updated from global requirements * Add Getting Started section to the README * Replace deprecated self.is\_agent\_down * Updated from global requirements * 'l2gw' entrypoint for Neutron service\_plugins * Updated from global requirements 11.0.0 ------ * Replace deprecated test.attr with decorators.attr * Update the documentation link for doc migration * Take allowed-address-pairs to ucast\_mac\_remote * Updated from global requirements * Use flake8-import-order plugin * Updated from global requirements * Fix the configuration of the tempest plugin * Adding l2gw section to tempest.conf within devstack plugin * Fix for tempest case test\_create\_l2gw\_connection\_with\_invalid\_network\_name * Tempest plugin expects the l2gw section instead of network * Add proper class skip * Fix tempest install issues * Fix networking-l2gw devstack systemd issues * Fixed networking-l2gw tempest plugin * Update latest requirements * Adapting networking-l2gw to use ovsdbapp library * Updated from global requirements * use MechanismDriver from neutron-lib * Remove log translations * Updated from global requirements * Use the new tempest module * Fix agent configuration breakage 10.1.0.a1 --------- * Use neutron-lib's context module * Updated from global requirements * Add DB migration milestone for Ocata * Prepare for using standard python tests * Switch to decorators.idempotent\_id * Use neutron-lib portbindings api-def * Remove white space between print () * Updated from global requirements * Cleanup after tox\_install run * devstack: Stop using Q\_PLUGIN\_EXTRA\_CONF\_FILES * Remove discover from test-requirements * Updated from global requirements * Use tempest instead of Tempest-lib * Use valid uuid when creating a network * Use ExtensionDescriptor from neutron-lib * Remove PLURALS * replace neutron with neutron\_lib * Adding OVSLIB * Use the new neutron manager * Updated from global requirements * Updated from global requirements * Updated from global requirements * Don't include openstack/common in flake8 exclude list * Delete python bytecode file * TrivialFix: Merge imports in code * Remove depracted warnings for model\_base * Updated from global requirements * Changed the home-page link * TrivialFix: Remove logging import unused * Include alembic migrations in module * Updated from global requirements * Remove logging import unused * blank was missed in config help message * Tag the alembic migration revisions for Newton * Remove \_i18n warnings * Use validators from neutron-lib * Use neutron-lib model\_base * Remove temporary local HasProject * Use constraint environemnts for testing * Introduce socket timeout for a blocking socket * use proper l2-gateway extension in the test cases * accomodate the changes from neutronclient module * Enable DeprecationWarning in test environments * Revert "Use neutron-lib add\_validator for registration" * Remove execute permission which is added by mistake in l2gw * verify the certificates presence * Remove the execute permission of two files * Replace assertEqual(None, \*) with assertIsNone in tests * Use neutron-lib add\_validator for registration * Retain ucast\_macs\_remote if l2gw connection exists * Updated from global requirements * UT: Fix "RuntimeError: stop called on unstarted patcher" * Cleanups after python 3 support * Rename DB columns: tenant -> project * Python 3: add support * Bring models in sync with migrations, add test * Revert "allow binding to vlan 0" * Fix translation imports * Fix dict.keys() PY3 compatible * Fix issues with Python 3 tests * allow binding to vlan 0 * New API SPEC for Border Gateway support * enabling L2GW to work with DVR * This cleans up a bit the code that fixed bug 1602523 * Fix the order of arguments in assertEqual * Populate Ucast\_Macs\_Remote table for multiple OVSDB servers * Return a valid socket when enable\_manager=true * Fix Q\_PLUGIN\_EXTRA\_CONF\_FILES usage * PY34 support change iteritems to items * Add testresources to test-requirements * Updated from global requirements * delete stale ovsdb table entries from mysql db * Add scripts to run on fullstack CI job * Create fullstack integration parts * handle SSL connection in l2gateway agent * Updated from global requirements * configurable port option for l2gw agent * Updated from global requirements 2016.1.0 -------- * Return 404 error against invalid id requests * Make use of neutron\_lib for exceptions * Address deprecated neutron\_lib warnings * Tag the alembic migration revisions for Mitaka * use mutate operation instead of update operation * create Vxlan tunnels from ESXi compute node * unit test case coverage for enable\_manager is False * remove unused parameter in rpc create\_connection * Tag the alembic migration revisions for Liberty * Fix \_get\_l2\_gateway call * Updated from global requirements * Add blank to message when creating resource without "seg\_id" * Fix L2GW service framework for out-of-tree drivers * check the interface type in the request * l2-gateway-api.rst: Make it clear where VNI is taken from * Updated from global requirements * Add these variables to devstack settings * Monitor new ovsdb server without L2gw agent restart * Fix for l2 gateway devstack stack.sh failure * blank was missed in error message * check for ovs agent is running on a host * L2GW: make use of neutron\_lib constants * switch to tempest lib from tempest\_lib * networking-l2gw: make use of neutron\_lib exceptions * adding missed parameter in function signature * support multi segment network for l2gw connection * Remove default config settings set to None * Formatting README in devstack * Remove ovsapp references form .coverage file * Remove deprecated warning * Networking-L2Gateway CI fails * Updated from global requirements * Fix l2-gateway-connection delete * Update translation setup * Fix for HP 3rd Party CI failure * Updated from global requirements * Updated from global requirements * Revert "Fix for HP 3rd Party CI failure" * remove python 2.6 trove classifier * Switch to internal \_i18n pattern, as per oslo\_i18n guidelines * Fix typos in networking-l2gw repo * Fix help string by adding appropriate spaces * Setup for translation * Updated from global requirements * Service driver support in L2gw for south bound * remove thread in l2gw\_callback 1.0.0 ----- * Include l2gw\_plugin.ini while starting Neutron server * tox.ini: Update whitelist\_externals * Remove an extra IFACE\_NAME\_ATTR definition * Fix for the L2gw third party CI failure * Fix for HP 3rd Party CI failure * Updated from global requirements * Updated from global requirements * Change ignore-errors to ignore\_errors * Updated from global requirements * Switch to online db migration * handle connection from multiple ovsdb servers * ovsdb server connection initiation implementation * Updated from global requirements * Increasing the OS\_TEST\_TIMEOUT to 90s * Fix for string type seg-id * End to end scenario test case for l2gateway * L2gateway CLI extension * Updated from global requirements * Fix for Ucast Mac Remote deletion after switch is deleted * Fix for Logical Switch not getting deleted * Register alembic\_migrations at install time * Stop doing any magic cloning of neutron during CI * Import oslo\_messaging instead of oslo.messaging * Remove get\_namespace() from l2gw extensions * Fix failures introduced by the new version of mock * Switch to new oslo library names * Stop using these variables * Fix import errors * Follow up on namespace renaming * Fix oslo imports * Update version for Liberty * Add API tests for L2Gateway extension * Update .gitreview file for project rename * Fix for multiple entries in ucast\_mac\_remote * Fix for internal server error * Fix for writing correct ovsdb identifier * README.rst: Fix a typo * Add API Negative tests for L2Gateway extension * checking the order in update l2gateway * Open development for 2015.2.1 2015.1.1 -------- * Recreation of tunnels and exception handling when adding vm port * Support for specifying segmentation id optional * Retry logic for insert/delete/update MAC failures * Deactivating the switch doesnot delete l2gw-conn * Adding Release and version management for L2GW package * L2gw connection fails for invalid device/interface * Fix for reading large data from OVSDB server * Constraint violation error of physical locator * connection-create/delete to report correct error * Fix for l2-gateway-connection-delete error * L2gw to support VM migration * Fix for L2Gateway fails if OVSDB has pre-populated entries * Add API tests for L2Gateway extension * MAC address of VM not getting populated in OVSDB * L2GW creation REST API output mismatch with input * Fix for OVSDB and Neutron (MYSQL) Tables are not in Sync * Fix for defect 1444183 * Exception priority moved ahead for L2GW not found * Replace neutron with neutron-l2gw in doc * Validation of duplicate segmentation ID * Fix for defect 1439161 * l2-gateway-connection-create reports OVSDBError * Fix for the launchpad bug 1440649 * ucast\_macs and physical\_locators deletion problem * networking-l2gw debian packaging and installation * port\_fault\_status and switch\_fault\_status * Difference in RPC arguments l2gateway Plugin and l2gateway Agent * Re-reading OVSDB data when agent heart beats fail * Add an initial version for the L2 Gateway project * L2 gateway Connection create fails with UnBoundLocalError * networking-l2gw debian packaging and installation * Making deletion of MACs from OVSDB faster * Making l2gateway connection-create/delete reliable * Fix for NoneType Exeception handling * L2 gateway connection creation is fails * networking-l2gw users guide * Changing the log imports * L2Gateway Service Plugin RPC Implementation * README for L2gw to OVSDB communication alternative * L2gateway agent writing to and reading from OVSDB * L2gw service plugin callback registration to ML2 * Modified l2gateway-create/update CLI * Enable E125 continuation line with same indent as next logical line * alembic migration check for 'tox -e pep8' * alembic migration scripts for networking\_l2gw * Defect fixed for l2 gateway DB implementation * Fix in l2gateway agent scheduler logic * get rid of NotFound exception on all() queries * Movement of OVSDB models to db directory * Monitor agent to monitor OVSDB servers immediately * Add pluggable devstack for networking-l2gw * Monkey patching l2gateway agent code * Appended https to the git url * L2gateway CLI Enhancement * Fix to avoid caching the context by L2gw agent * Use new name for callback\_class config option * Fix for l2gateway agent not coming up on devstack * L2Gateway Service Plugin * Fix startup deadlock * Add missing ini file * Refactored unit test cases to improve coverage * L2 gateway agent scheduler * Fix ImportError on l2gatewayclient.v2\_0.client * Refactor reraise of exception by l2 gateway agent * L2gateway extension loading issues fixed * Refactored logic of reading of report\_interval * Initial db migration file for networking\_l2gw * Duplicate entry\_points in setup.cfg * L2gateway db implementation for RESTful API * L2 gateway agent implementation * policy.json modification for L2gateway API * L2 gateway client implementation * L2Gateway DB Models for OVSDB Hardware\_vtep Schema * L2gateway policy.json file * Added python-neutronclient git path * L2 gateway basic agent side implementation * L2gateway common files * L2 gateway basic common config module * Fix tox and add Neutron as project dependency * L2 gateway API implementation * L2 Gateway API - initial manifesto * Initial cookiecutter setup * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/HACKING.rst0000664000175000017500000000024514572557755020035 0ustar00jamespagejamespagenetworking-l2gw Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/LICENSE0000664000175000017500000002363714572557755017256 0ustar00jamespagejamespage 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/MANIFEST.in0000664000175000017500000000046214572557755017776 0ustar00jamespagejamespageinclude AUTHORS include ChangeLog exclude .gitignore exclude .gitreview include networking_l2gw/db/migration/alembic_migrations/README include networking_l2gw/db/migration/alembic_migrations/script.py.mako recursive-include networking_l2gw/db/migration/alembic_migrations/versions * global-exclude *.pyc ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/PKG-INFO0000664000175000017500000001532214572557757017340 0ustar00jamespagejamespageMetadata-Version: 2.1 Name: networking-l2gw Version: 20.0.1.dev5 Summary: APIs and implementations to support L2 Gateways in Neutron. Home-page: https://opendev.org/x/networking-l2gw Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 Requires-Python: >=3.8 License-File: LICENSE =============== networking-l2gw =============== API's and implementations to support L2 Gateways in Neutron. * Free software: Apache license * Source: https://opendev.org/x/networking-l2gw L2 Gateways ----------- This project proposes a Neutron API extension that can be used to express and manage L2 Gateway components. In the simplest terms L2 Gateways are meant to bridge two or more networks together to make them look at a single L2 broadcast domain. Initial implementation ---------------------- There are a number of use cases that can be addressed by an L2 Gateway API. Most notably in cloud computing environments, a typical use case is bridging the virtual with the physical. Translate this to Neutron and the OpenStack world, and this means relying on L2 Gateway capabilities to extend Neutron logical (overlay) networks into physical (provider) networks that are outside the OpenStack realm. These networks can be, for instance, VLAN's that may or may not be managed by OpenStack. More information ---------------- For help using or hacking on L2GW, you can send an email to the `OpenStack Discuss Mailing List `; please use the [L2-Gateway] Tag in the subject. Most folks involved hang out on the IRC channel #openstack-neutron. Getting started --------------- To get started you have to install the l2gw plugin software on the Controller node where you are already running the Neutron server. Then you need a new node, that we call the l2gw node, where you do the actual bridging between a vxlan tenant network and a physical network. The l2gw node could be a bare metal switch that supports the OVSDB schema, or a server with OVS installed. In this example we are going to use a server. In this example the l2gw node has a `ens5` interface attached to a physical segment, and a management interface with IP 10.225.0.27. :: ip link set up dev ens5 apt-get update apt-get install openvswitch-vtep ovsdb-tool create /etc/openvswitch/vtep.db /usr/share/openvswitch/vtep.ovsschema ovsdb-tool create /etc/openvswitch/vswitch.db /usr/share/openvswitch/vswitch.ovsschema # Stop OVS services started by the installer. systemctl is-active --quiet ovs-vswitchd && systemctl stop ovs-vswitchd systemctl is-active --quiet ovsdb-server && systemctl stop ovsdb-server mkdir -p /var/run/openvswitch/ ovsdb-server --pidfile --detach --log-file --remote ptcp:6632:10.225.0.27 \ --remote punix:/var/run/openvswitch/db.sock --remote=db:hardware_vtep,Global,managers \ /etc/openvswitch/vswitch.db /etc/openvswitch/vtep.db ovs-vswitchd --log-file --detach --pidfile unix:/var/run/openvswitch/db.sock ovs-vsctl add-br myphyswitch vtep-ctl add-ps myphyswitch vtep-ctl set Physical_Switch myphyswitch tunnel_ips=10.225.0.27 ovs-vsctl add-port myphyswitch ens5 vtep-ctl add-port myphyswitch ens5 /usr/share/openvswitch/scripts/ovs-vtep \ --log-file=/var/log/openvswitch/ovs-vtep.log \ --pidfile=/var/run/openvswitch/ovs-vtep.pid \ --detach myphyswitch At this point your l2gw node is running. For the configuration of the Openstack control plane you have to check three files: ``neutron.conf``, `l2gw_plugin.ini `__, and `l2gateway_agent.ini `__ Edit your ``neutron.conf`` on the controller node and make sure that in the ``service_plugins`` you have the string ``networking_l2gw.services.l2gateway.plugin.L2GatewayPlugin``. You can add it with: :: sudo sed -ri 's/^(service_plugins.*)/\1,networking_l2gw.services.l2gateway.plugin.L2GatewayPlugin/' \ /etc/neutron/neutron.conf Make sure the neutron-server runs with ``--config-file=/etc/neutron/l2gw_plugin.ini``. The default for the l2gw_plugin.ini file should be okay. Now you are ready to create the database tables for the neutron l2gw plugin using the command: ``neutron-db-manage upgrade heads`` The file `l2gateway_agent.ini `__ is used to configure the neutron-l2gateway agent. The agent is the piece of software that will configure the l2gw node when you interact with the Openstack API. Here it is important to give the pointer to the switch. ``ovsdb_hosts = 'ovsdb1:10.225.0.27:6632'`` The name ``ovsdb1`` is just a name that will be used in the Openstack database to identify this switch. Now that both the l2gw node and the Openstack control plane are configured, we can use the API service to bridge a VXLAN tenant network to a physical interface of the l2gw node. First let's create in Openstack a l2-gateway object. We need to give the interface names and the name of the bridge that we used before in the OVS commands. ``l2-gateway-create --device name="myphyswitch",interface_names="ens5" openstackname`` Use the just created to feed the second command where you do the actual bridging between the VXLAN tenant network and the Physical L2 network. ``l2-gateway-connection-create `` Now let's see what happened. On the l2gw node you can do the commands: :: ovs-vsctl show vtep-ctl show You should see some VXLAN tunnels are created. You will see a vxlan tunnel to each compute node that is hosting an instance attached to the tenant network that you bridge. If there is also a router in this tenant network, you will find a VXLAN tunnel also to the network node. References: * http://networkop.co.uk/blog/2016/05/21/neutron-l2gw/ * http://kimizhang.com/neutron-l2-gateway-hp-5930-switch-ovsdb-integration/ * http://openvswitch.org/support/dist-docs-2.5/vtep/README.ovs-vtep.md.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/README.rst0000664000175000017500000001346214572557755017733 0ustar00jamespagejamespage=============== networking-l2gw =============== API's and implementations to support L2 Gateways in Neutron. * Free software: Apache license * Source: https://opendev.org/x/networking-l2gw L2 Gateways ----------- This project proposes a Neutron API extension that can be used to express and manage L2 Gateway components. In the simplest terms L2 Gateways are meant to bridge two or more networks together to make them look at a single L2 broadcast domain. Initial implementation ---------------------- There are a number of use cases that can be addressed by an L2 Gateway API. Most notably in cloud computing environments, a typical use case is bridging the virtual with the physical. Translate this to Neutron and the OpenStack world, and this means relying on L2 Gateway capabilities to extend Neutron logical (overlay) networks into physical (provider) networks that are outside the OpenStack realm. These networks can be, for instance, VLAN's that may or may not be managed by OpenStack. More information ---------------- For help using or hacking on L2GW, you can send an email to the `OpenStack Discuss Mailing List `; please use the [L2-Gateway] Tag in the subject. Most folks involved hang out on the IRC channel #openstack-neutron. Getting started --------------- To get started you have to install the l2gw plugin software on the Controller node where you are already running the Neutron server. Then you need a new node, that we call the l2gw node, where you do the actual bridging between a vxlan tenant network and a physical network. The l2gw node could be a bare metal switch that supports the OVSDB schema, or a server with OVS installed. In this example we are going to use a server. In this example the l2gw node has a `ens5` interface attached to a physical segment, and a management interface with IP 10.225.0.27. :: ip link set up dev ens5 apt-get update apt-get install openvswitch-vtep ovsdb-tool create /etc/openvswitch/vtep.db /usr/share/openvswitch/vtep.ovsschema ovsdb-tool create /etc/openvswitch/vswitch.db /usr/share/openvswitch/vswitch.ovsschema # Stop OVS services started by the installer. systemctl is-active --quiet ovs-vswitchd && systemctl stop ovs-vswitchd systemctl is-active --quiet ovsdb-server && systemctl stop ovsdb-server mkdir -p /var/run/openvswitch/ ovsdb-server --pidfile --detach --log-file --remote ptcp:6632:10.225.0.27 \ --remote punix:/var/run/openvswitch/db.sock --remote=db:hardware_vtep,Global,managers \ /etc/openvswitch/vswitch.db /etc/openvswitch/vtep.db ovs-vswitchd --log-file --detach --pidfile unix:/var/run/openvswitch/db.sock ovs-vsctl add-br myphyswitch vtep-ctl add-ps myphyswitch vtep-ctl set Physical_Switch myphyswitch tunnel_ips=10.225.0.27 ovs-vsctl add-port myphyswitch ens5 vtep-ctl add-port myphyswitch ens5 /usr/share/openvswitch/scripts/ovs-vtep \ --log-file=/var/log/openvswitch/ovs-vtep.log \ --pidfile=/var/run/openvswitch/ovs-vtep.pid \ --detach myphyswitch At this point your l2gw node is running. For the configuration of the Openstack control plane you have to check three files: ``neutron.conf``, `l2gw_plugin.ini `__, and `l2gateway_agent.ini `__ Edit your ``neutron.conf`` on the controller node and make sure that in the ``service_plugins`` you have the string ``networking_l2gw.services.l2gateway.plugin.L2GatewayPlugin``. You can add it with: :: sudo sed -ri 's/^(service_plugins.*)/\1,networking_l2gw.services.l2gateway.plugin.L2GatewayPlugin/' \ /etc/neutron/neutron.conf Make sure the neutron-server runs with ``--config-file=/etc/neutron/l2gw_plugin.ini``. The default for the l2gw_plugin.ini file should be okay. Now you are ready to create the database tables for the neutron l2gw plugin using the command: ``neutron-db-manage upgrade heads`` The file `l2gateway_agent.ini `__ is used to configure the neutron-l2gateway agent. The agent is the piece of software that will configure the l2gw node when you interact with the Openstack API. Here it is important to give the pointer to the switch. ``ovsdb_hosts = 'ovsdb1:10.225.0.27:6632'`` The name ``ovsdb1`` is just a name that will be used in the Openstack database to identify this switch. Now that both the l2gw node and the Openstack control plane are configured, we can use the API service to bridge a VXLAN tenant network to a physical interface of the l2gw node. First let's create in Openstack a l2-gateway object. We need to give the interface names and the name of the bridge that we used before in the OVS commands. ``l2-gateway-create --device name="myphyswitch",interface_names="ens5" openstackname`` Use the just created to feed the second command where you do the actual bridging between the VXLAN tenant network and the Physical L2 network. ``l2-gateway-connection-create `` Now let's see what happened. On the l2gw node you can do the commands: :: ovs-vsctl show vtep-ctl show You should see some VXLAN tunnels are created. You will see a vxlan tunnel to each compute node that is hosting an instance attached to the tenant network that you bridge. If there is also a router in this tenant network, you will find a VXLAN tunnel also to the network node. References: * http://networkop.co.uk/blog/2016/05/21/neutron-l2gw/ * http://kimizhang.com/neutron-l2-gateway-hp-5930-switch-ovsdb-integration/ * http://openvswitch.org/support/dist-docs-2.5/vtep/README.ovs-vtep.md.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/bindep.txt0000664000175000017500000000055514572557755020245 0ustar00jamespagejamespage# This is a cross-platform list tracking distribution packages needed for install and tests; # see https://docs.openstack.org/infra/bindep/ for additional information. mysql-client [platform:dpkg !platform:debian] mysql-server [platform:dpkg !platform:debian] mariadb-server [platform:debian] postgresql postgresql-client [platform:dpkg] libpq-dev [platform:dpkg] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/contrib/0000775000175000017500000000000014572557757017700 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/contrib/README_install_and_config_l2gateway_plugin.rst0000664000175000017500000000356714572557755030632 0ustar00jamespagejamespageDebian packaging, installation and configuration of neutron-l2gateway plugin. Prior requirements script install_and_config_l2gateway_plugin.sh will run on openstack controller. install_and_config_l2gateway_plugin.sh will create and install debian package of networking-l2gw, and it will enable neutron-l2gateway service plugin. Creation of debian package requires copyright, changelog, control, compat and rules file inside the debian folder. debian folder is to be placed inside the folder which needs to be packaged (networking-l2gw). command dpkg-buildpackage -b, builds debian package of networking-l2gw which uses the files mentioned inside debian folder to create debian package. please refer https://www.debian.org/doc/manuals/maint-guide/dreq.en.html for further details. Installation procedure example: The script will ask for further details for packaging and installing as shown below. press ENTER for assigning default values to debian/changelog and debian/control file. #info for debian/changelog file enter package name for debian/changelog networking-l2gw enter package version for debian/changelog 1.0 #info for debian/control file enter the networking-l2gw source name networking-l2gw enter the networking-l2gw package name networking-l2gw enter the version number 1.0 enter the maintainer info user@hp.com enter the architecture all enter the description title l2gateway package enter the description details description details of l2gateway package #info of neutron.conf file path press ENTER for assigning default file path /etc/neutron/neutron.conf for neutron.conf file. enter neutron.conf file path /etc/neutron/neutron.conf after execution of install_and_config_l2gateway_plugin.sh check neutron-server status. sudo service neutron-server status neutron-server start/running, process 17876 and also check service_plugins in neutron.conf file whether l2gw is enabled or not. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/contrib/README_install_l2gateway_agent.rst0000664000175000017500000000371214572557755026253 0ustar00jamespagejamespageDebian packaging and installation of neutron-l2gateway-agent. Prior requirements script install_l2gateway_agent.sh will run on neutron installed and configured nodes (controller, compute and network nodes). install_l2gateway_agent.sh will create and install debian package of networking-l2gw, and it will start neutron-l2gateway-agent. Creation of debian package requires copyright, changelog, control, compat and rules file inside the debian folder. debian folder is to be placed inside the folder which needs to be packaged (networking-l2gw). command dpkg-buildpackage -b, builds debian package of networking-l2gw which uses the files mentioned inside debian folder to create debian package. please refer https://www.debian.org/doc/manuals/maint-guide/dreq.en.html for further details. Installation procedure example: The script will ask for further details for packaging and installing as shown below. press ENTER for assigning default values to debian/changelog and debian/control file. #info for debian/changelog file enter package name for debian/changelog networking-l2gw enter package version for debian/changelog 1.0 #info for debian/control file enter the networking-l2gw source name networking-l2gw enter the networking-l2gw package name networking-l2gw enter the version number 1.0 enter the maintainer info user@hp.com enter the architecture all enter the description title l2gateway package enter the description details description details of l2gateway package #info for neutron-l2gateway-agent.conf file enter the networking-l2gw binary path /usr/bin/neutron-l2gateway-agent enter the neutron config file path /etc/neutron/neutron.conf enter the l2gateway agent config file path /usr/etc/neutron/l2gateway_agent.ini enter the l2gateway log file path /var/log/neutron/l2gateway-agent.log after execution of install_l2gateway_agent.sh check neutron-l2gateway-agent status sudo service neutron-l2gateway-agent status neutron-l2gateway-agent start/running, process 15276 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/contrib/install_and_config_l2gateway_plugin.sh0000775000175000017500000000643714572557755027421 0ustar00jamespagejamespage#!/bin/bash # 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 if [ $(id -u -r) -ne 0 ] then echo "Requires root privileges. Please re-run using sudo." exit 1 fi apt-get update -y apt-get install devscripts -y apt-get install debhelper -y apt-get install dh-make -y #read the package name and version,if not take default values and enter to #debian/changelog file. cd .. if [ -f "debian/changelog" ] then echo info for debian/changelog file echo enter package name for debian/changelog read pck sed -i 's/PACKAGE/'${pck:-networking-l2gw}'/' debian/changelog echo enter package version for debian/changelog read pck_ver sed -i 's/VERSION/'${pck_ver:-1.0}'/' debian/changelog fi #control file contains various values which dpkg, dselect, apt-get, apt-cache, aptitude, #and other package management tools will use to manage the package. #It is defined by the Debian Policy Manual, 5 "Control files and their fields". if [ -f "debian/control" ] then echo info for debian/control file echo enter the networking-l2gw source name read src_name echo enter the networking-l2gw package name read pck_name echo enter the version number read ver echo enter the maintainer info read maintainer_info echo enter the architecture read architecture echo enter the description title read description echo enter the description details read description_details sed -i 's/source/'${src_name:-networking-l2gw}'/' debian/control sed -i 's/package/'${pck_name:-networking-l2gw}'/' debian/control sed -i 's/version/'${ver:-1.0}'/' debian/control sed -i 's/maintainer/'${maintainer_info:-user@openstack}'/' debian/control sed -i 's/arch/'${architecture:-all}'/' debian/control sed -i 's/desc/'${description:-networking-l2gw}'/' debian/control sed -i 's/desc_details/'${description_details:-networking-l2gw}'/' debian/control fi #dpkg-buildpackage, build binary or source packages from sources. #-b Specifies a binary-only build, no source files are to be built and/or distributed. echo building debian package dpkg-buildpackage -b cd ../ if [ -z "$pck_name" ] then pck_name="networking-l2gw" fi if [ -z "$pck_ver" ] then pck_ver=1.0 fi if [ -z "$architecture" ] then architecture="all" fi echo installing $pck_name\_$pck_ver\_$architecture.deb dpkg -i $pck_name\_$pck_ver\_$architecture.deb echo enter neutron.conf file path read neutron_conf l2gw_plugin=", l2gw" while read line do if [[ $line == *"service_plugins"* ]] then if [[ $line != *$l2gw_plugin* ]] then serv_plugin=$line$l2gw_plugin sed -i "s|$line|$serv_plugin|" ${neutron_conf:-/etc/neutron/neutron.conf} fi fi done <${neutron_conf:-/etc/neutron/neutron.conf} service neutron-server restart ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/contrib/install_l2gateway_agent.sh0000775000175000017500000000714314572557755025045 0ustar00jamespagejamespage#!/bin/bash # 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 if [ $(id -u -r) -ne 0 ] then echo "Requires root privileges. Please re-run using sudo." exit 1 fi #apt-get update -y apt-get install devscripts -y apt-get install debhelper -y apt-get install dh-make -y #read the package name and version,if not take default values and enter to #debian/changelog file. cd .. if [ -f "debian/changelog" ] then echo info for debian/changelog file echo enter package name for debian/changelog read pck sed -i 's/PACKAGE/'${pck:-networking-l2gw}'/' debian/changelog echo enter package version for debian/changelog read pck_ver sed -i 's/VERSION/'${pck_ver:-1.0}'/' debian/changelog fi #control file contains various values which dpkg, dselect, apt-get, apt-cache, aptitude, #and other package management tools will use to manage the package. #It is defined by the Debian Policy Manual, 5 "Control files and their fields". if [ -f "debian/control" ] then echo info for debian/control file echo enter the networking-l2gw source name read src_name echo enter the networking-l2gw package name read pck_name echo enter the version number read ver echo enter the maintainer info read maintainer_info echo enter the architecture read architecture echo enter the description title read description echo enter the description details read description_details sed -i 's/source/'${src_name:-networking-l2gw}'/' debian/control sed -i 's/package/'${pck_name:-networking-l2gw}'/' debian/control sed -i 's/version/'${ver:-1.0}'/' debian/control sed -i 's/maintainer/'${maintainer_info:-user@openstack}'/' debian/control sed -i 's/arch/'${architecture:-all}'/' debian/control sed -i 's/desc/'${description:-networking-l2gw}'/' debian/control sed -i 's/desc_details/'${description_details:-networking-l2gw}'/' debian/control fi #dpkg-buildpackage, build binary or source packages from sources. #-b Specifies a binary-only build, no source files are to be built and/or distributed. echo building debian package dpkg-buildpackage -b cd ../ if [ -z "$pck_name" ] then pck_name="networking-l2gw" fi if [ -z "$pck_ver" ] then pck_ver=1.0 fi if [ -z "$architecture" ] then architecture="all" fi echo installing $pck_name\_$pck_ver\_$architecture.deb dpkg -i $pck_name\_$pck_ver\_$architecture.deb echo enter the networking-l2gw binary path read l2gw_bin_path echo enter the neutron config file path read neutron_conf echo enter the l2gateway config file path read l2gw_conf echo enter the l2gateway log file path read l2gw_log sed -i 's|l2gw_bin_path|'$l2gw_bin_path'|' networking-l2gw/contrib/neutron-l2gateway-agent.conf sed -i 's|neutron_conf|'$neutron_conf'|' networking-l2gw/contrib/neutron-l2gateway-agent.conf sed -i 's|l2gw_conf|'$l2gw_conf'|' networking-l2gw/contrib/neutron-l2gateway-agent.conf sed -i 's|l2gw_log|'$l2gw_log'|' networking-l2gw/contrib/neutron-l2gateway-agent.conf cp networking-l2gw/contrib/neutron-l2gateway-agent.conf /etc/init/ service neutron-l2gateway-agent restart ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/contrib/neutron-l2gateway-agent.conf0000664000175000017500000000057114572557755025233 0ustar00jamespagejamespage# vim:set ft=upstart ts=2 et: description "Neutron L2GW Agent" start on runlevel [2345] stop on runlevel [!2345] respawn chdir /var/run pre-start script mkdir -p /var/run/neutron chown neutron:root /var/run/neutron end script exec start-stop-daemon --start --chuid neutron --exec l2gw_bin_path -- --config-file=neutron_conf --config-file=l2gw_conf --log-file l2gw_log ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/debian/0000775000175000017500000000000014572557757017462 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/debian/changelog0000664000175000017500000000021414572557755021327 0ustar00jamespagejamespagePACKAGE (VERSION) UNRELEASED; urgency=medium * Initial release. (Closes: #XXXXXX) -- root Mon, 23 Mar 2015 02:17:32 -0700 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/debian/compat0000664000175000017500000000000214572557755020656 0ustar00jamespagejamespage9 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/debian/control0000664000175000017500000000017414572557755021065 0ustar00jamespagejamespageSource: source Version: version Maintainer: maintainer Package: package Architecture: arch Description: desc desc_details ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/debian/copyright0000664000175000017500000000000014572557755021401 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/debian/rules0000775000175000017500000000005614572557755020541 0ustar00jamespagejamespage#!/usr/bin/make -f %: dh $@ --with python2 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/devstack/0000775000175000017500000000000014572557757020044 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/devstack/README.rst0000775000175000017500000000167214572557755021542 0ustar00jamespagejamespage====================== Enabling in Devstack ====================== 1. Download DevStack 2. Add this repo as an external repository and configure following flags in ``local.conf``:: [[local|localrc]] enable_plugin networking-l2gw https://github.com/x/networking-l2gw enable_service l2gw-plugin l2gw-agent OVSDB_HOSTS=:: 3. If you want to override the default service driver for L2Gateway (which uses L2Gateway Agent with RPC) with an alternative service driver, please give that alternative service driver inside the parameter NETWORKING_L2GW_SERVICE_DRIVER of your ``local.conf``. For example, to configure ODL service driver to be used for L2Gateway, you need to include ODL Service Driver in ``local.conf`` as below: NETWORKING_L2GW_SERVICE_DRIVER=L2GW:OpenDaylight:networking_odl.l2gateway.driver.OpenDaylightL2gwDriver:default 3. Read the settings file for more details. 4. run ``stack.sh`` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/devstack/devstackgaterc0000664000175000017500000000160614572557755022762 0ustar00jamespagejamespage# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This script is executed in the OpenStack CI jobs located here: # http://git.openstack.org/cgit/openstack-infra/project-config/tree/jenkins/jobs/networking-l2gw.yaml # export OVERRIDE_ENABLED_SERVICES=key,n-api,n-cpu,n-cond,n-sch,n-crt,n-cauth,n-obj,g-api,g-reg,c-sch,c-api,c-vol,q-meta,q-dhcp,rabbit,mysql,dstat,l2gw-plugin,l2gw-agent,q-svc,q-l3,q-agt ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/devstack/plugin.sh0000775000175000017500000000571014572557755021702 0ustar00jamespagejamespage#!/bin/bash # devstack/plugin.sh # Functions to control the configuration and operation of the l2gw # Dependencies: # # ``functions`` file # ``DEST`` must be defined # ``STACK_USER`` must be defined # ``stack.sh`` calls the entry points in this order: # Save trace setting XTRACE=$(set +o | grep xtrace) set +o xtrace function install_l2gw { setup_develop $L2GW_DIR Q_PLUGIN_CONF_PATH=etc/neutron/plugins/ml2 Q_PLUGIN_CONF_FILENAME=ml2_conf.ini Q_PLUGIN_CONF_FILE=$Q_PLUGIN_CONF_PATH/$Q_PLUGIN_CONF_FILENAME } function configure_agent_conf { sudo cp $L2GW_DIR/etc/l2gateway_agent.ini $L2GW_CONF_FILE iniset $L2GW_CONF_FILE ovsdb ovsdb_hosts $OVSDB_HOSTS } function start_l2gw_agent { run_process l2gw-agent "$L2GW_AGENT_BINARY --config-file $NEUTRON_CONF --config-file=$L2GW_CONF_FILE" } function run_l2gw_alembic_migration { $NEUTRON_BIN_DIR/neutron-db-manage --config-file $NEUTRON_CONF --config-file /$Q_PLUGIN_CONF_FILE upgrade head } function configure_l2gw_plugin { sudo cp $L2GW_DIR/etc/l2gw_plugin.ini $L2GW_PLUGIN_CONF_FILE neutron_server_config_add $L2GW_PLUGIN_CONF_FILE } function configure_tempest_for_l2gw { if is_service_enabled tempest; then iniset $TEMPEST_CONFIG l2gw l2gw_switch "cell08-5930-01::FortyGigE1/0/1|100" source $TEMPEST_DIR/.tox/tempest/bin/activate pip install -r $L2GW_DIR/test-requirements.txt deactivate fi } # main loop if is_service_enabled l2gw-plugin; then if [[ "$1" == "source" ]]; then # no-op : elif [[ "$1" == "stack" && "$2" == "install" ]]; then install_l2gw elif [[ "$1" == "stack" && "$2" == "test-config" ]]; then configure_tempest_for_l2gw elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then neutron_service_plugin_class_add $L2GW_PLUGIN configure_l2gw_plugin run_l2gw_alembic_migration if is_service_enabled neutron-api || is_service_enabled q-svc; then echo_summary "Configuring networking-l2gw" if [ "$NETWORKING_L2GW_SERVICE_DRIVER" ]; then inicomment $L2GW_PLUGIN_CONF_FILE service_providers service_provider iniadd $L2GW_PLUGIN_CONF_FILE service_providers service_provider $NETWORKING_L2GW_SERVICE_DRIVER fi fi elif [[ "$1" == "stack" && "$2" == "post-extra" ]]; then # no-op : fi if [[ "$1" == "unstack" ]]; then # no-op : fi if [[ "$1" == "clean" ]]; then # no-op : fi fi if is_service_enabled l2gw-agent; then if [[ "$1" == "source" ]]; then # no-op : elif [[ "$1" == "stack" && "$2" == "install" ]]; then install_l2gw elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then configure_agent_conf start_l2gw_agent fi if [[ "$1" == "unstack" ]]; then #no-op : fi if [[ "$1" == "clean" ]]; then #no-op : fi fi # Restore xtrace $XTRACE ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/devstack/settings0000775000175000017500000000133514572557755021632 0ustar00jamespagejamespage# Devstack settings L2GW_DIR=$DEST/networking-l2gw L2GW_AGENT_BINARY="$NEUTRON_BIN_DIR/neutron-l2gateway-agent" L2GW_PLUGIN=${L2GW_PLUGIN:-"l2gw"} L2GW_CONF_FILE=/etc/neutron/l2gateway_agent.ini L2GW_PLUGIN_CONF_FILE=/etc/neutron/l2gw_plugin.ini #NETWORKING_L2GW_SERVICE_DRIVER=L2GW:OpenDaylight:networking_odl.l2gateway.driver.OpenDaylightL2gwDriver:default # # Each service you enable has the following meaning: # l2gw-plugin - Add this config flag to enable l2gw service plugin # l2gw-agent - Add this config flag to enable l2gw agent # # An example of enabling all-in-one l2gw is below. # enable_service l2gw-plugin l2gw-agent # # This can be overridden in the localrc file OVSDB_HOSTS=${OVSDB_HOSTS:-"ovsdb1:127.0.0.1:6632"} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/doc/0000775000175000017500000000000014572557757017005 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/requirements.txt0000664000175000017500000000056714572557755022277 0ustar00jamespagejamespage# 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. sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 sphinxcontrib-blockdiag>=1.5.4 # BSD sphinxcontrib-seqdiag>=0.8.4 # BSD ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/doc/source/0000775000175000017500000000000014572557757020305 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/source/conf.py0000775000175000017500000000563514572557755021616 0ustar00jamespagejamespage# -*- 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. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- 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', 'openstackdocstheme' ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/networking-l2gw' openstackdocs_pdf_link = True openstackdocs_auto_name = False openstackdocs_bug_project = 'networking-l2gw' openstackdocs_bug_tag = '' html_theme = 'openstackdocs' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'networking-l2gw' copyright = u'2013, OpenStack Foundation' # 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 # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # -- 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_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-%s.tex' % project, u'Networking L2GW Documentation', u'networking-l2gw developers', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} latex_elements = { 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', # openany: Skip blank pages in generated PDFs # oneside: Use the same page layout for both even and odd pages 'extraclassoptions': 'openany,oneside', } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/source/contributing.rst0000664000175000017500000000011314572557755023537 0ustar00jamespagejamespage============ Contributing ============ .. include:: ../../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/doc/source/images/0000775000175000017500000000000014572557757021552 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/source/images/L2GW_deployment.png0000664000175000017500000006744014572557755025244 0ustar00jamespagejamespagePNG  IHDR1`&qsRGBgAMA a pHYsodnIDATx^eUyiv?8"(>NΎfTX@TQPR*K(@bE*B%QJHhVRί9\c5ƘyگG}ך}Ss-n',doN|p2僓, `d'X>8N|p2僓, `d'X>8N|p2僓, `d'X>8N|p2僓, `d'X>8N|p2僓, `d'X>8N|p2僓, `d'X>89doq2%, `d~1^a `t_MȰNFN `tp22Fp2gd18' 8dddF'#c'pN0:8y>}):d#N;NCJ:o]{h0<|tW:m'pN0:88N9tʾ&,klleMtV2;'pN0:8[6XkS%7di٥&u*]̾Ofc l>Rɠn:r͝|g~?wny7?|' U]Uuz]@dP1?YWuuanyOμwnW B=_x= zɠ>N2!dA/~P8ԇmBH j'!AdP8!dqp2 '#,N5A}dɠFp288NB'ɠ>pٷH0m/ę_ל?xqy>3ݽ3g]{S1)ݴ3o+OMƝQ삆Xlj0B灝Gų6q7qddP#8N3T̟,L':b*`gޢTbQ~,ڀ]ЖWal]uŽϧ㖴#1YI-GN6Suk%t]N*"yžTgäOp2 '} [4/{R9z3:ϮKIaKz6ƜL^ZiwYj]'ɹ.'lg^}Az N5A}d=*J<=C_(.hS-}*5l&;״j]/Id-ͽYlODI-l;m{E.&}AdP8Y$—Ϳ~O5„#g2:d:cwLF5dVM5Eع/s [L$Tm2XE/Z:rc]+ŮIoI edP#8N3*dzU< -9o4XdD360I3;统xI[{3*4e;yMk&c6sM{/%~0Y-8NLt2G]_DŽnQhÞ5XRɰĽF [V=[w;jYQ8Zftм9jƮ.'K,_XKgY j'z&u2t߰@Ը5X(xa:Nィ50Rz)zl\vboWfnTuV8VqqvH'ɠ>pw]1L*&Lh#脩$cZݥc*CGD쳰ě[FlΆ_6`c;My}Raj8oIodg8̾YCg7L'ɠ>pb*HB'ɠ>p2BdP#8NFY j'!AdP8!dqp2 '#,N5A}dɠFp288NB'ɠ>p2BdP#8NFY j'!AdP8!dqp2 '#,N5A}dɠFp288NB'ɠ>p2BdP#8NFY j'!AdP8!dqp2 '#,N5A}λN & j'8`Bw[A} t0!X'޵k_N8㺇:]B,_-;B yGW!)O p2Ko}ńs'>sNP8TW_;ιN;&w}.^ *p2R 4eBHQ78? =6p2cZv閳_|ӝ !ef}O<|%[2 f}䖇Oӯۮ%w3͚RJι/c:PU|???k/F˾O?֙$%vcg>/6UU[v_Yp2<;1xWoupO\!G)"ǜ_`W}NV Y2Yp2WRۡ˾|8R2 E',~=υ(ՄBdk,p "dk QNV2*/#7]Pv4j%E,bYвed+'X@!eK' 8| -p2>dsXYвj'N%O,hٔd'xҩ?~;IGdӀN ` ~;I÷eN ` pbj'N8Yd'XXpZNV,8Y-d}'+p2>dk NV 8@p25Ɋo>`p2>dka'Xd#( 8d%Ad}'+} {^<_ux~џ< *8d%Ay.ɴ>wNɀFFNN mALIteZV\ qMڦm/h '[ <_ud2Ǻb$W9GbBFqrk8dP} SwލW[C'#E;m'Wc\?(ퟒg NNNF N8v27Mv.b*6Ч`{+^dyddd{qY\D7X ba`x Ch].tm8Uk8Ydddɢ~ilX eӷz:9VS鍔x8EjйHz'q'ǚ)N&cbM DvGɚ>zACN&˱=,>6ɴLwDCnfl\op

0jfl#OCSҌg3?-f&d^z3H/#AN':[;GiFeYSwdd+8Y/p22v6hKnX4dztifr#%ݯol޹O3F,*u)X ⼭vE׉a=PlŶ&!FT&ue}Nf"]5[N - 6snzwÚe'ӝeɍi h`26fZfciҙץI]eH&C4cY-qGwzễFlשLOwnhQn1ZT%ME@Zoz-(LJHLJ챝1q'xژ-HoG8Tbp^nad7T6rJiɔ -R'+3h_Y<&:"&b{_*,Nr7-[Rt3:+k`ɎdUw #%$t,:)2 -ɍT&՝x*Ml" E0U,,vMEdLśYuzd#p^nad4Y!˻Nduiq>e1ijCLRЄ)J$I&gb:wc;ڧ]6OD㸷dªN՛6Bqbk 8Z̢"5FD8tje1jNhYMdzMS1alpg[N֋Zܖ1MA?6;#Nk7j4 a͂e54Bb|8u0)[%: NBnXdѲ!i|8]߰D瓼.J'׌&F~ul2^\Wq635ofDL7:YŵgY,dTμѓ3H:O}̚Zౄz#8Y/*ja-؜ƬE5X+mI9Y*XvNA}t?&>*^*4߲}jC XҧѬ)dXkNF N9٬(K\O5T,u\bEkLR'[i536اй̜,,85 BF֕Zz'K&b'ǎW"D,%)&5Ng4UlZ7~") !#YMp2Rop2ZfGd(Q@I5+X|TijN;7=ę+٠,h4\-Hɠ ډ%:*t8oڤAzNc'ŊX{l~I4GqҮoԘ BF6Y-Hɠ& [OɈtjqoo'uVwjewO5ΖY?d|bLc5b6x*2 ddt '#'[X<6ƇɘSjtGuƦ6uJOqjNF N8N6z:;dh`r,K꜒iuJhZؠ={3ND-H'FOhE {fh?y;X\`htebMjNctn*J;L1-Hɀ艍O3D[o)ҠT̊Ni4{,NGi.eamЭ“yBN8=iy_}2$}cH3_!.l&gk.vxʜ敨z2N^Z44Rop24gZ&21C}%i#&gf4}AlIdѨcֳNfc; OFN8=Ɓv͞.q|lLcפ龿9lkI?65q}#x> -2-{ƶ^dZ~ojlNymۮ)QHɀFFFSo7oE19<YؼSR!44Rop2ѳZqnYn:B&hhd@ #gAAHvfLHɀFFF2AC#g뮗ë8Y/had?+ zf02v4!5=/?JiOy}YG]g"Uxg|hK;إ;;6 p22zz65Li*^mL6SXĂǺ̬${,Mx*U՜LL3tt&.t.hR-M:aixn^x'Ek-LfnXxW7E8h՚ҚB&lh%JM:Yoь4\vSqrE {ޥnq8Y/lad⬫,2 k,M! 6ԁJYdQҳjbwz` #gf5-ݰfbz hJ$Iut6uf Z-EK:Y$+uwz` #'2 k_D - l&} J>Φ3dfI LWК՜,.ѩNnҢkhS-'E-LF3e,8.!m64ά蔽w5h4O̊RHL' t|ɚFLvh5 N^!64}RTk-k>x.-QR'LNW藂F&FMe,;YfH~TYӒh H:?+R'SZz\'K1A-'녃sb=zD/xX_K gI{l)ڒvp{_`ZaҲ pHp^8haoiO궚ijN 7{;sAhi:q,5KŻhw[{6hL|7YZv;[6|# R'3\ZxNә4/'/ , \0)>V%12`H:?+R'SZz\'K1A ''#'Z=8YF N02zpG!:>NifӶ4u)Zgrٵ@zNFFNNN}+{^:dԮL)Ec Zc C]P֚csj#ɼ'#c'''KD'4QѴ4('O;j+vC^wÇd2DaZG.bof&@]9ٕ.2W5)[ƶ@ ژ',>8YLa9C #%1f/8U.]:J|`ǽ+8Y/p22vp<^><8Y'*dzJt)e6c ŕ&}4ieLYl{NFFNNNft\jv&*]G Sч>c,a -ql68dddyddflT̺JQsuJ]J1K-:5f[ǽt#s2oo*8dddyddӬp{7I3TbL7thD;l5'q\Tp22-1$U zFVoELOxz2Ћ-&GL/{.5Udы8oNN37 F/0jGo}DA˜ezAEFbBv*h3p p2Rop2èIO+h3p p2Rop2è=,dVh+p p2Rop2è1 -NNF NuqBfA˜ezAEFͰY2dddP7zQ2 ZV=8Y878ԍ^a2 ZV78Y878ԍ^a -^n{&@?p2Rop25BfA*F`A?p2Rop2/2 ZV+zzAEFNB,hY #NF Nuqyd8!e'.878ԍ^a䎡̂U0~ddP7z/2 ZVzц- AEFS,hY5 #GNIW|KA'ы80Yв:ГFN -s F/0rTBfA*@TA?]qm7V谳l<*̔ cdkZ!e)m{^D5@эN7ų !eE(aV֯M[߼d p2(k_Syk=.#zx1n0VY],iN'ٽڵ ߵ#ue;6†܅4ZBfHp28)V0'd) 'ɚE]AcG,kN'O#Ń5]8fXY2 6NF'kyh\!e8l?N,8YklL,ciN'O#Ń5 NCe-`#dixpfa8! e8l?N,8Y# -d 'ɚ'kq2dp24R<8YdS,iN'O#Ń5 N2dp24R<8YdJ,hN'O#Ń5 Ni̲Q-`#dixpf\ !lHp28)Yp2OӮ3oxS޹`n[iN'O#Ń5 N{_] r];6^H粝haC`#dixpfzXuG{#?O='kb=oc59`Pp24Rsdix068Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4 N {ƪk>sdix068Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4 N {ƪk>sdix068Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4 N {ƪk>ss^{ 'sL#SlO'kb=oc59`Pp24Rsdix068Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4 N {ƪk>sdix068Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4 N {ƪk>sdix068Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4 N {ƪk>sdix068Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4]'=@[yha)Gl U|0AHۣalp)6V]ў'O#SlzXuG{#?O='kb=oc59`Pp24Rsdix068Y[yha)Gd Rlm= NFgVCIg6m{AG;-5H6V]ў'O#ųf+ܼs3H-N yfzXuG{#?OVx_YN yfzXuG{#?OV16Yum A'HꚏF0(8)>02ٞgk`d >iFU|0AHlip;k`d >iF#RpYg#i͚먡壷ﰇׂF0(8)NZpJOwގ.feQCᵠ= NF'kARMO'}2 NF'kA{SMio U` NF'k=p zƴ%o;gՕ ԁF0(8)Yyի zMpC?S#?OV 8Yd ҿ޵XBfYuR]ў'O#œ TOoQuG{#?O^+LS]['kb=oc59`Pp24Rp޼sբ"8YO=oc59`Pp24R<=[ᮽdcZJum A'HꚏF0(8)PO0T$O}ҌTyha tHBR]['k=:o-OU5RpJdz_߾وuTWѶ'O#ųf+38Yd G}Sc%:v7fX]嘓:t'sL#ӧ.Num 96Czoߘi'?ҡꚏvF0(8)>pqk`|y.@w{w\w-#{ύ޷ꚏvF0(8)pAk`d ҿ޵80Hsc֝lhaߣWKum Az#b [V]dixR]['k>n>iڿ}ӿ~= NF+\-յE0pS](eT&U|0AH4յE0pYao0H׈k>sdixZa"8Y[yha)Gd Rlm= NF c5HꚏF0(8)b{4 N {ƪk>sdix禺N yfzXuG{#?OV'kiFU|0AHi8KpS4#{Ӱ@uG{#?OVG? }}qF57<)cuG{#?OyEO_A[kn$59`Pp24R<={tXB?$VkF=@'S>U|0AHiS][06S:C̬= NFg+\"DAz/,ڞ"-haYW+"DAz/=d}59 NFgp6յE #h>^iKqWw>Dp24R<}zT!/ {od =}amЧޫ٥= NF'-BDO_Aw9YX4&U|0AH4յE #hb=oc59`Pp24RiFU|0AHlwo;!SյE #h00eյEd>iF>1_#?OVo7kZO/Hgc7({ٟRW'O#ųf+7EC^p>MNîPvdixlZaM^pdZ<[J9N8tdKԀF0(8)=zdFzo9ً@dixpZgGo9Z'k>n`B* gq2~ "8)epS漢Mo2L⿻'O#ӧ.NV/8Yk>+ 5֬e훼FT]ў'O#œ T!ƚ}׈k>sdixZa"Dp(6V]ў'O#Sl ZzXuG{#?OVhZ?-B'k0AHiS][N=G6߾1K[I]G;#?OV88Yd.`}'}m NFO+\^pXLoֵ1d`dix'kd~"`Pp24R<}ZT!FOKpT|0AH4յEdѧ;N6߾kD59`Pp24Rsdix08Yk[yha)GdQlm= NF FꚏF0(8)b{4LN{ƪk>sdix08Yk[yha)GdQlm= NF FꚏF0(8)b{4LN{ƪk>sdix08Yk[yha)GdQlm= NF FꚏF0(8)b{4LN{ƪk>sdix08Yk[yha)GdQlm= NF 3k8(6V]ў'O#Sl xWo8(6V]ў'O#Sl ZzXuG{#?O=&'kb=oc59`Pp24Rsdix0/x{ {ƪk>sdix08Yk[yha)GdQlm= NF FꚏF0(8)b{4LN{ƪk>sdix08Yk[yha)GdQlm= NF FꚏF0(8)b{4LN'ZڱzE:aC`#dixpɜq |5vyã^WctS:tdp2(z ôMOwJu<@z`#dQ<&'s2lCB&p28On ɼ2mP8l?gZUN昩~W)~BYʄF0(8ϴZN~}nYW)2~b/N+sdWri Ռ z~J_hai!B!Kpڠ_OWBm>`Pp2V<-4Dt*{mx7ɚcuF0(8, Rҷʶػb3;󶕓ozOntW 's NOŃՒA}0Ѯٻ\6fT9Y:+X4|e`p2dT<8Y-7cG,^>iilvfd6*8<8cp2x*d8fl+9٬rul\3KLc߉i 5ZFdxdwYj*[d>NfNVKp2'#'s N柴+p[wFF# jb@'uosL3Ҹr١NI2jR'6fpɠ&t2EB8}lh`p2BȨ1'ؘ' jbX'Kog~g6 ZBF^?:G'ɢ?%z>ƱK'#55~}dHdP:b%J%l4v{fRiFBƜNV;8N&2J';NfD+fLvp2BܤNfD3j'ͭ[eλb,GfR*L^p2B:!3S P'8NVlpas-Osκ{g=|5S)K>-+GCNzO:ڇtٳnܡ[F]g ՜ AqW_?pfp2fuYUN[']v.v%wsG7>?g+O|]~?mkO59'␓Y53(a^q7~ 9[v\Vs~e_=3?8cp2(dpsMz?w?.a#EzS=ʯOcONp2dOSyd3;S/C_g?ߑG^{_;;gdd u0d-7:5wn׉su^Ǹ̟o{&]o_ Bp2dC Jt Ko~#.w?':_Bp2dC l |FrW|e_|AA>a4({^:4kcNZz.{O2 A>a48YkϾ4x^!;p2G!"ߌ-:pWge;8d9;yU"'|h*oQ]آy7?y;;/F'7_.|6}=_ /2*BCwdzѐSFS1mLq/?_{pU/}ft>S?cyU"cD'뼌ؙPu0u5FY:_|'g\ȉ~k/}{wgtN3?ȹI2 ѫ"4DpN'nFS|ƨ j_Y78mn ͻ֟__|Rbyw6'Ȼ.K>tT,^!;p2?Pu0uϴ:Hhߞq_龓/ 6xnq^g's5_<[+Oh=c_ynNOnY7~5KˀF8L_iM֒W~p'suqSzҥwv']z9W}}}7<~a9o> һ/>G9WKxo;ξ~ӯ}O~G(a'dd~2}a4Z˰{97=O?v ʖNӮ_u9k?劭gmzor73ΫXu7r5SEtw~z.n$lo08L_iM3 fV􅪛Tj-?lo08L_iM3 fV􅪛Tj-?lo08L_iM3 fV􅪛Tj-?lo08L_iM3 fV􅪛Tj-?lo08L_iM3 fV􅪛Tj-?lo08L_iM23~z}䞗űeӶ@52;Sv<&' U7 uZyʉ^[l޹O"2KṮ4HǦzO"v<&'.{@grL_iMֲg'S XaTf=w;}a4|j-%dvK-u)3ΖyNzJY /x2[=LFN{SKLڑWmE?w,`_ SoIMD3-8ؘc^xLa2v*jv75z7J[Icj]a׌k:vs=, U7 [Tk3BRqjMco6 elo0u;fN&ZCd+:1:cu)Mw:rz8Nw2F/T4oQex,4z-bTHuKXi[t"S+ fVQ{Eun󊩛UL }l׫]3z8[Tk3n6 mҴ@qM*;kf?^sJd#Of3}ɨ}2Evi7ӿiM#so-+ؤ8[Tk/RX] -a0qY$/>'dTdP7:J2rM'K/ X}a[TkiOf3}ɨdQORԌ4hi٬d6輻]t{w8R/T4oQg<&J' X!j;%CңNf3LٚNg'&ڀdoQg<&>'#eBMh*x]Ɵq^d6ӷz O/T4UkiOf3}dBMh*x]Ɵq^d6ӷz O/T4UkiOf3}dBMh*x]Ɵq^d6ӷzcNvA/T4UkiOf3}dBMh*x]Ɵq^d6ӷz O/T4UkiOf3}dBMh*x]Ɵq^d6ӷz O/T4UkiOf3}dBMh*x]Ɵq^d6ӷz O/T4UkiOf3}D_^L|O3 F8PZK8/x2Z}d~Bj-?lZh͂I 몵4'i7 N'-*3 ̦V,8PZK8/x2Z}d~Bj-?lZh͂I 몵4'i7 N'-*3 ̦V, 9ΤP|j-oTg'tHIg?k rnJgd_NWw&;?.3INlɖNZι鉷^xY76eg_x97=ޙ''sLsN&j -6VYET|H<88qw}]tW~ݟ92ߟ|}_!#s99'g5NZN618Y9?Kz'/.ɴ>wNM4攫8o-}.+}uyãcܮ%smCv}%w ?S#yLIteZV\ qMڦm/h ',y7?yMOHN}l>;D']Unt5vCg|;&mZ/y v"'p ꎧ|^B7xzE[Nx'sMM82qp2xs2+˫̜D#NS1!89lHUL5lTp2s?Y7~޵:cvze_{%-|VָX|t) 3[Ƚw6@P04DpC'k6-*+BH NF!9'FBOhO(Tz'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z'Op2z _} , `d'X>8N|9dK'X>8N|p2僓, `d'X>8N|p2僓, `d'X>;wS;XeIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/source/index.rst0000664000175000017500000000101714572557755022143 0ustar00jamespagejamespage.. networking-l2gw documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to networking-l2gw's documentation! ======================================================== .. toctree:: :maxdepth: 2 Introduction installation usage contributing .. only:: html .. rubric:: Indices: * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/source/installation.rst0000664000175000017500000000032614572557755023537 0ustar00jamespagejamespage============ Installation ============ At the command line:: $ pip install networking-l2gw Or, if you have virtualenvwrapper installed:: $ mkvirtualenv networking-l2gw $ pip install networking-l2gw ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/source/readme.rst0000664000175000017500000000003614572557755022271 0ustar00jamespagejamespage.. include:: ../../README.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/doc/source/usage.rst0000664000175000017500000001715214572557755022147 0ustar00jamespagejamespage===== Usage ===== .. _whatisl2gw: 1. What is L2 Gateway ============================= L2 Gateway (L2GW) is an API framework for OpenStack that offers bridging two or more networks together to make them look at a single broadcast domain. A typical use case is bridging the virtual with the physical networks .. _model: 2. The L2GW model ================= L2GW introduces a various models to describe the relationships between logical and the physical entities. ========================= ===================================================================== Models Description ========================= ===================================================================== l2gateways logical gateways that represents for the set of physical devices l2gatewaydevices l2 gateway devices that represents for logical gateways. l2gatewayinterfaces it represents the physical ports for the devices l2gatewayconnections represents connection between neutron network and the logical gateway ========================= ===================================================================== .. _usage: 3. L2GW NB API usage ===================== L2GW NB REST API definitions are below, 3.1 Openstack CLI ----------------- OpenStackClient provides `the basic network commands `__ and networking-l2gw has an extension for l2gw related commands. * Create l2gateway: **openstack l2gw create** --device name="",interface_names=”|[|[ * Delete l2gateway: **openstack l2gw delete** * Update l2gateway: **openstack l2gw update** --name --device name=,interface_names=”|[|[ --default-segmentation-id [seg-id] * List l2gateway-connection: **openstack l2gw connection list** * Show l2gateway-connection: **openstack l2gw connection show** * Delete l2gateway-connection: **openstack l2gw connection delete** 3.2 Neutron CLI --------------- .. warning:: neutron CLI is now deprecated, and will be removed in the future. Use openstack CLI instead. * Create l2gateway: neutron-l2gw l2-gateway-create --device name="",interface_names=”|[|[ * Delete l2gateway: neutron-l2gw l2-gateway-delete * Update l2gateway: neutron-l2gw l2-gateway-update --name --device name=,interface_names=”|[|[ --default-segmentation-id [seg-id] * List l2gateway-connection: neutron-l2gw l2-gateway-connection-list * Show l2gateway-connection: neutron-l2gw l2-gateway-connection-show * Delete l2gateway-connection: neutron-l2gw l2-gateway-connection-delete .. _l2gw_agent: 4. L2GW agent ============= Configure the OVSDB parameters in /etc/neutron/l2gateway_agent.ini in case for openstack deployment. Ex: [ovsdb ovsdb_hosts = ovsdb1:127.0.0.1:6632 In devstack local.conf will do a trick.(Refer - networking-l2gw/devstack/README.rst) L2GW agent will be listed as part of "neutron agent-list". Details of L2GW Agent can be seen using "neutron agent-show " command L2 Gateway Agent connects to ovsdb server to configure and fetch L2 Gateways .. _l2gw_deployment: 5. L2GW Deployment ================== .. image:: images/L2GW_deployment.png :height: 225px :width: 450px :align: center .. _l2gw_release_management: 6. L2GW Package Versioning and Release Management ================================================= Versioning of L2 Gateway Package -------------------------------- L2 Gateway package will be uploaded, as networking-l2gw, to https://pypi.python.org. In order to upload this package, it will be versioned. Any subsequent updates will require version updates. This sub-section describes the versioning and release management of this package. By keeping L2 Gateway repository out of Neutron main repository gives us flexibility in terms of development and enhancements. This flexibility is extended for versioning of this project as well - this means, if we wanted to, we could version this project sequentially. This means whenever a new fix is released, we could bump up the version to the next number. Flexibility comes with cost. Thinking in terms of future, assuming this API is deployed by many users along with different releases of Neutron. Many enhancements/fixes may be introduced to this project. If we incremented the version/release number sequentially, this may force uninterested users to upgrade as well. This may or may not be desirable. Therefore, following release/versioning proposal is suggested for this package. Versioning of L2 Gateway will be aligned closely with Neutron releases. Neutron releases are formatted as follows:: .. year = 2015, 2014, etc... major-release = 1 or 2 - only two releases in a year minor-release = 1,2,3 or b1,b2,b3, or rc1,rc2,rc3, etc 2015.1.1, 2014.2.rc2, etc… L2 Gateway package is versioned in the same manner with an exception that the last tuple is used for intermediate patches/fixes between major release. As an example, the first release will be:: 2015.1.X where X will continue to increment as we add fixes to this release When kilo is released, L2 Gateway repository will also be tagged as kilo/stable to match with Neutron release. At this time the version of this package will be tagged to 2015.1.X ("X" will continue to increase as bug fixes are added to kilo/stable). For liberty release, the version of this package will be changed to 2015.2.Y. All the new features will be added to 2015.2.Y and all the bug fixes for kilo will be back-ported to 2015.1.X. This gives the flexibility of keeping the contents/features of this package closely aligned with Neutron releases. Which Version of L2 Gateway Package to use? ------------------------------------------- Anybody who wants to use L2 Gateway package, they can install it by issuing:: pip install networking-l2gw This will always pick the latest version of the package. However, for those users who are already using this package and want to pick up point fixes for a given release may use the specific version. For example, if a user wants to pick the latest version of the package that is suitable for kilo/stable, may use the following:: pip install networking-l2gw>=2015.1.X,<2015.2.0 For information on deploying L2GW refer networking-l2gw/doc/source/installation.rst and in devstack , networking-l2gw/devstack/README.rst ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/etc/0000775000175000017500000000000014572557757017013 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/etc/l2gateway_agent.ini0000664000175000017500000000505414572557755022573 0ustar00jamespagejamespage[DEFAULT] # Show debugging output in log (sets DEBUG log level output) # debug = False [ovsdb] # (StrOpt) OVSDB server tuples in the format # ::[,::] # - ovsdb_name: a symbolic name that helps identifies keys and certificate files # - ip address: the address or dns name for the ovsdb server # - port: the port (ssl is supported) # ovsdb_hosts = # Example: ovsdb_hosts = 'ovsdb1:16.95.16.1:6632,ovsdb2:16.95.16.2:6632' # enable_manager = False # (BoolOpt) connection can be initiated by the ovsdb server. # By default 'enable_manager' value is False, turn on the variable to True # to initiate the connection from ovsdb server to l2gw agent. # manager_table_listening_port = 6632 # (PortOpt) set port number for l2gateway agent, so that it can listen # for ovsdb server,whenever its IP is entered in manager table of ovsdb server. # by default it is set to port 6632. # you can use vtep-ctl utility to populate manager table of ovsdb. # For Example: sudo vtep-ctl set-manager tcp:x.x.x.x:6640, # where x.x.x.x is IP of l2gateway agent and 6640 is a port. # (StrOpt) Base path to private key file(s). # Agent will find key file named # $l2_gw_agent_priv_key_base_path/$ovsdb_name.key # l2_gw_agent_priv_key_base_path = # Example: l2_gw_agent_priv_key_base_path = '/home/someuser/keys' # (StrOpt) Base path to cert file(s). # Agent will find cert file named # $l2_gw_agent_cert_base_path/$ovsdb_name.cert # l2_gw_agent_cert_base_path = # Example: l2_gw_agent_cert_base_path = '/home/someuser/certs' # (StrOpt) Base path to ca cert file(s). # Agent will find ca cert file named # $l2_gw_agent_ca_cert_base_path/$ovsdb_name.ca_cert # l2_gw_agent_ca_cert_base_path = # Example: l2_gw_agent_ca_cert_base_path = '/home/someuser/ca_certs' # (IntOpt) The L2 gateway agent checks connection state with the OVSDB # servers. # The interval is number of seconds between attempts. # periodic_interval = # Example: periodic_interval = 20 # (IntOpt) The L2 gateway agent retries to connect to the OVSDB server # if a socket does not get opened in the first attempt. # the max_connection_retries is the maximum number of such attempts # before giving up. # max_connection_retries = # Example: max_connection_retries = 10 # (IntOpt) The remote OVSDB server sends echo requests every 4 seconds. # If there is no echo request on the socket for socket_timeout seconds, # by default socket_timeout is set to 30 seconds. The agent can # safely assume that the connection with the remote OVSDB server is lost. # socket_timeout = # Example: socket_timeout = 30 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/etc/l2gw_plugin.ini0000664000175000017500000000157714572557755021755 0ustar00jamespagejamespage[DEFAULT] # (StrOpt) default interface name of the l2 gateway # default_interface_name = # Example: default_interface_name = "FortyGigE1/0/1" # (StrOpt) default device name of the l2 gateway # default_device_name = # Example: default_device_name = "Switch1" # (IntOpt) quota of the l2 gateway # quota_l2_gateway = # Example: quota_l2_gateway = 10 # (IntOpt) The periodic interval at which the plugin # checks for the monitoring L2 gateway agent # periodic_monitoring_interval = # Example: periodic_monitoring_interval = 5 [service_providers] # Must be in form: # service_provider=::[:default] # List of allowed service types includes L2GW # Combination of and must be unique; must also be unique # This is multiline option service_provider=L2GW:l2gw:networking_l2gw.services.l2gateway.service_drivers.rpc_l2gw.L2gwRpcDriver:default ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/etc/policy.json0000664000175000017500000000105114572557755021200 0ustar00jamespagejamespage{ "admin_only": "rule:context_is_admin", "admin_or_owner": "rule:context_is_admin or tenant_id:%(tenant_id)s", "create_l2_gateway": "rule:admin_only", "update_l2_gateway": "rule:admin_only", "get_l2_gateway": "rule:admin_only", "delete_l2_gateway": "rule:admin_only", "get_l2_gateways": "rule:admin_only", "create_l2_gateway_connection": "rule:admin_only", "get_l2_gateway_connections": "rule:admin_only", "get_l2_gateway_connection": "rule:admin_only", "delete_l2_gateway_connection": "rule:admin_only" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/lower-constraints.txt0000664000175000017500000000721714572557755022503 0ustar00jamespagejamespagealabaster==0.7.10 alembic==1.6.5 amqp==2.1.1 appdirs==1.4.3 asn1crypto==0.23.0 Babel==2.3.4 beautifulsoup4==4.6.0 blockdiag==1.5.3 cachetools==2.0.0 cliff==3.4.0 cmd2==0.8.0 contextlib2==0.4.0 coverage==4.0 cryptography==3.0 ddt==1.0.1 debtcollector==1.19.0 decorator==4.1.0 deprecation==1.0 Django==2.2 django-appconf==1.0.2 django-babel==0.6.2 django-compressor==2.0 django-pyscss==2.0.2 docutils==0.11 dogpile.cache==0.6.5 eventlet==0.26.1 exabgp==4.0.4 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 funcparserlib==0.3.6 future==0.16.0 futurist==1.2.0 horizon==17.1.0 httplib2==0.9.1 imagesize==0.7.1 iso8601==0.1.11 isort==4.3.21 Jinja2==2.10 jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 keystoneauth1==3.14.0 keystonemiddleware==5.1.0 kombu==4.0.0 linecache2==1.0.0 logilab-common==1.4.1 logutils==0.3.5 Mako==1.0.7 MarkupSafe==1.1.0 monotonic==1.4 mox3==0.20.0 msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 neutron==21.0.0 neutron-lib==3.1.0 openstacksdk==0.31.2 os-client-config==1.28.0 os-ken==2.2.0 os-service-types==1.7.0 os-testr==1.0.0 os-traits==2.4.0 os-xenapi==0.3.1 os-vif==1.15.1 osc-lib==1.12.0 oslo.cache==1.26.0 oslo.concurrency==3.26.0 oslo.config==8.0.0 oslo.context==2.22.0 oslo.db==4.44.0 oslo.i18n==3.20.0 oslo.log==4.5.0 oslo.messaging==7.0.0 oslo.middleware==3.31.0 oslo.policy==3.12.0 oslo.privsep==2.3.0 oslo.reports==1.18.0 oslo.rootwrap==5.15.0 oslo.serialization==2.25.0 oslo.service==2.8.0 oslo.upgradecheck==1.3.0 oslo.utils==4.8.0 oslo.versionedobjects==1.35.1 oslotest==3.2.0 osprofiler==2.3.0 ovs==2.10.0 ovsdbapp==1.16.0 paramiko==2.0.0 Paste==2.0.2 PasteDeploy==1.5.0 pbr==4.0.0 pecan==1.3.2 pika==0.10.0 pika-pool==0.1.3 Pillow==2.4.0 Pint==0.5 positional==1.2.1 prettytable==0.7.2 psycopg2==2.8.5 psutil==5.3.0 pyasn1==0.1.8 pycparser==2.18 Pygments==2.2.0 pyinotify==0.9.6 pymongo==3.0.2 PyMySQL==0.7.6 pyOpenSSL==19.1.0 pyparsing==2.1.0 pyperclip==1.5.27 pyroute2==0.6.6 pyScss==1.3.7 pytest==5.3.5 python-cinderclient==5.0.0 python-dateutil==2.7.0 python-designateclient==2.7.0 python-editor==1.0.3 python-glanceclient==2.8.0 python-keystoneclient==3.22.0 python-mimeparse==1.6.0 python-neutronclient==7.8.0 python-novaclient==9.1.0 python-subunit==1.0.0 python-swiftclient==3.2.0 pytz==2015.7 PyYAML==5.1 rcssmin==1.0.6 repoze.lru==0.7 requests==2.18.0 requests-mock==1.2.0 requestsexceptions==1.2.0 rfc3986==1.2.0 rjsmin==1.0.12 Routes==2.3.1 ryu==4.24 semantic-version==2.3.1 seqdiag==0.9.5 simplejson==3.5.1 snowballstemmer==1.2.1 Sphinx==2.0.0 SQLAlchemy==1.4.23 sqlparse==0.2.2 statsd==3.2.1 stestr==1.0.0 stevedore==2.0.1 tempest==17.1.0 Tempita==0.5.2 tenacity==6.0.0 testrepository==0.0.18 testresources==2.0.0 testscenarios==0.4 testtools==2.2.0 tinyrpc==0.6 tooz==1.58.0 traceback2==1.4.0 urllib3==1.21.1 vine==1.1.4 waitress==1.1.0 warlock==1.2.0 webcolors==1.7 WebOb==1.8.2 websocket-client==0.40.0 WebTest==2.0.27 wrapt==1.7.0 XStatic==1.0.0 XStatic-Angular==1.5.8.0 XStatic-Angular-Bootstrap==2.2.0.0 XStatic-Angular-FileUpload==12.0.4.0 XStatic-Angular-Gettext==2.3.8.0 XStatic-Angular-lrdragndrop==1.0.2.2 XStatic-Angular-Schema-Form==0.8.13.0 XStatic-Bootstrap-Datepicker==1.3.1.0 XStatic-Bootstrap-SCSS==3.3.7.1 XStatic-bootswatch==3.3.7.0 XStatic-D3==3.5.17.0 XStatic-Font-Awesome==4.7.0.0 XStatic-Hogan==2.0.0.2 XStatic-Jasmine==2.4.1.1 XStatic-jQuery==1.8.2.1 XStatic-JQuery-Migrate==1.2.1.1 XStatic-jquery-ui==1.10.4.1 XStatic-JQuery.quicksearch==2.0.3.1 XStatic-JQuery.TableSorter==2.14.5.1 XStatic-JSEncrypt==2.3.1.1 XStatic-mdi==1.4.57.0 XStatic-objectpath==1.2.1.0 XStatic-Rickshaw==1.5.0.0 XStatic-roboto-fontface==0.5.0.0 XStatic-smart-table==1.4.13.2 XStatic-Spin==1.2.5.2 XStatic-term.js==0.0.7.0 XStatic-tv4==1.2.7.0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9359853 networking-l2gw-20.0.1.dev5/networking_l2gw/0000775000175000017500000000000014572557757021362 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/__init__.py0000664000175000017500000000123714572557755023474 0ustar00jamespagejamespage# -*- 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. import pbr.version __version__ = pbr.version.VersionInfo( 'networking_l2gw').version_string() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/_i18n.py0000664000175000017500000000204114572557755022645 0ustar00jamespagejamespage# 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 = "networking_l2gw" _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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/cmd/0000775000175000017500000000000014572557757022125 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/cmd/__init__.py0000664000175000017500000000000014572557755024222 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/cmd/eventlet/0000775000175000017500000000000014572557757023753 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/cmd/eventlet/__init__.py0000664000175000017500000000175414572557755026071 0ustar00jamespagejamespage# 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. # import eventlet eventlet.monkey_patch() # Monkey patch the original current_thread to use the up-to-date _active # global variable. See https://bugs.launchpad.net/bugs/1863021 and # https://github.com/eventlet/eventlet/issues/592 import __original_module_threading as orig_threading # noqa import threading # noqa orig_threading.current_thread.__globals__['_active'] = threading._active ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/cmd/eventlet/agent.py0000664000175000017500000000130514572557755025420 0ustar00jamespagejamespage# 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. # from networking_l2gw.services.l2gateway import l2gw_agent def main(): l2gw_agent.main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/0000775000175000017500000000000014572557757021747 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/__init__.py0000664000175000017500000000000014572557755024044 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/0000775000175000017500000000000014572557757023646 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/__init__.py0000664000175000017500000000000014572557755025743 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/db_query.py0000664000175000017500000001107614572557755026035 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 neutron.db import models_v2 from neutron_lib.db import model_query from neutron_lib import exceptions from neutron_lib.plugins.ml2 import api import sqlalchemy as sa from sqlalchemy.orm import exc class L2GatewayCommonDbMixin(object): def _apply_filters_to_query(self, query, model, filters): """Apply filters to query for the models.""" if filters: for key, value in filters.items(): column = getattr(model, key, None) if column: query = query.filter(column.in_(value)) return query def _model_query(self, context, model): """Query model based on filter.""" query = context.session.query(model) query_filter = None if not context.is_admin and hasattr(model, 'tenant_id'): if hasattr(model, 'shared'): query_filter = ((model.tenant_id == context.tenant_id) | (model.shared == sa.true())) else: query_filter = (model.tenant_id == context.tenant_id) if query_filter is not None: query = query.filter(query_filter) return query def _get_collection_query(self, context, model, filters=None, sorts=None, limit=None, marker_obj=None, page_reverse=False): """Get collection query for the models.""" collection = self._model_query(context, model) collection = model_query.apply_filters(collection, model, filters) return collection def _get_marker_obj(self, context, resource, limit, marker): """Get marker object for the resource.""" if limit and marker: return getattr(self, '_get_%s' % resource)(context, marker) return None def _fields(self, resource, fields): """Get fields for the resource for get query.""" if fields: return dict(((key, item) for key, item in resource.items() if key in fields)) return resource def _get_tenant_id_for_create(self, context, resource): """Get tenant id for creation of resources.""" if context.is_admin and 'tenant_id' in resource: tenant_id = resource['tenant_id'] elif ('tenant_id' in resource and resource['tenant_id'] != context.tenant_id): reason = _('Cannot create resource for another tenant') raise exceptions.AdminRequired(reason=reason) else: tenant_id = context.tenant_id return tenant_id def _get_collection(self, context, model, dict_func, filters=None, fields=None, sorts=None, limit=None, marker_obj=None, page_reverse=False): """Get collection object based on query for resources.""" query = self._get_collection_query(context, model, filters=filters, sorts=sorts, limit=limit, marker_obj=marker_obj, page_reverse=page_reverse) items = [dict_func(c, fields) for c in query] if limit and page_reverse: items.reverse() return items def _make_segment_dict(self, record): """Make a segment dictionary out of a DB record.""" return {api.ID: record.id, api.NETWORK_TYPE: record.network_type, api.PHYSICAL_NETWORK: record.physical_network, api.SEGMENTATION_ID: record.segmentation_id} def _get_network(self, context, id): try: network = self._get_by_id(context, models_v2.Network, id) except exc.NoResultFound: raise exceptions.NetworkNotFound(net_id=id) return network def _get_by_id(self, context, model, id): query = self._model_query(context, model) return query.filter(model.id == id).one() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/head.py0000664000175000017500000000140514572557755025117 0ustar00jamespagejamespage# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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.migration.models import head import networking_l2gw.db.l2gateway.l2gateway_models # noqa import networking_l2gw.db.l2gateway.ovsdb.models # noqa def get_metadata(): return head.model_base.BASEV2.metadata ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/l2gateway_db.py0000664000175000017500000007023214572557755026566 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 networking_l2gw.db.l2gateway import db_query from networking_l2gw.db.l2gateway import l2gateway_models as models from networking_l2gw.extensions import l2gateway from networking_l2gw.extensions import l2gatewayconnection from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import constants from networking_l2gw.services.l2gateway.common import l2gw_validators from networking_l2gw.services.l2gateway import exceptions as l2gw_exc from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib.db import api as db_api from neutron_lib.db import model_query from neutron_lib import exceptions from neutron_lib.plugins import directory from oslo_log import log as logging from oslo_utils import uuidutils from sqlalchemy.orm import exc as sa_orm_exc LOG = logging.getLogger(__name__) class L2GatewayMixin(l2gateway.L2GatewayPluginBase, db_query.L2GatewayCommonDbMixin, l2gatewayconnection.L2GatewayConnectionPluginBase): """Class L2GatewayMixin for handling l2_gateway resource.""" gateway_resource = constants.GATEWAY_RESOURCE_NAME connection_resource = constants.CONNECTION_RESOURCE_NAME config.register_l2gw_opts_helper() @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def _get_l2_gateway(self, context, gw_id): try: gw = model_query.get_by_id(context, models.L2Gateway, gw_id) except sa_orm_exc.NoResultFound: raise l2gw_exc.L2GatewayNotFound(gateway_id=gw_id) return gw @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def _get_l2_gateways(self, context): return model_query.get_collection(context, models.L2Gateway, dict_func=None) @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def _get_l2_gw_interfaces(self, context, id): return context.session.query(models.L2GatewayInterface).filter_by( device_id=id).all() @db_api.CONTEXT_READER def _is_vlan_configured_on_any_interface_for_l2gw(self, context, l2gw_id): devices_db = self._get_l2_gateway_devices(context, l2gw_id) for device_model in devices_db: interfaces_db = self._get_l2_gw_interfaces(context, device_model.id) for int_model in interfaces_db: query = context.session.query(models.L2GatewayInterface) int_db = query.filter_by(id=int_model.id).first() seg_id = int_db[constants.SEG_ID] if seg_id > 0: return True return False @db_api.retry_if_session_inactive() def _get_l2_gateway_devices(self, context, l2gw_id): return context.session.query(models.L2GatewayDevice).filter_by( l2_gateway_id=l2gw_id).all() def _get_l2gw_devices_by_name_andl2gwid(self, context, device_name, l2gw_id): return context.session.query(models.L2GatewayDevice).filter_by( device_name=device_name, l2_gateway_id=l2gw_id).all() def _get_l2_gateway_connection(self, context, cn_id): try: con = context.session.query(models.L2GatewayConnection).get(cn_id) except sa_orm_exc.NoResultFound: raise l2gw_exc.L2GatewayConnectionNotFound(id=cn_id) return con def _make_l2gw_connections_dict(self, gw_conn, fields=None): if gw_conn is None: raise l2gw_exc.L2GatewayConnectionNotFound(id="") segmentation_id = gw_conn['segmentation_id'] if segmentation_id == 0: segmentation_id = "" res = {'id': gw_conn['id'], 'network_id': gw_conn['network_id'], 'l2_gateway_id': gw_conn['l2_gateway_id'], 'tenant_id': gw_conn['tenant_id'], 'segmentation_id': segmentation_id } return self._fields(res, fields) def _make_l2_gateway_dict(self, l2_gateway, fields=None): device_list = [] for d in l2_gateway.devices: interface_list = [] for interfaces_db in d.interfaces: seg_id = interfaces_db[constants.SEG_ID] if seg_id == 0: seg_id = "" interface_list.append({'name': interfaces_db['interface_name'], constants.SEG_ID: seg_id}) aligned_int__list = self._align_interfaces_list(interface_list) device_list.append({'device_name': d['device_name'], 'id': d['id'], 'interfaces': aligned_int__list}) res = {'id': l2_gateway['id'], 'name': l2_gateway['name'], 'devices': device_list, 'tenant_id': l2_gateway['tenant_id']} return self._fields(res, fields) def _set_mapping_info_defaults(self, mapping_info): if not mapping_info.get(constants.SEG_ID): mapping_info[constants.SEG_ID] = 0 def _retrieve_gateway_connections(self, context, gateway_id, mapping_info={}, only_one=False): filters = {'l2_gateway_id': [gateway_id]} for k, v in mapping_info.items(): if v and k != constants.SEG_ID: filters[k] = [v] query = self._get_collection_query(context, models.L2GatewayConnection, filters) return query.one() if only_one else query.all() def create_l2_gateway(self, context, l2_gateway): """Create a logical gateway.""" self._admin_check(context, 'CREATE') gw = l2_gateway[self.gateway_resource] tenant_id = self._get_tenant_id_for_create(context, gw) devices = gw['devices'] with db_api.CONTEXT_WRITER.using(context): gw_db = models.L2Gateway( id=gw.get('id', uuidutils.generate_uuid()), tenant_id=tenant_id, name=gw.get('name')) context.session.add(gw_db) l2gw_device_dict = {} for device in devices: l2gw_device_dict['l2_gateway_id'] = id device_name = device['device_name'] l2gw_device_dict['device_name'] = device_name l2gw_device_dict['id'] = uuidutils.generate_uuid() uuid = self._generate_uuid() dev_db = models.L2GatewayDevice(id=uuid, l2_gateway_id=gw_db.id, device_name=device_name) context.session.add(dev_db) for interface_list in device['interfaces']: int_name = interface_list.get('name') if constants.SEG_ID in interface_list: seg_id_list = interface_list.get(constants.SEG_ID) for seg_ids in seg_id_list: uuid = self._generate_uuid() interface_db = self._get_int_model(uuid, int_name, dev_db.id, seg_ids) context.session.add(interface_db) else: uuid = self._generate_uuid() interface_db = self._get_int_model(uuid, int_name, dev_db.id, 0) context.session.add(interface_db) context.session.query(models.L2GatewayDevice).all() return self._make_l2_gateway_dict(gw_db) @db_api.CONTEXT_WRITER def update_l2_gateway(self, context, id, l2_gateway): """Update L2Gateway.""" gw = l2_gateway[self.gateway_resource] devices = gw.get('devices') dev_db = None l2gw_db = None with db_api.CONTEXT_WRITER.using(context): l2gw_db = self._get_l2_gateway(context, id) if not devices and l2gw_db: l2gw_db.name = gw.get('name') return self._make_l2_gateway_dict(l2gw_db) if devices: for device in devices: dev_name = device['device_name'] dev_db = (self._get_l2gw_devices_by_name_andl2gwid( context, dev_name, id)) interface_dict_list = [i for i in device['interfaces']] interface_db = self._get_l2_gw_interfaces(context, dev_db[0].id) self._delete_l2_gateway_interfaces(context, interface_db) self._update_interfaces_db(context, interface_dict_list, dev_db) if l2gw_db: if gw.get('name'): l2gw_db.name = gw.get('name') return self._make_l2_gateway_dict(l2gw_db) def _update_interfaces_db(self, context, interface_dict_list, device_db): for interfaces in interface_dict_list: int_name = interfaces.get('name') if constants.SEG_ID in interfaces: seg_id_list = interfaces.get(constants.SEG_ID) for seg_ids in seg_id_list: uuid = self._generate_uuid() int_db = self._get_int_model(uuid, int_name, device_db[0].id, seg_ids) context.session.add(int_db) else: uuid = self._generate_uuid() interface_db = self._get_int_model(uuid, int_name, device_db[0].id, 0) context.session.add(interface_db) @db_api.CONTEXT_READER def get_l2_gateway(self, context, id, fields=None): """get the l2 gateway by id.""" self._admin_check(context, 'GET') gw_db = self._get_l2_gateway(context, id) return self._make_l2_gateway_dict(gw_db, fields) def delete_l2_gateway(self, context, id): """delete the l2 gateway by id.""" gw_db = self._get_l2_gateway(context, id) if gw_db: with db_api.CONTEXT_WRITER.using(context): context.session.delete(gw_db) LOG.debug("l2 gateway '%s' was deleted.", id) @db_api.CONTEXT_READER def get_l2_gateways(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): """list the l2 gateways available in the neutron DB.""" self._admin_check(context, 'GET') marker_obj = self._get_marker_obj( context, 'l2_gateway', limit, marker) return self._get_collection(context, models.L2Gateway, self._make_l2_gateway_dict, filters=filters, fields=fields, sorts=sorts, limit=limit, marker_obj=marker_obj, page_reverse=page_reverse) def _update_segmentation_id(self, context, l2gw_id, segmentation_id): """Update segmentation id for interfaces.""" device_db = self._get_l2_gateway_devices(context, l2gw_id) for device_model in device_db: interface_db = self._get_l2_gw_interfaces(context, device_model.id) for interface_model in interface_db: interface_model.segmentation_id = segmentation_id def _delete_l2_gateway_interfaces(self, context, int_db_list): """delete the l2 interfaces by id.""" with db_api.CONTEXT_WRITER.using(context): for interfaces in int_db_list: context.session.delete(interfaces) LOG.debug("l2 gateway interfaces was deleted.") def create_l2_gateway_connection(self, context, l2_gateway_connection): """Create l2 gateway connection.""" gw_connection = l2_gateway_connection[self.connection_resource] l2_gw_id = gw_connection.get('l2_gateway_id') network_id = gw_connection.get('network_id') nw_map = {} nw_map['network_id'] = network_id nw_map['l2_gateway_id'] = l2_gw_id segmentation_id = "" if constants.SEG_ID in gw_connection: segmentation_id = gw_connection.get(constants.SEG_ID) nw_map[constants.SEG_ID] = segmentation_id with db_api.CONTEXT_WRITER.using(context): gw_db = self._get_l2_gateway(context, l2_gw_id) tenant_id = self._get_tenant_id_for_create(context, gw_db) nw_map['tenant_id'] = tenant_id if not segmentation_id: nw_map['segmentation_id'] = "0" conn_db = self._retrieve_gateway_connections(context, l2_gw_id, nw_map) if not conn_db: connection_id = uuidutils.generate_uuid() nw_map['id'] = connection_id gw_db.network_connections.append( models.L2GatewayConnection(**nw_map)) gw_db = models.L2GatewayConnection( id=connection_id, tenant_id=tenant_id, network_id=network_id, l2_gateway_id=l2_gw_id, segmentation_id=segmentation_id) return self._make_l2gw_connections_dict(gw_db) return self._make_l2gw_connections_dict(conn_db[0]) @db_api.CONTEXT_READER def get_l2_gateway_connections_count(self, context, filters=None): return len(self._get_collection(context, models.L2GatewayConnection, self._make_l2gw_connections_dict, filters=filters)) @db_api.CONTEXT_READER def get_l2_gateway_connections(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): """List l2 gateway connections.""" self._admin_check(context, 'GET') marker_obj = self._get_marker_obj( context, 'l2_gateway_connection', limit, marker) return self._get_collection(context, models.L2GatewayConnection, self._make_l2gw_connections_dict, filters=filters, fields=fields, sorts=sorts, limit=limit, marker_obj=marker_obj, page_reverse=page_reverse) @db_api.CONTEXT_READER def get_l2_gateway_connection(self, context, id, fields=None): """Get l2 gateway connection.""" self._admin_check(context, 'GET') """Get the l2 gateway connection by id.""" gw_db = self._get_l2_gateway_connection(context, id) return self._make_l2gw_connections_dict(gw_db, fields) def delete_l2_gateway_connection(self, context, id): """Delete the l2 gateway connection by id.""" with db_api.CONTEXT_WRITER.using(context): gw_db = self._get_l2_gateway_connection(context, id) context.session.delete(gw_db) LOG.debug("l2 gateway '%s' was destroyed.", id) def _admin_check(self, context, action): """Admin role check helper.""" # TODO(selva): his check should be required if the tenant_id is # specified in the request, otherwise the policy.json do a trick # this need further revision. if not context.is_admin: reason = _('Cannot %s resource for non admin tenant') % action raise exceptions.AdminRequired(reason=reason) def _generate_uuid(self): """Generate uuid helper.""" uuid = uuidutils.generate_uuid() return uuid def _get_int_model(self, uuid, interface_name, dev_id, seg_id): return models.L2GatewayInterface(id=uuid, interface_name=interface_name, device_id=dev_id, segmentation_id=seg_id) def get_l2gateway_devices_by_gateway_id(self, context, l2_gateway_id): """Get l2gateway_devices_by id.""" session = context.session with session.begin(): return session.query(models.L2GatewayDevice).filter_by( l2_gateway_id=l2_gateway_id).all() def get_l2gateway_interfaces_by_device_id(self, context, device_id): """Get all l2gateway_interfaces_by device_id.""" session = context.session with session.begin(): return session.query(models.L2GatewayInterface).filter_by( device_id=device_id).all() def validate_device_name(self, context, device_name, l2gw_id): if device_name: devices_db = self._get_l2gw_devices_by_name_andl2gwid(context, device_name, l2gw_id) if not devices_db: raise l2gw_exc.L2GatewayDeviceNameNotFound(device_name=device_name) def _validate_any_seg_id_empty_in_interface_dict(self, devices): """Validate segmentation_id for consistency.""" for device in devices: interface_list = device['interfaces'] if not interface_list: raise l2gw_exc.L2GatewayInterfaceRequired() if constants.SEG_ID in interface_list[0]: for interfaces in interface_list[1:len(interface_list)]: if constants.SEG_ID not in interfaces: raise l2gw_exc.L2GatewaySegmentationRequired() if constants.SEG_ID not in interface_list[0]: for interfaces in interface_list[1:len(interface_list)]: if constants.SEG_ID in interfaces: raise l2gw_exc.L2GatewaySegmentationRequired() def _align_interfaces_list(self, interface_list): """Align interfaces list based on input dict for multiple seg ids.""" interface_dict = {} aligned_interface_list = [] for interfaces in interface_list: actual__name = interfaces.get('name') if actual__name in interface_dict: interface_name = interface_dict.get(actual__name) seg_id_list = interfaces.get(constants.SEG_ID) interface_name.append(str(seg_id_list)) interface_dict.update({actual__name: interface_name}) else: seg_id = str(interfaces.get(constants.SEG_ID)).split() interface_dict.update({actual__name: seg_id}) for name in interface_dict: aligned_interface_list.append({'segmentation_id': interface_dict[name], 'name': name}) return aligned_interface_list @db_api.CONTEXT_READER def _get_l2_gateway_connections(self, context): """Get l2 gateway connections.""" try: con = context.session.query(models.L2GatewayConnection).all() except sa_orm_exc.NoResultFound: raise l2gw_exc.L2GatewayConnectionNotFound( id="") return con @db_api.CONTEXT_READER def _get_l2gw_ids_by_interface_switch(self, context, interface_name, switch_name): """Get l2 gateway ids by interface and switch.""" connections = self._get_l2_gateway_connections(context) l2gw_id_list = [] if connections: for connection in connections: l2gw_id = connection.l2_gateway_id devices = self._get_l2_gateway_device_by_name_id(context, switch_name, l2gw_id) if devices: for device in devices: interfaces = self._get_l2_gw_interfaces(context, device.id) for interface in interfaces: if interface_name == interface.interface_name: l2gw_id_list.append(l2gw_id) else: LOG.debug("l2 gateway devices are empty") else: LOG.debug("l2 gateway connections are empty") return l2gw_id_list def _delete_connection_by_l2gw_id(self, context, l2gw_id): """Delete the l2 gateway connection by l2gw id.""" with db_api.CONTEXT_WRITER.using(context): con_db = self._get_l2_gateway_connection_by_l2gw_id(context, l2gw_id) if con_db: context.session.delete(con_db[0]) LOG.debug("l2 gateway connection was destroyed.") def _get_l2_gateway_connection_by_l2gw_id(self, context, l2gw_id): """Get the l2 gateway connection by l2gw id.""" try: con = context.session.query(models.L2GatewayConnection).filter_by( l2_gateway_id=l2gw_id).all() except sa_orm_exc.NoResultFound: raise l2gw_exc.L2GatewayConnectionNotFound( id=l2gw_id) return con def _get_l2_gateway_device_by_name_id(self, context, device_name, l2gw_id): """Get the l2 gateway device by name and id.""" try: gw = context.session.query(models.L2GatewayDevice).filter_by( device_name=device_name, l2_gateway_id=l2gw_id).all() except sa_orm_exc.NoResultFound: raise l2gw_exc.L2GatewayDeviceNotFound( device_id=device_name) return gw def validate_l2_gateway_for_create(self, context, l2_gateway): self._admin_check(context, 'CREATE') gw = l2_gateway[self.gateway_resource] devices = gw['devices'] self._validate_any_seg_id_empty_in_interface_dict(devices) def validate_l2_gateway_for_delete(self, context, l2gw_id): self._admin_check(context, 'DELETE') gw_db = self._get_l2_gateway(context, l2gw_id) if gw_db.network_connections: raise l2gw_exc.L2GatewayInUse(gateway_id=l2gw_id) return def validate_l2_gateway_for_update(self, context, id, l2_gateway): self._admin_check(context, 'UPDATE') gw = l2_gateway[self.gateway_resource] devices = None dev_db = None if 'devices' in gw: devices = gw['devices'] if not devices: return with db_api.CONTEXT_READER.using(context): # Attemp to retrieve l2gw gw_db = self._get_l2_gateway(context, id) if devices: for device in devices: dev_name = device['device_name'] dev_db = self._get_l2gw_devices_by_name_andl2gwid( context, dev_name, id) if not dev_db: raise l2gw_exc.L2GatewayDeviceNotFound( device_id=dev_name) self.validate_device_name(context, dev_name, id) interface_list = device['interfaces'] if not interface_list: raise l2gw_exc.L2GatewayInterfaceRequired() if constants.SEG_ID in interface_list[0]: for interfaces in interface_list: if constants.SEG_ID not in interfaces: raise l2gw_exc.L2GatewaySegmentationRequired() if constants.SEG_ID not in interface_list[0]: for interfaces in interface_list[1:]: if constants.SEG_ID in interfaces: raise l2gw_exc.L2GatewaySegmentationRequired() if gw_db.devices: if gw_db.devices[0].interfaces[0]['segmentation_id']: if constants.SEG_ID not in interface_list[0]: raise l2gw_exc.L2GatewaySegmentationIDExists() else: if constants.SEG_ID in interface_list[0]: raise l2gw_exc.\ L2GatewaySegmentationIDNotExists() def validate_l2_gateway_connection_for_create(self, context, l2_gateway_connection): self._admin_check(context, 'CREATE') gw_connection = l2_gateway_connection[self.connection_resource] l2_gw_id = gw_connection.get('l2_gateway_id') network_id = gw_connection.get('network_id') plugin = directory.get_plugin() plugin.get_network(context, network_id) nw_map = {} nw_map['network_id'] = network_id nw_map['l2_gateway_id'] = l2_gw_id segmentation_id = "" if constants.SEG_ID in gw_connection: segmentation_id = gw_connection.get(constants.SEG_ID) nw_map[constants.SEG_ID] = segmentation_id is_vlan = self._is_vlan_configured_on_any_interface_for_l2gw(context, l2_gw_id) network_id = l2gw_validators.validate_network_mapping_list(nw_map, is_vlan) with db_api.CONTEXT_WRITER.using(context): if self._retrieve_gateway_connections(context, l2_gw_id, nw_map): raise l2gw_exc.L2GatewayConnectionExists(mapping=nw_map, gateway_id=l2_gw_id) @db_api.CONTEXT_READER def validate_l2_gateway_connection_for_delete(self, context, l2_gateway_conn_id): self._admin_check(context, 'DELETE') gw_db = self._get_l2_gateway_connection(context, l2_gateway_conn_id) if gw_db is None: raise l2gw_exc.L2GatewayConnectionNotFound( id=l2_gateway_conn_id) def l2gw_callback(resource, event, trigger, **kwargs): l2gwservice = directory.get_plugin(constants.L2GW) context = kwargs.get('context') port_dict = kwargs.get('port') if l2gwservice: if event == events.AFTER_UPDATE: l2gwservice.add_port_mac(context, port_dict) elif event == events.AFTER_DELETE: l2gwservice.delete_port_mac(context, port_dict) def subscribe(): interested_events = (events.AFTER_UPDATE, events.AFTER_DELETE) for x in interested_events: registry.subscribe( l2gw_callback, resources.PORT, x) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/l2gateway_models.py0000664000175000017500000000640414572557755027464 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 neutron_lib.db import model_base from neutron_lib.db import standard_attr import sqlalchemy as sa from sqlalchemy import orm class L2GatewayConnection(standard_attr.HasStandardAttributes, model_base.BASEV2, model_base.HasProject, model_base.HasId): """Define an l2 gateway connection between a l2 gateway and a network.""" l2_gateway_id = sa.Column(sa.String(36), sa.ForeignKey('l2gateways.id', ondelete='CASCADE')) network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id', ondelete='CASCADE'), nullable=False) segmentation_id = sa.Column(sa.Integer) __table_args__ = (sa.UniqueConstraint(l2_gateway_id, network_id),) api_collections = ["l2_gateway_connections"] class L2GatewayInterface(model_base.BASEV2, model_base.HasId): """Define an l2 gateway interface.""" interface_name = sa.Column(sa.String(255)) device_id = sa.Column(sa.String(36), sa.ForeignKey('l2gatewaydevices.id', ondelete='CASCADE'), nullable=False) segmentation_id = sa.Column(sa.Integer) revises_on_change = ('devices', ) class L2GatewayDevice(standard_attr.HasStandardAttributes, model_base.BASEV2, model_base.HasId): """Define an l2 gateway device.""" device_name = sa.Column(sa.String(255), nullable=False) interfaces = orm.relationship( L2GatewayInterface, backref=sa.orm.backref("devices", uselist=False, lazy='joined', load_on_pending=True), cascade='all,delete') l2_gateway_id = sa.Column(sa.String(36), sa.ForeignKey('l2gateways.id', ondelete='CASCADE'), nullable=False) revises_on_change = ('gateways', ) api_collections = ["l2_gateway_devices"] class L2Gateway(standard_attr.HasStandardAttributes, model_base.BASEV2, model_base.HasId, model_base.HasProject): """Define an l2 gateway.""" name = sa.Column(sa.String(255)) devices = orm.relationship( L2GatewayDevice, backref=sa.orm.backref("gateways", uselist=False, lazy='joined', load_on_pending=True), cascade='all,delete') network_connections = orm.relationship(L2GatewayConnection, lazy='joined') api_collections = ["l2_gateways"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/ovsdb/0000775000175000017500000000000014572557757024763 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/ovsdb/__init__.py0000664000175000017500000000000014572557755027060 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/ovsdb/lib.py0000664000175000017500000005465714572557755026122 0ustar00jamespagejamespage# Copyright (c) 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. from oslo_log import log as logging from oslo_utils import timeutils from sqlalchemy import asc from sqlalchemy.orm import exc from neutron_lib.db import api as db_api from networking_l2gw.db.l2gateway.ovsdb import models LOG = logging.getLogger(__name__) def add_vlan_binding(context, record_dict): """Insert a vlan binding of a given physical port.""" session = context.session with db_api.CONTEXT_WRITER.using(context): binding = models.VlanBindings( port_uuid=record_dict['port_uuid'], vlan=record_dict['vlan'], logical_switch_uuid=record_dict['logical_switch_uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']) session.add(binding) def delete_vlan_binding(context, record_dict): """Delete vlan bindings of a given physical port.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if (record_dict['vlan'] is not None and record_dict['logical_switch_uuid']): session.query(models.VlanBindings).filter_by( port_uuid=record_dict['port_uuid'], vlan=record_dict['vlan'], logical_switch_uuid=record_dict['logical_switch_uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).delete() def add_physical_locator(context, record_dict): """Insert a new physical locator.""" session = context.session with db_api.CONTEXT_WRITER.using(context): locator = models.PhysicalLocators( uuid=record_dict['uuid'], dst_ip=record_dict['dst_ip'], ovsdb_identifier=record_dict['ovsdb_identifier']) session.add(locator) def delete_physical_locator(context, record_dict): """Delete physical locator that matches the supplied uuid.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if record_dict['uuid']: session.query(models.PhysicalLocators).filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).delete() def add_physical_switch(context, record_dict): """Insert a new physical switch.""" session = context.session with db_api.CONTEXT_WRITER.using(context): physical_switch = models.PhysicalSwitches( uuid=record_dict['uuid'], name=record_dict['name'], tunnel_ip=record_dict['tunnel_ip'], ovsdb_identifier=record_dict['ovsdb_identifier'], switch_fault_status=record_dict['switch_fault_status']) session.add(physical_switch) def delete_physical_switch(context, record_dict): """Delete physical switch that matches the supplied uuid.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if record_dict['uuid']: session.query(models.PhysicalSwitches).filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).delete() def add_logical_switch(context, record_dict): """Insert a new logical switch.""" session = context.session with db_api.CONTEXT_WRITER.using(context): logical_switch = models.LogicalSwitches( uuid=record_dict['uuid'], name=record_dict['name'], key=record_dict['key'], ovsdb_identifier=record_dict['ovsdb_identifier']) session.add(logical_switch) def delete_logical_switch(context, record_dict): """delete logical switch that matches the supplied uuid.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if record_dict['uuid']: session.query(models.LogicalSwitches).filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).delete() def add_physical_port(context, record_dict): """Insert a new physical port.""" session = context.session with db_api.CONTEXT_WRITER.using(context): physical_port = models.PhysicalPorts( uuid=record_dict['uuid'], name=record_dict['name'], physical_switch_id=record_dict['physical_switch_id'], ovsdb_identifier=record_dict['ovsdb_identifier'], port_fault_status=record_dict['port_fault_status']) session.add(physical_port) def update_physical_ports_status(context, record_dict): """Update physical port fault status.""" with db_api.CONTEXT_WRITER.using(context): (context.session.query(models.PhysicalPorts). filter(models.PhysicalPorts.uuid == record_dict['uuid']). update({'port_fault_status': record_dict['port_fault_status']}, synchronize_session=False)) def update_physical_switch_status(context, record_dict): """Update physical switch fault status.""" with context.session.begin(subtransactions=True): (context.session.query(models.PhysicalSwitches). filter(models.PhysicalSwitches.uuid == record_dict['uuid']). update({'switch_fault_status': record_dict['switch_fault_status']}, synchronize_session=False)) def delete_physical_port(context, record_dict): """Delete physical port that matches the supplied uuid.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if record_dict['uuid']: session.query(models.PhysicalPorts).filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).delete() def add_ucast_mac_local(context, record_dict): """Insert a new ucast mac local.""" session = context.session with db_api.CONTEXT_WRITER.using(context): ucast_mac_local = models.UcastMacsLocals( uuid=record_dict['uuid'], mac=record_dict['mac'], logical_switch_id=record_dict['logical_switch_id'], physical_locator_id=record_dict['physical_locator_id'], ip_address=record_dict['ip_address'], ovsdb_identifier=record_dict['ovsdb_identifier']) session.add(ucast_mac_local) def delete_ucast_mac_local(context, record_dict): """Delete ucast mac local that matches the supplied uuid.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if record_dict['uuid']: session.query(models.UcastMacsLocals).filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).delete() def add_ucast_mac_remote(context, record_dict): """Insert a new ucast mac remote.""" session = context.session with db_api.CONTEXT_WRITER.using(context): ucast_mac_remote = models.UcastMacsRemotes( uuid=record_dict['uuid'], mac=record_dict['mac'], logical_switch_id=record_dict['logical_switch_id'], physical_locator_id=record_dict['physical_locator_id'], ip_address=record_dict['ip_address'], ovsdb_identifier=record_dict['ovsdb_identifier']) session.add(ucast_mac_remote) def update_ucast_mac_remote(context, rec_dict): """Update ucast mac remote.""" try: with db_api.CONTEXT_WRITER.using(context): (context.session.query(models.UcastMacsRemotes).filter_by( uuid=rec_dict['uuid'], ovsdb_identifier=rec_dict['ovsdb_identifier']).update( {'physical_locator_id': rec_dict['physical_locator_id'], 'ip_address': rec_dict['ip_address']}, synchronize_session=False)) except exc.NoResultFound: LOG.debug('no Remote mac found for %s and %s', rec_dict['uuid'], rec_dict['ovsdb_identifier']) def delete_ucast_mac_remote(context, record_dict): """Delete ucast mac remote that matches the supplied uuid.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if record_dict['uuid']: session.query(models.UcastMacsRemotes).filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).delete() def get_physical_port(context, record_dict): """Get physical port that matches the uuid and ovsdb_identifier.""" try: query = context.session.query(models.PhysicalPorts) physical_port = query.filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no physical port found for %s and %s', record_dict['uuid'], record_dict['ovsdb_identifier']) return return physical_port def get_logical_switch(context, record_dict): """Get logical switch that matches the uuid and ovsdb_identifier.""" try: query = context.session.query(models.LogicalSwitches) logical_switch = query.filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no logical switch found for %s and %s', record_dict['uuid'], record_dict['ovsdb_identifier']) return return logical_switch def get_all_logical_switches_by_name(context, name): """Get logical switch that matches the supplied name.""" query = context.session.query(models.LogicalSwitches) return query.filter_by(name=name).all() def get_ucast_mac_remote(context, record_dict): """Get ucast macs remote that matches the uuid and ovsdb_identifier.""" try: query = context.session.query(models.UcastMacsRemotes) remote_mac = query.filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no Remote mac found for %s and %s', record_dict['uuid'], record_dict['ovsdb_identifier']) return return remote_mac def get_ucast_mac_local(context, record_dict): """Get ucast macs local that matches the uuid and ovsdb_identifier.""" try: with db_api.CONTEXT_READER.using(context): query = context.session.query(models.UcastMacsLocals) local_mac = query.filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no Local mac found for %s and %s', record_dict['uuid'], record_dict['ovsdb_identifier']) return return local_mac def get_ucast_mac_remote_by_mac_and_ls(context, record_dict): """Get ucast macs remote that matches the MAC address and ovsdb_identifier. """ try: query = context.session.query(models.UcastMacsRemotes) remote_mac = query.filter_by( mac=record_dict['mac'], ovsdb_identifier=record_dict['ovsdb_identifier'], logical_switch_id=record_dict['logical_switch_uuid']).one() except exc.NoResultFound: LOG.debug('no Remote mac found for %s and %s', record_dict['mac'], record_dict['logical_switch_uuid']) return return remote_mac def get_physical_switch(context, record_dict): """Get physical switch that matches the uuid and ovsdb_identifier.""" try: query = context.session.query(models.PhysicalSwitches) physical_switch = query.filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no physical switch found for %s and %s', record_dict['uuid'], record_dict['ovsdb_identifier']) return return physical_switch def get_physical_locator(context, record_dict): """Get physical locator that matches the supplied uuid.""" try: query = context.session.query(models.PhysicalLocators) physical_locator = query.filter_by( uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no physical locator found for %s and %s', record_dict['uuid'], record_dict['ovsdb_identifier']) return return physical_locator def get_physical_locator_by_dst_ip(context, record_dict): """Get physical locator that matches the supplied destination IP.""" try: query = context.session.query(models.PhysicalLocators) physical_locator = query.filter_by( dst_ip=record_dict['dst_ip'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no physical locator found for %s and %s', record_dict['dst_ip'], record_dict['ovsdb_identifier']) return return physical_locator def get_logical_switch_by_name(context, record_dict): """Get logical switch that matches the supplied name.""" try: with db_api.CONTEXT_READER.using(context): query = context.session.query(models.LogicalSwitches) logical_switch = query.filter_by( name=record_dict['logical_switch_name'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no logical switch found for %s and %s', record_dict['logical_switch_name'], record_dict['ovsdb_identifier']) return return logical_switch def get_all_vlan_bindings_by_physical_port(context, record_dict): """Get vlan bindings that matches the supplied physical port.""" query = context.session.query(models.VlanBindings) return query.filter_by( port_uuid=record_dict['uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).all() def get_vlan_binding(context, record_dict): """Get vlan bindings that matches the supplied physical port.""" try: query = context.session.query(models.VlanBindings) vlan_binding = query.filter_by( port_uuid=record_dict['port_uuid'], vlan=record_dict['vlan'], logical_switch_uuid=record_dict['logical_switch_uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no vlan binding found for %s and %s', record_dict['port_uuid'], record_dict['ovsdb_identifier']) return return vlan_binding def get_physical_switch_by_name(context, name): """Get logical switch that matches the supplied name.""" query = context.session.query(models.PhysicalSwitches) return query.filter_by(name=name).first() def get_physical_port_by_name_and_ps(context, record_dict): """Get vlan bindings that matches the supplied physical port.""" try: query = context.session.query(models.PhysicalPorts) physical_port = query.filter_by( name=record_dict['interface_name'], physical_switch_id=record_dict['physical_switch_id'], ovsdb_identifier=record_dict['ovsdb_identifier']).one() except exc.NoResultFound: LOG.debug('no physical port found for %s and %s', record_dict['physical_switch_id'], record_dict['interface_name']) return return physical_port def get_all_physical_switches_by_ovsdb_id(context, ovsdb_identifier): """Get Physical Switches that match the supplied ovsdb identifier.""" query = context.session.query(models.PhysicalSwitches) return query.filter_by( ovsdb_identifier=ovsdb_identifier).all() def get_all_logical_switches_by_ovsdb_id(context, ovsdb_identifier): """Get logical Switches that match the supplied ovsdb identifier.""" query = context.session.query(models.LogicalSwitches) return query.filter_by( ovsdb_identifier=ovsdb_identifier).all() def get_all_vlan_bindings_by_logical_switch(context, record_dict): """Get Vlan bindings that match the supplied logical switch.""" query = context.session.query(models.VlanBindings) return query.filter_by( logical_switch_uuid=record_dict['logical_switch_id'], ovsdb_identifier=record_dict['ovsdb_identifier']).all() def add_pending_ucast_mac_remote(context, operation, ovsdb_identifier, logical_switch_id, physical_locator, mac_remotes): """Insert a pending ucast_mac_remote (insert/update/delete).""" session = context.session with db_api.CONTEXT_WRITER.using(context): for mac in mac_remotes: pending_mac = models.PendingUcastMacsRemote( uuid=mac.get('uuid', None), mac=mac['mac'], logical_switch_uuid=logical_switch_id, vm_ip=mac['ip_address'], ovsdb_identifier=ovsdb_identifier, operation=operation, timestamp=timeutils.utcnow()) if physical_locator: pending_mac['dst_ip'] = physical_locator.get('dst_ip', None) pending_mac['locator_uuid'] = physical_locator.get('uuid', None) session.add(pending_mac) def delete_pending_ucast_mac_remote(context, operation, ovsdb_identifier, logical_switch_id, mac_remote): """Delete a pending ucast_mac_remote.""" session = context.session with db_api.CONTEXT_WRITER.using(context): if (mac_remote and logical_switch_id and ovsdb_identifier and operation): query = session.query(models.PendingUcastMacsRemote).filter_by( mac=mac_remote, ovsdb_identifier=ovsdb_identifier, logical_switch_uuid=logical_switch_id, operation=operation) row_count = query.count() query.delete() return row_count def get_pending_ucast_mac_remote(context, ovsdb_identifier, mac, logical_switch_uuid): """Get pending mac that matches the supplied parameters.""" try: query = context.session.query(models.PendingUcastMacsRemote) pending_mac = query.filter_by( ovsdb_identifier=ovsdb_identifier, logical_switch_uuid=logical_switch_uuid, mac=mac).one() return pending_mac except exc.NoResultFound: return def get_all_pending_remote_macs_in_asc_order(context, ovsdb_identifier): """Get all the pending remote macs in ascending order of timestamp.""" with db_api.CONTEXT_READER.using(context): session = context.session return session.query( models.PendingUcastMacsRemote).filter_by( ovsdb_identifier=ovsdb_identifier).order_by( asc(models.PendingUcastMacsRemote.timestamp)).all() def get_all_ucast_mac_remote_by_ls(context, record_dict): """Get ucast macs remote that matches ls_id and ovsdb_identifier.""" with db_api.CONTEXT_READER.using(context): session = context.session return session.query(models.UcastMacsRemotes).filter_by( ovsdb_identifier=record_dict['ovsdb_identifier'], logical_switch_id=record_dict['logical_switch_id']).all() def delete_all_physical_locators_by_ovsdb_identifier(context, ovsdb_identifier): """Delete all physical locators based on ovsdb identifier.""" session = context.session with db_api.CONTEXT_WRITER.using(context): session.query(models.PhysicalLocators).filter_by( ovsdb_identifier=ovsdb_identifier).delete() def delete_all_physical_switches_by_ovsdb_identifier(context, ovsdb_identifier): """Delete all physical switches based on ovsdb identifier.""" session = context.session with db_api.CONTEXT_WRITER.using(context): session.query(models.PhysicalSwitches).filter_by( ovsdb_identifier=ovsdb_identifier).delete() def delete_all_physical_ports_by_ovsdb_identifier(context, ovsdb_identifier): """Delete all physical ports based on ovsdb identifier.""" session = context.session with db_api.CONTEXT_WRITER.using(context): session.query(models.PhysicalPorts).filter_by( ovsdb_identifier=ovsdb_identifier).delete() def delete_all_logical_switches_by_ovsdb_identifier(context, ovsdb_identifier): """Delete all physical switches based on ovsdb identifier.""" session = context.session with db_api.CONTEXT_WRITER.using(context): session.query(models.LogicalSwitches).filter_by( ovsdb_identifier=ovsdb_identifier).delete() def delete_all_ucast_macs_locals_by_ovsdb_identifier(context, ovsdb_identifier): """Delete all ucast mac locals based on ovsdb identifier.""" session = context.session with db_api.CONTEXT_WRITER.using(context): session.query(models.UcastMacsLocals).filter_by( ovsdb_identifier=ovsdb_identifier).delete() def delete_all_ucast_macs_remotes_by_ovsdb_identifier(context, ovsdb_identifier): """Delete all ucast mac remotes based on ovsdb identifier.""" session = context.session with db_api.CONTEXT_WRITER.using(context): session.query(models.UcastMacsRemotes).filter_by( ovsdb_identifier=ovsdb_identifier).delete() def delete_all_vlan_bindings_by_ovsdb_identifier(context, ovsdb_identifier): """Delete all vlan bindings based on ovsdb identifier.""" session = context.session with db_api.CONTEXT_WRITER.using(context): session.query(models.VlanBindings).filter_by( ovsdb_identifier=ovsdb_identifier).delete() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/l2gateway/ovsdb/models.py0000664000175000017500000001032614572557755026620 0ustar00jamespagejamespage# Copyright (c) 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. from neutron_lib.db import model_base import sqlalchemy as sa class PhysicalLocators(model_base.BASEV2): __tablename__ = 'physical_locators' uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) dst_ip = sa.Column(sa.String(64), nullable=True) ovsdb_identifier = sa.Column(sa.String(64), nullable=False, primary_key=True) class PhysicalSwitches(model_base.BASEV2): __tablename__ = 'physical_switches' uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) name = sa.Column(sa.String(255), nullable=True) tunnel_ip = sa.Column(sa.String(64), nullable=True) ovsdb_identifier = sa.Column(sa.String(64), nullable=False, primary_key=True) switch_fault_status = sa.Column(sa.String(length=32), nullable=True) class PhysicalPorts(model_base.BASEV2): __tablename__ = 'physical_ports' uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) name = sa.Column(sa.String(255), nullable=True) physical_switch_id = sa.Column(sa.String(36), nullable=True) ovsdb_identifier = sa.Column(sa.String(64), nullable=False, primary_key=True) port_fault_status = sa.Column(sa.String(length=32), nullable=True) class LogicalSwitches(model_base.BASEV2): __tablename__ = 'logical_switches' uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) name = sa.Column(sa.String(255), nullable=True) key = sa.Column(sa.Integer, nullable=True) ovsdb_identifier = sa.Column(sa.String(64), nullable=False, primary_key=True) class UcastMacsLocals(model_base.BASEV2): __tablename__ = 'ucast_macs_locals' uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) mac = sa.Column(sa.String(32), nullable=True) logical_switch_id = sa.Column(sa.String(36), nullable=True) physical_locator_id = sa.Column(sa.String(36), nullable=True) ip_address = sa.Column(sa.String(64), nullable=True) ovsdb_identifier = sa.Column(sa.String(64), nullable=False, primary_key=True) class UcastMacsRemotes(model_base.BASEV2): __tablename__ = 'ucast_macs_remotes' uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) mac = sa.Column(sa.String(32), nullable=True) logical_switch_id = sa.Column(sa.String(36), nullable=True) physical_locator_id = sa.Column(sa.String(36), nullable=True) ip_address = sa.Column(sa.String(64), nullable=True) ovsdb_identifier = sa.Column(sa.String(64), nullable=False, primary_key=True) class VlanBindings(model_base.BASEV2): __tablename__ = 'vlan_bindings' port_uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) vlan = sa.Column(sa.Integer, nullable=False, primary_key=True) logical_switch_uuid = sa.Column(sa.String(36), nullable=False, primary_key=True) ovsdb_identifier = sa.Column(sa.String(64), nullable=False, primary_key=True) class PendingUcastMacsRemote(model_base.BASEV2, model_base.HasId): __tablename__ = 'pending_ucast_macs_remotes' uuid = sa.Column(sa.String(36), nullable=True) mac = sa.Column(sa.String(32), nullable=False) logical_switch_uuid = sa.Column(sa.String(36), nullable=False) locator_uuid = sa.Column(sa.String(36), nullable=True) dst_ip = sa.Column(sa.String(64)) vm_ip = sa.Column(sa.String(64)) ovsdb_identifier = sa.Column(sa.String(64), nullable=False) operation = sa.Column(sa.String(8), nullable=False) timestamp = sa.Column(sa.DateTime, nullable=False) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/0000775000175000017500000000000014572557757023740 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/__init__.py0000664000175000017500000000000014572557755026035 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/0000775000175000017500000000000014572557757027570 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/README0000664000175000017500000000011614572557755030444 0ustar00jamespagejamespageThis directory contains the migration scripts for the networking_l2gw project.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/__init__.py0000664000175000017500000000000014572557755031665 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/env.py0000664000175000017500000000544114572557755030734 0ustar00jamespagejamespage# 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. # 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.db.migration.alembic_migrations import external from neutron.db.migration.models import head # noqa MYSQL_ENGINE = None L2GW_VERSION_TABLE = 'l2gw_alembic_version' 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 include_object(object, name, type_, reflected, compare_to): if type_ == 'table' and name in external.TABLES: return False else: return True 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['include_object'] = include_object kwargs['version_table'] = L2GW_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, include_object=include_object, version_table=L2GW_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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/script.py.mako0000664000175000017500000000203514572557755032372 0ustar00jamespagejamespage# 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"} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/0000775000175000017500000000000014572557757031440 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000022500000000000010214 xustar00127 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/42438454c556_l2gateway_models.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/42438454c556_l20000664000175000017500000001055414572557755033307 0ustar00jamespagejamespage# 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. # """l2gateway_models Revision ID: 42438454c556 Revises: 54c9c8fe22bf Create Date: 2014-11-27 01:57:56.997665 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '42438454c556' down_revision = '54c9c8fe22bf' def upgrade(): op.create_table('l2gateways', sa.Column('id', sa.String(length=36), nullable=False), sa.Column('name', sa.String(length=255), nullable=True), sa.Column('tenant_id', sa.String(length=255), nullable=True), sa.PrimaryKeyConstraint('id')) op.create_table('l2gatewaydevices', sa.Column('id', sa.String(length=36), nullable=False), sa.Column('device_name', sa.String(length=255), nullable=False), sa.Column('l2_gateway_id', sa.String(length=36), nullable=False), sa.ForeignKeyConstraint(['l2_gateway_id'], ['l2gateways.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id')) op.create_table('l2gatewayinterfaces', sa.Column('id', sa.String(length=36), nullable=False), sa.Column('interface_name', sa.String(length=255), nullable=True), sa.Column('segmentation_id', sa.Integer(), nullable=True), sa.Column('device_id', sa.String(length=36), nullable=False), sa.ForeignKeyConstraint(['device_id'], ['l2gatewaydevices.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id')) op.create_table('l2gatewayconnections', sa.Column('id', sa.String(length=36), nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=True), sa.Column('l2_gateway_id', sa.String(length=36), nullable=True), sa.Column('network_id', sa.String(length=36), nullable=False), sa.Column('segmentation_id', sa.Integer(), nullable=True), sa.ForeignKeyConstraint(['l2_gateway_id'], ['l2gateways.id'], ondelete='CASCADE'), sa.ForeignKeyConstraint(['network_id'], ['networks.id'], ondelete='CASCADE'), sa.UniqueConstraint('l2_gateway_id', 'network_id'), sa.PrimaryKeyConstraint('id')) op.create_table('pending_ucast_macs_remotes', sa.Column('id', sa.String(length=36), nullable=False), sa.Column('uuid', sa.String(length=36), nullable=True), sa.Column('mac', sa.String(32), nullable=False), sa.Column('logical_switch_uuid', sa.String(36), nullable=False), sa.Column('locator_uuid', sa.String(36), nullable=True), sa.Column('dst_ip', sa.String(64)), sa.Column('vm_ip', sa.String(64)), sa.Column('ovsdb_identifier', sa.String(64), nullable=False), sa.Column('operation', sa.String(8), nullable=False), sa.Column('timestamp', sa.DateTime, nullable=False)) ././@PaxHeader0000000000000000000000000000025500000000000010217 xustar00151 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/54c9c8fe22bf_db_models_for_ovsdb_hardware_vtep_schema.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/54c9c8fe22bf_db0000664000175000017500000001175614572557755033746 0ustar00jamespagejamespage# 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. # """DB_Models_for_OVSDB_Hardware_VTEP_Schema Revision ID: 54c9c8fe22bf Revises: 42438454c556 Create Date: 2015-01-27 02:05:21.599215 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '54c9c8fe22bf' down_revision = 'start_networking_l2gw' def upgrade(): op.create_table('physical_locators', sa.Column('dst_ip', sa.String(length=64), nullable=True), sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('ovsdb_identifier', sa.String(length=64), nullable=False), sa.PrimaryKeyConstraint('uuid', 'ovsdb_identifier')) op.create_table('physical_switches', sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('name', sa.String(length=255), nullable=True), sa.Column('tunnel_ip', sa.String(length=64), nullable=True), sa.Column('ovsdb_identifier', sa.String(length=64), nullable=False), sa.Column('switch_fault_status', sa.String(length=32), nullable=True), sa.PrimaryKeyConstraint('uuid', 'ovsdb_identifier')) op.create_table('physical_ports', sa.Column('name', sa.String(length=255), nullable=True), sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('physical_switch_id', sa.String(length=36), nullable=True), sa.Column('ovsdb_identifier', sa.String(length=64), nullable=False), sa.Column('port_fault_status', sa.String(length=32), nullable=True), sa.PrimaryKeyConstraint('uuid', 'ovsdb_identifier')) op.create_table('logical_switches', sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('name', sa.String(length=255), nullable=True), sa.Column('key', sa.Integer(), nullable=True), sa.Column('ovsdb_identifier', sa.String(length=64), nullable=False), sa.PrimaryKeyConstraint('uuid', 'ovsdb_identifier')) op.create_table('ucast_macs_locals', sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('mac', sa.String(length=32), nullable=True), sa.Column('logical_switch_id', sa.String(length=36), nullable=True), sa.Column('physical_locator_id', sa.String(length=36), nullable=True), sa.Column('ip_address', sa.String(length=64), nullable=True), sa.Column('ovsdb_identifier', sa.String(length=64), nullable=False), sa.PrimaryKeyConstraint('uuid', 'ovsdb_identifier')) op.create_table('ucast_macs_remotes', sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('mac', sa.String(length=32), nullable=True), sa.Column('logical_switch_id', sa.String(length=36), nullable=True), sa.Column('physical_locator_id', sa.String(length=36), nullable=True), sa.Column('ip_address', sa.String(length=64), nullable=True), sa.Column('ovsdb_identifier', sa.String(length=64), nullable=False), sa.PrimaryKeyConstraint('uuid', 'ovsdb_identifier')) op.create_table('vlan_bindings', sa.Column('port_uuid', sa.String(length=36), nullable=False), sa.Column('vlan', sa.Integer(), nullable=False), sa.Column('logical_switch_uuid', sa.String(length=36), nullable=False), sa.Column('ovsdb_identifier', sa.String(length=64), nullable=False), sa.PrimaryKeyConstraint('port_uuid', 'ovsdb_identifier', 'vlan', 'logical_switch_uuid')) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/CONTRACT_HEAD0000664000175000017500000000001514572557755033353 0ustar00jamespagejamespage0fb45e525aa9 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/EXPAND_HEAD0000664000175000017500000000001514572557755033115 0ustar00jamespagejamespage8d7d772eafcf ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/__init__.py0000664000175000017500000000000014572557755033535 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/kilo_release.py0000664000175000017500000000151414572557755034447 0ustar00jamespagejamespage# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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: 42438454c556 Create Date: 2015-04-16 00:00:00.000000 """ # revision identifiers, used by Alembic. revision = 'kilo' down_revision = '42438454c556' def upgrade(): """A no-op migration for marking the Kilo release.""" pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9319854 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/0000775000175000017500000000000014572557757033112 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000021400000000000010212 xustar00112 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/contract/ 28 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/contrac0000775000175000017500000000000014572557757034464 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000024600000000000010217 xustar00144 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/contract/79919185aa99_initial_contract.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/contrac0000664000175000017500000000201614572557755034463 0ustar00jamespagejamespage# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 contract rule. Revision ID: 79919185aa99 Revises: kilo Create Date: 2015-07-16 00:00:00.000000 """ from neutron.db import migration from neutron.db.migration import cli # revision identifiers, used by Alembic. revision = '79919185aa99' down_revision = 'kilo' branch_labels = (cli.CONTRACT_BRANCH,) # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.LIBERTY, migration.MITAKA] def upgrade(): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/expand/0000775000175000017500000000000014572557757034371 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000024200000000000010213 xustar00140 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/expand/60019185aa99_initial_expand.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/liberty/expand/0000664000175000017500000000201214572557755034364 0ustar00jamespagejamespage# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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: 60019185aa99 Revises: kilo Create Date: 2015-07-16 00:00:00.000000 """ from neutron.db import migration from neutron.db.migration import cli # revision identifiers, used by Alembic. revision = '60019185aa99' down_revision = 'kilo' branch_labels = (cli.EXPAND_BRANCH,) # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.LIBERTY, migration.MITAKA] def upgrade(): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9319854 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/0000775000175000017500000000000014572557757032752 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000021300000000000010211 xustar00111 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/contract/ 28 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/contract0000775000175000017500000000000014572557757034510 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000025500000000000010217 xustar00151 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/contract/2f533f7705dd_rename_tenant_to_project.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/contract0000664000175000017500000000636514572557755034522 0ustar00jamespagejamespage# Copyright 2016 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. # """rename tenant to project Revision ID: 2f533f7705dd Create Date: 2016-07-15 01:53:43.922235 """ from alembic import op import sqlalchemy as sa from sqlalchemy.engine import reflection from neutron.db import migration # revision identifiers, used by Alembic. revision = '2f533f7705dd' down_revision = '79919185aa99' depends_on = ('49ce408ac349',) _INSPECTOR = None # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.NEWTON, migration.OCATA] def get_inspector(): """Reuse inspector.""" global _INSPECTOR if _INSPECTOR: return _INSPECTOR else: 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 = [ 'l2gateways', 'l2gatewayconnections', ] return tables def get_columns(table): """Returns list of columns for given table.""" inspector = get_inspector() return inspector.get_columns(table) def get_data(): """Returns combined list of tuples: [(table, column)]. The list is built from tables with a tenant_id column. """ output = [] tables = get_tables() for table in tables: columns = get_columns(table) for column in columns: if column['name'] == 'tenant_id': output.append((table, column)) return output def alter_column(table, column): old_name = 'tenant_id' new_name = 'project_id' op.alter_column( table_name=table, column_name=old_name, new_column_name=new_name, existing_type=column['type'], existing_nullable=column['nullable'] ) def recreate_index(index, table_name): old_name = index['name'] new_name = old_name.replace('tenant', 'project') op.drop_index(op.f(old_name), table_name) op.create_index(new_name, table_name, ['project_id']) def upgrade(): 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() } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/expand/0000775000175000017500000000000014572557757034231 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000025300000000000010215 xustar00149 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/expand/49ce408ac349_add_indexes_to_tenant_id.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/newton/expand/40000664000175000017500000000215314572557755034316 0ustar00jamespagejamespage# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 indexes to tenant_id Revision ID: 49ce408ac349 Create Date: 2016-07-22 10:42:14.495451 """ from alembic import op from neutron.db import migration # revision identifiers, used by Alembic. revision = '49ce408ac349' down_revision = '60019185aa99' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.NEWTON, migration.OCATA] def upgrade(): for table in ['l2gateways', 'l2gatewayconnections']: op.create_index(op.f('ix_%s_tenant_id' % table), table, ['tenant_id'], unique=False) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9319854 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/0000775000175000017500000000000014572557757032740 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000021300000000000010211 xustar00111 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/contract/ 28 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/contract0000775000175000017500000000000014572557757034476 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000026700000000000010222 xustar00161 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/contract/0fb45e525aa9_add_standard_attribute_id_for_l2gw.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/contract0000664000175000017500000000530014572557755034474 0ustar00jamespagejamespage# Copyright 2017 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 standard_attribute_id for l2gw Revision ID: 0fb45e525aa9 Revises: 2f533f7705dd Create Date: 2017-12-19 07:49:35.418145 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '0fb45e525aa9' down_revision = '2f533f7705dd' depends_on = ('8d7d772eafcf',) TABLES = ("l2gateways", "l2gatewayconnections", "l2gatewaydevices") TABLE_MODELS = [ (table, sa.Table(table, sa.MetaData(), sa.Column('id', sa.String(length=36), nullable=False), sa.Column('standard_attr_id', sa.BigInteger(), nullable=True))) for table in TABLES ] standardattrs = sa.Table( 'standardattributes', sa.MetaData(), sa.Column('id', sa.BigInteger(), primary_key=True, autoincrement=True), sa.Column('resource_type', sa.String(length=255), nullable=False)) def upgrade(): generate_records_for_existing() for table, model in TABLE_MODELS: op.alter_column(table, 'standard_attr_id', nullable=False, existing_type=sa.BigInteger(), existing_nullable=True, existing_server_default=False) op.create_foreign_key( constraint_name=None, source_table=table, referent_table='standardattributes', local_cols=['standard_attr_id'], remote_cols=['id'], ondelete='CASCADE') op.create_unique_constraint( constraint_name='uniq_%s0standard_attr_id' % table, table_name=table, columns=['standard_attr_id']) def generate_records_for_existing(): session = sa.orm.Session(bind=op.get_bind()) values = [] for table, model in TABLE_MODELS: for row in session.query(model): res = session.execute( standardattrs.insert().values(resource_type=table)) session.execute( model.update().values( standard_attr_id=res.inserted_primary_key[0]).where( model.c.id == row[0])) # this commit is necessary to allow further operations session.commit() return values ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/expand/0000775000175000017500000000000014572557757034217 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000026500000000000010220 xustar00159 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/expand/8d7d772eafcf_add_standard_attribute_id_for_l2gw.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/queens/expand/80000664000175000017500000000217014572557755034307 0ustar00jamespagejamespage# Copyright 2017 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 standard_attribute_id for l2gw Revision ID: 8d7d772eafcf Revises: 49ce408ac349 Create Date: 2017-12-19 07:49:23.362289 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '8d7d772eafcf' down_revision = '49ce408ac349' TABLES = ("l2gateways", "l2gatewayconnections", "l2gatewaydevices") def upgrade(): for table in TABLES: op.add_column(table, sa.Column('standard_attr_id', sa.BigInteger(), nullable=True)) ././@PaxHeader0000000000000000000000000000021500000000000010213 xustar00119 path=networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/start_networking_l2gw.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/db/migration/alembic_migrations/versions/start_networkin0000664000175000017500000000154114572557755034617 0ustar00jamespagejamespage# 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. # """start networking-l2gw chain Revision ID: start_networking_l2gw Revises: None Create Date: 2015-02-04 11:06:18.196062 """ # revision identifiers, used by Alembic. revision = 'start_networking_l2gw' down_revision = None def upgrade(): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/extensions/0000775000175000017500000000000014572557757023561 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/extensions/__init__.py0000664000175000017500000000000014572557755025656 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/extensions/l2gateway.py0000664000175000017500000000712014572557755026030 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 abc from neutron_lib.api import extensions as api_extensions from neutron_lib.api import validators from neutron.api import extensions from neutron.api.v2 import resource_helper from neutron.common import config as common_config from networking_l2gw import extensions as l2gw_extensions from networking_l2gw.services.l2gateway.common import constants from networking_l2gw.services.l2gateway.common import l2gw_validators common_config.register_common_config_options() extensions.append_api_extensions_path(l2gw_extensions.__path__) RESOURCE_ATTRIBUTE_MAP = { constants.L2_GATEWAYS: { 'id': {'allow_post': False, 'allow_put': False, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'devices': {'allow_post': True, 'allow_put': True, 'validate': {'type:l2gwdevice_list': None}, 'is_visible': True}, 'tenant_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'required_by_policy': True, 'is_visible': True} }, } validators.add_validator('l2gwdevice_list', l2gw_validators.validate_gwdevice_list) class L2gateway(api_extensions.ExtensionDescriptor): """API extension for Layer-2 Gateway support.""" @classmethod def get_name(cls): return "L2 Gateway" @classmethod def get_alias(cls): return "l2-gateway" @classmethod def get_description(cls): return "Connects Neutron networks with external networks at layer 2." @classmethod def get_updated(cls): return "2015-01-01T00:00:00-00:00" @classmethod def get_resources(cls): """Returns Ext Resources.""" plural_mappings = resource_helper.build_plural_mappings( {}, RESOURCE_ATTRIBUTE_MAP) resources = resource_helper.build_resource_info(plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.L2GW) return resources def get_extended_resources(self, version): if version == "2.0": return RESOURCE_ATTRIBUTE_MAP else: return {} class L2GatewayPluginBase(object): @abc.abstractmethod def create_l2_gateway(self, context, l2_gateway): pass @abc.abstractmethod def get_l2_gateway(self, context, id, fields=None): pass @abc.abstractmethod def delete_l2_gateway(self, context, id): pass @abc.abstractmethod def get_l2_gateways(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): pass @abc.abstractmethod def update_l2_gateway(self, context, id, l2_gateway): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/extensions/l2gatewayconnection.py0000664000175000017500000000741614572557755030120 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 abc from neutron_lib.api import extensions from neutron.api.v2 import resource_helper from networking_l2gw.services.l2gateway.common import constants RESOURCE_ATTRIBUTE_MAP = { constants.L2_GATEWAYS_CONNECTION: { 'id': {'allow_post': False, 'allow_put': False, 'is_visible': True}, 'l2_gateway_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'network_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'is_visible': True}, 'segmentation_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'tenant_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'required_by_policy': True, 'is_visible': True} }, } class L2gatewayconnection(extensions.ExtensionDescriptor): """API extension for Layer-2 Gateway connection support.""" @classmethod def get_name(cls): return "L2 Gateway connection" @classmethod def get_alias(cls): return "l2-gateway-connection" @classmethod def get_description(cls): return "Connects Neutron networks with external networks at layer 2." @classmethod def get_updated(cls): return "2014-01-01T00:00:00-00:00" @classmethod def get_resources(cls): """Returns Ext Resources.""" mem_actions = {} plural_mappings = resource_helper.build_plural_mappings( {}, RESOURCE_ATTRIBUTE_MAP) resources = resource_helper.build_resource_info(plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.L2GW, action_map=mem_actions, register_quota=True, translate_name=True) return resources def get_extended_resources(self, version): if version == "2.0": return RESOURCE_ATTRIBUTE_MAP else: return {} class L2GatewayConnectionPluginBase(object): @abc.abstractmethod def delete_l2_gateway_connection(self, context, l2_gateway_id, network_mapping_list): pass @abc.abstractmethod def create_l2_gateway_connection(self, context, l2_gateway_id, network_mapping_list): pass @abc.abstractmethod def get_l2_gateway_connections(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): pass @abc.abstractmethod def get_l2_gateway_connection(self, context, id, fields=None): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/l2gatewayclient/0000775000175000017500000000000014572557757024460 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/l2gatewayclient/__init__.py0000664000175000017500000000000014572557755026555 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/l2gatewayclient/osc/0000775000175000017500000000000014572557757025244 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/l2gatewayclient/osc/__init__.py0000664000175000017500000000000014572557755027341 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/l2gatewayclient/osc/l2gw.py0000664000175000017500000002252014572557755026470 0ustar00jamespagejamespage# All Rights Reserved 2018 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from neutronclient._i18n import _ from neutronclient.common import utils from neutronclient.osc import utils as nc_osc_utils LOG = logging.getLogger(__name__) INTERFACE_DELIMITER = ";" SEGMENTATION_ID_DELIMITER = "#" INTERFACE_SEG_ID_DELIMITER = "|" L2_GATEWAY = 'l2_gateway' L2_GATEWAYS = '%ss' % L2_GATEWAY path = 'l2-gateways' object_path = '/%s' % path resource_path = '/%s/%%s' % path _attr_map = ( ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('name', 'Name', column_util.LIST_BOTH), ('devices', 'Devices', column_util.LIST_BOTH), ) _formatters = { 'devices': format_columns.ListDictColumn, } def _get_common_parser(parser): """Adds to parser arguments common to create and update commands. :params ArgumentParser parser: argparse object contains all command's arguments """ parser.add_argument( '--device', metavar='name=name,interface_names=INTERFACE-DETAILS', action='append', dest='devices', type=utils.str2dict, help=_('Device name and Interface-names of l2gateway. ' 'INTERFACE-DETAILS is of form ' '\";[]' '[|[#]]\" ' '(--device option can be repeated)')) def get_interface(interfaces): interface_dict = [] for interface in interfaces: if INTERFACE_SEG_ID_DELIMITER in interface: int_name = interface.split(INTERFACE_SEG_ID_DELIMITER)[0] segid = interface.split(INTERFACE_SEG_ID_DELIMITER)[1] if SEGMENTATION_ID_DELIMITER in segid: segid = segid.split(SEGMENTATION_ID_DELIMITER) else: segid = [segid] interface_detail = {'name': int_name, 'segmentation_id': segid} else: interface_detail = {'name': interface} interface_dict.append(interface_detail) return interface_dict def _args2body(parsed_args, update=False): if parsed_args.devices: devices = parsed_args.devices interfaces = [] else: devices = [] device_dict = [] for device in devices: if 'interface_names' in device.keys(): interface = device['interface_names'] if INTERFACE_DELIMITER in interface: interface_dict = interface.split(INTERFACE_DELIMITER) interfaces = get_interface(interface_dict) else: interfaces = get_interface([interface]) if 'name' in device.keys(): device = {'device_name': device['name'], 'interfaces': interfaces} else: device = {'interfaces': interfaces} device_dict.append(device) if parsed_args.name: l2gw_name = parsed_args.name body = {L2_GATEWAY: {'name': l2gw_name, 'devices': device_dict}, } else: body = {L2_GATEWAY: {'devices': device_dict}, } return body class CreateL2gw(command.ShowOne): _description = _("Create l2gateway resource") def get_parser(self, prog_name): parser = super(CreateL2gw, self).get_parser(prog_name) nc_osc_utils.add_project_owner_option_to_parser(parser) parser.add_argument( 'name', metavar='', help=_('Descriptive name for logical gateway.')) _get_common_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.devices is not None: attrs['devices'] = str(parsed_args.devices) if 'project' in parsed_args and parsed_args.project is not None: project_id = nc_osc_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id attrs['tenant_id'] = project_id body = _args2body(parsed_args) obj = client.post(object_path, body)[L2_GATEWAY] columns, display_columns = column_util.get_columns(obj, _attr_map) data = osc_utils.get_dict_properties(obj, columns, formatters=_formatters) return display_columns, data class ListL2gw(command.Lister): _description = _("List l2gateway that belongs to a given tenant") def get_parser(self, prog_name): parser = super(ListL2gw, self).get_parser(prog_name) nc_osc_utils.add_project_owner_option_to_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient params = {} if parsed_args.project is not None: project_id = nc_osc_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id params['tenant_id'] = project_id objs = client.list(L2_GATEWAYS, object_path, retrieve_all=True, params=params)[L2_GATEWAYS] headers, columns = column_util.get_column_definitions( _attr_map, long_listing=True) return (headers, (osc_utils.get_dict_properties( s, columns, formatters=_formatters) for s in objs)) class ShowL2gw(command.ShowOne): _description = _("Show information of a given l2gateway") def get_parser(self, prog_name): parser = super(ShowL2gw, self).get_parser(prog_name) parser.add_argument( L2_GATEWAY, metavar="", help=_("ID or name of l2_gateway to look up."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient id = client.find_resource(L2_GATEWAY, parsed_args.l2_gateway)['id'] obj = client.get(resource_path % id)[L2_GATEWAY] columns, display_columns = column_util.get_columns(obj, _attr_map) data = osc_utils.get_dict_properties(obj, columns, formatters=_formatters) return display_columns, data class DeleteL2gw(command.Command): _description = _("Delete a given l2gateway") def get_parser(self, prog_name): parser = super(DeleteL2gw, self).get_parser(prog_name) parser.add_argument( L2_GATEWAYS, metavar="", nargs="+", help=_("ID(s) or name(s) of l2_gateway to delete."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient fails = 0 for id_or_name in parsed_args.l2_gateways: try: id = client.find_resource(L2_GATEWAY, id_or_name)['id'] client.delete(resource_path % id) LOG.warning("L2 Gateaway %(id)s deleted", {'id': id}) except Exception as e: fails += 1 LOG.error("Failed to delete L2 Gateway with name or ID " "'%(id_or_name)s': %(e)s", {'id_or_name': id_or_name, 'e': e}) if fails > 0: msg = (_("Failed to delete %(fails)s of %(total)s L2 Gateway.") % {'fails': fails, 'total': len(parsed_args.l2_gateways)}) raise exceptions.CommandError(msg) class UpdateL2gw(command.ShowOne): _description = _("Update a given l2gateway") def get_parser(self, prog_name): parser = super(UpdateL2gw, self).get_parser(prog_name) parser.add_argument( L2_GATEWAY, metavar="", help=_("ID or name of l2_gateway to update."), ) parser.add_argument('--name', metavar='name', help=_('Descriptive name for logical gateway.')) _get_common_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient id = client.find_resource(L2_GATEWAY, parsed_args.l2_gateway)['id'] if parsed_args.devices: body = _args2body(parsed_args) else: body = {L2_GATEWAY: {'name': parsed_args.name}} obj = client.put(resource_path % id, body)[L2_GATEWAY] columns, display_columns = column_util.get_columns(obj, _attr_map) data = osc_utils.get_dict_properties(obj, columns, formatters=_formatters) return display_columns, data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/l2gatewayclient/osc/l2gw_connection.py0000664000175000017500000001501414572557755030707 0ustar00jamespagejamespage# All Rights Reserved 2018 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from neutronclient._i18n import _ from neutronclient.neutron import v2_0 as n_v20 from neutronclient.osc import utils as nc_osc_utils LOG = logging.getLogger(__name__) L2_GATEWAY_CONNECTION = 'l2_gateway_connection' L2_GATEWAY_CONNECTIONS = '%ss' % L2_GATEWAY_CONNECTION path = 'l2-gateway-connections' object_path = '/%s' % path resource_path = '/%s/%%s' % path _attr_map = ( ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('l2_gateway_id', 'L2 GateWay ID', column_util.LIST_BOTH), ('network_id', 'Network ID', column_util.LIST_BOTH), ('segmentation_id', 'Segmentation ID', column_util.LIST_BOTH), ) class CreateL2gwConnection(command.ShowOne): _description = _("Create l2gateway-connection") def retrieve_ids(self, client, args): gateway_id = n_v20.find_resourceid_by_name_or_id( client, 'l2_gateway', args.gateway_name) network_id = n_v20.find_resourceid_by_name_or_id( client, 'network', args.network) return gateway_id, network_id def get_parser(self, prog_name): parser = super(CreateL2gwConnection, self).get_parser(prog_name) parser.add_argument('gateway_name', metavar='', help=_('Descriptive name for logical gateway.')) parser.add_argument( 'network', metavar='', help=_('Network name or uuid.')) parser.add_argument( '--default-segmentation-id', dest='seg_id', help=_('default segmentation-id that will ' 'be applied to the interfaces for which ' 'segmentation id was not specified ' 'in l2-gateway-create command.')) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient (gateway_id, network_id) = self.retrieve_ids(client, parsed_args) body = { L2_GATEWAY_CONNECTION: { 'l2_gateway_id': gateway_id, 'network_id': network_id } } if parsed_args.seg_id: body[L2_GATEWAY_CONNECTION]['segmentation_id'] = \ parsed_args.seg_id obj = client.post(object_path, body)[L2_GATEWAY_CONNECTION] columns, display_columns = column_util.get_columns(obj, _attr_map) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class ListL2gwConnection(command.Lister): _description = _("List l2gateway-connections") def get_parser(self, prog_name): parser = super(ListL2gwConnection, self).get_parser(prog_name) nc_osc_utils.add_project_owner_option_to_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient params = {} if parsed_args.project is not None: project_id = nc_osc_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id params['tenant_id'] = project_id objs = client.list( L2_GATEWAY_CONNECTIONS, object_path, retrieve_all=True, params=params)[L2_GATEWAY_CONNECTIONS] headers, columns = column_util.get_column_definitions( _attr_map, long_listing=True) return (headers, (osc_utils.get_dict_properties( s, columns) for s in objs)) class ShowL2gwConnection(command.ShowOne): _description = _("Show information of a given l2gateway-connection") def get_parser(self, prog_name): parser = super(ShowL2gwConnection, self).get_parser(prog_name) parser.add_argument( L2_GATEWAY_CONNECTION, metavar="", help=_("ID of l2_gateway_connection to look up."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient id = client.find_resource(L2_GATEWAY_CONNECTION, parsed_args.l2_gateway_connection)['id'] obj = client.get(resource_path % id)[L2_GATEWAY_CONNECTION] columns, display_columns = column_util.get_columns(obj, _attr_map) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class DeleteL2gwConnection(command.Command): _description = _("Delete a given l2gateway-connection") def get_parser(self, prog_name): parser = super(DeleteL2gwConnection, self).get_parser(prog_name) parser.add_argument( L2_GATEWAY_CONNECTIONS, metavar="", nargs="+", help=_("ID(s) of l2_gateway_connections(s) to delete."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.neutronclient fails = 0 for id_or_name in parsed_args.l2_gateway_connections: try: id = client.find_resource( L2_GATEWAY_CONNECTION, id_or_name)['id'] client.delete(resource_path % id) LOG.warning("L2 Gateaway Connection %(id)s deleted", {'id': id}) except Exception as e: fails += 1 LOG.error("Failed to delete L2 Gateway Connection with name " "or ID '%(id_or_name)s': %(e)s", {'id_or_name': id_or_name, 'e': e}) if fails > 0: msg = (_("Failed to delete %(fails)s of %(total)s L2 Gateway " "Connection.") % {'fails': fails, 'total': len( parsed_args.l2_gateway_connections)}) raise exceptions.CommandError(msg) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/0000775000175000017500000000000014572557757023205 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/__init__.py0000664000175000017500000000000014572557755025302 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/0000775000175000017500000000000014572557757025104 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/__init__.py0000664000175000017500000000000014572557755027201 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/0000775000175000017500000000000014572557757026202 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/__init__.py0000664000175000017500000000000014572557755030277 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/agent_api.py0000664000175000017500000000277114572557755030510 0ustar00jamespagejamespage# 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 neutron_lib import rpc as n_rpc import oslo_messaging as messaging class L2GatewayAgentApi(object): """Agent side of the Agent to Plugin RPC API.""" API_VERSION = '1.0' def __init__(self, topic, host): self.host = host target = messaging.Target(topic=topic, version=self.API_VERSION) self.client = n_rpc.get_client(target) def update_ovsdb_changes(self, context, activity, ovsdb_data): cctxt = self.client.prepare() return cctxt.cast(context, 'update_ovsdb_changes', activity=activity, ovsdb_data=ovsdb_data) def notify_ovsdb_states(self, context, ovsdb_states): cctxt = self.client.prepare() return cctxt.cast(context, 'notify_ovsdb_states', ovsdb_states=ovsdb_states) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/base_agent_manager.py0000664000175000017500000000675414572557755032350 0ustar00jamespagejamespage# 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 neutron.agent import rpc as agent_rpc from neutron_lib import context from oslo_config import cfg from oslo_log import log as logging from oslo_service import loopingcall from oslo_service import periodic_task from networking_l2gw.services.l2gateway.agent import agent_api from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway.common import topics LOG = logging.getLogger(__name__) class BaseAgentManager(periodic_task.PeriodicTasks): """Basic agent manager that handles basic RPCs and report states.""" def __init__(self, conf=None): conf = getattr(self, "conf", cfg.CONF) super(BaseAgentManager, self).__init__(conf) self.l2gw_agent_type = '' self.gateways = {} self.plugin_rpc = agent_api.L2GatewayAgentApi( topics.L2GATEWAY_PLUGIN, self.conf.host ) self._get_agent_state() self.admin_state_up = True self._setup_state_rpc() def _get_agent_state(self): self.agent_state = { 'binary': 'neutron-l2gateway-agent', 'host': self.conf.host, 'topic': topics.L2GATEWAY_AGENT, 'configurations': { 'report_interval': self.conf.AGENT.report_interval, n_const.L2GW_AGENT_TYPE: self.l2gw_agent_type, }, 'start_flag': True, 'agent_type': n_const.AGENT_TYPE_L2GATEWAY} def _setup_state_rpc(self): self.state_rpc = agent_rpc.PluginReportStateAPI( topics.L2GATEWAY_PLUGIN) report_interval = self.conf.AGENT.report_interval if report_interval: heartbeat = loopingcall.FixedIntervalLoopingCall( self._report_state) heartbeat.start(interval=report_interval) def _report_state(self): try: ctx = context.get_admin_context_without_session() self.state_rpc.report_state(ctx, self.agent_state, True) self.agent_state['start_flag'] = False except Exception: LOG.exception("Failed reporting state!") self.handle_report_state_failure() def handle_report_state_failure(self): pass def agent_updated(self, context, payload): LOG.info("agent_updated by server side %s!", payload) def set_monitor_agent(self, context, hostname): """Handle RPC call from plugin to update agent type. RPC call from the plugin to accept that I am a monitoring or a transact agent. This is a fanout cast message """ if hostname == self.conf.host: self.l2gw_agent_type = n_const.MONITOR else: self.l2gw_agent_type = '' self.agent_state.get('configurations')[n_const.L2GW_AGENT_TYPE ] = self.l2gw_agent_type ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/l2gateway_config.py0000664000175000017500000000260714572557755032003 0ustar00jamespagejamespage# 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 networking_l2gw.services.l2gateway.common import constants as n_const OVSDB_IP = 'ovsdb_ip' OVSDB_PORT = 'ovsdb_port' PRIVATE_KEY = 'private_key' USE_SSL = 'use_ssl' CERTIFICATE = 'certificate' CA_CERT = 'ca_cert' class L2GatewayConfig(object): def __init__(self, ovsdb_config): self.use_ssl = False if ovsdb_config.get(USE_SSL, None): self.use_ssl = ovsdb_config[USE_SSL] self.private_key = ovsdb_config[PRIVATE_KEY] self.certificate = ovsdb_config[CERTIFICATE] self.ca_cert = ovsdb_config[CA_CERT] self.ovsdb_identifier = ovsdb_config[n_const.OVSDB_IDENTIFIER] self.ovsdb_ip = ovsdb_config[OVSDB_IP] self.ovsdb_port = ovsdb_config[OVSDB_PORT] self.ovsdb_fd = None ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/0000775000175000017500000000000014572557757027317 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/README.rst0000664000175000017500000000534014572557755031006 0ustar00jamespagejamespageOther approaches for the L2 gateway agent to communicate with the OVSDB server ------------------------------------------------------------------------------ For an L2 gateway agent to communicate with an OVSDB server, IDL class provided by the OpenvSwitch library was explored. The source code for the IDL class can be found at: 1. https://github.com/osrg/openvswitch/tree/master/python/ovs/db/idl.py 2. Alternatively, the source code can be downloaded from http://openvswitch.org/download/ as a tar ball. Here are the findings. 1. IDL class maintains a socket connection with a given OVSDB server and maintains a local cache of the OVSDB tables. 2. Whenever there is a change in the OVSDB tables, the cache is updated automatically. 3. The caller has to pass the required data structures to perform a transaction on OVSDB tables. Advantages: 1. Logic of maintaining connection with the OVSDB servers is easy. 2. No need to write extra logic of performing the transaction on the OVSDB tables. Only rows to be inserted/modified/deleted are to be supplied. Disadvantages: 1. To perform a transaction on a table, the caller has to register that table with IDL so that the local cache is maintained. Without registering, a transaction cannot be performed. 2. Due to this, every transact L2 gateway agent will have to maintain unnecessary local cache of OVSDB tables (which gets updated automatically whenever there is a change in the OVSDB table state). 3. With the current approach, when the Monitor agent receives notifications for OVSDB changes, the agent instantly sends RPC to the plugin notifying the changes. The IDL class internally receives event notifications for any updates to the OVSDB tables and are processed internally. We may not be able to invoke RPCs to the plugin if IDL class is used. 4. It violates our basic concept of transact and monitor agent. 5. After browsing the code, could not find any option in this python binding to provide SSL keys/certs so as to open an SSL connection/stream to the OVSDB server. https://github.com/osrg/openvswitch/blob/master/python/ovs/socket_util.py https://github.com/osrg/openvswitch/blob/master/python/ovs/stream.py 6. We may have to package the openvswitch 2.3.1 library with the agent Advantages of the current code of the L2 gateway agent over the IDL library: 1. It is light weight – does not maintain local cache of OVSDB tables. 2. Complies with the proposed spec/architecture. 3. As it implements the RFC 7047 described at http://tools.ietf.org/html/rfc7047, code maintenance is simple. 4. Changing the code to use IDL at the last moment is a bit of risk (this involves development from scratch, change in the agent architecture and testing). We can always enhance the agent code to use IDL class in future. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/__init__.py0000664000175000017500000000000014572557755031414 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/api.py0000664000175000017500000000463614572557755030451 0ustar00jamespagejamespage# Copyright (c) 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. import abc class API(object, metaclass=abc.ABCMeta): def __init__(self, context): self.context = context @abc.abstractmethod def transaction(self, check_error=False, log_errors=True, **kwargs): """Create a transaction :param check_error: Allow the transaction to raise an exception? :type check_error: bool :param log_errors: Log an error if the transaction fails? :type log_errors: bool :returns: A new transaction :rtype: :class:`Transaction` """ @abc.abstractmethod def db_find(self, table, *conditions, **kwargs): """Create a command to return find OVSDB records matching conditions :param table: The OVS table to query :type table: string :param conditions:The conditions to satisfy the query :type conditions: 3-tuples containing (column, operation, match) Type of 'match' parameter MUST be identical to column type Examples: atomic: ('tag', '=', 7) map: ('external_ids' '=', {'iface-id': 'xxx'}) field exists? ('external_ids', '!=', {'iface-id', ''}) set contains?: ('protocols', '{>=}', 'OpenFlow13') See the ovs-vsctl man page for more operations :param columns: Limit results to only columns, None means all columns :type columns: list of column names or None :returns: :class:`Command` with [{'column', value}, ...] result """ @abc.abstractmethod def get_physical_sw_list(self): """Create a command to list Physical Switches.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/base_connection.py0000664000175000017500000003262314572557755033026 0ustar00jamespagejamespage# 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 copy import os.path import socket import ssl import time import eventlet from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils from networking_l2gw.services.l2gateway.common import constants as n_const LOG = logging.getLogger(__name__) OVSDB_UNREACHABLE_MSG = 'Unable to reach OVSDB server %s' OVSDB_CONNECTED_MSG = 'Connected to OVSDB server %s' class BaseConnection(object): """Connects to OVSDB server. Connects to an ovsdb server with/without SSL on a given host and TCP port. """ def __init__(self, conf, gw_config, mgr=None): self.responses = [] self.connected = False self.mgr = mgr self.enable_manager = cfg.CONF.ovsdb.enable_manager if self.enable_manager: self.manager_table_listening_port = ( cfg.CONF.ovsdb.manager_table_listening_port) self.ip_ovsdb_mapping = self._get_ovsdb_ip_mapping() self.s = None self.check_c_sock = None self.check_sock_rcv = False self.ovsdb_dicts = {} self.ovsdb_fd_states = {} self.ovsdb_conn_list = [] eventlet.greenthread.spawn(self._rcv_socket) else: self.gw_config = gw_config self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if gw_config.use_ssl: ssl_sock = ssl.wrap_socket( self.socket, server_side=False, keyfile=gw_config.private_key, certfile=gw_config.certificate, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1, ca_certs=gw_config.ca_cert) self.socket = ssl_sock retryCount = 0 while True: try: self.socket.connect((str(gw_config.ovsdb_ip), int(gw_config.ovsdb_port))) break except (socket.error, socket.timeout): LOG.warning(OVSDB_UNREACHABLE_MSG, gw_config.ovsdb_ip) if retryCount == conf.max_connection_retries: # Retried for max_connection_retries times. # Give up and return so that it can be tried in # the next periodic interval. with excutils.save_and_reraise_exception(reraise=True): LOG.exception("Socket error in connecting to " "the OVSDB server") else: time.sleep(1) retryCount += 1 # Successfully connected to the socket LOG.debug(OVSDB_CONNECTED_MSG, gw_config.ovsdb_ip) self.connected = True def _get_ovsdb_ip_mapping(self): ovsdb_ip_mapping = {} ovsdb_hosts = cfg.CONF.ovsdb.ovsdb_hosts if ovsdb_hosts != '': ovsdb_hosts = ovsdb_hosts.split(',') for host in ovsdb_hosts: host_splits = str(host).split(':') ovsdb_identifier = str(host_splits[0]).strip() ovsdb_ip = str(host_splits[1]).strip() ovsdb_ip_mapping[ovsdb_ip] = ovsdb_identifier return ovsdb_ip_mapping def _is_ssl_configured(self, addr, client_sock): priv_key_path = cfg.CONF.ovsdb.l2_gw_agent_priv_key_base_path cert_path = cfg.CONF.ovsdb.l2_gw_agent_cert_base_path ca_cert_path = cfg.CONF.ovsdb.l2_gw_agent_ca_cert_base_path use_ssl = priv_key_path and cert_path and ca_cert_path if use_ssl: LOG.debug("ssl is enabled with priv_key_path %s, cert_path %s, " "ca_cert_path %s", priv_key_path, cert_path, ca_cert_path) if addr in self.ip_ovsdb_mapping.keys(): ovsdb_id = self.ip_ovsdb_mapping.get(addr) priv_key_file = priv_key_path + "/" + ovsdb_id + ".key" cert_file = cert_path + "/" + ovsdb_id + ".cert" ca_cert_file = ca_cert_path + "/" + ovsdb_id + ".ca_cert" is_priv_key = os.path.isfile(priv_key_file) is_cert_file = os.path.isfile(cert_file) is_ca_cert_file = os.path.isfile(ca_cert_file) if is_priv_key and is_cert_file and is_ca_cert_file: ssl_conn_stream = ssl.wrap_socket( client_sock, server_side=True, keyfile=priv_key_file, certfile=cert_file, ssl_version=ssl.PROTOCOL_SSLv23, ca_certs=ca_cert_file) client_sock = ssl_conn_stream else: if not is_priv_key: LOG.error("Could not find private key in" " %(path)s dir, expecting in the " "file name %(file)s ", {'path': priv_key_path, 'file': ovsdb_id + ".key"}) if not is_cert_file: LOG.error("Could not find cert in %(path)s dir, " "expecting in the file name %(file)s", {'path': cert_path, 'file': ovsdb_id + ".cert"}) if not is_ca_cert_file: LOG.error("Could not find cacert in %(path)s " "dir, expecting in the file name " "%(file)s", {'path': ca_cert_path, 'file': ovsdb_id + ".ca_cert"}) else: LOG.error("you have enabled SSL for ovsdb %s, " "expecting the ovsdb identifier and ovsdb IP " "entry in ovsdb_hosts in l2gateway_agent.ini", addr) return client_sock def _rcv_socket(self): # Create a socket object. self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = '' # Get local machine name port = self.manager_table_listening_port # configured port for your service. self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.s.bind((host, port)) # Bind to the port self.s.listen(5) # Now wait for client connection. while True: # Establish connection with client. c_sock, ip_addr = self.s.accept() addr = ip_addr[0] c_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) c_sock = self._is_ssl_configured(addr, c_sock) LOG.debug("Got connection from %s ", addr) self.connected = True if addr in self.ovsdb_fd_states.keys(): del self.ovsdb_fd_states[addr] if addr in self.ovsdb_conn_list: self.ovsdb_conn_list.remove(addr) if addr in self.ovsdb_dicts.keys(): if self.ovsdb_dicts.get(addr): self.ovsdb_dicts.get(addr).close() del self.ovsdb_dicts[addr] self.ovsdb_dicts[addr] = c_sock eventlet.greenthread.spawn(self._common_sock_rcv_thread, addr) # Now that OVSDB server has sent a socket open request, let us wait # for echo request. After the first echo request, we will send the # "monitor" request to the OVSDB server. def _send_monitor_msg_to_ovsdb_connection(self, addr): if self.mgr.l2gw_agent_type == n_const.MONITOR: try: if (self.mgr.ovsdb_fd) and (addr in self.ovsdb_conn_list): eventlet.greenthread.spawn_n( self.mgr.ovsdb_fd._spawn_monitor_table_thread, addr) except Exception: LOG.warning("Could not send monitor message to the " "OVSDB server.") self.disconnect(addr) def _common_sock_rcv_thread(self, addr): chunks = [] lc = rc = 0 prev_char = None self.read_on = True check_monitor_msg = True self._echo_response(addr) if self.enable_manager and (addr in self.ovsdb_conn_list): while self.read_on: response = self.ovsdb_dicts.get(addr).recv(n_const.BUFFER_SIZE) self.ovsdb_fd_states[addr] = 'connected' self.check_sock_rcv = True eventlet.greenthread.sleep(0) if check_monitor_msg: self._send_monitor_msg_to_ovsdb_connection(addr) check_monitor_msg = False if response: response = response.decode('utf8') message_mark = 0 for i, c in enumerate(response): if c == '{' and not (prev_char and prev_char == '\\'): lc += 1 elif c == '}' and not (prev_char and prev_char == '\\'): rc += 1 if rc > lc: raise Exception("json string not valid") elif lc == rc and lc != 0: chunks.append(response[message_mark:i + 1]) message = "".join(chunks) eventlet.greenthread.spawn_n( self._on_remote_message, message, addr) eventlet.greenthread.sleep(0) lc = rc = 0 message_mark = i + 1 chunks = [] prev_char = c chunks.append(response[message_mark:]) else: self.read_on = False self.disconnect(addr) def _echo_response(self, addr): while True: try: if self.enable_manager: eventlet.greenthread.sleep(0) response = self.ovsdb_dicts.get(addr).recv( n_const.BUFFER_SIZE) sock_json_m = jsonutils.loads(response) sock_handler_method = sock_json_m.get('method', None) if sock_handler_method == 'echo': self.check_c_sock = True self.ovsdb_dicts.get(addr).send(jsonutils.dumps( {"result": sock_json_m.get("params", None), "error": None, "id": sock_json_m['id']})) if (addr not in self.ovsdb_conn_list): self.ovsdb_conn_list.append(addr) break except Exception: continue def send(self, message, callback=None, addr=None): """Sends a message to the OVSDB server.""" if callback: self.callbacks[message['id']] = callback retry_count = 0 bytes_sent = 0 while retry_count <= n_const.MAX_RETRIES: try: data = jsonutils.dumps(message) if not isinstance(data, bytes): data = data.encode() if self.enable_manager: bytes_sent = self.ovsdb_dicts.get(addr).send(data) else: bytes_sent = self.socket.send(data) if bytes_sent: return True except Exception as ex: LOG.exception("Exception [%s] occurred while sending " "message to the OVSDB server", ex) retry_count += 1 LOG.warning("Could not send message to the " "OVSDB server.") self.disconnect(addr) return False def disconnect(self, addr=None): """disconnects the connection from the OVSDB server.""" if self.enable_manager: self.ovsdb_dicts.get(addr).close() del self.ovsdb_dicts[addr] if addr in self.ovsdb_fd_states.keys(): del self.ovsdb_fd_states[addr] self.ovsdb_conn_list.remove(addr) else: self.socket.close() self.connected = False def _response(self, operation_id): x_copy = None to_delete = None for x in self.responses: if x['id'] == operation_id: x_copy = copy.deepcopy(x) to_delete = x break if to_delete: self.responses.remove(to_delete) return x_copy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/impl_idl.py0000664000175000017500000000405014572557755031457 0ustar00jamespagejamespage# Copyright (c) 2016 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 ovsdbapp.backend.ovs_idl.transaction import Transaction from networking_l2gw.services.l2gateway.agent.ovsdb import api from networking_l2gw.services.l2gateway.agent.ovsdb.native import ( commands as cmd) from networking_l2gw.services.l2gateway.agent.ovsdb.native import connection class OvsdbHardwareVtepIdl(api.API): def __init__(self, context, ovsdb_conn, timeout): self.context = context self.timeout = timeout self.ovsdb_connection = connection.Connection(ovsdb_conn, timeout, 'hardware_vtep') if self.is_passive(ovsdb_conn): self.ovsdb_connection.accept() else: self.ovsdb_connection.start() self.idl = self.ovsdb_connection.idl @property def _ovs(self): return self._tables['Global'].rows.values()[0] @property def _tables(self): return self.idl.tables def is_passive(self, ovsdb_conn): return ovsdb_conn.startswith("punix:") or ovsdb_conn.startswith( "ptcp:") def transaction(self, check_error=False, log_errors=True, **kwargs): return Transaction(self, self.ovsdb_connection, self.timeout, check_error, log_errors) def db_find(self, table, *conditions, **kwargs): pass def get_physical_sw_list(self): return cmd.ListPhysicalSwitchCommand(self) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/manager.py0000664000175000017500000004700514572557755031307 0ustar00jamespagejamespage# 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 contextlib import contextmanager import os.path import eventlet from neutron_lib import context as ctx from oslo_config import cfg from oslo_log import log as logging from oslo_service import loopingcall from networking_l2gw.services.l2gateway.agent import base_agent_manager from networking_l2gw.services.l2gateway.agent import l2gateway_config from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_common_class from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_monitor from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_writer from networking_l2gw.services.l2gateway.common import constants as n_const LOG = logging.getLogger(__name__) class OVSDBManager(base_agent_manager.BaseAgentManager): """OVSDB variant of agent manager. Listens to state change notifications from OVSDB servers and handles transactions (RPCs) destined to OVSDB servers. """ def __init__(self, conf=None): super(OVSDBManager, self).__init__(conf) self._extract_ovsdb_config(conf) self.enable_manager = cfg.CONF.ovsdb.enable_manager periodic_interval = self.conf.ovsdb.periodic_interval if self.enable_manager: self.ovsdb_fd = None self._sock_open_connection() self.looping_task_ovsdb_states = ( loopingcall.FixedIntervalLoopingCall(self._send_ovsdb_states)) self.looping_task_ovsdb_states.start(interval=periodic_interval) else: self.looping_task = loopingcall.FixedIntervalLoopingCall( self._connect_to_ovsdb_server) self.looping_task.start(interval=periodic_interval) def _extract_ovsdb_config(self, conf): self.conf = conf or cfg.CONF ovsdb_hosts = self.conf.ovsdb.ovsdb_hosts if ovsdb_hosts != '': ovsdb_hosts = ovsdb_hosts.split(',') for host in ovsdb_hosts: self._process_ovsdb_host(host) # Ensure that max_connection_retries is less than # the periodic interval. if (self.conf.ovsdb.max_connection_retries >= self.conf.ovsdb.periodic_interval): raise SystemExit("max_connection_retries should be " "less than periodic interval") def _process_ovsdb_host(self, host): try: host_splits = str(host).split(':') ovsdb_identifier = str(host_splits[0]).strip() ovsdb_conf = {n_const.OVSDB_IDENTIFIER: ovsdb_identifier, 'ovsdb_ip': str(host_splits[1]).strip(), 'ovsdb_port': str(host_splits[2]).strip()} priv_key_path = self.conf.ovsdb.l2_gw_agent_priv_key_base_path cert_path = self.conf.ovsdb.l2_gw_agent_cert_base_path ca_cert_path = self.conf.ovsdb.l2_gw_agent_ca_cert_base_path use_ssl = priv_key_path and cert_path and ca_cert_path if use_ssl: LOG.debug("ssl is enabled with priv_key_path %s, cert_path " "%s, ca_cert_path %s", priv_key_path, cert_path, ca_cert_path) priv_key_file = priv_key_path + "/" + ovsdb_identifier + ".key" cert_file = cert_path + "/" + ovsdb_identifier + ".cert" ca_cert_file = (ca_cert_path + "/" + ovsdb_identifier + ".ca_cert") is_priv_key = os.path.isfile(priv_key_file) is_cert_file = os.path.isfile(cert_file) is_ca_cert_file = os.path.isfile(ca_cert_file) if not is_priv_key: LOG.exception("Could not find private key in " "%(path)s dir, expecting in the " "file name %(file)s ", {'path': priv_key_path, 'file': ovsdb_identifier + ".key"}) if not is_cert_file: LOG.exception("Could not find cert in %(path)s dir, " "expecting in the file name %(file)s", {'path': cert_path, 'file': ovsdb_identifier + ".cert"}) if not is_ca_cert_file: LOG.exception("Could not find cacert in %(path)s " "dir, expecting in the file name " "%(file)s", {'path': ca_cert_path, 'file': ovsdb_identifier + ".ca_cert"}) ssl_ovsdb = {'use_ssl': True, 'private_key': "/".join([str(priv_key_path), '.'.join([str(host_splits[0]). strip(), 'key'])]), 'certificate': "/".join([str(cert_path), '.'.join([str(host_splits[0]). strip(), 'cert'])]), 'ca_cert': "/".join([str(ca_cert_path), '.'.join([str(host_splits[0]). strip(), 'ca_cert'])]) } ovsdb_conf.update(ssl_ovsdb) LOG.debug("ovsdb_conf = %s", str(ovsdb_conf)) gateway = l2gateway_config.L2GatewayConfig(ovsdb_conf) self.gateways[ovsdb_identifier] = gateway except Exception as ex: LOG.exception("Exception %(ex)s occurred while processing " "host %(host)s", {'ex': ex, 'host': host}) def _connect_to_ovsdb_server(self): """Initializes the connection to the OVSDB servers.""" ovsdb_states = {} if self.gateways and self.l2gw_agent_type == n_const.MONITOR: for key in self.gateways.keys(): gateway = self.gateways.get(key) ovsdb_fd = gateway.ovsdb_fd if not (ovsdb_fd and ovsdb_fd.connected): LOG.debug("OVSDB server %s is disconnected", str(gateway.ovsdb_ip)) try: ovsdb_fd = ovsdb_monitor.OVSDBMonitor( self.conf.ovsdb, gateway, self.agent_to_plugin_rpc) except Exception: ovsdb_states[key] = 'disconnected' # Log a warning and continue so that it can be # retried in the next iteration. LOG.error("OVSDB server %s is not " "reachable", gateway.ovsdb_ip) # Continue processing the next element in the list. continue gateway.ovsdb_fd = ovsdb_fd try: eventlet.greenthread.spawn_n( ovsdb_fd.set_monitor_response_handler) except Exception: raise SystemExit(Exception.message) if ovsdb_fd and ovsdb_fd.connected: ovsdb_states[key] = 'connected' LOG.debug("Calling notify_ovsdb_states") self.plugin_rpc.notify_ovsdb_states(ctx.get_admin_context(), ovsdb_states) def handle_report_state_failure(self): # Not able to deliver the heart beats to the Neutron server. # Let us change the mode to Transact so that when the # Neutron server is connected back, it will make an agent # Monitor agent and OVSDB data will be read entirely. This way, # the OVSDB data in Neutron database will be the latest and in # sync with that in the OVSDB server tables. if self.l2gw_agent_type == n_const.MONITOR: self.l2gw_agent_type = '' self.agent_state.get('configurations')[n_const.L2GW_AGENT_TYPE ] = self.l2gw_agent_type if not self.enable_manager: self._stop_looping_task() self._disconnect_all_ovsdb_servers() def _send_ovsdb_states(self): self.plugin_rpc.notify_ovsdb_states(ctx.get_admin_context(), self.ovsdb_fd.ovsdb_fd_states) def _disconnect_all_ovsdb_servers(self): if self.gateways: for key, gateway in self.gateways.items(): ovsdb_fd = gateway.ovsdb_fd if ovsdb_fd and ovsdb_fd.connected: gateway.ovsdb_fd.disconnect() def set_monitor_agent(self, context, hostname): """Handle RPC call from plugin to update agent type. RPC call from the plugin to accept that I am a monitoring or a transact agent. This is a fanout cast message """ super(OVSDBManager, self).set_monitor_agent(context, hostname) # If set to Monitor, then let us start monitoring the OVSDB # servers without any further delay. if ((self.l2gw_agent_type == n_const.MONITOR) and not ( self.enable_manager)): self._start_looping_task() elif self.enable_manager and self.l2gw_agent_type == n_const.MONITOR: if self.ovsdb_fd is None: self._sock_open_connection() elif ((self.ovsdb_fd) and not ( self.ovsdb_fd.check_monitor_table_thread) and ( self.ovsdb_fd.check_sock_rcv)): for key in self.ovsdb_fd.ovsdb_dicts.keys(): if key in self.ovsdb_fd.ovsdb_conn_list: eventlet.greenthread.spawn_n( self.ovsdb_fd._spawn_monitor_table_thread, key) self._start_looping_task_ovsdb_states() elif ((self.enable_manager) and not ( self.l2gw_agent_type == n_const.MONITOR)): self._stop_looping_task_ovsdb_states() elif (not (self.l2gw_agent_type == n_const.MONITOR) and not ( self.enable_manager)): # Otherwise, stop monitoring the OVSDB servers # and close the open connections if any. self._stop_looping_task() self._disconnect_all_ovsdb_servers() def _stop_looping_task_ovsdb_states(self): if self.looping_task_ovsdb_states._running: self.looping_task_ovsdb_states.stop() def _start_looping_task_ovsdb_states(self): if not self.looping_task_ovsdb_states._running: self.looping_task_ovsdb_states.start( interval=self.conf.ovsdb.periodic_interval) def _stop_looping_task(self): if self.looping_task._running: self.looping_task.stop() def _start_looping_task(self): if not self.looping_task._running: self.looping_task.start(interval=self.conf.ovsdb. periodic_interval) def _sock_open_connection(self): gateway = '' if self.ovsdb_fd is None: self.ovsdb_fd = ovsdb_common_class.OVSDB_commom_class( self.conf.ovsdb, gateway, self.agent_to_plugin_rpc, self) @contextmanager def _open_connection(self, ovsdb_identifier): ovsdb_fd = None gateway = self.gateways.get(ovsdb_identifier) try: ovsdb_fd = ovsdb_writer.OVSDBWriter(self.conf.ovsdb, gateway) yield ovsdb_fd finally: if ovsdb_fd: ovsdb_fd.disconnect() def _is_valid_request(self, ovsdb_identifier): val_req = ovsdb_identifier and ovsdb_identifier in self.gateways.keys() if not val_req: LOG.warning(n_const.ERROR_DICT [n_const.L2GW_INVALID_OVSDB_IDENTIFIER]) return val_req def delete_network(self, context, ovsdb_identifier, logical_switch_uuid): """Handle RPC cast from plugin to delete a network.""" if self.enable_manager and self.l2gw_agent_type == n_const.MONITOR: self.ovsdb_fd.delete_logical_switch(logical_switch_uuid, ovsdb_identifier, False) elif ((self.enable_manager) and ( not self.l2gw_agent_type == n_const.MONITOR)): self._sock_open_connection() if ovsdb_identifier in self.ovsdb_fd.ovsdb_conn_list: self.ovsdb_fd.delete_logical_switch(logical_switch_uuid, ovsdb_identifier, False) elif not self.enable_manager: if self._is_valid_request(ovsdb_identifier): with self._open_connection(ovsdb_identifier) as ovsdb_fd: ovsdb_fd.delete_logical_switch(logical_switch_uuid, ovsdb_identifier) def add_vif_to_gateway(self, context, ovsdb_identifier, logical_switch_dict, locator_dict, mac_dict): """Handle RPC cast from plugin to insert neutron port MACs.""" if self.enable_manager and self.l2gw_agent_type == n_const.MONITOR: self.ovsdb_fd.insert_ucast_macs_remote(logical_switch_dict, locator_dict, mac_dict, ovsdb_identifier, False) elif ((self.enable_manager) and ( not self.l2gw_agent_type == n_const.MONITOR)): self._sock_open_connection() if ovsdb_identifier in self.ovsdb_fd.ovsdb_conn_list: self.ovsdb_fd.insert_ucast_macs_remote( logical_switch_dict, locator_dict, mac_dict, ovsdb_identifier, False) elif not self.enable_manager: if self._is_valid_request(ovsdb_identifier): with self._open_connection(ovsdb_identifier) as ovsdb_fd: ovsdb_fd.insert_ucast_macs_remote(logical_switch_dict, locator_dict, mac_dict, ovsdb_identifier) def delete_vif_from_gateway(self, context, ovsdb_identifier, logical_switch_uuid, mac): """Handle RPC cast from plugin to delete neutron port MACs.""" if self.enable_manager and self.l2gw_agent_type == n_const.MONITOR: self.ovsdb_fd.delete_ucast_macs_remote(logical_switch_uuid, mac, ovsdb_identifier, False) elif ((self.enable_manager) and ( not self.l2gw_agent_type == n_const.MONITOR)): self._sock_open_connection() if ovsdb_identifier in self.ovsdb_fd.ovsdb_conn_list: self.ovsdb_fd.delete_ucast_macs_remote( logical_switch_uuid, mac, ovsdb_identifier, False) elif not self.enable_manager: if self._is_valid_request(ovsdb_identifier): with self._open_connection(ovsdb_identifier) as ovsdb_fd: ovsdb_fd.delete_ucast_macs_remote( logical_switch_uuid, mac, ovsdb_identifier) def update_vif_to_gateway(self, context, ovsdb_identifier, locator_dict, mac_dict): """Handle RPC cast from plugin to update neutron port MACs. for VM migration. """ if self.enable_manager and self.l2gw_agent_type == n_const.MONITOR: self.ovsdb_fd.update_ucast_macs_remote(locator_dict, mac_dict, ovsdb_identifier, False) elif ((self.enable_manager) and ( not self.l2gw_agent_type == n_const.MONITOR)): self._sock_open_connection() if ovsdb_identifier in self.ovsdb_fd.ovsdb_conn_list: self.ovsdb_fd.update_ucast_macs_remote(locator_dict, mac_dict, ovsdb_identifier, False) elif not self.enable_manager: if self._is_valid_request(ovsdb_identifier): with self._open_connection(ovsdb_identifier) as ovsdb_fd: ovsdb_fd.update_ucast_macs_remote( locator_dict, mac_dict, ovsdb_identifier) def update_connection_to_gateway(self, context, ovsdb_identifier, logical_switch_dict, locator_dicts, mac_dicts, port_dicts, op_method): """Handle RPC cast from plugin. Handle RPC cast from plugin to connect/disconnect a network to/from an L2 gateway. """ if self.enable_manager and self.l2gw_agent_type == n_const.MONITOR: self.ovsdb_fd.update_connection_to_gateway(logical_switch_dict, locator_dicts, mac_dicts, port_dicts, ovsdb_identifier, op_method, False) elif ((self.enable_manager) and ( not self.l2gw_agent_type == n_const.MONITOR)): self._sock_open_connection() if ovsdb_identifier in self.ovsdb_fd.ovsdb_conn_list: self.ovsdb_fd.update_connection_to_gateway( logical_switch_dict, locator_dicts, mac_dicts, port_dicts, ovsdb_identifier, op_method, False) elif not self.enable_manager: if self._is_valid_request(ovsdb_identifier): with self._open_connection(ovsdb_identifier) as ovsdb_fd: ovsdb_fd.update_connection_to_gateway(logical_switch_dict, locator_dicts, mac_dicts, port_dicts, ovsdb_identifier, op_method) def agent_to_plugin_rpc(self, activity, ovsdb_data): self.plugin_rpc.update_ovsdb_changes(ctx.get_admin_context(), activity, ovsdb_data) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/native/0000775000175000017500000000000014572557757030605 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/native/__init__.py0000664000175000017500000000000014572557755032702 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/native/commands.py0000664000175000017500000000172314572557755032761 0ustar00jamespagejamespage# Copyright (c) 2016 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 ovsdbapp.backend.ovs_idl import command as comm class ListPhysicalSwitchCommand(comm.BaseCommand): def __init__(self, api): super(ListPhysicalSwitchCommand, self).__init__(api) def run_idl(self, txn): self.result = [x.name for x in self.api._tables['Physical_Switch'].rows.values()] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/native/connection.py0000664000175000017500000000213314572557755033313 0ustar00jamespagejamespage# Copyright (c) 2016 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 os from ovs.db import idl from ovsdbapp.backend.ovs_idl import connection as conn def get_schema_helper_for_vtep(): current_dir = os.path.dirname(os.path.realpath(__file__)) return idl.SchemaHelper(current_dir + '/../vtep/vtep.ovsschema') class Connection(conn.Connection): def __init__(self, connection, timeout, schema_name): idl_ = idl.Idl(connection, get_schema_helper_for_vtep()) super(Connection, self).__init__(idl_, timeout) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_common_class.py0000664000175000017500000000155514572557755033547 0ustar00jamespagejamespage# 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 networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_monitor from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_writer class OVSDB_commom_class(ovsdb_monitor.OVSDBMonitor, ovsdb_writer.OVSDBWriter): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_model.py0000664000175000017500000000455614572557755032176 0ustar00jamespagejamespage# Copyright (c) 2016 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. class OvsdbObject(object): def __init__(self, uuid): self.uuid = uuid class LogicalSwitch(OvsdbObject): def __init__(self, uuid, name, description, tunnel_key): super(LogicalSwitch, self).__init__(uuid) self.name = name self.description = description self.tunnel_key = tunnel_key class PhysicalLocatorSet(OvsdbObject): def __init__(self, uuid, locator_uuid_list): super(PhysicalLocatorSet, self).__init__(uuid) self.locator_uuid_list = locator_uuid_list class PhysicalLocator(OvsdbObject): def __init__(self, uuid, dst_ip, tunnel_key=None, encapsulation_type='vxlan_over_ipv4'): super(PhysicalLocator, self).__init__(uuid) self.dst_ip = dst_ip self.encapsulation_type = encapsulation_type self.tunnel_key = tunnel_key class UcastMacs(OvsdbObject): def __init__(self, uuid, mac, ipaddr, logical_switch_uuid, locator_uuid): super(UcastMacs, self).__init__(uuid) self.mac = mac self.ipaddr = ipaddr self.logical_switch_uuid = logical_switch_uuid self.locator_uuid = locator_uuid class McastMacs(OvsdbObject): def __init__(self, uuid, mac, dst_ip, logical_switch_uuid, locator_set__uuid): super(McastMacs, self).__init__(uuid) self.mac = mac self.dst_ip = dst_ip self.logical_switch_uuid = logical_switch_uuid self.locator_set__uuid = locator_set__uuid class PhysicalPort(OvsdbObject): def __init__(self, uuid, name, description, vlan_bindings_dict): super(PhysicalPort, self).__init__(uuid) self.name = name self.description = description self.vlan_bindings_dict = vlan_bindings_dict ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_monitor.py0000664000175000017500000007077714572557755032575 0ustar00jamespagejamespage# 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 random import eventlet from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils from networking_l2gw.services.l2gateway.agent.ovsdb import base_connection from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway import exceptions LOG = logging.getLogger(__name__) class Activity(object): Initial, Update = range(2) class OVSDBMonitor(base_connection.BaseConnection): """Monitors OVSDB servers.""" def __init__(self, conf, gw_config, callback, mgr=None): super(OVSDBMonitor, self).__init__(conf, gw_config, mgr=None) self.mgr = mgr self.rpc_callback = callback self.callbacks = {} self._setup_dispatch_table() self.read_on = True self.handlers = {"echo": self._default_echo_handler} self.sock_timeout = cfg.CONF.ovsdb.socket_timeout if self.enable_manager: self.check_monitor_table_thread = False if not self.enable_manager: eventlet.greenthread.spawn(self._rcv_thread) def _spawn_monitor_table_thread(self, addr): self.set_monitor_response_handler(addr) self.check_monitor_table_thread = True def _initialize_data_dict(self): data_dict = {'new_local_macs': [], 'deleted_local_macs': [], 'modified_local_macs': [], 'new_remote_macs': [], 'deleted_remote_macs': [], 'modified_remote_macs': [], 'new_physical_ports': [], 'deleted_physical_ports': [], 'modified_physical_ports': [], 'new_physical_switches': [], 'deleted_physical_switches': [], 'modified_physical_switches': [], 'new_physical_locators': [], 'deleted_physical_locators': [], 'modified_physical_locators': [], 'new_logical_switches': [], 'deleted_logical_switches': [], 'modified_logical_switches': [], 'new_mlocal_macs': [], 'deleted_mlocal_macs': [], 'modified_mlocal_macs': [], 'new_locator_sets': [], 'deleted_locator_sets': [], 'modified_locator_sets': []} return data_dict def _setup_dispatch_table(self): self.dispatch_table = {'Logical_Switch': self._process_logical_switch, 'Ucast_Macs_Local': self._process_ucast_macs_local, 'Physical_Locator': self._process_physical_locator, 'Ucast_Macs_Remote': self._process_ucast_macs_remote, 'Mcast_Macs_Local': self._process_mcast_macs_local, 'Physical_Locator_Set': self._process_physical_locator_set } def set_monitor_response_handler(self, addr=None): """Monitor OVSDB tables to receive events for any changes in OVSDB.""" if self.connected: op_id = str(random.getrandbits(128)) props = {'select': {'initial': True, 'insert': True, 'delete': True, 'modify': True}} monitor_message = {'id': op_id, 'method': 'monitor', 'params': [n_const.OVSDB_SCHEMA_NAME, None, {'Logical_Switch': [props], 'Physical_Switch': [props], 'Physical_Port': [props], 'Ucast_Macs_Local': [props], 'Ucast_Macs_Remote': [props], 'Physical_Locator': [props], 'Mcast_Macs_Local': [props], 'Physical_Locator_Set': [props]} ]} self._set_handler("update", self._update_event_handler) if not self.send(monitor_message, addr=addr): # Return so that this will retried in the next iteration return try: response_result = self._process_response(op_id) except exceptions.OVSDBError: with excutils.save_and_reraise_exception(): if self.enable_manager: self.check_monitor_table_thread = False LOG.exception("Exception while receiving the " "response for the monitor message") self._process_monitor_msg(response_result, addr) def _update_event_handler(self, message, addr): self._process_update_event(message, addr) def _process_update_event(self, message, addr): """Process update event that is triggered by the OVSDB server.""" LOG.debug("_process_update_event: message = %s ", str(message)) data_dict = self._initialize_data_dict() if message.get('method') == 'update': params_list = message.get('params') param_dict = params_list[1] self._process_tables(param_dict, data_dict) self.rpc_callback(Activity.Update, self._form_ovsdb_data(data_dict, addr)) def _process_tables(self, param_dict, data_dict): # Process all the tables one by one. # OVSDB table name is the key in the dictionary. port_map = {} for table_name in param_dict.keys(): table_dict = param_dict.get(table_name) for uuid in table_dict.keys(): uuid_dict = table_dict.get(uuid) if table_name == 'Physical_Switch': self._process_physical_switch(uuid, uuid_dict, port_map, data_dict) elif table_name == 'Physical_Port': self._process_physical_port(uuid, uuid_dict, port_map, data_dict) else: self.dispatch_table.get(table_name)(uuid, uuid_dict, data_dict) def _process_response(self, op_id): result = self._response(op_id) count = 0 while (not result and count <= n_const.MAX_RETRIES): count = count + 1 eventlet.greenthread.sleep(0) result = self._response(op_id) if not result and count >= n_const.MAX_RETRIES: raise exceptions.OVSDBError( message="OVSDB server did not respond within " "max retry attempts.") error = result.get("error", None) if error: raise exceptions.OVSDBError( message="Error from the OVSDB server %s" % error ) return result def _default_echo_handler(self, message, addr): """Message handler for the OVSDB server's echo request.""" self.send({"result": message.get("params", None), "error": None, "id": message['id']}, addr=addr) def _set_handler(self, method_name, handler): self.handlers[method_name] = handler def _on_remote_message(self, message, addr=None): """Processes the message received on the socket.""" try: json_m = jsonutils.loads(message) handler_method = json_m.get('method', None) if handler_method: self.handlers.get(handler_method)(json_m, addr) else: self.responses.append(json_m) except Exception as e: LOG.exception("Exception [%s] while handling " "message", e) def _rcv_thread(self): chunks = [] lc = rc = 0 prev_char = None while self.read_on: try: # self.socket.recv() is a blocked call # (if timeout value is not passed) due to which we cannot # determine if the remote OVSDB server has died. The remote # OVSDB server sends echo requests every 4 seconds. # If there is no echo request on the socket for socket_timeout # seconds(by default its 30 seconds), # the agent can safely assume that the connection with the # remote OVSDB server is lost. Better to retry by reopening # the socket. self.socket.settimeout(self.sock_timeout) response = self.socket.recv(n_const.BUFFER_SIZE) eventlet.greenthread.sleep(0) if response: response = response.decode('utf8') message_mark = 0 for i, c in enumerate(response): if c == '{' and not (prev_char and prev_char == '\\'): lc += 1 elif c == '}' and not (prev_char and prev_char == '\\'): rc += 1 if rc > lc: raise Exception("json string not valid") elif lc == rc and lc != 0: chunks.append(response[message_mark:i + 1]) message = "".join(chunks) eventlet.greenthread.spawn_n( self._on_remote_message, message) lc = rc = 0 message_mark = i + 1 chunks = [] prev_char = c chunks.append(response[message_mark:]) else: self.read_on = False self.disconnect() except Exception as ex: self.read_on = False self.disconnect() LOG.exception("Exception [%s] occurred while receiving" "message from the OVSDB server", ex) def disconnect(self, addr=None): """disconnects the connection from the OVSDB server.""" self.read_on = False super(OVSDBMonitor, self).disconnect(addr) def _process_monitor_msg(self, message, addr=None): """Process initial set of records in the OVSDB at startup.""" result_dict = message.get('result') data_dict = self._initialize_data_dict() try: self._process_tables(result_dict, data_dict) self.rpc_callback(Activity.Initial, self._form_ovsdb_data(data_dict, addr)) except Exception as e: LOG.exception("_process_monitor_msg:ERROR %s ", e) def _get_list(self, resource_list): return [element.__dict__ for element in resource_list] def _form_ovsdb_data(self, data_dict, addr): return {n_const.OVSDB_IDENTIFIER: str(addr) if ( self.enable_manager) else (self.gw_config.ovsdb_identifier), 'new_logical_switches': self._get_list( data_dict.get('new_logical_switches')), 'new_physical_switches': self._get_list( data_dict.get('new_physical_switches')), 'new_physical_ports': self._get_list( data_dict.get('new_physical_ports')), 'new_physical_locators': self._get_list( data_dict.get('new_physical_locators')), 'new_local_macs': self._get_list( data_dict.get('new_local_macs')), 'new_remote_macs': self._get_list( data_dict.get('new_remote_macs')), 'new_mlocal_macs': self._get_list( data_dict.get('new_mlocal_macs')), 'new_locator_sets': self._get_list( data_dict.get('new_locator_sets')), 'deleted_logical_switches': self._get_list( data_dict.get('deleted_logical_switches')), 'deleted_physical_switches': self._get_list( data_dict.get('deleted_physical_switches')), 'deleted_physical_ports': self._get_list( data_dict.get('deleted_physical_ports')), 'deleted_physical_locators': self._get_list( data_dict.get('deleted_physical_locators')), 'deleted_local_macs': self._get_list( data_dict.get('deleted_local_macs')), 'deleted_remote_macs': self._get_list( data_dict.get('deleted_remote_macs')), 'deleted_mlocal_macs': self._get_list( data_dict.get('deleted_mlocal_macs')), 'deleted_locator_sets': self._get_list( data_dict.get('deleted_locator_sets')), 'modified_logical_switches': self._get_list( data_dict.get('modified_logical_switches')), 'modified_physical_switches': self._get_list( data_dict.get('modified_physical_switches')), 'modified_physical_ports': self._get_list( data_dict.get('modified_physical_ports')), 'modified_physical_locators': self._get_list( data_dict.get('modified_physical_locators')), 'modified_local_macs': self._get_list( data_dict.get('modified_local_macs')), 'modified_remote_macs': self._get_list( data_dict.get('modified_remote_macs')), 'modified_mlocal_macs': self._get_list( data_dict.get('modified_mlocal_macs')), 'modified_locator_sets': self._get_list( data_dict.get('modified_locator_sets'))} def _process_physical_port(self, uuid, uuid_dict, port_map, data_dict): """Processes Physical_Port record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: port_fault_status = new_row.get('port_fault_status') if type(port_fault_status) is list: port_fault_status = None port = ovsdb_schema.PhysicalPort(uuid, new_row.get('name'), None, None, port_fault_status) switch_id = port_map.get(uuid, None) if switch_id: port.physical_switch_id = switch_id # Update the vlan bindings outer_binding_list = new_row.get('vlan_bindings') # First element is "map" outer_binding_list.remove(outer_binding_list[0]) vlan_bindings = [] if len(outer_binding_list) > 0: for binding in outer_binding_list: if len(binding) > 0: for element in binding: vlan = element[0] inner_most_list = element[1] ls_id = inner_most_list[1] vb = ovsdb_schema.VlanBinding(vlan, ls_id).__dict__ vlan_bindings.append(vb) port.vlan_bindings = vlan_bindings if old_row: modified_physical_ports = data_dict.get( 'modified_physical_ports') modified_physical_ports.append(port) else: new_physical_ports = data_dict.get( 'new_physical_ports') new_physical_ports.append(port) elif old_row: # Port is deleted permanently from OVSDB server port_fault_status = old_row.get('port_fault_status') if type(port_fault_status) is list: port_fault_status = None port = ovsdb_schema.PhysicalPort(uuid, old_row.get('name'), None, None, port_fault_status) deleted_physical_ports = data_dict.get('deleted_physical_ports') deleted_physical_ports.append(port) def _process_physical_switch(self, uuid, uuid_dict, port_map, data_dict): """Processes Physical_Switch record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: # insert or modify operation ports = new_row.get('ports') # First element in the list is either 'set' or 'uuid' # Let us remove it. is_set = False if ports[0] == 'set': is_set = True ports.remove(ports[0]) all_ports = [] if not is_set: all_ports.append(ports[0]) else: for port_list in ports: # each port variable is again list for port in port_list: for inner_port in port: if inner_port != 'uuid': all_ports.append(inner_port) switch_fault_status = new_row.get('switch_fault_status') if type(switch_fault_status) is list: switch_fault_status = None phys_switch = ovsdb_schema.PhysicalSwitch( uuid, new_row.get('name'), new_row.get('tunnel_ips'), switch_fault_status) # Now, store mapping of physical ports to # physical switch so that it is useful while # processing Physical_Switch record for port in all_ports: port_map[port] = uuid for pport in data_dict['new_physical_ports']: if pport.uuid == port: pport.physical_switch_id = uuid if old_row: modified_physical_switches = data_dict.get( 'modified_physical_switches') modified_physical_switches.append(phys_switch) else: new_physical_switches = data_dict.get( 'new_physical_switches') new_physical_switches.append(phys_switch) elif old_row: # Physical switch is deleted permanently from OVSDB # server switch_fault_status = old_row.get('switch_fault_status') if type(switch_fault_status) is list: switch_fault_status = None phys_switch = ovsdb_schema.PhysicalSwitch( uuid, old_row.get('name'), old_row.get('tunnel_ips'), switch_fault_status) deleted_physical_switches = data_dict.get( 'deleted_physical_switches') deleted_physical_switches.append(phys_switch) def _process_logical_switch(self, uuid, uuid_dict, data_dict): """Processes Logical_Switch record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: l_switch = ovsdb_schema.LogicalSwitch(uuid, new_row.get('name'), new_row.get('tunnel_key'), new_row.get('description')) if old_row: modified_logical_switches = data_dict.get( 'modified_logical_switches') modified_logical_switches.append(l_switch) else: new_logical_switches = data_dict.get( 'new_logical_switches') new_logical_switches.append(l_switch) elif old_row: l_switch = ovsdb_schema.LogicalSwitch(uuid, old_row.get('name'), old_row.get('tunnel_key'), old_row.get('description')) deleted_logical_switches = data_dict.get( 'deleted_logical_switches') deleted_logical_switches.append(l_switch) def _process_ucast_macs_local(self, uuid, uuid_dict, data_dict): """Processes Ucast_Macs_Local record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: locator_list = new_row.get('locator') locator_id = locator_list[1] logical_switch_list = new_row.get('logical_switch') logical_switch_id = logical_switch_list[1] mac_local = ovsdb_schema.UcastMacsLocal(uuid, new_row.get('MAC'), logical_switch_id, locator_id, new_row.get('ipaddr')) if old_row: modified_local_macs = data_dict.get( 'modified_local_macs') modified_local_macs.append(mac_local) else: new_local_macs = data_dict.get( 'new_local_macs') new_local_macs.append(mac_local) elif old_row: # A row from UcastMacLocal is deleted. logical_switch_list = old_row.get('logical_switch') l_sw_id = logical_switch_list[1] mac_local = ovsdb_schema.UcastMacsLocal(uuid, old_row.get('MAC'), l_sw_id, None, None) deleted_local_macs = data_dict.get( 'deleted_local_macs') deleted_local_macs.append(mac_local) def _process_ucast_macs_remote(self, uuid, uuid_dict, data_dict): """Processes Ucast_Macs_Remote record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: locator_list = new_row.get('locator') locator_id = locator_list[1] logical_switch_list = new_row.get('logical_switch') logical_switch_id = logical_switch_list[1] mac_remote = ovsdb_schema.UcastMacsRemote(uuid, new_row.get('MAC'), logical_switch_id, locator_id, new_row.get('ipaddr')) if old_row: modified_remote_macs = data_dict.get( 'modified_remote_macs') modified_remote_macs.append(mac_remote) else: new_remote_macs = data_dict.get( 'new_remote_macs') new_remote_macs.append(mac_remote) elif old_row: logical_switch_list = old_row.get('logical_switch') l_sw_id = logical_switch_list[1] mac_remote = ovsdb_schema.UcastMacsRemote(uuid, old_row.get('MAC'), l_sw_id, None, None) deleted_remote_macs = data_dict.get( 'deleted_remote_macs') deleted_remote_macs.append(mac_remote) def _process_physical_locator(self, uuid, uuid_dict, data_dict): """Processes Physical_Locator record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: dstip = new_row['dst_ip'] locator = ovsdb_schema.PhysicalLocator(uuid, dstip) if old_row: modified_physical_locators = data_dict.get( 'modified_physical_locators') modified_physical_locators.append(locator) else: new_physical_locators = data_dict.get( 'new_physical_locators') new_physical_locators.append(locator) elif old_row: dstip = old_row['dst_ip'] locator = ovsdb_schema.PhysicalLocator(uuid, dstip) deleted_physical_locators = data_dict.get( 'deleted_physical_locators') deleted_physical_locators.append(locator) def _process_mcast_macs_local(self, uuid, uuid_dict, data_dict): """Processes Mcast_Macs_Local record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: locator_set_list = new_row.get('locator_set') logical_switch_list = new_row.get('logical_switch') mcast_local = ovsdb_schema.McastMacsLocal(uuid, new_row['MAC'], logical_switch_list[1], locator_set_list[1], new_row['ipaddr']) if old_row: modified_mlocal_macs = data_dict.get( 'modified_mlocal_macs') modified_mlocal_macs.append(mcast_local) else: new_mlocal_macs = data_dict.get( 'new_mlocal_macs') new_mlocal_macs.append(mcast_local) elif old_row: logical_switch_list = old_row.get('logical_switch') l_sw_id = logical_switch_list[1] mcast_local = ovsdb_schema.McastMacsLocal(uuid, old_row.get('MAC'), l_sw_id, None, None) deleted_mlocal_macs = data_dict.get( 'deleted_mlocal_macs') deleted_mlocal_macs.append(mcast_local) def _process_physical_locator_set(self, uuid, uuid_dict, data_dict): """Processes Physical_Locator_Set record from the OVSDB event.""" new_row = uuid_dict.get('new', None) old_row = uuid_dict.get('old', None) if new_row: locator_set = self._form_locator_set(uuid, new_row) if old_row: modified_locator_sets = data_dict.get( 'modified_locator_sets') modified_locator_sets.append(locator_set) else: new_locator_sets = data_dict.get( 'new_locator_sets') new_locator_sets.append(locator_set) elif old_row: locator_set = self._form_locator_set(uuid, old_row) deleted_locator_sets = data_dict.get( 'deleted_locator_sets') deleted_locator_sets.append(locator_set) def _form_locator_set(self, uuid, row): locators = [] locator_set_list = row.get('locators') if locator_set_list[0] == 'set': locator_set_list = locator_set_list[1] for locator in locator_set_list: locators.append(locator[1]) else: locators.append(locator_set_list[1]) locator_set = ovsdb_schema.PhysicalLocatorSet(uuid, locators) return locator_set ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_writer.py0000664000175000017500000004732614572557755032414 0ustar00jamespagejamespage# 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 random import socket from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils from networking_l2gw.services.l2gateway.agent.ovsdb import base_connection from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway import exceptions LOG = logging.getLogger(__name__) class OVSDBWriter(base_connection.BaseConnection): """Performs transactions to OVSDB server tables.""" def __init__(self, conf, gw_config, mgr=None): super(OVSDBWriter, self).__init__(conf, gw_config, mgr=None) self.mgr = mgr def _process_response(self, op_id): result = self._response(op_id) error = result.get("error", None) if error: raise exceptions.OVSDBError( message="Error from the OVSDB server: %s" % error ) # Check errors in responses of all the subqueries outcomes = result.get("result", None) if outcomes: for outcome in outcomes: error = outcome.get("error", None) if error: raise exceptions.OVSDBError( message="Error from the OVSDB server: %s" % error) return result def _get_reply(self, operation_id, ovsdb_identifier): count = 0 while count <= n_const.MAX_RETRIES: response = self._recv_data(ovsdb_identifier) LOG.debug("Response from OVSDB server = %s", str(response)) if response: try: json_m = jsonutils.loads(response) self.responses.append(json_m) method_type = json_m.get('method', None) if method_type == "echo" and self.enable_manager: self.ovsdb_dicts.get(ovsdb_identifier).send( jsonutils.dumps( {"result": json_m.get("params", None), "error": None, "id": json_m['id']})) else: if self._process_response(operation_id): return True except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception("Exception while receiving the " "response for the write request:" " [%s]", ex) count += 1 with excutils.save_and_reraise_exception(): LOG.error("Could not obtain response from the OVSDB server " "for the request") def _send_and_receive(self, query, operation_id, ovsdb_identifier, rcv_required): if not self.send(query, addr=ovsdb_identifier): return if rcv_required: self._get_reply(operation_id, ovsdb_identifier) def delete_logical_switch(self, logical_switch_uuid, ovsdb_identifier, rcv_required=True): """Delete an entry from Logical_Switch OVSDB table.""" commit_dict = {"op": "commit", "durable": True} op_id = str(random.getrandbits(128)) query = {"method": "transact", "params": [n_const.OVSDB_SCHEMA_NAME, {"op": "delete", "table": "Logical_Switch", "where": [["_uuid", "==", ["uuid", logical_switch_uuid]]]}, commit_dict], "id": op_id} LOG.debug("delete_logical_switch: query: %s", query) self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) def insert_ucast_macs_remote(self, l_switch_dict, locator_dict, mac_dict, ovsdb_identifier, rcv_required=True): """Insert an entry in Ucast_Macs_Remote OVSDB table.""" # To insert an entry in Ucast_Macs_Remote table, it requires # corresponding entry in Physical_Locator (Compute node VTEP IP) # and Logical_Switch (Neutron network) tables. logical_switch = ovsdb_schema.LogicalSwitch(l_switch_dict['uuid'], l_switch_dict['name'], l_switch_dict['key'], l_switch_dict['description' ]) locator = ovsdb_schema.PhysicalLocator(locator_dict['uuid'], locator_dict['dst_ip']) macObject = ovsdb_schema.UcastMacsRemote(mac_dict['uuid'], mac_dict['mac'], mac_dict['logical_switch_id'], mac_dict['physical_locator_id' ], mac_dict['ip_address']) # Form the insert query now. commit_dict = {"op": "commit", "durable": True} op_id = str(random.getrandbits(128)) params = [n_const.OVSDB_SCHEMA_NAME] if locator.uuid: locator_list = ['uuid', locator.uuid] else: locator.uuid = ''.join(['a', str(random.getrandbits(128))]) locator_list = ["named-uuid", locator.uuid] params.append(self._get_physical_locator_dict(locator)) if logical_switch.uuid: l_switches = ['uuid', logical_switch.uuid] else: logical_switch.uuid = ''.join(['a', str(random.getrandbits(128))]) l_switches = ["named-uuid", logical_switch.uuid] params.append(self._get_logical_switch_dict(logical_switch)) params.append(self._get_ucast_macs_remote_dict( macObject, locator_list, l_switches)) params.append(commit_dict) query = {"method": "transact", "params": params, "id": op_id} LOG.debug("insert_ucast_macs_remote: query: %s", query) self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) def update_ucast_macs_remote(self, locator_dict, mac_dict, ovsdb_identifier, rcv_required=True): """Update an entry in Ucast_Macs_Remote OVSDB table.""" # It is possible that the locator may not exist already. locator = ovsdb_schema.PhysicalLocator(locator_dict['uuid'], locator_dict['dst_ip']) macObject = ovsdb_schema.UcastMacsRemote(mac_dict['uuid'], mac_dict['mac'], mac_dict['logical_switch_id'], mac_dict['physical_locator_id' ], mac_dict['ip_address']) # Form the insert query now. commit_dict = {"op": "commit", "durable": True} op_id = str(random.getrandbits(128)) params = [n_const.OVSDB_SCHEMA_NAME] # If the physical_locator does not exist (VM moving to a new compute # node), then insert a new record in Physical_Locator first. if locator.uuid: locator_list = ['uuid', locator.uuid] else: locator.uuid = ''.join(['a', str(random.getrandbits(128))]) locator_list = ["named-uuid", locator.uuid] params.append(self._get_physical_locator_dict(locator)) params.append(self._get_dict_for_update_ucast_mac_remote( macObject, locator_list)) params.append(commit_dict) query = {"method": "transact", "params": params, "id": op_id} LOG.debug("update_ucast_macs_remote: query: %s", query) self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) def delete_ucast_macs_remote(self, logical_switch_uuid, macs, ovsdb_identifier, rcv_required=True): """Delete entries from Ucast_Macs_Remote OVSDB table.""" commit_dict = {"op": "commit", "durable": True} op_id = str(random.getrandbits(128)) params = [n_const.OVSDB_SCHEMA_NAME] for mac in macs: sub_query = {"op": "delete", "table": "Ucast_Macs_Remote", "where": [["MAC", "==", mac], ["logical_switch", "==", ["uuid", logical_switch_uuid]]]} params.append(sub_query) params.append(commit_dict) query = {"method": "transact", "params": params, "id": op_id} LOG.debug("delete_ucast_macs_remote: query: %s", query) self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) def update_connection_to_gateway(self, logical_switch_dict, locator_dicts, mac_dicts, port_dicts, ovsdb_identifier, op_method, rcv_required=True): """Updates Physical Port's VNI to VLAN binding.""" # Form the JSON Query so as to update the physical port with the # vni-vlan (logical switch uuid to vlan) binding update_dicts = self._get_bindings_to_update(logical_switch_dict, locator_dicts, mac_dicts, port_dicts, op_method) op_id = str(random.getrandbits(128)) query = {"method": "transact", "params": update_dicts, "id": op_id} LOG.debug("update_connection_to_gateway: query = %s", query) self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) def _recv_data(self, ovsdb_identifier): chunks = [] lc = rc = 0 prev_char = None while True: try: if self.enable_manager: response = self.ovsdb_dicts.get(ovsdb_identifier).recv( n_const.BUFFER_SIZE) else: response = self.socket.recv(n_const.BUFFER_SIZE) if response: response = response.decode('utf8') for i, c in enumerate(response): if c == '{' and not (prev_char and prev_char == '\\'): lc += 1 elif c == '}' and not (prev_char and prev_char == '\\'): rc += 1 if lc == rc and lc != 0: chunks.append(response[0:i + 1]) message = "".join(chunks) return message prev_char = c chunks.append(response) else: LOG.warning("Did not receive any reply from the OVSDB " "server") return except (socket.error, socket.timeout): LOG.warning("Did not receive any reply from the OVSDB " "server") return def _get_bindings_to_update(self, l_switch_dict, locator_dicts, mac_dicts, port_dicts, op_method): # For connection-create, there are two cases to be handled # Case 1: VMs exist in a network on compute nodes. # Connection request will contain locators, ports, MACs and # network. # Case 2: VMs do not exist in a network on compute nodes. # Connection request will contain only ports and network # # For connection-delete, we do not need logical_switch and locators # information, we just need ports. locator_list = [] port_list = [] ls_list = [] logical_switch = None # Convert logical switch dict to a class object if l_switch_dict: logical_switch = ovsdb_schema.LogicalSwitch( l_switch_dict['uuid'], l_switch_dict['name'], l_switch_dict['key'], l_switch_dict['description']) # Convert locator dicts into class objects for locator in locator_dicts: locator_list.append(ovsdb_schema.PhysicalLocator(locator['uuid'], locator['dst_ip']) ) # Convert MAC dicts into class objects. mac_dicts is a dictionary with # locator VTEP IP as the key and list of MACs as the value. locator_macs = {} for locator_ip, mac_list in mac_dicts.items(): mac_object_list = [] for mac_dict in mac_list: mac_object = ovsdb_schema.UcastMacsRemote( mac_dict['uuid'], mac_dict['mac'], mac_dict['logical_switch_id'], mac_dict['physical_locator_id'], mac_dict['ip_address']) mac_object_list.append(mac_object) locator_macs[locator_ip] = mac_object_list # Convert port dicts into class objects for port in port_dicts: phys_port = ovsdb_schema.PhysicalPort(port['uuid'], port['name'], port['physical_switch_id'], port['vlan_bindings'], port['port_fault_status']) port_list.append(phys_port) bindings = [] bindings.append(n_const.OVSDB_SCHEMA_NAME) # Form the query. commit_dict = {"op": "commit", "durable": True} params = [n_const.OVSDB_SCHEMA_NAME] # Use logical switch if logical_switch: ls_list = self._form_logical_switch(logical_switch, params) # Use physical locators if locator_list: self._form_physical_locators(ls_list, locator_list, locator_macs, params) # Use ports self._form_ports(ls_list, port_list, params, op_method) params.append(commit_dict) return params def _form_logical_switch(self, logical_switch, params): ls_list = [] if logical_switch.uuid: ls_list = ['uuid', logical_switch.uuid] else: logical_switch.uuid = ''.join(['a', str(random.getrandbits(128))]) ls_list = ["named-uuid", logical_switch.uuid] params.append(self._get_logical_switch_dict(logical_switch)) return ls_list def _form_physical_locators(self, ls_list, locator_list, locator_macs, params): for locator in locator_list: if locator.uuid: loc_list = ['uuid', locator.uuid] else: locator.uuid = ''.join(['a', str(random.getrandbits(128))]) loc_list = ["named-uuid", locator.uuid] params.append(self._get_physical_locator_dict(locator)) macs = locator_macs.get(locator.dst_ip, None) if macs: for mac in macs: query = self._get_ucast_macs_remote_dict(mac, loc_list, ls_list) params.append(query) def _form_ports(self, ls_list, port_list, params, op_method): for port in port_list: port_vlan_bindings = [] outer_list = [] port_vlan_bindings.append("map") if port.vlan_bindings: for vlan_binding in port.vlan_bindings: if vlan_binding.logical_switch_uuid: outer_list.append([vlan_binding.vlan, ['uuid', vlan_binding.logical_switch_uuid]]) else: outer_list.append([vlan_binding.vlan, ls_list]) port_vlan_bindings.append(outer_list) if op_method == 'CREATE': update_dict = {"op": "mutate", "table": "Physical_Port", "where": [["_uuid", "==", ["uuid", port.uuid]]], "mutations": [["vlan_bindings", "insert", port_vlan_bindings]]} elif op_method == 'DELETE': update_dict = {"op": "mutate", "table": "Physical_Port", "where": [["_uuid", "==", ["uuid", port.uuid]]], "mutations": [["vlan_bindings", "delete", port_vlan_bindings]]} params.append(update_dict) def _get_physical_locator_dict(self, locator): return {"op": "insert", "table": "Physical_Locator", "uuid-name": locator.uuid, "row": {"dst_ip": locator.dst_ip, "encapsulation_type": "vxlan_over_ipv4"}} def _get_logical_switch_dict(self, logical_switch): return {"op": "insert", "uuid-name": logical_switch.uuid, "table": "Logical_Switch", "row": {"description": logical_switch.description, "name": logical_switch.name, "tunnel_key": int(logical_switch.key)}} def _get_ucast_macs_remote_dict(self, mac, locator_list, logical_switch_list): named_string = str(random.getrandbits(128)) return {"op": "insert", "uuid-name": ''.join(['a', named_string]), "table": "Ucast_Macs_Remote", "row": {"MAC": mac.mac, "ipaddr": mac.ip_address, "locator": locator_list, "logical_switch": logical_switch_list}} def _get_dict_for_update_ucast_mac_remote(self, mac, locator_list): return {"op": "update", "table": "Ucast_Macs_Remote", "where": [["_uuid", "==", ["uuid", mac.uuid]]], "row": {"locator": locator_list}} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/vtep/0000775000175000017500000000000014572557757030275 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent/ovsdb/vtep/vtep.ovsschema0000664000175000017500000002631014572557755033165 0ustar00jamespagejamespage{ "name": "hardware_vtep", "cksum": "353943336 11434", "tables": { "Global": { "columns": { "managers": { "type": {"key": {"type": "uuid", "refTable": "Manager"}, "min": 0, "max": "unlimited"}}, "switches": { "type": {"key": {"type": "uuid", "refTable": "Physical_Switch"}, "min": 0, "max": "unlimited"}}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}} }, "maxRows": 1, "isRoot": true}, "Physical_Switch": { "columns": { "ports": { "type": {"key": {"type": "uuid", "refTable": "Physical_Port"}, "min": 0, "max": "unlimited"}}, "name": {"type": "string"}, "description": {"type": "string"}, "management_ips": { "type": {"key": {"type": "string"}, "min": 0, "max": "unlimited"}}, "tunnel_ips": { "type": {"key": {"type": "string"}, "min": 0, "max": "unlimited"}}, "tunnels": { "type": {"key": {"type": "uuid", "refTable": "Tunnel"}, "min": 0, "max": "unlimited"}}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "switch_fault_status": { "type": { "key": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}, "indexes": [["name"]]}, "Physical_Port": { "columns": { "name": {"type": "string"}, "description": {"type": "string"}, "vlan_bindings": { "type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 4095}, "value": {"type": "uuid", "refTable": "Logical_Switch"}, "min": 0, "max": "unlimited"}}, "acl_bindings": { "type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 4095}, "value": {"type": "uuid", "refTable": "ACL"}, "min": 0, "max": "unlimited"}}, "vlan_stats": { "type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 4095}, "value": {"type": "uuid", "refTable": "Logical_Binding_Stats"}, "min": 0, "max": "unlimited"}, "ephemeral": true}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "port_fault_status": { "type": { "key": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}}, "Tunnel": { "columns": { "local": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"}}}, "remote": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"}}}, "bfd_config_local": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "bfd_config_remote": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "bfd_params": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "bfd_status": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}}, "Logical_Binding_Stats": { "columns": { "bytes_from_local": {"type": "integer", "ephemeral": true}, "packets_from_local": {"type": "integer", "ephemeral": true}, "bytes_to_local": {"type": "integer", "ephemeral": true}, "packets_to_local": {"type": "integer", "ephemeral": true}}}, "Logical_Switch": { "columns": { "name": {"type": "string"}, "description": {"type": "string"}, "tunnel_key": {"type": {"key": "integer", "min": 0, "max": 1}}, "replication_mode": { "type": { "key": { "enum": ["set", ["service_node", "source_node"]], "type": "string"},"min": 0, "max": 1}}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true, "indexes": [["name"]]}, "Ucast_Macs_Local": { "columns": { "MAC": {"type": "string"}, "logical_switch": { "type": {"key": {"type": "uuid", "refTable": "Logical_Switch"}}}, "locator": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"}}}, "ipaddr": {"type": "string"}}, "isRoot": true}, "Ucast_Macs_Remote": { "columns": { "MAC": {"type": "string"}, "logical_switch": { "type": {"key": {"type": "uuid", "refTable": "Logical_Switch"}}}, "locator": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"}}}, "ipaddr": {"type": "string"}}, "isRoot": true}, "Mcast_Macs_Local": { "columns": { "MAC": {"type": "string"}, "logical_switch": { "type": {"key": {"type": "uuid", "refTable": "Logical_Switch"}}}, "locator_set": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator_Set"}}}, "ipaddr": {"type": "string"}}, "isRoot": true}, "Mcast_Macs_Remote": { "columns": { "MAC": {"type": "string"}, "logical_switch": { "type": {"key": {"type": "uuid", "refTable": "Logical_Switch"}}}, "locator_set": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator_Set"}}}, "ipaddr": {"type": "string"}}, "isRoot": true}, "Logical_Router": { "columns": { "name": {"type": "string"}, "description": {"type": "string"}, "switch_binding": { "type": {"key": {"type": "string"}, "value": {"type": "uuid", "refTable": "Logical_Switch"}, "min": 0, "max": "unlimited"}}, "static_routes": { "type": {"key": {"type": "string"}, "value": {"type" : "string"}, "min": 0, "max": "unlimited"}}, "acl_binding": { "type": {"key": {"type": "string"}, "value": {"type": "uuid", "refTable": "ACL"}, "min": 0, "max": "unlimited"}}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "LR_fault_status": { "type": { "key": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}, "isRoot": true, "indexes": [["name"]]}, "Arp_Sources_Local": { "columns": { "src_mac": {"type": "string"}, "locator": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"}}}}, "isRoot": true}, "Arp_Sources_Remote": { "columns": { "src_mac": {"type": "string"}, "locator": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"}}}}, "isRoot": true}, "Physical_Locator_Set": { "columns": { "locators": { "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"}, "min": 1, "max": "unlimited"}, "mutable": false}}}, "Physical_Locator": { "columns": { "encapsulation_type": { "type": { "key": { "enum": ["set", ["vxlan_over_ipv4"]], "type": "string"}}, "mutable": false}, "dst_ip": {"type": "string", "mutable": false}, "tunnel_key": {"type": {"key": "integer", "min": 0, "max": 1}}}, "indexes": [["encapsulation_type", "dst_ip", "tunnel_key"]]}, "ACL_entry": { "columns": { "sequence": {"type": "integer"}, "source_mac": { "type": { "key": "string", "min": 0, "max": 1}}, "dest_mac": { "type": { "key": "string", "min": 0, "max": 1}}, "ethertype": { "type": { "key": "string", "min": 0, "max": 1}}, "source_ip": { "type": { "key": "string", "min": 0, "max": 1}}, "source_mask": { "type": { "key": "string", "min": 0, "max": 1}}, "dest_ip": { "type": { "key": "string", "min": 0, "max": 1}}, "dest_mask": { "type": { "key": "string", "min": 0, "max": 1}}, "protocol": { "type": { "key": "integer", "min": 0, "max": 1}}, "source_port_min": { "type": { "key": "integer", "min": 0, "max": 1}}, "source_port_max": { "type": { "key": "integer", "min": 0, "max": 1}}, "dest_port_min": { "type": { "key": "integer", "min": 0, "max": 1}}, "dest_port_max": { "type": { "key": "integer", "min": 0, "max": 1}}, "tcp_flags": { "type": { "key": "integer", "min": 0, "max": 1}}, "tcp_flags_mask": { "type": { "key": "integer", "min": 0, "max": 1}}, "icmp_code": { "type": { "key": "integer", "min": 0, "max": 1}}, "icmp_type": { "type": { "key": "integer", "min": 0, "max": 1}}, "direction": { "type": { "key": {"type": "string", "enum": ["set", ["ingress", "egress"]]}}}, "action": { "type": { "key": {"type": "string", "enum": ["set", ["permit", "deny"]]}}}, "acle_fault_status": { "type": { "key": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}, "isRoot": true}, "ACL": { "columns": { "acl_entries": { "type": {"key": {"type": "uuid", "refTable": "ACL_entry"}, "min": 1, "max": "unlimited"}}, "acl_name": {"type": "string"}, "acl_fault_status": { "type": { "key": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}, "indexes": [["acl_name"]], "isRoot": true}, "Manager": { "columns": { "target": {"type": "string"}, "max_backoff": { "type": {"key": {"type": "integer", "minInteger": 1000}, "min": 0, "max": 1}}, "inactivity_probe": { "type": {"key": "integer", "min": 0, "max": 1}}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "is_connected": { "type": "boolean", "ephemeral": true}, "status": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}, "indexes": [["target"]], "isRoot": false}}, "version": "1.7.0"} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/agent_scheduler.py0000664000175000017500000001261314572557755030613 0ustar00jamespagejamespage# Copyright (c) 2015 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 random from neutron_lib.plugins import directory from oslo_config import cfg from oslo_log import log as logging from oslo_service import loopingcall from neutron.agent.common import utils from neutron.db import agents_db from neutron_lib import context as neutron_context from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import constants as srv_const LOG = logging.getLogger(__name__) # DNM: Do Not Merge, test patch only! class L2GatewayAgentScheduler(agents_db.AgentDbMixin): """L2gateway agent scheduler class. This maintains active and inactive agents and select monitor and transact agents. """ _plugin = None _l2gwplugin = None def __init__(self, agent_rpc, notifier=None): super(L2GatewayAgentScheduler, self).__init__() self.notifier = notifier config.register_l2gw_opts_helper() self.monitor_interval = cfg.CONF.periodic_monitoring_interval self.agent_rpc = agent_rpc @property def l2gwplugin(self): if self._l2gwplugin is None: self._l2gwplugin = directory.get_plugin(srv_const.L2GW) return self._l2gwplugin @property def plugin(self): if self._plugin is None: self._plugin = directory.get_plugin() return self._plugin def initialize_thread(self): """Initialization of L2gateway agent scheduler thread.""" try: monitor_thread = loopingcall.FixedIntervalLoopingCall( self.monitor_agent_state) monitor_thread.start( interval=self.monitor_interval, initial_delay=random.randint(self.monitor_interval, self.monitor_interval * 2)) LOG.debug("Successfully initialized L2gateway agent scheduler" " thread with loop interval %s", self.monitor_interval) except Exception: LOG.error("Cannot initialize agent scheduler thread") def _select_agent_type(self, context, agents_to_process): """Select the Monitor agent.""" # Various cases to be handled: # 1. Check if there is a single active L2 gateway agent. # If only one agent is active, then make it the Monitor agent. # 2. Else, in the list of the active agents, if there does not # exist Monitor agent, then make the agent that # started first as the Monitor agent. # 3. If multiple Monitor agents exist (case where the Monitor agent # gets disconnected from the Neutron server and another agent # becomes the Monitor agent and then the original Monitor agent # connects back within the agent downtime value), then we need to # send the fanout message so that only one becomes the Monitor # agent. # Check if there already exists Monitor agent and it's the only one. monitor_agents = [ x for x in agents_to_process if x['configurations'].get(srv_const.L2GW_AGENT_TYPE) == srv_const.MONITOR] if len(monitor_agents) == 1: return # We either have more than one Monitor agent, # or there does not exist Monitor agent. # We will decide which agent should be the Monitor agent. chosen_agent = None if len(agents_to_process) == 1: # Only one agent is configured. # Make it the Monitor agent chosen_agent = agents_to_process[0] else: # Select the agent with the oldest started_at # timestamp as the Monitor agent. sorted_active_agents = sorted(agents_to_process, key=lambda k: k['started_at']) chosen_agent = sorted_active_agents[0] self.agent_rpc.set_monitor_agent(context, chosen_agent['host']) def monitor_agent_state(self): """Represents L2gateway agent scheduler thread. Maintains list of active and inactive agents based on the heartbeat recorded. """ context = neutron_context.get_admin_context() try: all_agents = self.plugin.get_agents( context, filters={'agent_type': [srv_const.AGENT_TYPE_L2GATEWAY]}) except Exception: LOG.exception("Unable to get the agent list. Continuing...") return # Reset the agents that will be processed for selecting the # Monitor agent agents_to_process = [] for agent in all_agents: if not utils.is_agent_down(agent['heartbeat_timestamp']): agents_to_process.append(agent) if agents_to_process: self._select_agent_type(context, agents_to_process) return ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/0000775000175000017500000000000014572557757026374 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/__init__.py0000664000175000017500000000000014572557755030471 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/config.py0000664000175000017500000000703114572557755030212 0ustar00jamespagejamespage# 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. from neutron.common import config from oslo_config import cfg from networking_l2gw._i18n import _ OVSDB_OPTS = [ cfg.StrOpt('ovsdb_hosts', default='host1:127.0.0.1:6632', help=_("OVSDB server name:host/IP:port")), cfg.StrOpt('l2_gw_agent_priv_key_base_path', help=_('L2 gateway agent private key')), cfg.StrOpt('l2_gw_agent_cert_base_path', help=_('L2 gateway agent public certificate')), cfg.StrOpt('l2_gw_agent_ca_cert_base_path', help=_('Trusted issuer CA cert')), cfg.IntOpt('periodic_interval', default=20, help=_('Seconds between periodic task runs')), cfg.IntOpt('socket_timeout', default=30, help=_('Socket timeout in seconds. ' 'If there is no echo request on the socket for ' 'socket_timeout seconds, the agent can safely ' 'assume that the connection with the remote ' 'OVSDB server is lost')), cfg.BoolOpt('enable_manager', default=False, help=_('Set to True if ovsdb Manager manages the client')), cfg.PortOpt('manager_table_listening_port', default=6632, help=_('Set port number for l2gw agent, so that it can ' 'listen to whenever its IP is entered in manager ' 'table of ovsdb server, For Ex: tcp:x.x.x.x:6640, ' 'where x.x.x.x is IP of l2gw agent')), cfg.IntOpt('max_connection_retries', default=10, help=_('Maximum number of retries to open a socket ' 'with the OVSDB server')) ] L2GW_OPTS = [ cfg.StrOpt('default_interface_name', default='FortyGigE1/0/1', help=_('default_interface_name of the l2 gateway')), cfg.StrOpt('default_device_name', default='Switch1', help=_('default_device_name of the l2 gateway')), cfg.IntOpt('quota_l2_gateway', default=5, help=_('Number of l2 gateways allowed per tenant, ' '-1 for unlimited')), cfg.IntOpt('periodic_monitoring_interval', default=5, help=_('Periodic interval at which the plugin ' 'checks for the monitoring L2 gateway agent')), cfg.StrOpt('l2gw_callback_class', default='networking_l2gw.services.l2gateway.ovsdb.' 'data.L2GatewayOVSDBCallbacks', help=_('L2 gateway plugin callback class where the ' 'RPCs from the agent are going to get invoked')) ] def register_l2gw_opts_helper(): cfg.CONF.register_opts(L2GW_OPTS) def register_ovsdb_opts_helper(conf): conf.register_opts(OVSDB_OPTS, 'ovsdb') # add a logging setup method here for convenience setup_logging = config.setup_logging ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/constants.py0000664000175000017500000000304414572557755030761 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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. # service type constants: L2GW = "L2GW" l2gw = "l2gw" AGENT_TYPE_L2GATEWAY = 'L2 Gateway agent' L2GW_INVALID_OVSDB_IDENTIFIER = 101 ERROR_DICT = {L2GW_INVALID_OVSDB_IDENTIFIER: "Invalid ovsdb_identifier in the " "request"} MONITOR = 'monitor' OVSDB_SCHEMA_NAME = 'hardware_vtep' OVSDB_IDENTIFIER = 'ovsdb_identifier' L2GW_AGENT_TYPE = 'l2gw_agent_type' NETWORK_ID = 'network_id' SEG_ID = 'segmentation_id' L2GATEWAY_ID = 'l2_gateway_id' GATEWAY_RESOURCE_NAME = 'l2_gateway' L2_GATEWAYS = 'l2-gateways' DEVICE_ID_ATTR = 'device_name' IFACE_NAME_ATTR = 'interfaces' CONNECTION_RESOURCE_NAME = 'l2_gateway_connection' EXT_ALIAS = 'l2-gateway-connection' L2_GATEWAYS_CONNECTION = "%ss" % EXT_ALIAS BUFFER_SIZE = 4096 MAX_RETRIES = 1000 L2_GATEWAY_SERVICE_PLUGIN = "Neutron L2 gateway Service Plugin" PORT_FAULT_STATUS_UP = "UP" SWITCH_FAULT_STATUS_UP = "UP" VXLAN = "vxlan" CREATE = "CREATE" DELETE = "DELETE" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/l2gw_validators.py0000664000175000017500000001151114572557755032046 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 neutron_lib.api import validators from neutron_lib import exceptions from networking_l2gw._i18n import _ from networking_l2gw.services.l2gateway.common import constants ALLOWED_CONNECTION_ATTRIBUTES = set((constants.NETWORK_ID, constants.SEG_ID, constants.L2GATEWAY_ID )) def validate_gwdevice_list(data, valid_values=None): """Validate the list of devices.""" if not data: # Devices must be provided msg = _("Cannot create a gateway with an empty device list") return msg try: for device in data: interface_data = device.get(constants.IFACE_NAME_ATTR) device_name = device.get(constants.DEVICE_ID_ATTR) if not device_name: msg = _("Cannot create a gateway with an empty device_name") return msg if not interface_data: msg = _("Cannot create a gateway with an empty interfaces") return msg if not isinstance(interface_data, list): msg = _("interfaces format is not a type list of dicts") return msg for int_dict in interface_data: if not isinstance(int_dict, dict): msg = _("interfaces format is not a type dict") return msg err_msg = validators.validate_dict(int_dict, None) if not int_dict.get('name'): msg = _("Cannot create a gateway with an empty " "interface name") return msg if constants.SEG_ID in int_dict: seg_id_list = int_dict.get(constants.SEG_ID) if seg_id_list and type(seg_id_list) is not list: msg = _("segmentation_id type should be of list type ") return msg if not seg_id_list: msg = _("segmentation_id_list should not be empty") return msg for seg_id in seg_id_list: is_valid_vlan_id(seg_id) if err_msg: return err_msg except TypeError: return (_("%s: provided data are not iterable") % validate_gwdevice_list.__name__) def validate_network_mapping_list(network_mapping, check_vlan): """Validate network mapping list in connection.""" if network_mapping.get('segmentation_id'): if check_vlan: raise exceptions.InvalidInput( error_message=_("default segmentation_id should not be" " provided when segmentation_id is assigned" " during l2gateway creation")) seg_id = network_mapping.get(constants.SEG_ID) is_valid_vlan_id(seg_id) if not network_mapping.get('segmentation_id'): if check_vlan is False: raise exceptions.InvalidInput( error_message=_("Segmentation id must be specified in create " "l2gateway connections")) network_id = network_mapping.get(constants.NETWORK_ID) if not network_id: raise exceptions.InvalidInput( error_message=_("A valid network identifier must be specified " "when connecting a network to a network " "gateway. Unable to complete operation")) connection_attrs = set(network_mapping.keys()) if not connection_attrs.issubset(ALLOWED_CONNECTION_ATTRIBUTES): raise exceptions.InvalidInput( error_message=(_("Invalid keys found among the ones provided " "in request : %(connection_attrs)s."), connection_attrs)) return network_id def is_valid_vlan_id(seg_id): try: int_seg_id = int(seg_id) except ValueError: msg = _("Segmentation id must be a valid integer") raise exceptions.InvalidInput(error_message=msg) if int_seg_id < 0 or int_seg_id >= 4095: msg = _("Segmentation id is out of range") raise exceptions.InvalidInput(error_message=msg) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/ovsdb_schema.py0000664000175000017500000000574314572557755031412 0ustar00jamespagejamespage# 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. class PhysicalLocator(object): def __init__(self, uuid, dst_ip): self.uuid = uuid self.dst_ip = dst_ip class PhysicalSwitch(object): def __init__(self, uuid, name, tunnel_ip, switch_fault_status): self.uuid = uuid self.name = name self.tunnel_ip = tunnel_ip self.switch_fault_status = switch_fault_status class PhysicalPort(object): def __init__(self, uuid, name, phys_switch_id, vlan_binding_dicts, port_fault_status): self.uuid = uuid self.name = name self.physical_switch_id = phys_switch_id self.vlan_bindings = [] self.port_fault_status = port_fault_status if vlan_binding_dicts: for vlan_binding in vlan_binding_dicts: v_binding = VlanBinding(vlan_binding['vlan'], vlan_binding['logical_switch_uuid']) self.vlan_bindings.append(v_binding) class LogicalSwitch(object): def __init__(self, uuid, name, key, description): self.uuid = uuid self.name = name self.key = key self.description = description class UcastMacsLocal(object): def __init__(self, uuid, mac, logical_switch_id, physical_locator_id, ip_address): self.uuid = uuid self.mac = mac self.logical_switch_id = logical_switch_id self.physical_locator_id = physical_locator_id self.ip_address = ip_address class UcastMacsRemote(object): def __init__(self, uuid, mac, logical_switch_id, physical_locator_id, ip_address): self.uuid = uuid self.mac = mac self.logical_switch_id = logical_switch_id self.physical_locator_id = physical_locator_id self.ip_address = ip_address class VlanBinding(object): def __init__(self, vlan, logical_switch_uuid): self.vlan = vlan self.logical_switch_uuid = logical_switch_uuid class McastMacsLocal(object): def __init__(self, uuid, mac, logical_switch, locator_set, ip_address): self.uuid = uuid self.mac = mac self.logical_switch_id = logical_switch self.locator_set = locator_set self.ip_address = ip_address class PhysicalLocatorSet(object): def __init__(self, uuid, locators): self.uuid = uuid self.locators = locators ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/topics.py0000664000175000017500000000132314572557755030244 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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. L2GATEWAY_PLUGIN = 'l2gateway_plugin' L2GATEWAY_AGENT = 'l2gateway_agent' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/common/tunnel_calls.py0000664000175000017500000000374114572557755031434 0ustar00jamespagejamespage# Copyright (c) 2016 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 neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import rpc as rpc from neutron_lib.agent import topics class Tunnel_Calls(object): """Common tunnel calls for L2 agent.""" def __init__(self): self._construct_rpc_stuff() def _construct_rpc_stuff(self): self.notifier = rpc.AgentNotifierApi(topics.AGENT) self.type_manager = managers.TypeManager() self.tunnel_rpc_obj = rpc.RpcCallbacks(self.notifier, self.type_manager) def trigger_tunnel_sync(self, context, tunnel_ip): """Sends tunnel sync RPC message to the neutron L2 agent. """ tunnel_dict = {'tunnel_ip': tunnel_ip, 'tunnel_type': 'vxlan'} self.tunnel_rpc_obj.tunnel_sync(context, **tunnel_dict) def trigger_l2pop_sync(self, context, other_fdb_entries): """Sends L2pop ADD RPC message to the neutron L2 agent.""" l2pop_rpc.L2populationAgentNotifyAPI().add_fdb_entries( context, other_fdb_entries) def trigger_l2pop_delete(self, context, other_fdb_entries, host=None): """Sends L2pop DELETE RPC message to the neutron L2 agent.""" l2pop_rpc.L2populationAgentNotifyAPI().remove_fdb_entries( context, other_fdb_entries, host) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/exceptions.py0000664000175000017500000001275414572557755027646 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 neutron_lib.api import faults from neutron_lib import exceptions from webob import exc as web_exc from networking_l2gw._i18n import _ class L2GatewayInUse(exceptions.InUse): message = _("L2 Gateway '%(gateway_id)s' still has active mappings " "with one or more neutron networks.") class L2GatewayNotFound(exceptions.NotFound): message = _("L2 Gateway %(gateway_id)s could not be found") class L2GatewayDeviceInUse(exceptions.InUse): message = _("L2 Gateway Device '%(device_id)s' is still used by " "one or more network gateways.") class L2AgentNotFoundByHost(exceptions.NotFound): message = _("L2 Agent for host '%(host)s' could not be found.") class L2GatewayDeviceNotFound(exceptions.NotFound): message = _("L2 Gateway Device %(device_id)s could not be found.") class L2GatewayDeviceNameNotFound(exceptions.NotFound): message = _("L2 Gateway Device %(device_name)s could not be found.") class L2GatewayPortInUse(exceptions.InUse): message = _("Port '%(port_id)s' is owned by '%(device_owner)s' and " "therefore cannot be deleted directly via the port API.") class L2GatewayConnectionExists(exceptions.InUse): message = _("The specified mapping '%(mapping)s' exists on " "network gateway '%(gateway_id)s'.") class L2MultipleGatewayConnections(exceptions.NeutronException): message = _("Multiple network connections found on '%(gateway_id)s' " "with provided criteria.") class L2GatewayInterfaceNotFound(exceptions.NeutronException): message = _("L2 Gateway interface '%(interface_id)s' not found.") class L2GatewayPhysicalPortNotFound(exceptions.NeutronException): message = _("Physical Port '%(int_name)s' in Physical switch " "'%(device_name)s' could not be found") class L2GatewayPhysicalPortFaultStatus(exceptions.NeutronException): message = _("Physical Port '%(int_name)s' in Physical switch " "'%(device_name)s' port_fault_status is '%(fault_status)s'") class L2GatewayPhysicalSwitchFaultStatus(exceptions.NeutronException): message = _("Physical Switch '%(device_name)s' switch_fault_status is " "'%(fault_status)s'") class L2GatewayConnectionNotFound(exceptions.NotFound): message = _("The connection %(id)s not found on the l2 gateway") class L2gatewaySegmentationIDNotFound(exceptions.NotFound): message = _("The segmentation id not found on gateway " "'%(gateway_id)s'") class MultipleVxlanSegmentsFound(exceptions.NeutronException): message = _("Multiple Vxlan segments found for the network " "'%(network_id)s'") class VxlanSegmentationIDNotFound(exceptions.NotFound): message = _("vxlan segmentation id not found for the " "network '%(network_id)s'") class L2GatewayInterfaceRequired(exceptions.NeutronException): message = _("L2 Gateway Interface required") class L2GatewaySegmentationIDExists(exceptions.NeutronException): message = _("The segmentation id is already specified in " "existing gateway interfaces.") class L2GatewaySegmentationIDNotExists(exceptions.NeutronException): message = _("The segmentation id is not specified in " "existing gateway interfaces.") class L2GatewaySegmentationRequired(exceptions.NeutronException): message = _("L2 gateway segmentation id must be consistent for all " "the interfaces") class L2GatewayDuplicateSegmentationID(exceptions.Conflict): message = _("%(message)s") class OVSDBError(exceptions.NeutronException): message = _("%(message)s") class L2GatewayServiceDriverError(exceptions.NeutronException): """Service driver call failed.""" message = _("%(method)s failed.") class InvalidMethod(exceptions.NeutronException): message = _("invalid method '%(op_method)s'") faults.FAULT_MAP.update({L2GatewayInUse: web_exc.HTTPConflict, L2GatewayPortInUse: web_exc.HTTPConflict, L2GatewayConnectionExists: web_exc.HTTPConflict, L2GatewayConnectionNotFound: web_exc.HTTPNotFound, MultipleVxlanSegmentsFound: web_exc.HTTPConflict, VxlanSegmentationIDNotFound: web_exc.HTTPNotFound, L2GatewaySegmentationRequired: web_exc.HTTPConflict, L2MultipleGatewayConnections: web_exc.HTTPConflict, L2GatewayDuplicateSegmentationID: web_exc.HTTPConflict, L2AgentNotFoundByHost: web_exc.HTTPNotFound, OVSDBError: web_exc.HTTPConflict}) class L3DvrAgentNotFound(exceptions.NotFound): message = _("L3 agent could not be found") class DvrAgentHostnameNotFound(exceptions.NeutronException): message = _("Hostname '%(host)' has 127.0.0.1 address") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/l2gw_agent.py0000664000175000017500000000311114572557755027501 0ustar00jamespagejamespage# 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 sys from neutron.common import config as common_config from neutron.conf.agent import common as agent_config from neutron_lib import rpc as n_rpc from oslo_config import cfg from oslo_service import service from networking_l2gw.services.l2gateway.agent.ovsdb import manager from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import topics class L2gatewayAgentService(n_rpc.Service): def start(self): super(L2gatewayAgentService, self).start() def main(): common_config.register_common_config_options() config.register_ovsdb_opts_helper(cfg.CONF) agent_config.register_agent_state_opts_helper(cfg.CONF) common_config.init(sys.argv[1:]) config.setup_logging() mgr = manager.OVSDBManager(cfg.CONF) svc = L2gatewayAgentService( host=cfg.CONF.host, topic=topics.L2GATEWAY_AGENT, manager=mgr ) service.launch(cfg.CONF, svc).wait() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/ovsdb/0000775000175000017500000000000014572557757026221 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/ovsdb/__init__.py0000664000175000017500000000000014572557755030316 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/ovsdb/data.py0000664000175000017500000006330414572557755027510 0ustar00jamespagejamespage# Copyright (c) 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. from networking_l2gw.db.l2gateway import l2gateway_db from networking_l2gw.db.l2gateway.ovsdb import lib as db from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway.common import topics from networking_l2gw.services.l2gateway.common import tunnel_calls from networking_l2gw.services.l2gateway import exceptions as l2gw_exc from networking_l2gw.services.l2gateway.service_drivers import agent_api from neutron_lib.api.definitions import portbindings from neutron_lib import constants from neutron_lib.plugins import directory from oslo_config import cfg from oslo_log import log as logging import oslo_messaging as messaging LOG = logging.getLogger(__name__) class L2GatewayOVSDBCallbacks(object): """Implement the rpc call back functions from OVSDB.""" target = messaging.Target(version='1.0') def __init__(self, plugin): super(L2GatewayOVSDBCallbacks, self).__init__() self.plugin = plugin self.ovsdb = None def update_ovsdb_changes(self, context, activity, ovsdb_data): """RPC to update the changes from OVSDB in the database.""" self.ovsdb = self.get_ovsdbdata_object( ovsdb_data.get(n_const.OVSDB_IDENTIFIER)) self.ovsdb.update_ovsdb_changes(context, activity, ovsdb_data) def notify_ovsdb_states(self, context, ovsdb_states): """RPC to notify the OVSDB servers connection state.""" if ovsdb_states: self.ovsdb = self.get_ovsdbdata_object( list(ovsdb_states.keys())[0]) if self.ovsdb: LOG.debug("ovsdb_states = %s", ovsdb_states) self.ovsdb.notify_ovsdb_states(context, ovsdb_states) def get_ovsdbdata_object(self, ovsdb_identifier): return OVSDBData(ovsdb_identifier) class OVSDBData(object): """Process the data coming from OVSDB.""" def __init__(self, ovsdb_identifier=None): self.ovsdb_identifier = ovsdb_identifier self._setup_entry_table() self.agent_rpc = agent_api.L2gatewayAgentApi( topics.L2GATEWAY_AGENT, cfg.CONF.host) self.l2gw_mixin = l2gateway_db.L2GatewayMixin() self.core_plugin = directory.get_plugin() self.tunnel_call = tunnel_calls.Tunnel_Calls() def _cleanup_all_ovsdb_tables(self, context, ovsdb_identifier): db.delete_all_physical_locators_by_ovsdb_identifier( context, ovsdb_identifier) db.delete_all_physical_switches_by_ovsdb_identifier( context, ovsdb_identifier) db.delete_all_physical_ports_by_ovsdb_identifier( context, ovsdb_identifier) db.delete_all_logical_switches_by_ovsdb_identifier( context, ovsdb_identifier) db.delete_all_ucast_macs_locals_by_ovsdb_identifier( context, ovsdb_identifier) db.delete_all_ucast_macs_remotes_by_ovsdb_identifier( context, ovsdb_identifier) db.delete_all_vlan_bindings_by_ovsdb_identifier( context, ovsdb_identifier) def update_ovsdb_changes(self, context, activity, ovsdb_data): """RPC to update the changes from OVSDB in the database.""" ovsdb_identifier = ovsdb_data.get('ovsdb_identifier') if not activity: self._cleanup_all_ovsdb_tables(context, ovsdb_identifier) for item, value in ovsdb_data.items(): lookup = self.entry_table.get(item, None) if lookup: lookup(context, value) if ovsdb_data.get('new_remote_macs'): self._handle_l2pop(context, ovsdb_data.get('new_remote_macs')) def notify_ovsdb_states(self, context, ovsdb_states): """RPC to notify the OVSDB servers connection state.""" for ovsdb_identifier, state in ovsdb_states.items(): if state == 'connected': pending_recs = db.get_all_pending_remote_macs_in_asc_order( context, ovsdb_identifier) if pending_recs: for pending_mac in pending_recs: logical_switch_uuid = pending_mac['logical_switch_uuid' ] mac = pending_mac['mac'] operation = pending_mac['operation'] try: if operation == 'insert' or operation == 'update': l_switch = ovsdb_schema.LogicalSwitch( logical_switch_uuid, None, None, None) locator_uuid = pending_mac.get( 'locator_uuid', None) dst_ip = pending_mac.get( 'dst_ip', None) locator = ovsdb_schema.PhysicalLocator( locator_uuid, dst_ip) mac_remote = ovsdb_schema.UcastMacsRemote( pending_mac.get('uuid', None), mac, logical_switch_uuid, locator_uuid, pending_mac['vm_ip']) if operation == 'insert': self.agent_rpc.add_vif_to_gateway( context, ovsdb_identifier, l_switch.__dict__, locator.__dict__, mac_remote.__dict__) else: # update operation self.agent_rpc.update_vif_to_gateway( context, ovsdb_identifier, locator.__dict__, mac_remote.__dict__) else: self.agent_rpc.delete_vif_from_gateway( context, ovsdb_identifier, logical_switch_uuid, [mac]) # As the pending operation is over, delete the # record from the pending_ucast_mac_remote table db.delete_pending_ucast_mac_remote( context, operation, ovsdb_identifier, logical_switch_uuid, mac) except Exception as ex: LOG.exception("Exception occurred = %s", str(ex)) def _setup_entry_table(self): self.entry_table = {'new_logical_switches': self._process_new_logical_switches, 'new_physical_ports': self._process_new_physical_ports, 'new_physical_switches': self._process_new_physical_switches, 'new_physical_locators': self._process_new_physical_locators, 'new_local_macs': self._process_new_local_macs, 'new_remote_macs': self._process_new_remote_macs, 'modified_remote_macs': self._process_modified_remote_macs, 'modified_physical_ports': self._process_modified_physical_ports, 'deleted_logical_switches': self._process_deleted_logical_switches, 'deleted_physical_ports': self._process_deleted_physical_ports, 'deleted_physical_switches': self._process_deleted_physical_switches, 'deleted_physical_locators': self._process_deleted_physical_locators, 'deleted_local_macs': self._process_deleted_local_macs, 'deleted_remote_macs': self._process_deleted_remote_macs, 'modified_physical_switches': self._process_modified_physical_switches, } return def _process_new_logical_switches(self, context, new_logical_switches): for logical_switch in new_logical_switches: ls_dict = logical_switch ls_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier l_switch = db.get_logical_switch(context, ls_dict) if not l_switch: db.add_logical_switch(context, ls_dict) def _process_new_physical_switches(self, context, new_physical_switches): for physical_switch in new_physical_switches: ps_dict = physical_switch ps_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier if (ps_dict.get('tunnel_ip'))[0] == 'set': ps_dict['tunnel_ip'] = None p_switch = db.get_physical_switch(context, ps_dict) if not p_switch: db.add_physical_switch(context, ps_dict) def _process_new_physical_ports(self, context, new_physical_ports): for physical_port in new_physical_ports: pp_dict = physical_port pp_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier p_port = db.get_physical_port(context, pp_dict) if not p_port: db.add_physical_port(context, pp_dict) if pp_dict.get('vlan_bindings'): for vlan_binding in pp_dict.get('vlan_bindings'): vlan_binding[ n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier vlan_binding['port_uuid'] = pp_dict.get('uuid') v_binding = db.get_vlan_binding(context, vlan_binding) if not v_binding: db.add_vlan_binding(context, vlan_binding) def _process_new_physical_locators(self, context, new_physical_locators): for physical_locator in new_physical_locators: pl_dict = physical_locator pl_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier p_locator = db.get_physical_locator(context, pl_dict) if not p_locator: db.add_physical_locator(context, pl_dict) def _process_new_local_macs(self, context, new_local_macs): for local_mac in new_local_macs: lm_dict = local_mac lm_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier lm_dict['logical_switch_uuid'] = local_mac.get('logical_switch_id') l_mac = db.get_ucast_mac_local(context, lm_dict) if not l_mac: db.add_ucast_mac_local(context, lm_dict) def _process_new_remote_macs(self, context, new_remote_macs): for remote_mac in new_remote_macs: rm_dict = remote_mac rm_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier r_mac = db.get_ucast_mac_remote(context, rm_dict) if not r_mac: db.add_ucast_mac_remote(context, rm_dict) def _process_modified_remote_macs(self, context, modified_remote_macs): for remote_mac in modified_remote_macs: remote_mac[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier db.update_ucast_mac_remote(context, remote_mac) def _get_physical_switch_ips(self, context, mac): physical_switch_ips = set() record_dict = {n_const.OVSDB_IDENTIFIER: self.ovsdb_identifier} vlan_bindings = db.get_all_vlan_bindings_by_logical_switch( context, mac) for vlan_binding in vlan_bindings: record_dict['uuid'] = vlan_binding.get('port_uuid') physical_port = db.get_physical_port(context, record_dict) record_dict['uuid'] = physical_port.get('physical_switch_id') physical_switch = db.get_physical_switch(context, record_dict) physical_switch_ips.add(physical_switch.get('tunnel_ip')) return list(physical_switch_ips) def _get_agent_by_mac(self, context, mac): host = None mac_addr = mac.get('mac') port = self._get_port_by_mac(context, mac_addr) for port_dict in port: host = port_dict[portbindings.HOST_ID] agent_l2_pop_enabled = self._get_agent_details_by_host(context, host) return agent_l2_pop_enabled def _get_port_by_mac(self, context, mac_addr): port = self.core_plugin.get_ports( context, filters={'mac_address': [mac_addr]}) return port def _get_agent_details_by_host(self, context, host): l2_agent = None agent_l2_pop_enabled = None agents = self.core_plugin.get_agents( context, filters={'host': [host]}) for agent in agents: agent_tunnel_type = agent['configurations'].get('tunnel_types', []) agent_l2_pop_enabled = agent['configurations'].get('l2_population', None) if n_const.VXLAN in agent_tunnel_type: l2_agent = agent break if not l2_agent: raise l2gw_exc.L2AgentNotFoundByHost( host=host) return agent_l2_pop_enabled def _handle_l2pop(self, context, new_remote_macs): """handle vxlan tunnel creation based on l2pop is enabled or not. if l2pop is enabled in L2 agent on a host to which port belongs, then call add_fdb_entries. otherwise, call tunnel_sync. """ for mac in new_remote_macs: try: agent_l2_pop_enabled = self._get_agent_by_mac(context, mac) except l2gw_exc.L2AgentNotFoundByHost as e: LOG.debug(e.message) continue physical_switches = self._get_physical_switch_ips(context, mac) for physical_switch in physical_switches: other_fdb_entries = self._get_fdb_entries( context, physical_switch, mac.get('logical_switch_id')) if agent_l2_pop_enabled: self.tunnel_call.trigger_l2pop_sync(context, other_fdb_entries) else: self.tunnel_call.trigger_tunnel_sync(context, physical_switch) def _process_modified_physical_ports(self, context, modified_physical_ports): for physical_port in modified_physical_ports: pp_dict = physical_port pp_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier modified_port = db.get_physical_port(context, pp_dict) if modified_port: db.update_physical_ports_status(context, pp_dict) port_vlan_bindings = physical_port.get('vlan_bindings') vlan_bindings = db.get_all_vlan_bindings_by_physical_port( context, pp_dict) for vlan_binding in vlan_bindings: db.delete_vlan_binding(context, vlan_binding) for port_vlan_binding in port_vlan_bindings: port_vlan_binding['port_uuid'] = pp_dict['uuid'] port_vlan_binding[ n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier db.add_vlan_binding(context, port_vlan_binding) else: db.add_physical_port(context, pp_dict) def _process_modified_physical_switches(self, context, modified_physical_switches): for physical_switch in modified_physical_switches: db.update_physical_switch_status(context, physical_switch) def _process_deleted_logical_switches(self, context, deleted_logical_switches): for logical_switch in deleted_logical_switches: ls_dict = logical_switch ls_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier db.delete_logical_switch(context, ls_dict) def _process_deleted_physical_switches(self, context, deleted_physical_switches): for physical_switch in deleted_physical_switches: ps_dict = physical_switch ps_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier db.delete_physical_switch(context, ps_dict) physical_switches = db.get_all_physical_switches_by_ovsdb_id( context, self.ovsdb_identifier) if not physical_switches: logical_switches = db.get_all_logical_switches_by_ovsdb_id( context, self.ovsdb_identifier) if logical_switches: for logical_switch in logical_switches: self.agent_rpc.delete_network( context, self.ovsdb_identifier, logical_switch.get('uuid')) def _process_deleted_physical_ports(self, context, deleted_physical_ports): for physical_port in deleted_physical_ports: pp_dict = physical_port pp_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier port_name = pp_dict['name'] p_port = db.get_physical_port(context, pp_dict) if not p_port: raise l2gw_exc.L2GatewayInterfaceNotFound( interface_id=port_name) p_switch_id = p_port.get('physical_switch_id') switch_dict = {} switch_dict['uuid'] = p_switch_id switch_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier switch_db = db.get_physical_switch(context, switch_dict) if not switch_db: raise l2gw_exc.L2GatewayDeviceNotFound( device_id=p_switch_id) switch_name = switch_db.get('name') l2gw_id_list = self.l2gw_mixin._get_l2gw_ids_by_interface_switch( context, port_name, switch_name) if l2gw_id_list: for l2gw_id in l2gw_id_list: self.l2gw_mixin._delete_connection_by_l2gw_id(context, l2gw_id) vlan_bindings = db.get_all_vlan_bindings_by_physical_port( context, pp_dict) ls_set = set() for vlan_binding in vlan_bindings: vlan_binding['logical_switch_id'] = vlan_binding.get( 'logical_switch_uuid') if vlan_binding.get('logical_switch_uuid') in ls_set: db.delete_vlan_binding(context, vlan_binding) continue bindings = db.get_all_vlan_bindings_by_logical_switch( context, vlan_binding) if bindings and len(bindings) == 1: self._delete_macs_from_ovsdb( context, vlan_binding.get('logical_switch_uuid'), self.ovsdb_identifier) elif bindings and len(bindings) > 1: flag = True for binding in bindings: if binding[ 'ovsdb_identifier'] == self.ovsdb_identifier: flag = False break if flag: self._delete_macs_from_ovsdb( context, vlan_binding.get('logical_switch_uuid'), self.ovsdb_identifier) ls_set.add(vlan_binding.get('logical_switch_uuid')) db.delete_vlan_binding(context, vlan_binding) db.delete_physical_port(context, pp_dict) def _delete_macs_from_ovsdb(self, context, logical_switch_id, ovsdb_identifier): mac_list = [] ls_dict = {'logical_switch_id': logical_switch_id, 'ovsdb_identifier': ovsdb_identifier} macs = db.get_all_ucast_mac_remote_by_ls(context, ls_dict) for mac in macs: mac_list.append(mac.get('mac')) self.agent_rpc.delete_vif_from_gateway( context, ovsdb_identifier, logical_switch_id, mac_list) def _process_deleted_physical_locators(self, context, deleted_physical_locators): physical_switch_ips = [] logical_switch_ids = self._get_logical_switch_ids(context) physical_switches = db.get_all_physical_switches_by_ovsdb_id( context, self.ovsdb_identifier) for physical_switch in physical_switches: physical_switch_ips.append( physical_switch.get('tunnel_ip')) tunneling_ip_dict = self._get_agent_ips(context) for physical_locator in deleted_physical_locators: pl_dict = physical_locator pl_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier agent_ip = physical_locator.get('dst_ip') if agent_ip in tunneling_ip_dict.keys(): for logical_switch_id in logical_switch_ids: for physical_switch_ip in physical_switch_ips: other_fdb_entries = self._get_fdb_entries( context, physical_switch_ip, logical_switch_id) agent_host = tunneling_ip_dict.get(agent_ip) self.tunnel_call.trigger_l2pop_delete( context, other_fdb_entries, agent_host) else: for logical_switch_id in logical_switch_ids: other_fdb_entries = self._get_fdb_entries( context, agent_ip, logical_switch_id) self.tunnel_call.trigger_l2pop_delete( context, other_fdb_entries) db.delete_physical_locator(context, pl_dict) def _process_deleted_local_macs(self, context, deleted_local_macs): for local_mac in deleted_local_macs: lm_dict = local_mac lm_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier db.delete_ucast_mac_local(context, lm_dict) def _process_deleted_remote_macs(self, context, deleted_remote_macs): for remote_mac in deleted_remote_macs: rm_dict = remote_mac rm_dict[n_const.OVSDB_IDENTIFIER] = self.ovsdb_identifier db.delete_ucast_mac_remote(context, rm_dict) def _get_logical_switch_ids(self, context): logical_switch_ids = set() logical_switches = db.get_all_logical_switches_by_ovsdb_id( context, self.ovsdb_identifier) for logical_switch in logical_switches: logical_switch_ids.add(logical_switch.get('uuid')) return list(logical_switch_ids) def _get_agent_ips(self, context): agent_ip_dict = {} agents = self.core_plugin.get_agents( context) for agent in agents: conf_dict_tunnel_type = agent['configurations'].get( "tunnel_types", []) if n_const.VXLAN in conf_dict_tunnel_type: tunnel_ip = agent['configurations'].get('tunneling_ip') agent_ip_dict[tunnel_ip] = agent.get('host') return agent_ip_dict def _get_fdb_entries(self, context, agent_ip, logical_switch_uuid): ls_dict = {'uuid': logical_switch_uuid, n_const.OVSDB_IDENTIFIER: self.ovsdb_identifier} logical_switch = db.get_logical_switch(context, ls_dict) network_id = logical_switch.get('name') segment_id = logical_switch.get('key') port_fdb_entries = constants.FLOODING_ENTRY other_fdb_entries = {network_id: {'segment_id': segment_id, 'network_type': 'vxlan', 'ports': {agent_ip: [port_fdb_entries] }}} return other_fdb_entries ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/plugin.py0000664000175000017500000002163414572557755026760 0ustar00jamespagejamespage# Copyright (c) 2015 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.plugins import directory from oslo_utils import excutils from neutron.db import servicetype_db as st_db from neutron.services import provider_configuration as pconf from neutron.services import service_base from networking_l2gw.db.l2gateway import l2gateway_db from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import constants from networking_l2gw.services.l2gateway import exceptions as exc from neutron_lib.db import api as db_api from neutron_lib import exceptions as n_exc from oslo_log import log as logging LOG = logging.getLogger(__name__) class L2GatewayPlugin(l2gateway_db.L2GatewayMixin): """Implementation of the Neutron l2 gateway Service Plugin. This class manages the workflow of L2 gateway request/response. """ supported_extension_aliases = ["l2-gateway", "l2-gateway-connection"] def __init__(self): """Do the initialization for the l2 gateway service plugin here.""" config.register_l2gw_opts_helper() self.service_type_manager = st_db.ServiceTypeManager.get_instance() self.service_type_manager.add_provider_configuration( constants.L2GW, pconf.ProviderConfiguration('networking_l2gw')) self._load_drivers() LOG.info("L2Gateway Service Plugin using Service Driver: %s", self.default_provider) self.driver = self.drivers[self.default_provider] if len(self.drivers) > 1: LOG.warning("Multiple drivers configured for L2Gateway, " "although running multiple drivers in parallel" " is not yet supported") super(L2GatewayPlugin, self).__init__() l2gateway_db.subscribe() def _load_drivers(self): """Loads plugin-drivers specified in configuration.""" self.drivers, self.default_provider = service_base.load_drivers( 'L2GW', self) def _get_driver_for_provider(self, provider): if provider in self.drivers: return self.drivers[provider] # raise if not associated (should never be reached) raise n_exc.Invalid("Error retrieving driver for provider %s" % provider) @property def _core_plugin(self): return directory.get_plugin() def get_plugin_type(self): """Get type of the plugin.""" return constants.L2GW def get_plugin_description(self): """Get description of the plugin.""" return constants.L2_GATEWAY_SERVICE_PLUGIN def add_port_mac(self, context, port_dict): """Process a created Neutron port.""" self.driver.add_port_mac(context, port_dict) def delete_port_mac(self, context, port): """Process a deleted Neutron port.""" self.driver.delete_port_mac(context, port) def create_l2_gateway(self, context, l2_gateway): """Create the L2Gateway.""" self.validate_l2_gateway_for_create(context, l2_gateway) self.driver.create_l2_gateway(context, l2_gateway) with db_api.CONTEXT_WRITER.using(context): l2_gateway_instance = super(L2GatewayPlugin, self).create_l2_gateway(context, l2_gateway) self.driver.create_l2_gateway_precommit(context, l2_gateway_instance) try: self.driver.create_l2_gateway_postcommit( context, l2_gateway_instance) except exc.L2GatewayServiceDriverError: with excutils.save_and_reraise_exception(): LOG.error("L2GatewayPlugin.create_l2_gateway_postcommit " "failed, deleting l2gateway '%s'", l2_gateway_instance['id']) self.delete_l2_gateway(context, l2_gateway_instance['id']) return l2_gateway_instance def update_l2_gateway(self, context, l2_gateway_id, l2_gateway): """Update the L2Gateway.""" self.validate_l2_gateway_for_update(context, l2_gateway_id, l2_gateway) self.driver.update_l2_gateway(context, l2_gateway_id, l2_gateway) with db_api.CONTEXT_WRITER.using(context): l2_gateway_instance = super(L2GatewayPlugin, self).update_l2_gateway(context, l2_gateway_id, l2_gateway) self.driver.update_l2_gateway_precommit(context, l2_gateway_instance) try: self.driver.update_l2_gateway_postcommit( context, l2_gateway_instance) except exc.L2GatewayServiceDriverError: with excutils.save_and_reraise_exception(): LOG.error("L2GatewayPlugin.update_l2_gateway_postcommit" " failed for l2gateway %s", l2_gateway_id) return l2_gateway_instance def delete_l2_gateway(self, context, l2_gateway_id): """Delete the L2Gateway.""" self.validate_l2_gateway_for_delete(context, l2_gateway_id) self.driver.delete_l2_gateway(context, l2_gateway_id) with db_api.CONTEXT_WRITER.using(context): super(L2GatewayPlugin, self).delete_l2_gateway(context, l2_gateway_id) self.driver.delete_l2_gateway_precommit(context, l2_gateway_id) try: self.driver.delete_l2_gateway_postcommit(context, l2_gateway_id) except exc.L2GatewayServiceDriverError: with excutils.save_and_reraise_exception(): LOG.error("L2GatewayPlugin.delete_l2_gateway_postcommit" " failed for l2gateway %s", l2_gateway_id) def create_l2_gateway_connection(self, context, l2_gateway_connection): """Create an L2Gateway Connection Bind a Neutron VXLAN Network to Physical Network Segment. """ self.validate_l2_gateway_connection_for_create( context, l2_gateway_connection) self.driver.create_l2_gateway_connection(context, l2_gateway_connection) with db_api.CONTEXT_WRITER.using(context): l2_gateway_conn_instance = super( L2GatewayPlugin, self).create_l2_gateway_connection( context, l2_gateway_connection) self.driver.create_l2_gateway_connection_precommit( context, l2_gateway_conn_instance) try: self.driver.create_l2_gateway_connection_postcommit( context, l2_gateway_conn_instance) except exc.L2GatewayServiceDriverError: with excutils.save_and_reraise_exception(): LOG.error("L2GatewayPlugin." "create_l2_gateway_connection_postcommit " "failed, deleting connection '%s'", l2_gateway_conn_instance['id']) self.delete_l2_gateway_connection( context, l2_gateway_conn_instance['id']) return l2_gateway_conn_instance def delete_l2_gateway_connection(self, context, l2_gateway_connection_id): """Delete an L2Gateway Connection Unbind a Neutron VXLAN Network from Physical Network Segment. """ self.validate_l2_gateway_connection_for_delete( context, l2_gateway_connection_id) self.driver.delete_l2_gateway_connection( context, l2_gateway_connection_id) with db_api.CONTEXT_WRITER.using(context): super(L2GatewayPlugin, self).delete_l2_gateway_connection( context, l2_gateway_connection_id) self.driver.delete_l2_gateway_connection_precommit( context, l2_gateway_connection_id) try: self.driver.delete_l2_gateway_connection_postcommit( context, l2_gateway_connection_id) except exc.L2GatewayServiceDriverError: with excutils.save_and_reraise_exception(): LOG.error( "L2GatewayPlugin.delete_l2_gateway_connection_postcommit" " failed for connection %s", l2_gateway_connection_id) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/service_drivers/0000775000175000017500000000000014572557757030302 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/service_drivers/__init__.py0000664000175000017500000001116614572557755032416 0ustar00jamespagejamespage# Copyright (c) 2015 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 class L2gwDriverBase(object, metaclass=abc.ABCMeta): def __init__(self, service_plugin, validator=None): self.service_plugin = service_plugin @property def service_type(self): pass @abc.abstractmethod def add_port_mac(self, context, port_dict): pass @abc.abstractmethod def delete_port_mac(self, context, port): pass @abc.abstractmethod def create_l2_gateway(self, context, l2_gateway): pass @abc.abstractmethod def update_l2_gateway(self, context, l2_gateway_id, l2_gateway): pass @abc.abstractmethod def delete_l2_gateway(self, context, l2_gateway_id): pass @abc.abstractmethod def create_l2_gateway_connection(self, context, l2_gateway_connection): pass @abc.abstractmethod def delete_l2_gateway_connection(self, context, l2_gateway_connection_id): pass @abc.abstractmethod def create_l2_gateway_precommit(self, context, l2_gateway): pass @abc.abstractmethod def update_l2_gateway_precommit(self, context, l2_gateway): pass @abc.abstractmethod def delete_l2_gateway_precommit(self, context, l2_gateway_id): pass @abc.abstractmethod def create_l2_gateway_connection_precommit(self, context, l2_gateway_connection): pass @abc.abstractmethod def delete_l2_gateway_connection_precommit(self, context, l2_gateway_connection_id): pass @abc.abstractmethod def create_l2_gateway_postcommit(self, context, l2_gateway): pass @abc.abstractmethod def delete_l2_gateway_postcommit(self, context, l2_gateway_id): pass @abc.abstractmethod def update_l2_gateway_postcommit(self, context, l2_gateway): pass @abc.abstractmethod def create_l2_gateway_connection_postcommit(self, context, l2_gateway_connection): pass @abc.abstractmethod def delete_l2_gateway_connection_postcommit(self, context, l2_gateway_connection_id): pass class L2gwDriver(L2gwDriverBase, metaclass=abc.ABCMeta): def __init__(self, service_plugin, validator=None): super(L2gwDriver, self).__init__(service_plugin) @property def service_type(self): pass def add_port_mac(self, context, port_dict): pass def delete_port_mac(self, context, port): pass def create_l2_gateway(self, context, l2_gateway): pass def update_l2_gateway(self, context, l2_gateway_id, l2_gateway): pass def delete_l2_gateway(self, context, l2_gateway_id): pass def create_l2_gateway_connection(self, context, l2_gateway_connection): pass def delete_l2_gateway_connection(self, context, l2_gateway_connection_id): pass def create_l2_gateway_precommit(self, context, l2_gateway): pass def update_l2_gateway_precommit(self, context, l2_gateway): pass def delete_l2_gateway_precommit(self, context, l2_gateway_id): pass def create_l2_gateway_connection_precommit(self, context, l2_gateway_connection): pass def delete_l2_gateway_connection_precommit(self, context, l2_gateway_connection_id): pass def create_l2_gateway_postcommit(self, context, l2_gateway): pass def delete_l2_gateway_postcommit(self, context, l2_gateway_id): pass def update_l2_gateway_postcommit(self, context, l2_gateway): pass def create_l2_gateway_connection_postcommit(self, context, l2_gateway_connection): pass def delete_l2_gateway_connection_postcommit(self, context, l2_gateway_connection_id): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/service_drivers/agent_api.py0000664000175000017500000001142514572557755032604 0ustar00jamespagejamespage# Copyright (c) 2015 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 import rpc as n_rpc from networking_l2gw._i18n import _ from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway import exceptions as l2gw_exc import oslo_messaging as messaging class L2gatewayAgentApi(object): """L2gateway plugin to agent RPC API.""" API_VERSION = '1.0' def __init__(self, topic, host): """Initialize L2gateway plugin.""" self.host = host target = messaging.Target(topic=topic, version=self.API_VERSION) self.client = n_rpc.get_client(target) def set_monitor_agent(self, context, hostname): """RPC to select Monitor/Transact agent.""" cctxt = self.client.prepare(fanout=True) return cctxt.cast(context, 'set_monitor_agent', hostname=hostname) def add_vif_to_gateway(self, context, ovsdb_identifier, logical_switch, physical_locator, mac_remote): """RPC to enter the VM MAC details to gateway.""" cctxt = self.client.prepare() return cctxt.call(context, 'add_vif_to_gateway', ovsdb_identifier=ovsdb_identifier, logical_switch_dict=logical_switch, locator_dict=physical_locator, mac_dict=mac_remote) def update_vif_to_gateway(self, context, ovsdb_identifier, physical_locator, mac_remote): """RPC to update the VM MAC details to gateway.""" cctxt = self.client.prepare() return cctxt.call(context, 'update_vif_to_gateway', ovsdb_identifier=ovsdb_identifier, locator_dict=physical_locator, mac_dict=mac_remote) def delete_vif_from_gateway(self, context, ovsdb_identifier, logical_switch_uuid, macs): """RPC to delete the VM MAC details from gateway.""" cctxt = self.client.prepare() return cctxt.call(context, 'delete_vif_from_gateway', ovsdb_identifier=ovsdb_identifier, logical_switch_uuid=logical_switch_uuid, mac=macs) def delete_network(self, context, ovsdb_identifier, logical_switch_uuid): """RPC to delete the Network from gateway.""" cctxt = self.client.prepare() return cctxt.cast(context, 'delete_network', ovsdb_identifier=ovsdb_identifier, logical_switch_uuid=logical_switch_uuid) def _validate_request_op_method(self, context, op_method): """validate the method in the request.""" method_list = [n_const.CREATE, n_const.DELETE] if op_method not in method_list: raise l2gw_exc.InvalidMethod(op_method=op_method) def update_connection_to_gateway(self, context, ovsdb_identifier, ls_dict, locator_list, mac_dict, port_dict, op_method): """RPC to update the connection to gateway.""" self._validate_request_op_method(context, op_method) cctxt = self.client.prepare() try: return cctxt.call(context, 'update_connection_to_gateway', ovsdb_identifier=ovsdb_identifier, logical_switch_dict=ls_dict, locator_dicts=locator_list, mac_dicts=mac_dict, port_dicts=port_dict, op_method=op_method) except messaging.MessagingTimeout: message = _("Communication error with the L2 gateway agent") raise l2gw_exc.OVSDBError(message=message) except Exception as ex: message = str(ex) msg_splits = message.split('\n') raise l2gw_exc.OVSDBError(message="Error on the OVSDB " "server: " + msg_splits[0]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/services/l2gateway/service_drivers/rpc_l2gw.py0000664000175000017500000012607214572557755032401 0ustar00jamespagejamespage# Copyright (c) 2015 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.db import agents_db from neutron_lib import rpc as n_rpc from networking_l2gw._i18n import _ from networking_l2gw.db.l2gateway import l2gateway_db as l2_gw_db from networking_l2gw.db.l2gateway.ovsdb import lib as db from networking_l2gw.services.l2gateway import agent_scheduler from networking_l2gw.services.l2gateway.common import constants from networking_l2gw.services.l2gateway.common import l2gw_validators from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway.common import topics from networking_l2gw.services.l2gateway import exceptions as l2gw_exc from networking_l2gw.services.l2gateway import service_drivers from networking_l2gw.services.l2gateway.service_drivers import agent_api from neutron.plugins.ml2 import managers from neutron_lib.api.definitions import portbindings from neutron_lib import constants as n_const from neutron_lib.db import api as db_api from neutron_lib import exceptions from oslo_config import cfg from oslo_log import log as logging import oslo_messaging as messaging from oslo_utils import importutils LOG = logging.getLogger(__name__) L2GW = 'l2gw' L2GW_CALLBACK = ("networking_l2gw.services.l2gateway.ovsdb." "data.L2GatewayOVSDBCallbacks") class L2gwRpcDriver(service_drivers.L2gwDriver, metaclass=abc.ABCMeta): """L2gw RPC Service Driver class.""" def __init__(self, service_plugin, validator=None): super(L2gwRpcDriver, self).__init__(service_plugin, validator) self.ovsdb_callback = importutils.import_object( L2GW_CALLBACK, self) self.endpoints = ( [self.ovsdb_callback, agents_db.AgentExtRpcCallback()]) self.conn = n_rpc.Connection() self.conn.create_consumer(topics.L2GATEWAY_PLUGIN, self.endpoints, fanout=False) self.conn.consume_in_threads() self.create_rpc_conn() LOG.debug("starting l2gateway agent scheduler") self.start_l2gateway_agent_scheduler() self.gateway_resource = constants.GATEWAY_RESOURCE_NAME self.l2gateway_db = l2_gw_db.L2GatewayMixin() self.type_manager = managers.TypeManager() self.port_dict_before_update = [] @property def service_type(self): return L2GW def create_rpc_conn(self): self.agent_rpc = agent_api.L2gatewayAgentApi(topics.L2GATEWAY_AGENT, cfg.CONF.host) def start_l2gateway_agent_scheduler(self): """Start l2gateway agent scheduler thread.""" self.agentscheduler = agent_scheduler.L2GatewayAgentScheduler( self.agent_rpc) self.agentscheduler.initialize_thread() def _get_dict(self, resource): return resource.__dict__ def add_port_mac(self, context, port_dict): """Process the created port and trigger the RPC to add to the gateway. """ port_id = port_dict.get("id") port = self.service_plugin._core_plugin.get_port(context, port_id) if port['device_owner']: network_id = port.get("network_id") dst_ip, ip_address = self._get_ip_details(context, port) network = self._get_network_details(context, network_id) l2gateway_connections = ( self.service_plugin.get_l2_gateway_connections( context, filters={'network_id': [network_id]})) if not l2gateway_connections: return logical_switches = db.get_all_logical_switches_by_name(context, network_id) if not logical_switches: return for logical_switch in logical_switches: logical_switch['description'] = network.get('name') ovsdb_identifier = logical_switch.get('ovsdb_identifier') locator_dict = {'dst_ip': dst_ip, 'ovsdb_identifier': ovsdb_identifier} physical_locator = self._form_physical_locator_schema( context, locator_dict) locator_uuid = physical_locator.get('uuid') logical_switch_uuid = logical_switch.get('uuid') mac_remote = self._get_dict(ovsdb_schema.UcastMacsRemote( uuid=None, mac=port['mac_address'], logical_switch_id=logical_switch_uuid, physical_locator_id=locator_uuid, ip_address=ip_address)) mac_dict = mac_remote mac_dict['ovsdb_identifier'] = ovsdb_identifier mac_dict['logical_switch_uuid'] = logical_switch_uuid ucast_mac_remote = db.get_ucast_mac_remote_by_mac_and_ls( context, mac_dict) if ucast_mac_remote: # check whether locator got changed in vm migration if ucast_mac_remote['physical_locator_id' ] != physical_locator['uuid']: mac_remote['uuid'] = ucast_mac_remote['uuid'] try: self.agent_rpc.update_vif_to_gateway( context, ovsdb_identifier, physical_locator, mac_remote) LOG.debug( "VM migrated from %s to %s. Update" "locator in Ucast_Macs_Remote", ucast_mac_remote['physical_locator_id'], physical_locator['uuid']) except messaging.MessagingTimeout: # If RPC is timed out, then the RabbitMQ # will retry the operation. LOG.exception("Communication error with " "the L2 gateway agent") except Exception: # The remote OVSDB server may be down. # We need to retry this operation later. db.add_pending_ucast_mac_remote( context, 'update', ovsdb_identifier, logical_switch_uuid, physical_locator, [mac_remote]) else: LOG.debug("add_port_mac: MAC %s exists " "in Gateway", mac_dict['mac']) ovsdb_data_handler = ( self.ovsdb_callback.get_ovsdbdata_object( ovsdb_identifier)) ovsdb_data_handler._handle_l2pop( context, [ucast_mac_remote]) continue # else it is a new port created try: self.agent_rpc.add_vif_to_gateway( context, ovsdb_identifier, logical_switch, physical_locator, mac_remote) except messaging.MessagingTimeout: # If RPC is timed out, then the RabbitMQ # will retry the operation. LOG.exception("Communication error with " "the L2 gateway agent") except Exception: # The remote OVSDB server may be down. # We need to retry this operation later. LOG.debug("The remote OVSDB server may be down") db.add_pending_ucast_mac_remote( context, 'insert', ovsdb_identifier, logical_switch_uuid, physical_locator, [mac_remote]) def _form_physical_locator_schema(self, context, pl_dict): locator_uuid = None locator = db.get_physical_locator_by_dst_ip( context, pl_dict) if locator: locator_uuid = locator.get('uuid') physical_locator = self._get_dict( ovsdb_schema.PhysicalLocator(uuid=locator_uuid, dst_ip=pl_dict.get('dst_ip'))) return physical_locator def delete_port_mac(self, context, port): """Process the deleted port and trigger the RPC to delete from the gateway. When the ML2 plugin invokes this call, the argument port is a single port dict, whereas the L2gateway service plugin sends it as a list of port dicts. """ ls_dict = {} mac_list = [] logical_switches = [] ovsdb_identifier = None if isinstance(port, list): from_l2gw_plugin = True network_id = port[0].get('network_id') ovsdb_identifier = port[0].get('ovsdb_identifier') lg_dict = {'logical_switch_name': network_id, 'ovsdb_identifier': ovsdb_identifier} logical_switch = db.get_logical_switch_by_name( context, lg_dict) logical_switches.append(logical_switch) port_list = port else: from_l2gw_plugin = False network_id = port.get('network_id') logical_switches = ( db.get_all_logical_switches_by_name( context, network_id)) l2gateway_connections = ( self.service_plugin.get_l2_gateway_connections( context, filters={'network_id': [network_id]})) if not l2gateway_connections: return port_list = [port] for port_dict in port_list: if port_dict['device_owner']: if logical_switches: for logical_switch in logical_switches: logical_switch_uuid = logical_switch.get('uuid') macs = [] if isinstance(port_dict.get("allowed_address_pairs"), list): for address_pair in port_dict[ 'allowed_address_pairs']: mac = address_pair['mac_address'] macs.append(mac) macs.append(port_dict.get("mac_address")) if port_dict.get('ovsdb_identifier', None): ovsdb_identifier = port_dict.get( 'ovsdb_identifier') else: ovsdb_identifier = logical_switch.get( 'ovsdb_identifier') if from_l2gw_plugin: ls = logical_switch.get('name') l2gateway_connections = ( self.service_plugin. get_l2_gateway_connections( context, filters={'network_id': [ls]})) if len(l2gateway_connections) > 1: continue for mac in macs: record_dict = {'mac': mac, 'logical_switch_uuid': logical_switch_uuid, 'ovsdb_identifier': ovsdb_identifier} ucast_mac_remote = ( db.get_ucast_mac_remote_by_mac_and_ls( context, record_dict)) del_count = 0 if not ucast_mac_remote: LOG.debug("delete_port_mac: MAC %s does" " not exist", mac) # It is possible that this MAC is present # in the pending_ucast_mac_remote table. # Delete this MAC as it was not inserted # into the OVSDB server earlier. del_count = db.delete_pending_ucast_mac_remote( context, 'insert', ovsdb_identifier, logical_switch_uuid, mac) if not del_count: mac_list = ls_dict.get(logical_switch_uuid, []) mac_list.append(mac) ls_dict[logical_switch_uuid] = mac_list else: LOG.debug("delete_port_mac:Logical Switch %s " "does not exist ", port_dict.get('network_id')) return for logical_switch_uuid, mac_list in ls_dict.items(): try: if mac_list: self.agent_rpc.delete_vif_from_gateway(context, ovsdb_identifier, logical_switch_uuid, mac_list) except messaging.MessagingTimeout: # If RPC is timed out, then the RabbitMQ # will retry the operation. LOG.exception("Communication error with " "the L2 gateway agent") except Exception as ex: # The remote OVSDB server may be down. # We need to retry this operation later. LOG.debug("Exception occurred %s", str(ex)) db.add_pending_ucast_mac_remote( context, 'delete', ovsdb_identifier, logical_switch_uuid, None, mac_list) def _check_port_fault_status_and_switch_fault_status(self, context, l2_gateway_id): l2gw = self.service_plugin.get_l2_gateway(context, l2_gateway_id) if not l2gw: raise l2gw_exc.L2GatewayNotFound(gateway_id=l2_gateway_id) devices = l2gw['devices'] rec_dict = {} for device in devices: device_name = device['device_name'] dev_db = db.get_physical_switch_by_name(context, device_name) if not dev_db: raise l2gw_exc.L2GatewayDeviceNotFound(device_id=device_name) rec_dict['physical_switch_id'] = dev_db['uuid'] rec_dict['ovsdb_identifier'] = dev_db['ovsdb_identifier'] status = dev_db.get('switch_fault_status') if status and status != constants.SWITCH_FAULT_STATUS_UP: raise l2gw_exc.L2GatewayPhysicalSwitchFaultStatus( device_name=device_name, fault_status=status) for interface_list in device['interfaces']: int_name = interface_list.get('name') rec_dict['interface_name'] = int_name port_db = db.get_physical_port_by_name_and_ps(context, rec_dict) if not port_db: raise l2gw_exc.L2GatewayInterfaceNotFound( interface_id=int_name) port_status = port_db['port_fault_status'] if (port_status and port_status != constants.PORT_FAULT_STATUS_UP): raise l2gw_exc.L2GatewayPhysicalPortFaultStatus( int_name=int_name, device_name=device_name, fault_status=port_status) def _validate_gateway_for_update(self, context, gw): LOG.debug("L2gwRpcDriver._validate_gateway_for_update gw=%s", gw) # Note(lajoskatona): If the update is updating other keys than # devices, just return, as the tricky part is updating devices. devices = gw['l2_gateway'].get('devices', []) for device in devices: interfaces = device.get('interfaces') device_name = device.get("device_name") for interface in interfaces: interface_name = interface.get('name') physical_switch = db.get_physical_switch_by_name( context, device.get('device_name')) if not physical_switch: raise l2gw_exc.L2GatewayDeviceNotFound( device_id=device_name) ovsdb_identifier = physical_switch.get('ovsdb_identifier') pp_dict = {'interface_name': interface_name, 'ovsdb_identifier': ovsdb_identifier, 'physical_switch_id': physical_switch.get('uuid')} ps_port = db.get_physical_port_by_name_and_ps(context, pp_dict) if not ps_port: raise l2gw_exc.L2GatewayPhysicalPortNotFound( int_name=interface_name, device_name=device_name) def _validate_connection(self, context, gw_connection): seg_id = gw_connection.get('segmentation_id', None) l2_gw_id = gw_connection.get('l2_gateway_id') self._check_port_fault_status_and_switch_fault_status(context, l2_gw_id) check_vlan = ( self.service_plugin._is_vlan_configured_on_any_interface_for_l2gw( context, l2_gw_id)) nw_map = {} network_id = gw_connection.get(constants.NETWORK_ID) nw_map[constants.NETWORK_ID] = network_id nw_map['l2_gateway_id'] = l2_gw_id if seg_id: nw_map[constants.SEG_ID] = gw_connection.get(constants.SEG_ID) if not self.service_plugin._get_network(context, network_id): raise exceptions.NetworkNotFound(net_id=network_id) if self.service_plugin._retrieve_gateway_connections(context, l2_gw_id, nw_map): raise l2gw_exc.L2GatewayConnectionExists(mapping=nw_map, gateway_id=l2_gw_id) l2gw_validators.validate_network_mapping_list(nw_map, check_vlan) gw_db = self.service_plugin._get_l2_gateway(context, l2_gw_id) tenant_id = self.service_plugin._get_tenant_id_for_create( context, gw_db) l2gw_connection = self.service_plugin.get_l2_gateway_connections( context, filters={'network_id': [network_id], 'tenant_id': [tenant_id], 'l2_gateway_id': [l2_gw_id]}) if l2gw_connection: raise l2gw_exc.L2GatewayConnectionExists(mapping=nw_map, gateway_id=l2_gw_id) def _process_port_list(self, context, device, gw_connection, method, gw_connection_ovsdb_set=None): port_dicts = [] port_dict = {} logical_switch_uuid = None seg_id = gw_connection.get('segmentation_id', None) interfaces = self.service_plugin.get_l2gateway_interfaces_by_device_id( context, device['id']) LOG.debug("L2gwRpcDriver._process_port_list: ints=%s", interfaces) for interface in interfaces: interface_name = interface.get('interface_name') LOG.debug("L2gwRpcDriver._process_port_list: int_name=%s", interface_name) physical_switch = db.get_physical_switch_by_name( context, device.get('device_name')) if not physical_switch: msg = _('The PHYSICAL SWITCH data not found in the server') raise Exception(msg) ovsdb_identifier = physical_switch.get('ovsdb_identifier') pp_dict = {'interface_name': interface_name, 'ovsdb_identifier': ovsdb_identifier, 'physical_switch_id': physical_switch.get('uuid'), 'logical_switch_name': gw_connection.get( 'network_id')} logical_switch = db.get_logical_switch_by_name( context, pp_dict) if method == "DELETE": if not logical_switch: msg = _('The LOGICAL SWITCH data not found in the server') raise Exception(msg) if not (ovsdb_identifier in list(gw_connection_ovsdb_set)): continue if logical_switch: logical_switch_uuid = logical_switch.get('uuid') ps_port = db.get_physical_port_by_name_and_ps(context, pp_dict) if not ps_port: msg = _('The PHYSICAL PORT data not found in the server') raise Exception(msg) pp_dict['uuid'] = ps_port.get('uuid') pp_dict['name'] = ps_port.get('name') LOG.debug("L2gwRpcDriver._process_port_list: pp_dict.name=%s", pp_dict['name']) port_dict = self._generate_port_list( context, method, seg_id, interface, pp_dict, logical_switch_uuid, gw_connection) port_dicts.append(port_dict) return ovsdb_identifier, logical_switch, port_dicts def _generate_port_list(self, context, method, seg_id, interface, pp_dict, logical_switch_uuid, gw_connection=None): port_list = [] vlan_bindings = db.get_all_vlan_bindings_by_physical_port( context, pp_dict) if method == "CREATE": if not seg_id: vlan_id = interface.get('segmentation_id') else: vlan_id = int(seg_id) vlan_dict = {'vlan': vlan_id, 'logical_switch_uuid': logical_switch_uuid} port_list.append(vlan_dict) for vlan_binding in vlan_bindings: if vlan_binding.get('vlan') == vlan_id: msg = _('Duplicate segmentation ID for the interface ' 'name=%(name)s uuid=%(uuid)s' ) % {'name': pp_dict['name'], 'uuid': pp_dict['uuid']} raise l2gw_exc.L2GatewayDuplicateSegmentationID(message=msg ) physical_port = self._get_dict( ovsdb_schema.PhysicalPort( uuid=pp_dict.get('uuid'), name=pp_dict.get('interface_name'), phys_switch_id=pp_dict.get('physical_switch_id'), vlan_binding_dicts=None, port_fault_status=None)) physical_port['vlan_bindings'] = port_list else: vlan_id = gw_connection.get('segmentation_id') if not vlan_id: vlan_id = interface.get('segmentation_id') vlan_dict = {'vlan': vlan_id, 'logical_switch_uuid': logical_switch_uuid} port_list.append(vlan_dict) physical_port = self._get_dict( ovsdb_schema.PhysicalPort( uuid=pp_dict.get('uuid'), name=pp_dict.get('interface_name'), phys_switch_id=pp_dict.get('physical_switch_id'), vlan_binding_dicts=None, port_fault_status=None)) physical_port['vlan_bindings'] = port_list return physical_port def _get_ip_details(self, context, port): host = port[portbindings.HOST_ID] if (not host and (port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE)): return self._get_l3_dvr_agent_details(context, port) agent = self._get_agent_details(context, host) conf_dict = agent.get("configurations") dst_ip = conf_dict.get("tunneling_ip") fixed_ip_list = port.get('fixed_ips') fixed_ip_list = fixed_ip_list[0] return dst_ip, fixed_ip_list.get('ip_address') def _get_network_details(self, context, network_id): network = self.service_plugin._core_plugin.get_network(context, network_id) return network def _get_port_details(self, context, network_id): ports = self.service_plugin._core_plugin.get_ports( context, filters={'network_id': [network_id]}) return ports def _get_agent_details(self, context, host): l2_agent = None agents = self.service_plugin._core_plugin.get_agents( context, filters={'host': [host]}) for agent in agents: agent_tunnel_type = agent['configurations'].get('tunnel_types', []) if constants.VXLAN in agent_tunnel_type: l2_agent = agent break if not l2_agent: raise l2gw_exc.L2AgentNotFoundByHost( host=host) return l2_agent def _get_l3_dvr_agent_details(self, context, port): """Getting host IP for router port in DVR mode in case of DVR, the router port will not have host information as there is router in each Compute Node and in the Network Node. Here we look for the L3 Agent in the Network Node, get its hostname and resolve its hostname to IP address to be used as destination IP. """ endpoints = self.type_manager.drivers.get('vxlan').obj.get_endpoints() agents = self.service_plugin._core_plugin.get_agents( context, filters={ 'agent_type': [n_const.AGENT_TYPE_L3], 'alive': ['true'] }) for agent in agents: conf_dict = agent.get("configurations") if conf_dict.get("agent_mode") == n_const.L3_AGENT_MODE_DVR_SNAT: hostname = agent.get('host') for endpoint in endpoints: if endpoint.get('host') == hostname: dst_ip = endpoint.get('ip_address') fixed_ip_list = port.get('fixed_ips') fixed_ip = fixed_ip_list[0].get('ip_address') LOG.debug( 'Adding DVR dst_ip: %s, fixed_ip: %s', dst_ip, fixed_ip ) return dst_ip, fixed_ip raise l2gw_exc.DvrAgentHostnameNotFound(host=hostname) raise l2gw_exc.L3DvrAgentNotFound() def _get_logical_switch_dict(self, context, logical_switch, gw_connection): if logical_switch: uuid = logical_switch.get('uuid') else: uuid = None network_id = gw_connection.get('network_id') ls_dict = {'uuid': uuid, 'name': network_id} network = self._get_network_details(context, network_id) ls_dict['description'] = network.get('name') logical_segments = network.get('segments') if logical_segments: vxlan_seg_id = None for seg in logical_segments: if seg.get('provider:network_type') == constants.VXLAN: if vxlan_seg_id: raise l2gw_exc.MultipleVxlanSegmentsFound( network_id=network_id) vxlan_seg_id = seg.get('provider:segmentation_id') ls_dict['key'] = vxlan_seg_id if not vxlan_seg_id: raise l2gw_exc.VxlanSegmentationIDNotFound( network_id=network_id) elif network.get('provider:network_type') == constants.VXLAN: ls_dict['key'] = network.get('provider:segmentation_id') else: raise l2gw_exc.VxlanSegmentationIDNotFound(network_id=network_id) return ls_dict def _get_physical_locator_dict(self, dst_ip, uuid=None, macs=None, ovsdb_identifier=None): pl_dict = {'uuid': uuid, 'dst_ip': dst_ip, 'ovsdb_identifier': ovsdb_identifier} if macs: pl_dict['macs'] = macs else: pl_dict['macs'] = [] return pl_dict def _get_locator_list(self, context, dst_ip, ovsdb_identifier, mac_list, locator_list): locator_uuid = None locator_dict = self._get_physical_locator_dict( dst_ip, None, None, ovsdb_identifier) locator = db.get_physical_locator_by_dst_ip( context, locator_dict) if locator: locator_uuid = locator.get('uuid') for locator in locator_list: if locator.get('dst_ip') == dst_ip: (locator.get('macs')).extend(mac_list) return locator_list pl_dict = self._get_physical_locator_dict( dst_ip, locator_uuid, mac_list) locator_list.append(pl_dict) return locator_list def create_l2_gateway(self, context, l2_gateway): pass def create_l2_gateway_postcommit(self, context, l2_gateway): pass def update_l2_gateway(self, context, l2_gateway_id, l2_gateway): """Check the port list for update""" self.service_plugin._admin_check(context, 'UPDATE') self._validate_gateway_for_update(context, l2_gateway) self.port_dict_before_update = [] # get l2 gateway devices l2gateway_devices = ( self.service_plugin.get_l2gateway_devices_by_gateway_id( context, l2_gateway_id)) # get l2 gateway connections gw_connections = self.service_plugin._get_l2_gateway_connections( context) for gw_connection in gw_connections: for device in l2gateway_devices: ovsdb_identifier, logical_switch, port_dict \ = (self._process_port_list(context, device, gw_connection, "UPDATE")) self.port_dict_before_update.extend(port_dict) def update_l2_gateway_postcommit(self, context, l2_gateway): """Process the call from the CLI and trigger the RPC, to update the connection of the gateway. """ self.service_plugin._admin_check(context, 'UPDATE') # get l2 gateway devices l2gateway_devices = ( self.service_plugin.get_l2gateway_devices_by_gateway_id( context, l2_gateway['id'])) # get l2 gateway connections gw_connections = self.service_plugin._get_l2_gateway_connections( context) for gw_connection in gw_connections: u_mac_dict = {} mac_dict = {} for device in l2gateway_devices: locator_list = [] ovsdb_identifier, logical_switch, port_dict = ( self._process_port_list(context, device, gw_connection, "UPDATE")) ls_dict = self._get_logical_switch_dict( context, logical_switch, gw_connection) port_dict_before_update_con = \ [port for port in self.port_dict_before_update if port['vlan_bindings'][0]['logical_switch_uuid'] == logical_switch['uuid']] port_dict_add = \ [port for port in port_dict if port not in port_dict_before_update_con] port_dict_delete = \ [port for port in port_dict_before_update_con if port not in port_dict] if port_dict_add: ports = self._get_port_details( context, gw_connection.get('network_id')) LOG.debug("L2gwRpcDriver.update_l2_gw: ports=%s", ports) for port in ports: mac_list = [] if port['device_owner']: dst_ip, ip_address = self._get_ip_details(context, port) mac_ip_pairs = [] if isinstance( port.get("allowed_address_pairs"), list): for address_pair in \ port['allowed_address_pairs']: mac = address_pair['mac_address'] mac_ip_pairs.append( (mac, address_pair['ip_address'])) mac_ip_pairs.append((port.get('mac_address'), ip_address)) for mac, ip in mac_ip_pairs: mac_remote = self._get_dict( ovsdb_schema.UcastMacsRemote( uuid=None, mac=mac, logical_switch_id=None, physical_locator_id=None, ip_address=ip)) if logical_switch: u_mac_dict['mac'] = mac u_mac_dict['ovsdb_identifier'] = \ ovsdb_identifier u_mac_dict['logical_switch_uuid'] = ( logical_switch.get('uuid')) ucast_mac_remote = ( db.get_ucast_mac_remote_by_mac_and_ls( context, u_mac_dict)) if not ucast_mac_remote: mac_list.append(mac_remote) else: mac_list.append(mac_remote) locator_list = self._get_locator_list( context, dst_ip, ovsdb_identifier, mac_list, locator_list) for locator in locator_list: mac_dict[locator.get('dst_ip')] = locator.pop('macs') locator.pop('ovsdb_identifier') self.agent_rpc.update_connection_to_gateway( context, ovsdb_identifier, ls_dict, locator_list, mac_dict, port_dict_add, 'CREATE') if port_dict_delete: self.agent_rpc.update_connection_to_gateway( context, ovsdb_identifier, ls_dict, locator_list, mac_dict, port_dict_delete, 'DELETE') def delete_l2_gateway(self, context, id): """delete the l2 gateway by id.""" self.l2gateway_db._admin_check(context, 'DELETE') with db_api.CONTEXT_WRITER.using(context): gw_db = self.l2gateway_db._get_l2_gateway(context, id) if gw_db is None: raise l2gw_exc.L2GatewayNotFound(gateway_id=id) if gw_db.network_connections: raise l2gw_exc.L2GatewayInUse(gateway_id=id) return gw_db def delete_l2_gateway_postcommit(self, context, l2_gateway_id): pass def create_l2_gateway_connection(self, context, l2_gateway_connection): """Process the call from the CLI and trigger the RPC, to update the connection to the gateway. """ u_mac_dict = {} ls_dict = {} mac_dict = {} self.service_plugin._admin_check(context, 'CREATE') gw_connection = l2_gateway_connection.get('l2_gateway_connection') # validate connection self._validate_connection(context, gw_connection) # get l2 gateway devices l2gateway_devices = ( self.service_plugin.get_l2gateway_devices_by_gateway_id( context, gw_connection.get('l2_gateway_id'))) for device in l2gateway_devices: locator_list = [] ovsdb_identifier, logical_switch, port_dict = ( self._process_port_list(context, device, gw_connection, "CREATE")) ls_dict = self._get_logical_switch_dict( context, logical_switch, gw_connection) ports = self._get_port_details(context, gw_connection.get('network_id')) for port in ports: mac_list = [] if port['device_owner']: dst_ip, ip_address = self._get_ip_details(context, port) mac_ip_pairs = [] if isinstance(port.get("allowed_address_pairs"), list): for address_pair in port['allowed_address_pairs']: mac = address_pair['mac_address'] mac_ip_pairs.append((mac, address_pair['ip_address'])) mac_ip_pairs.append((port.get('mac_address'), ip_address)) for mac, ip in mac_ip_pairs: mac_remote = self._get_dict( ovsdb_schema.UcastMacsRemote( uuid=None, mac=mac, logical_switch_id=None, physical_locator_id=None, ip_address=ip)) if logical_switch: u_mac_dict['mac'] = mac u_mac_dict['ovsdb_identifier'] = ovsdb_identifier u_mac_dict['logical_switch_uuid'] = ( logical_switch.get('uuid')) ucast_mac_remote = ( db.get_ucast_mac_remote_by_mac_and_ls( context, u_mac_dict)) if not ucast_mac_remote: mac_list.append(mac_remote) else: mac_list.append(mac_remote) locator_list = self._get_locator_list( context, dst_ip, ovsdb_identifier, mac_list, locator_list) for locator in locator_list: mac_dict[locator.get('dst_ip')] = locator.pop('macs') locator.pop('ovsdb_identifier') self.agent_rpc.update_connection_to_gateway( context, ovsdb_identifier, ls_dict, locator_list, mac_dict, port_dict, 'CREATE') def _get_identifer_list(self, context, gw_connection): identifier_list = [] l2gateway_devices = ( self.service_plugin.get_l2gateway_devices_by_gateway_id( context, gw_connection.get('l2_gateway_id'))) for device in l2gateway_devices: physical_switch = db.get_physical_switch_by_name( context, device.get('device_name')) if not physical_switch: msg = _('The PHYSICAL SWITCH data not found in the server') raise Exception(msg) ovsdb_identifier = physical_switch.get('ovsdb_identifier') identifier_list.append(ovsdb_identifier) return set(identifier_list) def _get_set_of_ovsdb_ids(self, context, gw_connection, gw_connection_ovsdb_set): ovsdb_id_set = set() network_id = gw_connection.get('network_id') l2gateway_connections = self.service_plugin.get_l2_gateway_connections( context, filters={'network_id': [network_id]}) for l2gatewayconnection in l2gateway_connections: if l2gatewayconnection['id'] == gw_connection['id']: l2gateway_connections.remove(l2gatewayconnection) else: ovsdb_id_set.union(self._get_identifer_list( context, l2gatewayconnection)) ovsdb_id_set = gw_connection_ovsdb_set.difference(ovsdb_id_set) return ovsdb_id_set def _remove_vm_macs(self, context, network_id, ovsdb_id_set): ports = self._get_port_details(context, network_id) if ports: for ovsdb_id in list(ovsdb_id_set): for port in ports: port['ovsdb_identifier'] = ovsdb_id self.delete_port_mac(context, ports) def create_l2_gateway_connection_postcommit(self, context, l2_gateway_connection): pass def delete_l2_gateway_connection(self, context, l2_gateway_connection): """Process the call from the CLI and trigger the RPC, to update the connection from the gateway. """ locator_list = [] ls_dict = {} mac_dict = {} self.service_plugin._admin_check(context, 'DELETE') gw_connection = self.service_plugin.get_l2_gateway_connection( context, l2_gateway_connection) if not gw_connection: raise l2gw_exc.L2GatewayConnectionNotFound(l2_gateway_connection) gw_connection_ovsdb_set = self._get_identifer_list(context, gw_connection) network_id = gw_connection.get('network_id') # get list of ovsdb_ids ovsdb_id_set = self._get_set_of_ovsdb_ids(context, gw_connection, gw_connection_ovsdb_set) # call delete connection RPC to gw_connection_ovsdb_set l2gateway_devices = ( self.service_plugin.get_l2gateway_devices_by_gateway_id( context, gw_connection.get('l2_gateway_id'))) for device in l2gateway_devices: port_dict = {} (ovsdb_identifier, logical_switch, port_dict) = ( self._process_port_list( context, device, gw_connection, "DELETE", gw_connection_ovsdb_set)) self.agent_rpc.update_connection_to_gateway( context, ovsdb_identifier, ls_dict, locator_list, mac_dict, port_dict, 'DELETE') # call delete vif_from_gateway for ovsdb_id_set self._remove_vm_macs(context, network_id, ovsdb_id_set) def delete_l2_gateway_connection_postcommit(self, context, l2_gateway_connection): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/0000775000175000017500000000000014572557757022524 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/__init__.py0000664000175000017500000000000014572557755024621 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/contrib/0000775000175000017500000000000014572557757024164 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/contrib/gate_hook.sh0000664000175000017500000000023014572557755026451 0ustar00jamespagejamespage#!/usr/bin/env bash set -ex VENV=${1:-"fullstack"} GATE_DEST=$BASE/new DEVSTACK_PATH=$GATE_DEST/devstack $BASE/new/devstack-gate/devstack-vm-gate.sh././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/contrib/post_test_hook.sh0000664000175000017500000000341114572557755027561 0ustar00jamespagejamespage#!/usr/bin/env bash set -xe NETWORKING_L2GW_DIR="$BASE/new/networking-l2gw" TEMPEST_DIR="$BASE/new/tempest" SCRIPTS_DIR="/usr/os-testr-env/bin/" venv=${1:-"fullstack"} function generate_test_logs { local path="$1" # Compress all $path/*.txt files and move the directories holding those # files to /opt/stack/logs. Files with .log suffix have their # suffix changed to .txt (so browsers will know to open the compressed # files and not download them). if [[ -d "$path" ]] ; then sudo find "$path" -iname "*.log" -type f -exec mv {} {}.txt \; -exec gzip -9 {}.txt \; sudo mv "$path/*" /opt/stack/logs/ fi } 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 .testrepository if [[ -f ".testrepository/0" ]] ; then ".tox/$venv/bin/subunit-1to2" < .testrepository/0 > ./testrepository.subunit $SCRIPTS_DIR/subunit2html ./testrepository.subunit testr_results.html gzip -9 ./testrepository.subunit gzip -9 ./testr_results.html sudo mv ./*.gz /opt/stack/logs/ fi if [[ "$venv" == fullstack* ]] ; then generate_test_logs "/tmp/${venv}-logs" fi } owner=stack sudo_env= # virtuelenv 14.0.6 gives a strange error which appears solved in version 15. # Therefore, we force the newer version. sudo pip uninstall -y virtualenv sudo pip install "virtualenv>=15.0.1" # Set owner permissions according to job's requirements. cd "$NETWORKING_L2GW_DIR" sudo chown -R $owner:stack "$NETWORKING_L2GW_DIR" # Run tests echo "Running networking-l2gw $venv tests" set +e sudo -H -u "$owner" tox -e "$venv" testr_exit_code=$? set -e # Collect and parse results generate_testr_results exit $testr_exit_code ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/0000775000175000017500000000000014572557757023503 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/__init__.py0000664000175000017500000000000014572557755025600 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/0000775000175000017500000000000014572557757024070 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/__init__.py0000664000175000017500000000000014572557755026165 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9439855 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/ovsdb/0000775000175000017500000000000014572557757025205 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/ovsdb/__init__.py0000664000175000017500000000000014572557755027302 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/ovsdb/test_idl_impl.py0000664000175000017500000000622614572557755030413 0ustar00jamespagejamespage# 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_lib import rpc as n_rpc from oslo_config import cfg import ovs.jsonrpc from ovsdbapp.backend.ovs_idl import command as cmd from ovsdbapp.backend.ovs_idl import idlutils from networking_l2gw.services.l2gateway.agent.ovsdb import impl_idl class Msg(object): id = 0 type = 1 method = 'update' params = [ None, {'Global': { '381d60b5-6171-484a-842f-49f3e83bb586': {'new': { 'switches': ['uuid', '03eebc5d-153f-4b03-8efa-45d22eb9942f'], 'other_config': [ 'map', [] ], 'managers': ['set', []]} }}, 'Physical_Switch': {'03eebc5d-153f-4b03-8efa-45d22eb9942f': { 'new': {'management_ips': ['set', []], 'description': '', 'other_config': ['map', []], 'tunnel_ips': ['set', []], 'switch_fault_status': ['set', []], 'ports': ['set', []], 'tunnels': ['set', []], 'name': 'ps1'}}}} ] class SimpleIdlTests(base.BaseTestCase): def setUp(self): super(SimpleIdlTests, self).setUp() n_rpc.init(cfg.CONF) def test_list_physical_switches(self): session_mock = mock.patch.object( ovs.jsonrpc.Session, 'open', return_value=ovs.jsonrpc.Session(None, None, None) ) wait_mock = mock.patch.object(idlutils, 'wait_for_change') execute_mock = mock.patch.object( cmd.BaseCommand, 'execute', return_value=['ps1']) with session_mock, execute_mock, wait_mock: ovs.jsonrpc.Session.run = mock.MagicMock( return_value=mock.MagicMock(return_value='myrun')) ovs.jsonrpc.Session.get_seqno = mock.MagicMock(return_value=None) ovs.jsonrpc.Session.wait = mock.MagicMock(return_value=None) ovs.jsonrpc.Session.is_connected = mock.MagicMock( return_value=True) ovs.jsonrpc.Session.recv = mock.MagicMock( side_effect=[Msg, None, None, None, None]) self.db = impl_idl.OvsdbHardwareVtepIdl(self, 'tcp:fake_ip:fake_port', 3) sw_list = self.db.get_physical_sw_list().execute() assert (sw_list is not None) & (sw_list[0] == 'ps1') print('physical switch: %s' % sw_list) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/ovsdb/test_lib.py0000664000175000017500000006100414572557755027363 0ustar00jamespagejamespage# Copyright (c) 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. from oslo_db import exception as d_exc from oslo_utils import timeutils from oslo_utils import uuidutils from neutron.tests.unit import testlib_api from neutron_lib import context from neutron_lib.db import api as db_api from networking_l2gw.db.l2gateway.ovsdb import lib from networking_l2gw.db.l2gateway.ovsdb import models _uuid = uuidutils.generate_uuid class OvsdbLibTestCase(testlib_api.SqlTestCase): def setUp(self): super(OvsdbLibTestCase, self).setUp() self.ctx = context.get_admin_context() def _get_logical_switch_dict(self): uuid = _uuid() record_dict = {'uuid': uuid, 'name': 'logical_switch1', 'key': '100', 'ovsdb_identifier': "host1"} return record_dict def _create_logical_switch(self, record_dict, name=None): if name: record_dict['name'] = name with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.LogicalSwitches( uuid=record_dict['uuid'], name=record_dict['name'], key=record_dict['key'], ovsdb_identifier=record_dict['ovsdb_identifier']) self.ctx.session.add(entry) return entry def _assert_model_equals(self, entry, result): for field, res in result.items(): self.assertEqual(str(entry[field]), str(res)) def test_get_logical_switch(self): record_dict = self._get_logical_switch_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_logical_switch(record_dict) result = lib.get_logical_switch(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_get_logical_switch_return_none(self): record_dict = {'uuid': 'foo_uuid', 'ovsdb_identifier': 'foo_ovsdb_id'} result = lib.get_logical_switch(self.ctx, record_dict) self.assertIsNone(result) def test_add_logical_switch(self): record_dict = self._get_logical_switch_dict() self._create_logical_switch(record_dict) count = self.ctx.session.query(models.LogicalSwitches).count() self.assertEqual(1, count) def test_delete_logical_switch(self): record_dict = self._get_logical_switch_dict() self._create_logical_switch(record_dict) lib.delete_logical_switch(self.ctx, record_dict) count = self.ctx.session.query(models.LogicalSwitches).count() self.assertEqual(count, 0) def _get_physical_locator_dict(self): uuid = _uuid() record_dict = {'uuid': uuid, 'dst_ip': '10.0.0.1', 'ovsdb_identifier': 'host1'} return record_dict def _create_physical_locator(self, record_dict, dst_ip=None): if dst_ip: record_dict['dst_ip'] = dst_ip with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.PhysicalLocators( uuid=record_dict['uuid'], dst_ip=record_dict['dst_ip'], ovsdb_identifier=record_dict['ovsdb_identifier']) self.ctx.session.add(entry) return entry def test_get_physical_locator(self): record_dict = self._get_physical_locator_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_physical_locator(record_dict) result = lib.get_physical_locator(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_add_physical_locator(self): record_dict = self._get_physical_locator_dict() self._create_physical_locator(record_dict) count = self.ctx.session.query(models.PhysicalLocators).count() self.assertEqual(1, count) def test_delete_physical_locator(self): record_dict = self._get_physical_locator_dict() self._create_physical_locator(record_dict) lib.delete_physical_locator(self.ctx, record_dict) count = self.ctx.session.query(models.PhysicalLocators).count() self.assertEqual(count, 0) def _get_physical_switch_dict(self): uuid = _uuid() record_dict = {'uuid': uuid, 'name': 'physical_switch1', 'tunnel_ip': '10.0.0.1', 'ovsdb_identifier': 'host1'} return record_dict def _create_physical_switch(self, record_dict, name=None): if name: record_dict['name'] = name with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.PhysicalSwitches( uuid=record_dict['uuid'], name=record_dict['name'], tunnel_ip=record_dict['tunnel_ip'], ovsdb_identifier=record_dict['ovsdb_identifier']) self.ctx.session.add(entry) return entry def test_get_physical_switch(self): record_dict = self._get_physical_switch_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_physical_switch(record_dict) result = lib.get_physical_switch(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_add_physical_switch(self): record_dict = self._get_physical_switch_dict() self._create_physical_switch(record_dict) count = self.ctx.session.query(models.PhysicalSwitches).count() self.assertEqual(1, count) def test_add_physical_switch_raise_on_duplicate_constraint(self): record_dict = self._get_physical_switch_dict() self._create_physical_switch(record_dict) # Call the method twice to trigger a db duplicate constraint error, # this time with a different switch name! self.assertRaises(d_exc.DBDuplicateEntry, self._create_physical_switch, record_dict, 'physical_switch2') def test_delete_physical_switch(self): record_dict = self._get_physical_switch_dict() self._create_physical_switch(record_dict) lib.delete_physical_switch(self.ctx, record_dict) count = self.ctx.session.query(models.PhysicalSwitches).count() self.assertEqual(count, 0) def _get_physical_port_dict(self): uuid = _uuid() ps_id = _uuid() record_dict = {'uuid': uuid, 'name': 'physical_port1', 'physical_switch_id': ps_id, 'ovsdb_identifier': 'host1'} return record_dict def _create_physical_port(self, record_dict, name=None, physical_switch_id=None): if name and physical_switch_id: record_dict['name'] = name record_dict['physical_switch_id'] = physical_switch_id with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.PhysicalPorts( uuid=record_dict['uuid'], name=record_dict['name'], physical_switch_id=record_dict['physical_switch_id'], ovsdb_identifier=record_dict['ovsdb_identifier']) self.ctx.session.add(entry) return entry def test_get_physical_port(self): record_dict = self._get_physical_port_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_physical_port(record_dict) result = lib.get_physical_port(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_add_physical_port(self): record_dict = self._get_physical_port_dict() self._create_physical_port(record_dict) count = self.ctx.session.query(models.PhysicalPorts).count() self.assertEqual(1, count) def test_add_physical_port_raise_on_duplicate_constraint(self): record_dict = self._get_physical_port_dict() self._create_physical_port(record_dict) # Call the method twice to trigger a db duplicate constraint error, # this time with a different switch name and physical switch id! self.assertRaises(d_exc.DBDuplicateEntry, self._create_physical_port, record_dict, 'physical_port2', _uuid()) def test_delete_physical_port(self): record_dict = self._get_physical_port_dict() self._create_physical_port(record_dict) lib.delete_physical_port(self.ctx, record_dict) count = self.ctx.session.query(models.PhysicalPorts).count() self.assertEqual(count, 0) def _get_ucast_mac_local_dict(self): uuid = _uuid() ls_id = _uuid() pl_id = _uuid() record_dict = {'uuid': uuid, 'mac': '12:34:56:78:90:aa:bb', 'logical_switch_id': ls_id, 'physical_locator_id': pl_id, 'ip_address': '10.0.0.1', 'ovsdb_identifier': 'host1'} return record_dict def _create_ucast_mac_local(self, record_dict): with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.UcastMacsLocals( uuid=record_dict['uuid'], mac=record_dict['mac'], logical_switch_id=record_dict['logical_switch_id'], physical_locator_id=record_dict['physical_locator_id'], ip_address=record_dict['ip_address'], ovsdb_identifier=record_dict['ovsdb_identifier']) self.ctx.session.add(entry) return entry def test_get_ucast_mac_local(self): record_dict = self._get_ucast_mac_local_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_ucast_mac_local(record_dict) result = lib.get_ucast_mac_local(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_add_ucast_mac_local(self): record_dict = self._get_ucast_mac_local_dict() self._create_ucast_mac_local(record_dict) count = self.ctx.session.query(models.UcastMacsLocals).count() self.assertEqual(1, count) def test_add_ucast_mac_local_raise_on_duplicate_constraint(self): record_dict = self._get_ucast_mac_local_dict() self._create_ucast_mac_local(record_dict) # Call the method twice to trigger a db duplicate constraint error, # this time with a different mac and logical switch id! record_dict['mac'] = '11:22:33:44:55:66:77' record_dict['logical_switch_id'] = _uuid() self.assertRaises(d_exc.DBDuplicateEntry, self._create_ucast_mac_local, record_dict) def test_delete_ucast_mac_local(self): record_dict = self._get_ucast_mac_local_dict() self._create_ucast_mac_local(record_dict) lib.delete_ucast_mac_local(self.ctx, record_dict) count = self.ctx.session.query(models.UcastMacsLocals).count() self.assertEqual(count, 0) def _get_ucast_mac_remote_dict(self): uuid = _uuid() ls_id = _uuid() pl_id = _uuid() record_dict = {'uuid': uuid, 'mac': '12:34:56:78:90:aa:bb', 'logical_switch_id': ls_id, 'physical_locator_id': pl_id, 'ip_address': '10.0.0.1', 'ovsdb_identifier': 'host1'} return record_dict def _create_ucast_mac_remote(self, record_dict, mac=None, logical_switch_uuid=None): if mac and logical_switch_uuid: record_dict['mac'] = mac record_dict['logical_switch_id'] = logical_switch_uuid with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.UcastMacsRemotes( uuid=record_dict['uuid'], mac=record_dict['mac'], logical_switch_id=record_dict['logical_switch_id'], physical_locator_id=record_dict['physical_locator_id'], ip_address=record_dict['ip_address'], ovsdb_identifier=record_dict['ovsdb_identifier']) self.ctx.session.add(entry) return entry def test_get_ucast_mac_remote(self): record_dict = self._get_ucast_mac_remote_dict() entry = self._create_ucast_mac_remote(record_dict) result = lib.get_ucast_mac_remote(self.ctx, record_dict) for ent_key, ent_val in entry.items(): self.assertEqual(ent_val, result[ent_key]) def test_add_ucast_mac_remote(self): record_dict = self._get_ucast_mac_remote_dict() self._create_ucast_mac_remote(record_dict) count = self.ctx.session.query(models.UcastMacsRemotes).count() self.assertEqual(1, count) def test_add_ucast_mac_remote_raise_on_duplicate_constraint(self): record_dict = self._get_ucast_mac_remote_dict() self._create_ucast_mac_remote(record_dict) # Call the method twice to trigger a db duplicate constraint error, # this time with a different mac and logical switch id! self.assertRaises(d_exc.DBDuplicateEntry, self._create_ucast_mac_remote, record_dict, '11:22:33:44:55:66:77', _uuid()) def test_delete_ucast_mac_remote(self): record_dict = self._get_ucast_mac_remote_dict() self._create_ucast_mac_remote(record_dict) lib.delete_ucast_mac_remote(self.ctx, record_dict) count = self.ctx.session.query(models.UcastMacsRemotes).count() self.assertEqual(count, 0) def _get_vlan_binding_dict(self): port_uuid = _uuid() ls_uuid = _uuid() record_dict = {'port_uuid': port_uuid, 'vlan': 200, 'logical_switch_uuid': ls_uuid, 'ovsdb_identifier': 'host1'} return record_dict def _get_vlan0_binding_dict(self): port_uuid = _uuid() ls_uuid = _uuid() record_dict = {'port_uuid': port_uuid, 'vlan': 0, 'logical_switch_uuid': ls_uuid, 'ovsdb_identifier': 'host1'} return record_dict def _create_vlan_binding(self, record_dict, port_uuid=None): if port_uuid: record_dict['port_uuid'] = port_uuid with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.VlanBindings( port_uuid=record_dict['port_uuid'], vlan=record_dict['vlan'], logical_switch_uuid=record_dict['logical_switch_uuid'], ovsdb_identifier=record_dict['ovsdb_identifier']) self.ctx.session.add(entry) return entry def test_get_vlan_binding(self): record_dict = self._get_vlan_binding_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_vlan_binding(record_dict) result = lib.get_vlan_binding(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_add_vlan_binding(self): record_dict = self._get_vlan_binding_dict() self._create_vlan_binding(record_dict) count = self.ctx.session.query(models.VlanBindings).count() self.assertEqual(1, count) def test_add_vlan_binding_raise_on_duplicate_constraint(self): record_dict = self._get_vlan_binding_dict() self._create_vlan_binding(record_dict) # Call the method twice to trigger a db duplicate constraint error, # this time with a same entries self.assertRaises(d_exc.DBDuplicateEntry, self._create_vlan_binding, record_dict) def test_delete_vlan_binding(self): record_dict = self._get_vlan_binding_dict() self._create_vlan_binding(record_dict) lib.delete_vlan_binding(self.ctx, record_dict) count = self.ctx.session.query(models.VlanBindings).count() self.assertEqual(count, 0) def test_delete_vlan0_binding(self): record_dict = self._get_vlan0_binding_dict() self._create_vlan_binding(record_dict) lib.delete_vlan_binding(self.ctx, record_dict) count = self.ctx.session.query(models.VlanBindings).count() self.assertEqual(count, 0) def test_get_logical_switch_by_name(self): record_dict = self._get_logical_switch_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_logical_switch(record_dict, 'logical_switch2') record_dict['logical_switch_name'] = 'logical_switch2' result = lib.get_logical_switch_by_name(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_get_all_logical_switch_by_name(self): record_dict1 = self._get_logical_switch_dict() self._create_logical_switch(record_dict1, 'logical_switch2') record_dict2 = self._get_logical_switch_dict() self._create_logical_switch(record_dict2, 'logical_switch2') ls_list = lib.get_all_logical_switches_by_name(self.ctx, 'logical_switch2') self.assertEqual(2, len(ls_list)) def test_get_physical_locator_by_dst_ip(self): record_dict = self._get_physical_locator_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_physical_locator(record_dict, '20.0.0.1') record_dict['dst_ip'] = '20.0.0.1' record_dict.pop('uuid') result = lib.get_physical_locator_by_dst_ip(self.ctx, record_dict) self._assert_model_equals(entry, result) def test_get_physical_switch_by_name(self): record_dict = self._get_physical_switch_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_physical_switch(record_dict, 'physical_switch2') result = lib.get_physical_switch_by_name(self.ctx, 'physical_switch2') self._assert_model_equals(entry, result) def test_get_all_vlan_bindings_by_physical_port(self): record_dict1 = {'port_uuid': 'ps123', 'vlan': 200, 'logical_switch_uuid': 'ls123', 'ovsdb_identifier': 'host1'} self._create_vlan_binding(record_dict1) record_dict1['vlan'] = 300 record_dict1['logical_switch_uuid'] = 'ls456' self._create_vlan_binding(record_dict1) record_dict1['uuid'] = record_dict1.get('port_uuid') vlan_list = lib.get_all_vlan_bindings_by_physical_port(self.ctx, record_dict1) self.assertEqual(2, len(vlan_list)) def test_get_ucast_mac_remote_by_mac_and_ls(self): record_dict = self._get_ucast_mac_remote_dict() entry = self._create_ucast_mac_remote(record_dict, '00:11:22:33:44:55:66', 'ls123') record_dict['mac'] = '00:11:22:33:44:55:66' record_dict['logical_switch_uuid'] = 'ls123' result = lib.get_ucast_mac_remote_by_mac_and_ls(self.ctx, record_dict) for ent_key, ent_val in entry.items(): self.assertEqual(ent_val, result[ent_key]) def test_get_ucast_mac_remote_by_mac_and_ls_when_not_found(self): record_dict = self._get_ucast_mac_remote_dict() record_dict['mac'] = '00:11:22:33:44:55:66' record_dict['logical_switch_uuid'] = 'ls123' result = lib.get_ucast_mac_remote_by_mac_and_ls(self.ctx, record_dict) self.assertIsNone(result) def test_get_physical_port_by_name_and_ps(self): record_dict = self._get_physical_port_dict() with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_physical_port(record_dict, 'port1', 'ps123') record_dict['interface_name'] = 'port1' record_dict['physical_switch_id'] = 'ps123' result = lib.get_physical_port_by_name_and_ps(self.ctx, record_dict) self._assert_model_equals(entry, result) def _get_pending_mac_dict(self, timestamp): record_dict = {'uuid': _uuid(), 'mac': 'aa:aa:aa:aa:aa:aa', 'logical_switch_uuid': 'fake_ls_id', 'locator_uuid': _uuid(), 'dst_ip': '1.1.1.1', 'vm_ip': '2.2.2.2', 'ovsdb_identifier': 'ovsdb1', 'operation': 'insert', 'timestamp': timestamp} return record_dict def _create_pending_mac(self, record_dict): with db_api.CONTEXT_WRITER.using(self.ctx): entry = models.PendingUcastMacsRemote( uuid=record_dict['uuid'], mac=record_dict['mac'], logical_switch_uuid=record_dict['logical_switch_uuid'], locator_uuid=record_dict['locator_uuid'], dst_ip=record_dict['dst_ip'], vm_ip=record_dict['vm_ip'], ovsdb_identifier=record_dict['ovsdb_identifier'], operation=record_dict['operation'], timestamp=record_dict['timestamp']) self.ctx.session.add(entry) return entry def test_add_pending_ucast_mac_remote(self): timestamp = timeutils.utcnow() record_dict = self._get_pending_mac_dict(timestamp) self._create_pending_mac(record_dict) count = self.ctx.session.query(models.PendingUcastMacsRemote).count() self.assertEqual(1, count) def test_get_pending_ucast_mac_remote(self): timestamp = timeutils.utcnow() record_dict = self._get_pending_mac_dict(timestamp) with db_api.CONTEXT_WRITER.using(self.ctx): entry = self._create_pending_mac(record_dict) result = lib.get_pending_ucast_mac_remote( self.ctx, record_dict['ovsdb_identifier'], record_dict['mac'], record_dict['logical_switch_uuid']) self._assert_model_equals(entry, result) def test_get_all_pending_remote_macs_in_asc_order(self): timestamp1 = timeutils.utcnow() record_dict1 = self._get_pending_mac_dict(timestamp1) timestamp2 = timeutils.utcnow() record_dict2 = self._get_pending_mac_dict(timestamp2) entry = [] with db_api.CONTEXT_WRITER.using(self.ctx): entry.append(self._create_pending_mac(record_dict1)) entry.append(self._create_pending_mac(record_dict2)) result = lib.get_all_pending_remote_macs_in_asc_order( self.ctx, record_dict1['ovsdb_identifier']) for index, res in enumerate(result): for k, v in res.items(): self.assertEqual(res[k], entry[index][k]) def test_delete_pending_ucast_mac_remote(self): timestamp = timeutils.utcnow() record_dict = self._get_pending_mac_dict(timestamp) self._create_pending_mac(record_dict) lib.delete_pending_ucast_mac_remote(self.ctx, record_dict['operation'], record_dict['ovsdb_identifier'], record_dict['logical_switch_uuid'], record_dict['mac']) count = self.ctx.session.query(models.UcastMacsRemotes).count() self.assertEqual(count, 0) def test_get_all_ucast_mac_remote_by_ls(self): record_dict = self._get_ucast_mac_remote_dict() record_dict1 = self._create_ucast_mac_remote(record_dict) record_dict = self._get_ucast_mac_remote_dict() record_dict['mac'] = '00:11:22:33:44:55:66' record_dict['logical_switch_id'] = record_dict1.get( 'logical_switch_id') self._create_ucast_mac_remote(record_dict) mac_list = lib.get_all_ucast_mac_remote_by_ls(self.ctx, record_dict) self.assertEqual(2, len(mac_list)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/test_l2gw_db.py0000664000175000017500000005310214572557755027020 0ustar00jamespagejamespage# Copyright 2015 OpenStack Foundation # Copyright (c) 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 unittest import mock from neutron.tests.unit import testlib_api from neutron_lib.callbacks import events from neutron_lib.callbacks import resources from neutron_lib import context from neutron_lib.db import api as db_api from networking_l2gw.db.l2gateway import l2gateway_db from networking_l2gw.services.l2gateway.common import constants from networking_l2gw.services.l2gateway.common import l2gw_validators from networking_l2gw.services.l2gateway import exceptions from neutron_lib import exceptions as exc from neutron_lib.plugins import directory from oslo_utils import importutils from oslo_utils import uuidutils DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' _uuid = uuidutils.generate_uuid class L2GWTestCase(testlib_api.SqlTestCase): """Unit test for l2 Gateway DB support.""" def setUp(self): super(L2GWTestCase, self).setUp() self.ctx = context.get_admin_context() self.mixin = l2gateway_db.L2GatewayMixin() self.gw_resource = constants.L2_GATEWAYS self.con_resource = constants.CONNECTION_RESOURCE_NAME self.plugin = importutils.import_object(DB_PLUGIN_KLASS) def _create_l2gateway(self, l2gateway): """Create l2gateway helper method.""" with db_api.CONTEXT_WRITER.using(self.ctx): return self.mixin.create_l2_gateway(self.ctx, l2gateway) def _get_l2_gateway_data(self, name, device_name): """Get l2 gateway data helper method.""" data = {"l2_gateway": {"name": name, "devices": [{"interfaces": [{"name": "port1", "segmentation_id": ["111"]}], "device_name": device_name}]}} return data def _get_l2_gateway_data_with_multiple_segid(self, name, device_name): """Get l2 gateway data helper method for multiple seg id.""" data = {"l2_gateway": {"name": name, "devices": [{"interfaces": [{"name": "port1", "segmentation_id": ["111", "123"]}], "device_name": device_name}]}} return data def _get_l2_gateway_multiple_interface_data(self, name, device_name): """Get l2 gateway data helper method with multiple interface data.""" data = {"l2_gateway": {"name": name, "devices": [{"interfaces": [{"name": "port1", "segmentation_id": ["4076"]}, {"name": "port1", "segmentation_id": ["4074"]}], "device_name": device_name}]}} return data def _get_l2_gw_multiple_interface_partial_seg_id_data(self, name, device_name): """Get l2 gateway data helper method with partial seg id.""" data = {"l2_gateway": {"name": name, "devices": [{"interfaces": [{"name": "port1", "segmentation_id": ["4076"]}, {"name": "port1"}], "device_name": device_name}]}} return data def _get_l2_gw_multiple_interface_without_seg_id_data(self, name, device_name): """Get l2 gateway data helper method with partial seg id.""" return {"l2_gateway": {"name": name, "devices": [{"interfaces": [{"name": "port1"}, {"name": "port2"}], "device_name": device_name}]}} def _get_l2_gw_invalid_seg_id_data(self, name, device_name): """Get l2 gateway data helper method with invalid seg id.""" data = {"interfaces": [{"name": "port1", "segmentation_id": ["test"]}], "device_name": device_name} return [data] def _get_nw_data(self): return {'network': {'id': _uuid(), 'name': 'net1', 'admin_state_up': True, 'tenant_id': 'test-tenant', 'shared': False}} def _get_l2_gateway_data_without_seg_id(self, name, device_name): """Get l2 gateway data helper method.""" data = {"l2_gateway": {"name": name, "devices": [{"interfaces": [{"name": "port1"}], "device_name": device_name}]}} return data def test_l2_gateway_get(self): """Test l2 gateway get.""" name = "l2gw_1" device_name = "device1" data = self._get_l2_gateway_data(name, device_name) result = self._create_l2gateway(data) get_result = self._get_l2_gateway(result['id']) self.assertEqual(name, get_result['name']) def test_l2_gateway_get_invalid_id_failure(self): """Test l2 gateway get for an invalid L2 gateway UUID.""" # Generate a random UUID and try to retrieve a L2 gateway # using that UUID. self.assertRaises(exceptions.L2GatewayNotFound, self._get_l2_gateway, _uuid()) def test_l2_gateway_create(self): """Test l2 gateway create.""" name = "l2gw_1" device_name = "device1" data = self._get_l2_gateway_data(name, device_name) result = self._create_l2gateway(data) self.assertEqual(result['name'], name) def _get_l2_gateway(self, l2gw_id): with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.get_l2_gateway(self.ctx, l2gw_id) def _get_l2_gateways(self): """Update l2gateway helper.""" with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.get_l2_gateways(self.ctx) def test_l2gateway_list(self): """Test l2 gateway list.""" name = "l2gw_1" device_name = "device1" data = self._get_l2_gateway_data(name, device_name) self._create_l2gateway(data) result2 = self._get_l2_gateways() self.assertIn('id', result2[0]) def test_l2gateway_show(self): """Test l2 gateway show.""" name = "l2gw_1" device_name = "device1" data = self._get_l2_gateway_data(name, device_name) gw = self._create_l2gateway(data) l2gw_id = gw['id'] result = self._get_l2_gateway(l2gw_id) self.assertEqual(name, result['name']) def _update_l2_gateway(self, id, l2gateway): """Update l2gateway helper.""" with db_api.CONTEXT_WRITER.using(self.ctx): return self.mixin.update_l2_gateway(self.ctx, id, l2gateway) def test_l2_gateway_update(self): """Test l2 gateway update.""" name_create = "l2gw_1" name_update = "l2gw_2" device_name = "device1" data_l2gw_create = self._get_l2_gateway_data(name_create, device_name) gw_org = self._create_l2gateway(data_l2gw_create) l2gw_id = gw_org['id'] l2_gw_update_dict = self._get_l2_gateway_data(name_update, device_name) result = self._update_l2_gateway(l2gw_id, l2_gw_update_dict) self.assertNotEqual(result['name'], name_create) def test_l2_gateway_update_without_devices(self): """Test l2 gateway update without devices.""" name_create = "l2gw_1" name_update = "l2gw_updated" device_name = "device1" data_l2gw_create = self._get_l2_gateway_data(name_create, device_name) gw_org = self._create_l2gateway(data_l2gw_create) l2gw_id = gw_org['id'] l2_gw_update_dict = {"l2_gateway": {"name": name_update}} result = self._update_l2_gateway(l2gw_id, l2_gw_update_dict) self.assertNotEqual(result['name'], name_create) self.assertEqual(result['name'], name_update) def _create_l2gateway_connection(self, l2gateway_con): """Create L2 gateway connection resource helper method.""" with db_api.CONTEXT_WRITER.using(self.ctx): return self.mixin.create_l2_gateway_connection(self.ctx, l2gateway_con) def _list_l2gateway_connection(self): """Create L2 gateway connection resource helper method.""" with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.get_l2_gateway_connections(self.ctx) def _get_no_l2gateway_connections(self): """Create L2 gateway connection resource helper method.""" with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.get_l2_gateway_connections_count(self.ctx) def _delete_l2gw_connection_by_l2gw_id(self, l2gw_id): """Delete l2 gateway connection.""" with db_api.CONTEXT_WRITER.using(self.ctx): return self.mixin._delete_connection_by_l2gw_id(self.ctx, l2gw_id) def test_get_l2gw_ids_by_interface_switch(self): """Test get L2 gateway ids by interface and switch name.""" name = "l2gw_con1" device_name = "device1" data_l2gw = self._get_l2_gateway_data(name, device_name) gw = self._create_l2gateway(data_l2gw) net_data = self._get_nw_data() net = self.plugin.create_network(self.ctx, net_data) l2gw_id = gw['id'] data_con = {self.con_resource: {'l2_gateway_id': l2gw_id, 'network_id': net['id']}} self._create_l2gateway_connection(data_con) l2gw_id_list = self.mixin._get_l2gw_ids_by_interface_switch( self.ctx, 'port1', 'device1') self.assertEqual(l2gw_id_list[0], l2gw_id) def test_l2gateway_connection_create_delete_list(self): """Test l2 gateway connection create and delete.""" name = "l2gw_con1" device_name = "device_name1" data_l2gw = self._get_l2_gateway_data(name, device_name) gw = self._create_l2gateway(data_l2gw) net_data = self._get_nw_data() net = self.plugin.create_network(self.ctx, net_data) l2gw_id = gw['id'] data_con = {self.con_resource: {'l2_gateway_id': l2gw_id, 'network_id': net['id']}} gw_con = self._create_l2gateway_connection(data_con) exp_net_id = gw_con['network_id'] self.assertEqual(net['id'], exp_net_id) list_con = self._list_l2gateway_connection() self.assertEqual(1, self._get_no_l2gateway_connections()) self.assertIn('id', list_con[0]) result = self._delete_l2gw_connection_by_l2gw_id(l2gw_id) self.assertIsNone(result) def _validate_l2_gateway_for_delete(self, l2gw_id): """Delete l2 gateway helper method.""" with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.validate_l2_gateway_for_delete(self.ctx, l2gw_id) def _validate_l2_gateway_for_create(self, l2gw): """Create l2 gateway helper method.""" with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.validate_l2_gateway_for_create(self.ctx, l2gw) def _validate_l2_gateway_for_update(self, l2gw_id, l2gw): """Update l2 gateway helper method.""" with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.validate_l2_gateway_for_update(self.ctx, l2gw_id, l2gw) def _validate_l2_gateway_connection_for_create(self, l2gw_con): """Create l2 gateway connection helper method.""" with db_api.CONTEXT_READER.using(self.ctx): return self.mixin.validate_l2_gateway_connection_for_create( self.ctx, l2gw_con) def test_l2gateway_con_create_and_delete_in_use_without_seg_id(self): """Test l2 gateway connection create without seg id when use.""" name = "l2gw_con2" device_name = "device_name2" data_l2gw = self._get_l2_gateway_data(name, device_name) gw = self._create_l2gateway(data_l2gw) net_data = self._get_nw_data() net = self.plugin.create_network(self.ctx, net_data) l2gw_id = gw['id'] data_con = {self.con_resource: {'l2_gateway_id': l2gw_id, 'network_id': net['id']}} self._create_l2gateway_connection(data_con) self.assertRaises(exceptions.L2GatewayInUse, self._validate_l2_gateway_for_delete, l2gw_id) def test_l2gateway_con_create_with_invalid_net_id(self): """Test l2 gateway connection create with invalid net id.""" name = "l2gw_con2" device_name = "device_name2" data_l2gw = self._get_l2_gateway_data(name, device_name) gw = self._create_l2gateway(data_l2gw) net_id = 'invalid_net_id' l2gw_id = gw['id'] data_con = {self.con_resource: {'l2_gateway_id': l2gw_id, 'network_id': net_id}} directory.add_plugin('CORE', self.plugin) self.assertRaises(exc.NetworkNotFound, self._validate_l2_gateway_connection_for_create, data_con) def _delete_l2gateway(self, l2gw_id): """Delete l2 gateway helper method.""" with db_api.CONTEXT_WRITER.using(self.ctx): return self.mixin.delete_l2_gateway(self.ctx, l2gw_id) def test_l2gateway_delete(self): """Test l2 gateway delete.""" data_l2gw = self._get_l2_gateway_data("gateway_delete", "device_name") gw_actual = self._create_l2gateway(data_l2gw) l2gw_id = gw_actual['id'] result = self._delete_l2gateway(l2gw_id) self.assertIsNone(result) def _delete_l2gw_connection(self, con_id): """Delete l2 gateway connection.""" with db_api.CONTEXT_WRITER.using(self.ctx): return self.mixin.delete_l2_gateway_connection(self.ctx, con_id) def test_l2_gateway_create_with_mul_interfaces(self): """Test l2 gateway create with multiple interfaces all seg id.""" name = "l2gw_1" device_name = "device1" data = self._get_l2_gateway_multiple_interface_data(name, device_name) result = self._create_l2gateway(data) self.assertEqual(result['name'], name) def test_l2_gateway_create_with_mul_interfaces_inconsistent_seg_id(self): """Test l2 gateway create with multiple interfaces.""" name = "l2gw_1" dev_name = "device1" data = self._get_l2_gw_multiple_interface_partial_seg_id_data(name, dev_name) self.assertRaises(exceptions.L2GatewaySegmentationRequired, self._validate_l2_gateway_for_create, data) def test_l2_gateway_update_with_mul_interfaces_inconsistent_seg_id(self): """Test l2 gateway update with multiple interfaces.""" name = "l2gw_1" dev_name = "device1" data_orig = self._get_l2_gateway_multiple_interface_data(name, dev_name) data = self._get_l2_gw_multiple_interface_partial_seg_id_data(name, dev_name) gw_org = self._create_l2gateway(data_orig) l2gw_id = gw_org['id'] self.assertRaises(exceptions.L2GatewaySegmentationRequired, self._validate_l2_gateway_for_update, l2gw_id, data) def test_l2_gateway_update_with_mul_interfaces_without_seg_id(self): """Test l2 gateway update with multiple interfaces without seg_id.""" name = "l2gw_1" dev_name = "device1" data1 = self._get_l2_gateway_multiple_interface_data( name, dev_name) data2 = self._get_l2_gw_multiple_interface_without_seg_id_data( name, dev_name) gw_org1 = self._create_l2gateway(data1) l2gw_id1 = gw_org1['id'] gw_org2 = self._create_l2gateway(data2) l2gw_id2 = gw_org2['id'] self.assertRaises(exceptions.L2GatewaySegmentationIDExists, self._validate_l2_gateway_for_update, l2gw_id1, data2) self.assertRaises(exceptions.L2GatewaySegmentationIDNotExists, self._validate_l2_gateway_for_update, l2gw_id2, data1) def test_l2_gateway_create_with_invalid_seg_id(self): """Test l2 gateway create with invalid seg-id.""" name = "l2gw_1" dev_name = "device1" data = self._get_l2_gw_invalid_seg_id_data(name, dev_name) self.assertRaises(exc.InvalidInput, l2gw_validators.validate_gwdevice_list, data) def test_l2_gateway_create_with_multiple_segid(self): """Test l2 gateway create with multiple seg id.""" name = "l2gw_1" device_name = "device1" data = self._get_l2_gateway_data_with_multiple_segid(name, device_name) result = self._create_l2gateway(data) self.assertEqual(result['name'], name) def test_l2_gateway_update_invalid_device_name(self): """Test l2 gateway update with invalid device name.""" name_create = "l2gw_1" device_name = "device1" invalid_device_name = "invalid_device" data_l2gw_create = self._get_l2_gateway_data(name_create, device_name) data_l2gw_update = self._get_l2_gateway_data(name_create, invalid_device_name) gw_org = self._create_l2gateway(data_l2gw_create) l2gw_id = gw_org['id'] self.assertRaises(exceptions.L2GatewayDeviceNotFound, self._validate_l2_gateway_for_update, l2gw_id, data_l2gw_update) def test_l2gw_callback_update_port(self): service_plugin = mock.Mock() directory.add_plugin(constants.L2GW, service_plugin) fake_context = mock.Mock() fake_port = mock.Mock() fake_kwargs = {'context': fake_context, 'port': fake_port} l2gateway_db.l2gw_callback(resources.PORT, events.AFTER_UPDATE, mock.Mock(), **fake_kwargs) self.assertTrue(service_plugin.add_port_mac.called) def test_l2gw_callback_delete_port(self): service_plugin = mock.Mock() directory.add_plugin(constants.L2GW, service_plugin) fake_context = mock.Mock() fake_port = mock.Mock() fake_kwargs = {'context': fake_context, 'port': fake_port} l2gateway_db.l2gw_callback(resources.PORT, events.AFTER_DELETE, mock.Mock(), **fake_kwargs) self.assertTrue(service_plugin.delete_port_mac.called) def test_l2_gateway_create_output_aligned_with_input(self): """Test l2 gateway create output that is aligned with input dict.""" name = "l2gw_1" device_name = "device1" data = self._get_l2_gateway_data_with_multiple_segid(name, device_name) result = self._create_l2gateway(data) gw_input = data['l2_gateway'] devices_input = gw_input['devices'] devices_output = result['devices'] input_seg_list = devices_input[0]['interfaces'][0]['segmentation_id'] output_seg_list = devices_output[0]['interfaces'][0]['segmentation_id'] self.assertEqual(len(input_seg_list), len(output_seg_list)) def test_l2gateway_show_update_delete_invalid_id(self): """Test l2 gateway show, update and delete with invalid id.""" name = "l2gw_1" name_update = "l2gw_2" device_name = "device1" invalid_l2gw_id = "invalid_id" data_l2gw_create = self._get_l2_gateway_data(name, device_name) data_l2gw_update = self._get_l2_gateway_data(name_update, device_name) self._create_l2gateway(data_l2gw_create) self.assertRaises(exceptions.L2GatewayNotFound, self._get_l2_gateway, invalid_l2gw_id) self.assertRaises(exceptions.L2GatewayNotFound, self._validate_l2_gateway_for_update, invalid_l2gw_id, data_l2gw_update) self.assertRaises(exceptions.L2GatewayNotFound, self._validate_l2_gateway_for_delete, invalid_l2gw_id) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/db/test_migrations.py0000664000175000017500000000440714572557755027660 0ustar00jamespagejamespage# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 networking_l2gw.db.l2gateway import head # EXTERNAL_TABLES should contain all names of tables that are not related to # current repo. EXTERNAL_TABLES = set(external.TABLES) VERSION_TABLE = 'l2gw_alembic_version' class _TestModelsMigrationsL2GW(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, _TestModelsMigrationsL2GW, testlib_api.SqlTestCaseLight): pass class TestModelsMigrationsPostgresql(testlib_api.PostgreSQLTestCaseMixin, _TestModelsMigrationsL2GW, testlib_api.SqlTestCaseLight): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/0000775000175000017500000000000014572557757026601 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/__init__.py0000664000175000017500000000000014572557755030676 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/osc/0000775000175000017500000000000014572557757027365 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/osc/__init__.py0000664000175000017500000000000014572557755031462 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/osc/fakes.py0000664000175000017500000000610114572557755031024 0ustar00jamespagejamespage# All Rights Reserved 2018 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_utils import uuidutils from networking_l2gw.l2gatewayclient.osc import l2gw as osc_l2gw from networking_l2gw.l2gatewayclient.osc import l2gw_connection as \ osc_l2gw_conn class FakeL2GW(object): @staticmethod def create_l2gw(num_dev=1, num_if=1, attrs=None): """Create one fake L2 Gateway.""" attrs = attrs or {} interfaces = [{'name': 'interface' + uuidutils.generate_uuid(dashed=False)} for iface in range(num_if)] devices = [{'device_name': 'device' + uuidutils.generate_uuid(dashed=False), 'interfaces': interfaces} for dev in range(num_dev)] l2gw_attrs = { 'id': uuidutils.generate_uuid(), 'name': 'test-l2gw' + uuidutils.generate_uuid(dashed=False), 'tenant_id': uuidutils.generate_uuid(), 'devices': devices } l2gw_attrs.update(attrs) return copy.deepcopy(l2gw_attrs) @staticmethod def create_l2gws(attrs=None, count=1): """Create multiple fake L2 Gateways.""" l2gws = [] for i in range(0, count): if attrs is None: attrs = {'id': 'fake_id%d' % i} elif getattr(attrs, 'id', None) is None: attrs['id'] = 'fake_id%d' % i l2gws.append(FakeL2GW.create_l2gw(attrs=attrs)) return {osc_l2gw.L2_GATEWAYS: l2gws} class FakeL2GWConnection(object): @staticmethod def create_l2gw_connection(attrs=None): """Create a fake l2gw connection.""" attrs = attrs or {} l2gw_connection_attrs = { 'network_id': uuidutils.generate_uuid(), 'l2_gateway_id': uuidutils.generate_uuid(), 'segmentation_id': '42', 'tenant_id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid() } l2gw_connection_attrs.update(attrs) return copy.deepcopy(l2gw_connection_attrs) @staticmethod def create_l2gw_connections(attrs=None, count=1): l2gw_connections = [] for i in range(0, count): if attrs is None: attrs = {'id': 'fake_id%d' % i} elif getattr(attrs, 'id', None) is None: attrs['id'] = 'fake_id%d' % i l2gw_connections.append(FakeL2GWConnection.create_l2gw_connection( attrs=attrs)) return {osc_l2gw_conn.L2_GATEWAY_CONNECTIONS: l2gw_connections} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/osc/test_osc_l2gw.py0000664000175000017500000004351314572557755032521 0ustar00jamespagejamespage# All Rights Reserved 2018 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 operator from unittest import mock from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from neutronclient.tests.unit.osc.v2 import fakes as test_fakes from networking_l2gw.l2gatewayclient.osc import l2gw as osc_l2gw from networking_l2gw.tests.unit.l2gatewayclient.osc import fakes columns_long = tuple(col for col, _, listing_mode in osc_l2gw._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) headers_long = tuple(head for _, head, listing_mode in osc_l2gw._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) sorted_attr_map = sorted(osc_l2gw._attr_map, key=operator.itemgetter(1)) sorted_columns = tuple(col for col, _, _ in sorted_attr_map) sorted_headers = tuple(head for _, head, _ in sorted_attr_map) def _get_data(attrs, columns=sorted_columns): return osc_utils.get_dict_properties(attrs, columns, formatters=osc_l2gw._formatters) class TestCreateL2gw(test_fakes.TestNeutronClientOSCV2): columns = ( 'Devices', 'ID', 'Name', 'Tenant' ) def setUp(self): super(TestCreateL2gw, self).setUp() self.cmd = osc_l2gw.CreateL2gw(self.app, self.namespace) def _assert_create_succeeded(self, fake_l2gw, arg_list, verify_list): parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) self.neutronclient.post.assert_called_once_with( osc_l2gw.object_path, {osc_l2gw.L2_GATEWAY: {'name': fake_l2gw['name'], 'devices': fake_l2gw['devices']}} ) self.assertEqual(self.columns, columns) self.assertItemEqual(_get_data(fake_l2gw), data) def test_create_l2gw(self): """Test Create l2gateway.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() self.neutronclient.post = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: fake_l2gw}) l2gw_device = fake_l2gw['devices'][0] arg_list = [ '--device', 'name=' + l2gw_device['device_name'] + ',interface_names=' + l2gw_device['interfaces'][0]['name'], fake_l2gw['name'] ] verify_list = [ ('devices', [ {'interface_names': l2gw_device['interfaces'][0]['name'], 'name': l2gw_device['device_name']}]), ('name', fake_l2gw['name']), ] self._assert_create_succeeded(fake_l2gw, arg_list, verify_list) def test_create_l2gateway_with_multiple_devices(self): """Test Create l2gateway for multiple devices.""" fake_l2gw = fakes.FakeL2GW.create_l2gw(num_dev=2) self.neutronclient.post = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: fake_l2gw}) l2gw_device_1 = fake_l2gw['devices'][0] l2gw_device_2 = fake_l2gw['devices'][1] arg_list = [ '--device', 'name=' + l2gw_device_1['device_name'] + ',interface_names=' + l2gw_device_1['interfaces'][0]['name'], '--device', 'name=' + l2gw_device_2['device_name'] + ',interface_names=' + l2gw_device_2['interfaces'][0]['name'], fake_l2gw['name'] ] verify_list = [ ('devices', [ {'interface_names': l2gw_device_1['interfaces'][0]['name'], 'name': l2gw_device_1['device_name']}, {'interface_names': l2gw_device_2['interfaces'][0]['name'], 'name': l2gw_device_2['device_name']}, ]), ('name', fake_l2gw['name']), ] self._assert_create_succeeded(fake_l2gw, arg_list, verify_list) def test_create_l2gateway_with_multiple_interfaces(self): """Test Create l2gateway with multiple interfaces.""" fake_l2gw = fakes.FakeL2GW.create_l2gw(num_if=2) self.neutronclient.post = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: fake_l2gw}) l2gw_device = fake_l2gw['devices'][0] l2gw_interface_1 = l2gw_device['interfaces'][0] l2gw_interface_2 = l2gw_device['interfaces'][1] arg_list = [ '--device', 'name=' + l2gw_device['device_name'] + ',interface_names=' + l2gw_interface_1['name'] + ';' + l2gw_interface_2['name'], fake_l2gw['name'] ] verify_list = [ ('devices', [ { 'interface_names': l2gw_interface_1['name'] + ';' + l2gw_interface_2['name'], 'name': l2gw_device['device_name'] } ]), ('name', fake_l2gw['name']), ] self._assert_create_succeeded(fake_l2gw, arg_list, verify_list) def test_create_l2gateway_with_segmentation_id(self): """Test Create l2gateway with segmentation-id.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() fake_l2gw['devices'][0]['interfaces'][0]['segmentation_id'] = ['42'] self.neutronclient.post = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: fake_l2gw}) l2gw_device = fake_l2gw['devices'][0] l2gw_interface = l2gw_device['interfaces'][0] arg_list = [ '--device', 'name=' + l2gw_device['device_name'] + ',interface_names=' + l2gw_interface['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), fake_l2gw['name'] ] verify_list = [ ('devices', [ {'interface_names': l2gw_device['interfaces'][0]['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), 'name': l2gw_device['device_name']}]), ('name', fake_l2gw['name']), ] self._assert_create_succeeded(fake_l2gw, arg_list, verify_list) def test_create_l2gateway_with_mul_segmentation_id(self): """Test Create l2gateway with multiple segmentation-ids.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() fake_l2gw['devices'][0]['interfaces'][0]['segmentation_id'] = ['42', '43'] self.neutronclient.post = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: fake_l2gw}) l2gw_device = fake_l2gw['devices'][0] l2gw_interface = l2gw_device['interfaces'][0] arg_list = [ '--device', 'name=' + l2gw_device['device_name'] + ',interface_names=' + l2gw_interface['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), fake_l2gw['name'], ] verify_list = [ ('devices', [ {'interface_names': l2gw_device['interfaces'][0]['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), 'name': l2gw_device['device_name']}]), ('name', fake_l2gw['name']), ] self._assert_create_succeeded(fake_l2gw, arg_list, verify_list) class TestListL2gw(test_fakes.TestNeutronClientOSCV2): def setUp(self): super(TestListL2gw, self).setUp() self.cmd = osc_l2gw.ListL2gw(self.app, self.namespace) def test_list_l2gateway(self): """Test List l2gateways.""" fake_l2gws = fakes.FakeL2GW.create_l2gws(count=4) self.neutronclient.list = mock.Mock(return_value=fake_l2gws) arg_list = [] verify_list = [] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.neutronclient.list.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertListItemEqual( list(data), [_get_data(fake_l2gw, columns_long) for fake_l2gw in fake_l2gws[osc_l2gw.L2_GATEWAYS]] ) class TestDeleteL2gw(test_fakes.TestNeutronClientOSCV2): def setUp(self): super(TestDeleteL2gw, self).setUp() self.neutronclient.find_resource = mock.Mock( side_effect=lambda _, name_or_id: {'id': name_or_id}) self.cmd = osc_l2gw.DeleteL2gw(self.app, self.namespace) def test_delete_l2gateway(self): """Test Delete l2gateway.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() self.neutronclient.delete = mock.Mock() arg_list = [ fake_l2gw['id'], ] verify_list = [ (osc_l2gw.L2_GATEWAYS, [fake_l2gw['id']]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) result = self.cmd.take_action(parsed_args) self.neutronclient.delete.assert_called_once_with( osc_l2gw.resource_path % fake_l2gw['id']) self.assertIsNone(result) class TestShowL2gw(test_fakes.TestNeutronClientOSCV2): def setUp(self): super(TestShowL2gw, self).setUp() self.neutronclient.find_resource = mock.Mock( side_effect=lambda _, name_or_id: {'id': name_or_id}) self.cmd = osc_l2gw.ShowL2gw(self.app, self.namespace) def test_show_l2gateway(self): """Test Show l2gateway: --fields id --fields name myid.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() self.neutronclient.get = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: fake_l2gw}) arg_list = [ fake_l2gw['id'], ] verify_list = [ (osc_l2gw.L2_GATEWAY, fake_l2gw['id']), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.neutronclient.get.assert_called_once_with( osc_l2gw.resource_path % fake_l2gw['id']) self.assertEqual(sorted_headers, headers) self.assertItemEqual(_get_data(fake_l2gw), data) class TestUpdateL2gw(test_fakes.TestNeutronClientOSCV2): _new_device_name = 'new_device' _new_interface = 'new_interface' _new_name = 'new_name' columns = ( 'Devices', 'ID', 'Name', 'Tenant' ) def setUp(self): super(TestUpdateL2gw, self).setUp() self.cmd = osc_l2gw.UpdateL2gw(self.app, self.namespace) self.neutronclient.find_resource = mock.Mock( side_effect=lambda _, name_or_id: {'id': name_or_id}) def _assert_update_succeeded(self, new_l2gw, attrs, columns, data): self.neutronclient.put.assert_called_once_with( osc_l2gw.resource_path % new_l2gw['id'], {osc_l2gw.L2_GATEWAY: attrs}) self.assertEqual(self.columns, columns) self.assertItemEqual(_get_data(new_l2gw), data) def test_update_l2gateway(self): """Test Update l2gateway.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() new_l2gw = copy.deepcopy(fake_l2gw) new_l2gw['name'] = self._new_name new_l2gw['devices'][0]['device_name'] = self._new_device_name new_l2gw['devices'][0]['interfaces'][0]['name'] = self._new_interface self.neutronclient.put = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: new_l2gw}) arg_list = [ fake_l2gw['id'], '--name', self._new_name, '--device', 'name=' + self._new_device_name + ',interface_names=' + self._new_interface, ] verify_list = [ ('name', self._new_name), ('devices', [ {'interface_names': self._new_interface, 'name': self._new_device_name}]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = { 'name': self._new_name, 'devices': [ {'interfaces': [ {'name': self._new_interface}], 'device_name': self._new_device_name} ] } self._assert_update_succeeded(new_l2gw, attrs, columns, data) def test_update_l2gateway_name(self): """Test Update l2gateway name.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() new_l2gw = copy.deepcopy(fake_l2gw) new_l2gw['name'] = self._new_name self.neutronclient.put = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: new_l2gw}) arg_list = [ fake_l2gw['id'], '--name', self._new_name, ] verify_list = [('name', self._new_name)] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = {'name': self._new_name} self._assert_update_succeeded(new_l2gw, attrs, columns, data) def test_update_l2gateway_with_multiple_interfaces(self): """Test Update l2gateway with multiple interfaces.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() new_l2gw = copy.deepcopy(fake_l2gw) new_l2gw['devices'][0]['interfaces'].append( {'name': self._new_interface}) self.neutronclient.put = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: new_l2gw}) l2gw_device = new_l2gw['devices'][0] l2gw_interface_1 = l2gw_device['interfaces'][0] l2gw_interface_2 = l2gw_device['interfaces'][1] arg_list = [ fake_l2gw['id'], '--device', 'name=' + l2gw_device['device_name'] + ',interface_names=' + l2gw_interface_1['name'] + ';' + l2gw_interface_2['name'] ] verify_list = [ ('devices', [ { 'interface_names': l2gw_interface_1['name'] + ';' + l2gw_interface_2['name'], 'name': l2gw_device['device_name'] } ]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = { 'devices': [ {'device_name': l2gw_device['device_name'], 'interfaces': [ {'name': l2gw_interface_1['name']}, {'name': self._new_interface}]} ] } self._assert_update_succeeded(new_l2gw, attrs, columns, data) def test_update_l2gateway_with_segmentation_id(self): """Test Update l2gateway with segmentation-id.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() new_l2gw = copy.deepcopy(fake_l2gw) new_l2gw['devices'][0]['interfaces'][0]['segmentation_id'] = ['42'] self.neutronclient.put = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: new_l2gw}) l2gw_device = new_l2gw['devices'][0] l2gw_interface = l2gw_device['interfaces'][0] arg_list = [ fake_l2gw['id'], '--device', 'name=' + l2gw_device['device_name'] + ',interface_names=' + l2gw_interface['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), ] verify_list = [ ('devices', [ {'interface_names': l2gw_device['interfaces'][0]['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), 'name': l2gw_device['device_name']}]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = { 'devices': [ {'device_name': l2gw_device['device_name'], 'interfaces': [ {'name': l2gw_interface['name'], 'segmentation_id': l2gw_interface['segmentation_id']}] } ] } self._assert_update_succeeded(new_l2gw, attrs, columns, data) def test_update_l2gateway_with_mul_segmentation_ids(self): """Test Update l2gateway with multiple segmentation-ids.""" fake_l2gw = fakes.FakeL2GW.create_l2gw() new_l2gw = copy.deepcopy(fake_l2gw) new_l2gw['devices'][0]['interfaces'][0]['segmentation_id'] = ['42', '43'] self.neutronclient.put = mock.Mock( return_value={osc_l2gw.L2_GATEWAY: new_l2gw}) l2gw_device = new_l2gw['devices'][0] l2gw_interface = l2gw_device['interfaces'][0] arg_list = [ fake_l2gw['id'], '--device', 'name=' + l2gw_device['device_name'] + ',interface_names=' + l2gw_interface['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), ] verify_list = [ ('devices', [ {'interface_names': l2gw_device['interfaces'][0]['name'] + '|' + '#'.join(l2gw_interface['segmentation_id']), 'name': l2gw_device['device_name']}]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = { 'devices': [ {'device_name': l2gw_device['device_name'], 'interfaces': [ {'name': l2gw_interface['name'], 'segmentation_id': l2gw_interface['segmentation_id']}] } ] } self._assert_update_succeeded(new_l2gw, attrs, columns, data) ././@PaxHeader0000000000000000000000000000020600000000000010213 xustar00112 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/osc/test_osc_l2gw_connection.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/l2gatewayclient/osc/test_osc_l2gw_connection.0000664000175000017500000001513714572557755034370 0ustar00jamespagejamespage# All Rights Reserved 2018 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 operator from unittest import mock from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from neutronclient.tests.unit.osc.v2 import fakes as test_fakes from networking_l2gw.l2gatewayclient.osc import l2gw_connection as \ osc_l2gw_conn from networking_l2gw.tests.unit.l2gatewayclient.osc import fakes columns_long = tuple(col for col, _, listing_mode in osc_l2gw_conn._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) headers_long = tuple(head for _, head, listing_mode in osc_l2gw_conn._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) sorted_attr_map = sorted(osc_l2gw_conn._attr_map, key=operator.itemgetter(1)) sorted_columns = tuple(col for col, _, _ in sorted_attr_map) sorted_headers = tuple(head for _, head, _ in sorted_attr_map) def _get_data(attrs, columns=sorted_columns): return osc_utils.get_dict_properties(attrs, columns) class TestCreateL2gwConnection(test_fakes.TestNeutronClientOSCV2): columns = ( 'ID', 'L2 GateWay ID', 'Network ID', 'Segmentation ID', 'Tenant' ) def setUp(self): super(TestCreateL2gwConnection, self).setUp() self.cmd = osc_l2gw_conn.CreateL2gwConnection( self.app, self.namespace) self.neutronclient.find_resource = mock.Mock( side_effect=lambda _, name_or_id, *x: {'id': name_or_id}) def test_create_l2gateway_connection(self): """Test Create l2gateway-connection.""" fake_connection = fakes.FakeL2GWConnection.create_l2gw_connection() self.neutronclient.post = mock.Mock( return_value={ osc_l2gw_conn.L2_GATEWAY_CONNECTION: fake_connection }) arg_list = [ fake_connection['l2_gateway_id'], fake_connection['network_id'], '--default-segmentation-id', fake_connection['segmentation_id'] ] verify_list = [ ('gateway_name', fake_connection['l2_gateway_id']), ('network', fake_connection['network_id']), ('seg_id', fake_connection['segmentation_id']) ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) self.neutronclient.post.assert_called_once_with( osc_l2gw_conn.object_path, {osc_l2gw_conn.L2_GATEWAY_CONNECTION: {'segmentation_id': fake_connection['segmentation_id'], 'network_id': fake_connection['network_id'], 'l2_gateway_id': fake_connection['l2_gateway_id'] } } ) self.assertEqual(self.columns, columns) self.assertItemEqual(_get_data(fake_connection), data) class TestListL2gwConnection(test_fakes.TestNeutronClientOSCV2): def setUp(self): super(TestListL2gwConnection, self).setUp() self.cmd = osc_l2gw_conn.ListL2gwConnection(self.app, self.namespace) def test_list_l2gateway_connection(self): """Test List l2gateway-connections.""" fake_connections = fakes.FakeL2GWConnection.create_l2gw_connections( count=3) self.neutronclient.list = mock.Mock(return_value=fake_connections) arg_list = [] verify_list = [] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.neutronclient.list.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertListItemEqual( list(data), [_get_data(fake_connection, columns_long) for fake_connection in fake_connections[osc_l2gw_conn.L2_GATEWAY_CONNECTIONS]] ) class TestShowL2gwConnection(test_fakes.TestNeutronClientOSCV2): def setUp(self): super(TestShowL2gwConnection, self).setUp() self.cmd = osc_l2gw_conn.ShowL2gwConnection(self.app, self.namespace) self.neutronclient.find_resource = mock.Mock( side_effect=lambda _, name_or_id, *x: {'id': name_or_id}) def test_show_l2gateway_connection(self): """Test Show l2gateway-connection.""" fake_connection = fakes.FakeL2GWConnection.create_l2gw_connection() self.neutronclient.get = mock.Mock( return_value={ osc_l2gw_conn.L2_GATEWAY_CONNECTION: fake_connection }) arg_list = [fake_connection['id']] verify_list = [ (osc_l2gw_conn.L2_GATEWAY_CONNECTION, fake_connection['id']) ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.neutronclient.get.assert_called_once_with( osc_l2gw_conn.resource_path % fake_connection['id']) self.assertEqual(sorted_headers, headers) self.assertItemEqual(_get_data(fake_connection), data) class TestDeleteL2gwConnection(test_fakes.TestNeutronClientOSCV2): def setUp(self): super(TestDeleteL2gwConnection, self).setUp() self.neutronclient.find_resource = mock.Mock( side_effect=lambda _, name_or_id: {'id': name_or_id}) self.cmd = osc_l2gw_conn.DeleteL2gwConnection(self.app, self.namespace) def test_delete_l2gateway_connection(self): """Test Delete l2gateway-connection.""" fake_connection = fakes.FakeL2GWConnection.create_l2gw_connection() self.neutronclient.delete = mock.Mock(return_value=fake_connection) arg_list = [fake_connection['id']] verify_list = [ (osc_l2gw_conn.L2_GATEWAY_CONNECTIONS, [fake_connection['id']]) ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) result = self.cmd.take_action(parsed_args) self.neutronclient.delete.assert_called_once_with( osc_l2gw_conn.resource_path % fake_connection['id']) self.assertIsNone(result) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/0000775000175000017500000000000014572557757025326 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/__init__.py0000664000175000017500000000000014572557755027423 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/0000775000175000017500000000000014572557757027225 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/__init__.py0000664000175000017500000000000014572557755031322 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/0000775000175000017500000000000014572557757030323 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/__init__.py0000664000175000017500000000000014572557755032420 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/0000775000175000017500000000000014572557757031440 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/__init__.py0000664000175000017500000000000014572557755033535 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000021500000000000010213 xustar00119 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_base_connection.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_base_conn0000664000175000017500000003552014572557755034354 0ustar00jamespagejamespage# 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 os.path import socket import ssl import time from unittest import mock import eventlet from neutron.tests import base from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils from networking_l2gw.services.l2gateway.agent import l2gateway_config as conf from networking_l2gw.services.l2gateway.agent.ovsdb import base_connection from networking_l2gw.services.l2gateway.agent.ovsdb import manager from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import constants as n_const LOG = logging.getLogger(__name__) class FakeConf(object): def __init__(self): self.use_ssl = False self.ovsdb_ip = '1.1.1.1' self.ovsdb_port = 6632 class FakeDecodeClass(object): def __init__(self, fake_data): self.fake_data = fake_data def decode(self, utf8): return self.fake_data class SocketClass(object): def __init__(self, connect_error=None, send_error=None, recv_error=None, rcv_data=None): self.connect_error = connect_error self.rcv_data = rcv_data self.send_error = send_error self.recv_error = recv_error def connect(self, ip_port): if self.connect_error: raise self.connect_error def send(self, data): if self.send_error: raise self.send_error return len(data) def recv(self, length): if self.recv_error: raise self.recv_error return self.rcv_data def close(self): pass class TestBaseConnection(base.BaseTestCase): def setUp(self): super(TestBaseConnection, self).setUp() self.conf = mock.patch.object(conf, 'L2GatewayConfig').start() config.register_ovsdb_opts_helper(cfg.CONF) cfg.CONF.set_override('max_connection_retries', 0, 'ovsdb') self.sock = mock.patch('socket.socket').start() self.ssl_sock = mock.patch.object(ssl, 'wrap_socket').start() self.l2gw_ovsdb = base_connection.BaseConnection(mock.Mock(), self.conf) self.op_id = 'abcd' self.fake_message = {'id': self.op_id, 'fake_key': 'fake_value'} self.l2gw_ovsdb.responses = [self.fake_message] def test_init(self): """Test case to test __init__.""" fakesocket = SocketClass() with mock.patch.object(base_connection.LOG, 'debug') as logger_call, \ mock.patch.object(socket, 'socket', return_value=fakesocket): self.l2gw_ovsdb.__init__(mock.Mock(), self.conf) self.assertTrue(self.l2gw_ovsdb.connected) self.assertTrue(logger_call.called) self.assertTrue(self.sock.called) def test_init_with_socket_error(self): """Test case to test __init__ with socket error exception.""" fakesocket = SocketClass(socket.error) with mock.patch.object(base_connection.LOG, 'exception') as logger_exc, \ mock.patch.object(base_connection.LOG, 'warning') as logger_warn, \ mock.patch.object(socket, 'socket', return_value=fakesocket) as sock_connect, \ mock.patch.object(time, 'sleep'): ovsdb_conf = FakeConf() self.assertRaises(socket.error, base_connection.BaseConnection, cfg.CONF.ovsdb, ovsdb_conf) self.assertTrue(logger_warn.called) self.assertTrue(logger_exc.called) self.assertTrue(sock_connect.called) def test_init_with_timeout(self): """Test case to test __init__ with socket timeout exception.""" fakesocket = SocketClass(socket.timeout) with mock.patch.object(base_connection.LOG, 'exception') as logger_exc, \ mock.patch.object(base_connection.LOG, 'warning') as logger_warn, \ mock.patch.object(socket, 'socket', return_value=fakesocket) as sock_connect, \ mock.patch.object(time, 'sleep'): ovsdb_conf = FakeConf() self.assertRaises(socket.timeout, base_connection.BaseConnection, cfg.CONF.ovsdb, ovsdb_conf) self.assertTrue(logger_warn.called) self.assertTrue(logger_exc.called) self.assertTrue(sock_connect.called) def test_response(self): """Test case to test _response.""" response = self.l2gw_ovsdb._response(self.op_id) self.assertIsNotNone(response) self.assertEqual(response, self.fake_message) def test_send(self): """Test case to test send.""" with mock.patch.object(self.l2gw_ovsdb.socket, 'send', side_effect=Exception) as send: with mock.patch.object(base_connection.LOG, 'exception'): with mock.patch.object(self.l2gw_ovsdb, 'disconnect') as mock_disconnect: with mock.patch.object(base_connection.LOG, 'warning'): self.l2gw_ovsdb.send(mock.Mock()) self.assertTrue(send.called) self.assertTrue(mock_disconnect.called) def test_disconnect(self): """Test case to test disconnect socket.""" self.l2gw_ovsdb.monitor = True with mock.patch.object(self.l2gw_ovsdb.socket, 'close') as sock_close: self.l2gw_ovsdb.disconnect() self.assertTrue(sock_close.called) self.assertFalse(self.l2gw_ovsdb.connected) class TestBaseConnection_with_enable_manager(base.BaseTestCase): def setUp(self): super(TestBaseConnection_with_enable_manager, self).setUp() self.conf = mock.patch.object(conf, 'L2GatewayConfig').start() config.register_ovsdb_opts_helper(cfg.CONF) cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.mgr = mock.patch.object(manager, 'OVSDBManager').start() self.l2gw_ovsdb_conn = base_connection.BaseConnection( mock.Mock(), self.conf, self.mgr) self.mock_sock = mock.patch('socket.socket').start() self.l2gw_ovsdb_conn.s = mock.patch('socket.socket').start() self.fakesocket = SocketClass() self.fake_ip = 'fake_ip' self.l2gw_ovsdb_conn.ovsdb_conn_list = ['fake_ip'] self.l2gw_ovsdb_conn.ovsdb_dicts = {'fake_ip': self.fakesocket} def test_init_with_enable_manager(self): fake_dict = {} with mock.patch.object(eventlet.greenthread, 'spawn') as (mock_thread): self.l2gw_ovsdb_conn.__init__(mock.Mock(), self.conf) self.assertEqual(mock.ANY, self.l2gw_ovsdb_conn.s) self.assertFalse(self.l2gw_ovsdb_conn.check_sock_rcv) self.assertIsNone(self.l2gw_ovsdb_conn.check_c_sock) self.assertEqual(fake_dict, self.l2gw_ovsdb_conn.ovsdb_dicts) self.assertEqual(fake_dict, self.l2gw_ovsdb_conn.ovsdb_fd_states) self.assertEqual(6632, self.l2gw_ovsdb_conn.manager_table_listening_port) self.assertTrue(mock_thread.called) def test_send_monitor_msg_to_ovsdb_connection(self): fake_ip = 'fake_ip' self.l2gw_ovsdb_conn.ovsdb_fd_states = {fake_ip: 'fake_status'} self.l2gw_ovsdb_conn.mgr.l2gw_agent_type = n_const.MONITOR self.l2gw_ovsdb_conn.ovsdb_conn_list = [fake_ip] with mock.patch.object(eventlet.greenthread, 'spawn_n') as ( mock_thread): self.l2gw_ovsdb_conn._send_monitor_msg_to_ovsdb_connection( fake_ip) self.assertTrue(mock_thread.called) def test_exception_for_send_monitor_msg_to_ovsdb_connection(self): fake_ip = 'fake_ip' self.l2gw_ovsdb_conn.ovsdb_fd_states = {fake_ip: 'fake_status'} self.l2gw_ovsdb_conn.mgr.l2gw_agent_type = n_const.MONITOR self.l2gw_ovsdb_conn.ovsdb_conn_list = [fake_ip] with mock.patch.object(eventlet.greenthread, 'spawn_n', side_effect=Exception) as mock_thread, \ mock.patch.object(base_connection.LOG, 'warning') as mock_warning, \ mock.patch.object(self.l2gw_ovsdb_conn, 'disconnect') as mock_disconnect: self.l2gw_ovsdb_conn._send_monitor_msg_to_ovsdb_connection( fake_ip) self.assertTrue(mock_thread.called) self.assertTrue(mock_warning.called) mock_disconnect.assert_called_with(fake_ip) def test_echo_response(self): fake_resp = {"method": "echo", "params": "fake_params", "id": "fake_id", } with mock.patch.object(eventlet.greenthread, 'sleep') as fake_thread, \ mock.patch.object(jsonutils, 'loads', return_value=fake_resp) as mock_loads, \ mock.patch.object(self.fakesocket, 'recv', return_value=fake_resp) as mock_sock_rcv, \ mock.patch.object(self.fakesocket, 'send') as mock_sock_send: self.l2gw_ovsdb_conn._echo_response(self.fake_ip) self.assertTrue(fake_thread.called) self.assertTrue(mock_sock_rcv.called) mock_loads.assert_called_with(fake_resp) self.assertTrue(self.l2gw_ovsdb_conn.check_c_sock) self.assertTrue(mock_sock_send.called) def test_common_sock_rcv_thread_none(self): with mock.patch.object(base_connection.BaseConnection, '_echo_response') as mock_resp, \ mock.patch.object(eventlet.greenthread, 'sleep' ) as green_thrd_sleep, \ mock.patch.object(self.fakesocket, 'recv', return_value=None) as mock_rcv, \ mock.patch.object(base_connection.BaseConnection, 'disconnect') as mock_disconnect: self.l2gw_ovsdb_conn.check_c_sock = True self.l2gw_ovsdb_conn.read_on = True self.l2gw_ovsdb_conn._common_sock_rcv_thread(self.fake_ip) self.assertTrue(mock_resp.called) self.assertTrue(green_thrd_sleep.called) self.assertTrue(mock_rcv.called) self.assertTrue(mock_disconnect.called) self.assertFalse(self.l2gw_ovsdb_conn.connected) self.assertFalse(self.l2gw_ovsdb_conn.read_on) def test_disconnect_with_enable_manager(self): fake_ip = 'fake_ip' self.l2gw_ovsdb_conn.ovsdb_fd_states = {fake_ip: 'fake_status'} self.l2gw_ovsdb_conn.ovsdb_conn_list = [fake_ip] with mock.patch.object(self.fakesocket, 'close') as (mock_close): self.l2gw_ovsdb_conn.disconnect(fake_ip) self.assertTrue(mock_close.called) self.assertNotIn(fake_ip, self.l2gw_ovsdb_conn.ovsdb_fd_states) self.assertNotIn(fake_ip, self.l2gw_ovsdb_conn.ovsdb_conn_list) def test_get_ovsdb_ip_mapping(self): expected_ovsdb_ip_mapping = {'10.10.10.10': 'ovsdb1'} cfg.CONF.set_override('ovsdb_hosts', 'ovsdb1:10.10.10.10:6632', 'ovsdb') return_mapping = self.l2gw_ovsdb_conn._get_ovsdb_ip_mapping() self.assertEqual(expected_ovsdb_ip_mapping, return_mapping) def test_is_ssl_configured(self): self.l2gw_ovsdb_conn.ip_ovsdb_mapping = {'10.10.10.10': 'ovsdb1'} cfg.CONF.set_override('l2_gw_agent_priv_key_base_path', '/home', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_cert_base_path', '/home', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_ca_cert_base_path', '/home', 'ovsdb') with mock.patch.object(os.path, 'isfile', return_value=True) as mock_isfile, \ mock.patch.object(base_connection.LOG, 'error') as mock_error, \ mock.patch.object(ssl, 'wrap_socket') as mock_wrap_sock: self.l2gw_ovsdb_conn._is_ssl_configured('10.10.10.10', self.mock_sock) self.assertTrue(mock_isfile.called) self.assertTrue(mock_wrap_sock.called) self.assertFalse(mock_error.called) mock_wrap_sock.assert_called_with( self.mock_sock, server_side=True, keyfile='/home/ovsdb1.key', certfile='/home/ovsdb1.cert', ssl_version=ssl.PROTOCOL_SSLv23, ca_certs='/home/ovsdb1.ca_cert') def test_is_ssl_configured_for_certs_not_found(self): self.l2gw_ovsdb_conn.ip_ovsdb_mapping = {'10.10.10.10': 'ovsdb1'} cfg.CONF.set_override('l2_gw_agent_priv_key_base_path', '/home/', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_cert_base_path', '/home/', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_ca_cert_base_path', '/home/', 'ovsdb') with mock.patch.object(os.path, 'isfile', return_value=False) as mock_isfile, \ mock.patch.object(base_connection.LOG, 'error' ) as mock_error, \ mock.patch.object(ssl, 'wrap_socket') as mock_wrap_sock: self.l2gw_ovsdb_conn._is_ssl_configured('10.10.10.10', self.mock_sock) self.assertTrue(mock_isfile.called) self.assertFalse(mock_wrap_sock.called) self.assertTrue(mock_error.called) ././@PaxHeader0000000000000000000000000000020500000000000010212 xustar00111 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_manager.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_manager.p0000664000175000017500000007520414572557755034300 0ustar00jamespagejamespage# 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 os.path import socket from unittest import mock import eventlet from neutron.agent import rpc as agent_rpc from neutron.conf.agent import common as agent_config from neutron.tests import base from neutron_lib import context from neutron_lib import rpc from oslo_config import cfg from oslo_service import loopingcall from networking_l2gw.services.l2gateway.agent import agent_api from networking_l2gw.services.l2gateway.agent import base_agent_manager from networking_l2gw.services.l2gateway.agent import l2gateway_config from networking_l2gw.services.l2gateway.agent.ovsdb import manager from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_common_class from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_monitor from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_writer from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import constants as n_const class TestManager(base.BaseTestCase): def setUp(self): super(TestManager, self).setUp() self.conf = cfg.CONF config.register_ovsdb_opts_helper(self.conf) agent_config.register_agent_state_opts_helper(self.conf) self.driver_mock = mock.Mock() self.fake_record_dict = {n_const.OVSDB_IDENTIFIER: 'fake_ovsdb_id'} cfg.CONF.set_override('report_interval', 1, 'AGENT') self.plugin_rpc = mock.patch.object(agent_api, 'L2GatewayAgentApi').start() self.context = mock.Mock self.cntxt = mock.patch.object(context, 'get_admin_context_without_session' ).start() self.test_rpc = mock.patch.object(rpc, 'get_client').start() self.mock_looping_call = mock.patch.object(loopingcall, 'FixedIntervalLoopingCall' ).start() self.l2gw_agent_manager = manager.OVSDBManager( self.conf) self.l2gw_agent_manager.plugin_rpc = self.plugin_rpc self.fake_config_json = {n_const.OVSDB_IDENTIFIER: 'fake_ovsdb_identifier', 'ovsdb_ip': '5.5.5.5', 'ovsdb_port': '6672', 'private_key': 'dummy_key', 'enable_ssl': False, 'certificate': 'dummy_cert', 'ca_cert': 'dummy_ca'} def test_process_ovsdb_host(self): fake_host = "ovsdb1:10.10.10.10:6632" cfg.CONF.set_override('l2_gw_agent_priv_key_base_path', '/home/someuser/fakedir', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_cert_base_path', '/home/someuser/fakedir', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_ca_cert_base_path', '/home/someuser/fakedir', 'ovsdb') with mock.patch.object(os.path, 'isfile', return_value=True) as mock_isfile, \ mock.patch.object(manager.LOG, 'exception') as mock_log_exc: self.l2gw_agent_manager._process_ovsdb_host(fake_host) self.assertTrue(mock_isfile.called) self.assertFalse(mock_log_exc.called) def test_process_ovsdb_host_for_certs_not_found(self): fake_host = "ovsdb1:10.10.10.10:6632" cfg.CONF.set_override('l2_gw_agent_priv_key_base_path', '/home/someuser/fakedir', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_cert_base_path', '/home/someuser/fakedir', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_ca_cert_base_path', '/home/someuser/fakedir', 'ovsdb') with mock.patch.object(os.path, 'isfile', return_value=False) as mock_isfile, \ mock.patch.object(manager.LOG, 'exception') as mock_log_exc: self.l2gw_agent_manager._process_ovsdb_host(fake_host) self.assertTrue(mock_isfile.called) self.assertTrue(mock_log_exc.called) def test_extract_ovsdb_config(self): fake_ovsdb_config = {n_const.OVSDB_IDENTIFIER: 'host2', 'ovsdb_ip': '10.10.10.10', 'ovsdb_port': '6632', 'private_key': '/home/someuser/fakedir/host2.key', 'use_ssl': True, 'certificate': '/home/someuser/fakedir/host2.cert', 'ca_cert': '/home/someuser/fakedir/host2.ca_cert'} cfg.CONF.set_override('ovsdb_hosts', 'host2:10.10.10.10:6632', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_priv_key_base_path', '/home/someuser/fakedir', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_cert_base_path', '/home/someuser/fakedir', 'ovsdb') cfg.CONF.set_override('l2_gw_agent_ca_cert_base_path', '/home/someuser/fakedir', 'ovsdb') self.l2gw_agent_manager._extract_ovsdb_config(cfg.CONF) l2gwconfig = l2gateway_config.L2GatewayConfig(fake_ovsdb_config) gw = self.l2gw_agent_manager.gateways.get( fake_ovsdb_config[n_const.OVSDB_IDENTIFIER]) self.assertEqual(l2gwconfig.ovsdb_identifier, gw.ovsdb_identifier) self.assertEqual(l2gwconfig.use_ssl, gw.use_ssl) self.assertEqual(l2gwconfig.ovsdb_ip, gw.ovsdb_ip) self.assertEqual(l2gwconfig.ovsdb_port, gw.ovsdb_port) self.assertEqual(l2gwconfig.private_key, gw.private_key) self.assertEqual(l2gwconfig.certificate, gw.certificate) self.assertEqual(l2gwconfig.ca_cert, gw.ca_cert) cfg.CONF.set_override('max_connection_retries', 25, 'ovsdb') self.assertRaises(SystemExit, self.l2gw_agent_manager. _extract_ovsdb_config, cfg.CONF) def test_connect_to_ovsdb_server(self): self.l2gw_agent_manager.gateways = {} self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR gateway = l2gateway_config.L2GatewayConfig(self.fake_config_json) ovsdb_ident = self.fake_config_json.get(n_const.OVSDB_IDENTIFIER) self.l2gw_agent_manager.gateways[ovsdb_ident] = gateway with mock.patch.object(ovsdb_monitor, 'OVSDBMonitor') as ovsdb_connection, \ mock.patch.object(eventlet.greenthread, 'spawn_n') as event_spawn, \ mock.patch.object(manager.OVSDBManager, 'agent_to_plugin_rpc') as call_back, \ mock.patch.object(self.plugin_rpc, 'notify_ovsdb_states') as notify: self.l2gw_agent_manager._connect_to_ovsdb_server() self.assertTrue(event_spawn.called) self.assertTrue(ovsdb_connection.called) ovsdb_connection.assert_called_with( self.conf.ovsdb, gateway, call_back) notify.assert_called_once_with(mock.ANY, mock.ANY) def test_connect_to_ovsdb_server_with_exc(self): self.l2gw_agent_manager.gateways = {} self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR gateway = l2gateway_config.L2GatewayConfig(self.fake_config_json) ovsdb_ident = self.fake_config_json.get(n_const.OVSDB_IDENTIFIER) self.l2gw_agent_manager.gateways[ovsdb_ident] = gateway with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '__init__', side_effect=socket.error ), \ mock.patch.object(eventlet.greenthread, 'spawn_n') as event_spawn, \ mock.patch.object(manager.LOG, 'error'): self.l2gw_agent_manager._connect_to_ovsdb_server() event_spawn.assert_not_called() def test_handle_report_state_failure(self): self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR with mock.patch.object(self.l2gw_agent_manager, '_disconnect_all_ovsdb_servers' ) as mock_disconnect_ovsdb_servers, \ mock.patch.object(agent_rpc.PluginReportStateAPI, 'report_state', side_effect=Exception), \ mock.patch.object(base_agent_manager.LOG, 'exception'), \ mock.patch.object(self.l2gw_agent_manager, '_stop_looping_task') as mock_stop_looping: self.l2gw_agent_manager._report_state() self.assertEqual(self.l2gw_agent_manager.l2gw_agent_type, '') self.assertEqual(self.l2gw_agent_manager. agent_state.get('configurations' )[n_const.L2GW_AGENT_TYPE ], '') self.assertTrue(mock_disconnect_ovsdb_servers.called) self.assertTrue(mock_stop_looping.called) def test_is_valid_request_fails(self): self.l2gw_agent_manager.gateways = {} fake_ovsdb_identifier = 'fake_ovsdb_identifier_2' gateway = l2gateway_config.L2GatewayConfig(self.fake_config_json) self.l2gw_agent_manager.gateways['fake_ovsdb_identifier'] = gateway with mock.patch.object(manager.LOG, 'warning') as logger_call: self.l2gw_agent_manager._is_valid_request( fake_ovsdb_identifier) self.assertEqual(1, logger_call.call_count) def test_open_connection(self): self.l2gw_agent_manager.gateways = {} fake_ovsdb_identifier = 'fake_ovsdb_identifier' gateway = l2gateway_config.L2GatewayConfig(self.fake_config_json) self.l2gw_agent_manager.gateways['fake_ovsdb_identifier'] = gateway with mock.patch.object(manager.LOG, 'warning') as logger_call: with mock.patch.object(ovsdb_writer, 'OVSDBWriter') as ovsdb_connection: is_valid_request = self.l2gw_agent_manager._is_valid_request( fake_ovsdb_identifier) with self.l2gw_agent_manager._open_connection( fake_ovsdb_identifier): self.assertTrue(is_valid_request) self.assertEqual(0, logger_call.call_count) self.assertTrue(ovsdb_connection.called) def test_open_connection_with_socket_error(self): self.l2gw_agent_manager.gateways = {} gateway = l2gateway_config.L2GatewayConfig(self.fake_config_json) self.l2gw_agent_manager.gateways['fake_ovsdb_identifier'] = gateway with mock.patch.object(manager.LOG, 'warning') as logger_call, \ mock.patch.object(socket.socket, 'connect') as mock_connect, \ mock.patch.object(ovsdb_writer.OVSDBWriter, 'delete_logical_switch') as mock_del_ls: mock_connect.side_effect = socket.error self.l2gw_agent_manager.delete_network(self.context, mock.Mock(), mock.Mock()) self.assertEqual(1, logger_call.call_count) self.assertFalse(mock_del_ls.called) def test_init_with_enable_manager(self): cfg.CONF.set_override('enable_manager', True, 'ovsdb') with mock.patch.object(manager.OVSDBManager, '_sock_open_connection' ) as mock_sock_open_conn, \ mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') as mock_loop: self.l2gw_agent_manager.__init__() self.assertTrue(mock_sock_open_conn.called) self.assertTrue(mock_loop.called) def test_sock_open_connection(self): cfg.CONF.set_override('enable_manager', True, 'ovsdb') with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class') as mock_ovsdb_common: self.l2gw_agent_manager.__init__() self.l2gw_agent_manager._sock_open_connection() self.assertTrue(mock_ovsdb_common.called) def test_set_monitor_agent_with_ovsdb_fd_None(self): cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.conf.host = 'fake_host' self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR self.l2gw_agent_manager.ovsdb_fd = None with mock.patch.object(manager.OVSDBManager, '_sock_open_connection') as mock_open_conn: self.l2gw_agent_manager.set_monitor_agent(self.context, 'fake_host') self.assertTrue(mock_open_conn.called) def test_set_monitor_agent_with_ovsdb_fd_not_None(self): cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.conf.host = 'fake_host' self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class') as mock_ovsdb_common, \ mock.patch.object(eventlet.greenthread, 'spawn_n') as mock_thread, \ mock.patch.object( self.l2gw_agent_manager, '_start_looping_task_ovsdb_states') as mock_looping: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.ovsdb_fd.check_monitor_table_thread = False self.l2gw_agent_manager.ovsdb_fd.check_sock_rcv = True self.l2gw_agent_manager.ovsdb_fd.ovsdb_conn_list = ["fake_ip"] self.l2gw_agent_manager.ovsdb_fd.ovsdb_dicts = { "fake_ip": "fake_sock"} self.l2gw_agent_manager.set_monitor_agent(self.context, 'fake_host') self.assertTrue(mock_thread.called) self.assertTrue(mock_looping.called) def test_update_connection_to_gateway_for_monitor_agent(self): """Test case to test update_connection_to_gateway for monitor agent with enable_manager. """ cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR fake_op_method = 'CREATE' with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class') as mock_ovsdb_common: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.update_connection_to_gateway( self.context, mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), fake_op_method) (self.l2gw_agent_manager.ovsdb_fd.update_connection_to_gateway. assert_called_with(mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, fake_op_method, False)) def test_update_connection_to_gateway_for_transact_agent(self): """Test case to test update_connection_to_gateway with enable_manager. """ cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = '' fake_op_method = 'CREATE' with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class' ) as mock_ovsdb_common, \ mock.patch.object(manager.OVSDBManager, '_sock_open_connection') as mock_open_conn: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.ovsdb_fd.ovsdb_conn_list = ['fake_ip'] self.l2gw_agent_manager.update_connection_to_gateway( self.context, 'fake_ip', mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), fake_op_method) self.assertTrue(mock_open_conn.called) (self.l2gw_agent_manager.ovsdb_fd.update_connection_to_gateway. assert_called_with(mock.ANY, mock.ANY, mock.ANY, mock.ANY, 'fake_ip', fake_op_method, False)) def test_update_connection_to_gateway_for_enable_manager_false(self): """Test case to test update_connection_to_gateway with enable_manager=False. """ cfg.CONF.set_override('enable_manager', False, 'ovsdb') self.l2gw_agent_manager.__init__() fake_op_method = 'CREATE' with mock.patch.object(self.l2gw_agent_manager, '_is_valid_request', return_value=True ) as mock_valid_req, \ mock.patch.object(ovsdb_writer, 'OVSDBWriter' ) as mock_ovsdb_fd: self.l2gw_agent_manager.update_connection_to_gateway( self.context, 'fake_ovsdb_id', "fake_logical_switch_dict", "fake_locator_dicts", "fake_mac_dicts", "fake_port_dicts", fake_op_method) ovsdb_sock_fd = mock_ovsdb_fd.return_value mock_valid_req.assert_called_with('fake_ovsdb_id') (ovsdb_sock_fd.update_connection_to_gateway. assert_called_with("fake_logical_switch_dict", "fake_locator_dicts", "fake_mac_dicts", "fake_port_dicts", "fake_ovsdb_id", fake_op_method)) def test_delete_network_for_monitor_agent(self): """Test case to test delete_network with enable_manager.""" cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class') as mock_ovsdb_common: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.delete_network( self.context, mock.Mock(), "fake_logical_switch_uuid") (self.l2gw_agent_manager.ovsdb_fd.delete_logical_switch. assert_called_with("fake_logical_switch_uuid", mock.ANY, False)) def test_delete_network_for_enable_manager_false(self): """Test case to test delete_network with enable_manager=False.""" cfg.CONF.set_override('enable_manager', False, 'ovsdb') self.l2gw_agent_manager.__init__() with mock.patch.object(self.l2gw_agent_manager, '_is_valid_request', return_value=True ) as mock_valid_req, \ mock.patch.object(ovsdb_writer, 'OVSDBWriter' ) as mock_ovsdb_fd: self.l2gw_agent_manager.delete_network( self.context, 'fake_ovsdb_id', "fake_logical_switch_uuid") ovsdb_sock_fd = mock_ovsdb_fd.return_value mock_valid_req.assert_called_with('fake_ovsdb_id') (ovsdb_sock_fd.delete_logical_switch. assert_called_with("fake_logical_switch_uuid", "fake_ovsdb_id")) def test_delete_network_for_transact_agent(self): """Test case to test delete_network with enable_manager.""" cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = '' with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class' ) as mock_ovsdb_common, \ mock.patch.object(manager.OVSDBManager, '_sock_open_connection') as mock_open_conn: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.ovsdb_fd.ovsdb_conn_list = ['fake_ip'] self.l2gw_agent_manager.delete_network( self.context, 'fake_ip', "fake_logical_switch_uuid") self.assertTrue(mock_open_conn.called) (self.l2gw_agent_manager.ovsdb_fd.delete_logical_switch. assert_called_with("fake_logical_switch_uuid", 'fake_ip', False)) def test_add_vif_to_gateway_for_monitor_agent(self): """Test case to test add_vif_to_gateway with enable_manager.""" cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class') as mock_ovsdb_common: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.add_vif_to_gateway( self.context, mock.Mock(), "fake_logical_switch_dict", "fake_locator_dict", "fake_mac_dict") (self.l2gw_agent_manager.ovsdb_fd.insert_ucast_macs_remote. assert_called_with("fake_logical_switch_dict", "fake_locator_dict", "fake_mac_dict", mock.ANY, False)) def test_add_vif_to_gateway_for_transact_agent(self): """Test case to test add_vif_to_gateway with enable_manager.""" cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = '' with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class' ) as mock_ovsdb_common, \ mock.patch.object(manager.OVSDBManager, '_sock_open_connection') as mock_open_conn: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.ovsdb_fd.check_c_sock = True self.l2gw_agent_manager.ovsdb_fd.ovsdb_conn_list = ['fake_ip'] self.l2gw_agent_manager.add_vif_to_gateway( self.context, 'fake_ip', "fake_logical_switch_dict", "fake_locator_dict", "fake_mac_dict") self.assertTrue(mock_open_conn.called) (self.l2gw_agent_manager.ovsdb_fd.insert_ucast_macs_remote. assert_called_with("fake_logical_switch_dict", "fake_locator_dict", "fake_mac_dict", 'fake_ip', False)) def test_add_vif_to_gateway_for_enable_manager_false(self): """Test case to test add_vif_to_gateway with enable_manager=False. """ cfg.CONF.set_override('enable_manager', False, 'ovsdb') self.l2gw_agent_manager.__init__() with mock.patch.object(self.l2gw_agent_manager, '_is_valid_request', return_value=True) as mock_valid_req, \ mock.patch.object(ovsdb_writer, 'OVSDBWriter' ) as mock_ovsdb_fd: self.l2gw_agent_manager.add_vif_to_gateway( self.context, 'fake_ovsdb_id', "fake_logical_switch_dict", "fake_locator_dict", "fake_mac_dict") ovsdb_sock_fd = mock_ovsdb_fd.return_value mock_valid_req.assert_called_with('fake_ovsdb_id') (ovsdb_sock_fd.insert_ucast_macs_remote. assert_called_with("fake_logical_switch_dict", "fake_locator_dict", "fake_mac_dict", "fake_ovsdb_id")) def test_delete_vif_from_gateway_for_monitor_agent(self): """Test case to test delete_vif_to_gateway with enable_manager.""" cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class') as mock_ovsdb_common: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.delete_vif_from_gateway( self.context, mock.Mock(), "fake_logical_switch_uuid", "fake_mac") (self.l2gw_agent_manager.ovsdb_fd.delete_ucast_macs_remote. assert_called_with("fake_logical_switch_uuid", "fake_mac", mock.ANY, False)) def test_delete_vif_from_gateway_for_transact_agent(self): """Test case to test delete_vif_from_gateway with enable_manager.""" cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = '' with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class' ) as mock_ovsdb_common, \ mock.patch.object(manager.OVSDBManager, '_sock_open_connection') as mock_open_conn: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.ovsdb_fd.ovsdb_conn_list = ['fake_ip'] self.l2gw_agent_manager.delete_vif_from_gateway( self.context, 'fake_ip', "fake_logical_switch_uuid", "fake_mac") self.assertTrue(mock_open_conn.called) (self.l2gw_agent_manager.ovsdb_fd.delete_ucast_macs_remote. assert_called_with("fake_logical_switch_uuid", "fake_mac", 'fake_ip', False)) def test_delete_vif_from_gateway_for_enable_manager_false(self): """Test case to test delete_vif_from_gateway with enable_manager=False. """ cfg.CONF.set_override('enable_manager', False, 'ovsdb') self.l2gw_agent_manager.__init__() with mock.patch.object(self.l2gw_agent_manager, '_is_valid_request', return_value=True ) as mock_valid_req, \ mock.patch.object(ovsdb_writer, 'OVSDBWriter' ) as mock_ovsdb_fd: self.l2gw_agent_manager.delete_vif_from_gateway( self.context, 'fake_ovsdb_id', "fake_logical_switch_uuid", "fake_mac") ovsdb_sock_fd = mock_ovsdb_fd.return_value mock_valid_req.assert_called_with('fake_ovsdb_id') (ovsdb_sock_fd.delete_ucast_macs_remote. assert_called_with("fake_logical_switch_uuid", "fake_mac", "fake_ovsdb_id")) def test_update_vif_to_gateway_for_monitor_agent(self): """Test case to test update_vif_to_gateway with enable_manager.""" cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = n_const.MONITOR with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class') as mock_ovsdb_common: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.update_vif_to_gateway( self.context, mock.Mock(), "fake_logical_switch_uuid", "fake_mac") (self.l2gw_agent_manager.ovsdb_fd.update_ucast_macs_remote. assert_called_with( "fake_logical_switch_uuid", "fake_mac", mock.ANY, False)) def test_update_vif_to_gateway_for_transact_agent(self): """Test case to test update_vif_to_gateway with enable_manager. """ cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.l2gw_agent_manager.__init__() self.l2gw_agent_manager.l2gw_agent_type = '' with mock.patch.object(ovsdb_common_class, 'OVSDB_commom_class' ) as mock_ovsdb_common, \ mock.patch.object(manager.OVSDBManager, '_sock_open_connection') as mock_open_conn: self.l2gw_agent_manager.ovsdb_fd = mock_ovsdb_common.return_value self.l2gw_agent_manager.ovsdb_fd.ovsdb_conn_list = ['fake_ip'] self.l2gw_agent_manager.update_vif_to_gateway( self.context, 'fake_ip', "fake_logical_switch_uuid", "fake_mac") self.assertTrue(mock_open_conn.called) (self.l2gw_agent_manager.ovsdb_fd.update_ucast_macs_remote. assert_called_with("fake_logical_switch_uuid", "fake_mac", 'fake_ip', False)) def test_update_vif_to_gateway_for_enable_manager_false(self): """Test case to test update_vif_to_gateway with enable_manager=False. """ cfg.CONF.set_override('enable_manager', False, 'ovsdb') self.l2gw_agent_manager.__init__() with mock.patch.object(self.l2gw_agent_manager, '_is_valid_request', return_value=True ) as mock_valid_req, \ mock.patch.object(ovsdb_writer, 'OVSDBWriter' ) as mock_ovsdb_fd: self.l2gw_agent_manager.update_vif_to_gateway( self.context, 'fake_ovsdb_id', "fake_locator_dict", "fake_mac_dict") ovsdb_sock_fd = mock_ovsdb_fd.return_value mock_valid_req.assert_called_with('fake_ovsdb_id') (ovsdb_sock_fd.update_ucast_macs_remote. assert_called_with("fake_locator_dict", "fake_mac_dict", "fake_ovsdb_id")) ././@PaxHeader0000000000000000000000000000021300000000000010211 xustar00117 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_ovsdb_monitor.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_ovsdb_mon0000664000175000017500000010766114572557755034421 0ustar00jamespagejamespage# 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 socket import ssl from unittest import mock import eventlet from neutron.tests import base from networking_l2gw.services.l2gateway.agent import l2gateway_config as conf from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_monitor from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway import exceptions from networking_l2gw.tests.unit.services.l2gateway.agent.ovsdb import ( test_base_connection as base_test) from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils LOG = logging.getLogger(__name__) class TestOVSDBMonitor(base.BaseTestCase): def setUp(self): super(TestOVSDBMonitor, self).setUp() self.conf = mock.patch.object(conf, 'L2GatewayConfig').start() config.register_ovsdb_opts_helper(cfg.CONF) self.callback = mock.Mock() cfg.CONF.set_override('max_connection_retries', 0, 'ovsdb') self.sock = mock.patch('socket.socket').start() self.ssl_sock = mock.patch.object(ssl, 'wrap_socket').start() self.greenthread = mock.patch.object(eventlet.greenthread, 'spawn_n').start() self.l2gw_ovsdb = ovsdb_monitor.OVSDBMonitor(mock.Mock(), self.conf, self.callback) self.op_id = 'abcd' props = {'select': {'initial': True, 'insert': True, 'delete': True, 'modify': True}} self.monitor_message = {'id': self.op_id, 'method': 'monitor', 'params': [n_const.OVSDB_SCHEMA_NAME, None, {'Logical_Switch': [props], 'Physical_Switch': [props], 'Physical_Port': [props], 'Ucast_Macs_Local': [props], 'Ucast_Macs_Remote': [props], 'Physical_Locator': [props], 'Mcast_Macs_Local': [props], 'Physical_Locator_Set': [props]}]} fake_message = {'Logical_Switch': props, 'Physical_Switch': props, 'Physical_Port': props, 'Ucast_Macs_Local': props, 'Ucast_Macs_Remote': props, 'Physical_Locator': props, 'Mcast_Macs_Local': props, 'Physical_Locator_Set': props} self.msg = self.monitor_message self.msg1 = {'result': fake_message} self.msg2 = {'method': 'update', 'params': ['', fake_message]} self.l2gw_ovsdb.responses = [self.monitor_message] def test_init(self): """Test case to test __init__.""" fakesocket = base_test.SocketClass() with mock.patch.object(ovsdb_monitor.LOG, 'debug'), \ mock.patch.object(eventlet.greenthread, 'spawn') as gt, \ mock.patch.object(socket, 'socket', return_value=fakesocket): self.l2gw_ovsdb.__init__(mock.Mock(), self.conf, self.callback) self.assertTrue(self.l2gw_ovsdb.connected) self.assertTrue(gt.called) self.assertTrue(self.sock.called) def test_setup_dispatch_table(self): expected_dict = {'Logical_Switch': self.l2gw_ovsdb._process_logical_switch, 'Ucast_Macs_Local': self.l2gw_ovsdb._process_ucast_macs_local, 'Physical_Locator': self.l2gw_ovsdb._process_physical_locator, 'Ucast_Macs_Remote': self.l2gw_ovsdb._process_ucast_macs_remote, 'Mcast_Macs_Local': self.l2gw_ovsdb._process_mcast_macs_local, 'Physical_Locator_Set': self.l2gw_ovsdb._process_physical_locator_set } self.l2gw_ovsdb._setup_dispatch_table() self.assertEqual(expected_dict, self.l2gw_ovsdb.dispatch_table) def test_set_monitor_response_handler(self): """Test case to test _set_monitor_response_handler with error_msg.""" self.l2gw_ovsdb.connected = True with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_set_handler') as set_handler, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, 'send', return_value=True) as send, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_response', return_value=(mock.ANY, False) ) as process_resp, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_monitor_msg' ) as process_monitor_msg, \ mock.patch.object(ovsdb_monitor.LOG, 'warning'): self.l2gw_ovsdb.set_monitor_response_handler() self.assertTrue(set_handler.called) self.assertTrue(send.called) self.assertTrue(process_monitor_msg.called) self.assertTrue(process_resp.called) def test_set_monitor_response_handler_with_error_in_send(self): """Test case to test _set_monitor_response_handler.""" self.l2gw_ovsdb.connected = True with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_set_handler') as set_handler, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, 'send', return_value=False) as send, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_response', return_value=(mock.ANY, True)) as process_resp, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_monitor_msg') as process_monitor_msg, \ mock.patch.object(ovsdb_monitor.LOG, 'warning'): self.l2gw_ovsdb.set_monitor_response_handler() self.assertTrue(set_handler.called) self.assertTrue(send.called) self.assertFalse(process_resp.called) self.assertFalse(process_monitor_msg.called) def test_update_event_handler(self): """Test case to test _update_event_handler.""" with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_update_event' ) as process_update_event: self.l2gw_ovsdb._update_event_handler(self.msg, mock.ANY) process_update_event.assert_called_once_with(self.msg, mock.ANY) def test_process_update_event(self): """Test case to test _process_update_event.""" with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_physical_port' ) as proc_phy_port, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_physical_switch' ) as proc_phy_switch, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_logical_switch' ) as proc_logic_switch, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_ucast_macs_local' ) as proc_ucast_mac, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_physical_locator' ) as proc_phy_loc, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_ucast_macs_remote' ) as proc_ucast_mac_remote, \ mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_process_mcast_macs_local' ) as proc_mcast_mac_local, \ mock.patch.object( ovsdb_monitor.OVSDBMonitor, '_process_physical_locator_set') as proc_phys_loc_set: self.l2gw_ovsdb._setup_dispatch_table() self.l2gw_ovsdb._process_update_event(self.msg2, mock.ANY) self.assertTrue(proc_phy_port.called) self.assertTrue(proc_phy_switch.called) self.assertTrue(proc_logic_switch.called) self.assertTrue(proc_ucast_mac.called) self.assertTrue(proc_phy_loc.called) self.assertTrue(proc_ucast_mac_remote.called) self.assertTrue(proc_mcast_mac_local.called) self.assertTrue(proc_phys_loc_set.called) self.assertTrue(self.callback.called) def test_process_response_raise_exception(self): """Test case to test _process_response with exception.""" with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_response') as resp: with mock.patch.object(ovsdb_monitor.LOG, 'debug'): self.assertRaises(exceptions.OVSDBError, self.l2gw_ovsdb._process_response, self.op_id) resp.assert_called_once_with(self.op_id) def test_process_response(self): """Test case to test _process_response.""" with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_response', return_value={'key': 'some_value' }) as resp: with mock.patch.object(ovsdb_monitor.LOG, 'debug'): self.l2gw_ovsdb._process_response(self.op_id) resp.assert_called_once_with(self.op_id) def test_default_echo_handler(self): """Test case to test _default_echo_handler.""" dummy_msg = {'params': 'fake_params', 'id': 'fake_id'} with mock.patch.object(ovsdb_monitor.OVSDBMonitor, 'send') as send: self.l2gw_ovsdb._default_echo_handler(dummy_msg, mock.ANY) self.assertTrue(send.called) def test_set_handler(self): """Test case to test _set_handler.""" dummy_method = "dummy" dummy_handler = "handler" self.l2gw_ovsdb._set_handler(dummy_method, dummy_handler) self.assertEqual(self.l2gw_ovsdb.handlers[dummy_method], dummy_handler) def test_on_remote_message(self): """Test case to test _on_remote_message.""" self.l2gw_ovsdb.handlers = mock.Mock() with mock.patch.object(ovsdb_monitor.LOG, 'debug'): with mock.patch.object(jsonutils, 'loads') as json_load: self.l2gw_ovsdb._on_remote_message(self.msg) self.assertTrue(json_load.called) handler_method = json_load.return_value handler_method.get.assert_called_once_with(mock.ANY, mock.ANY) def test_rcv_thread_none(self): """Test case to test _rcv_thread receives None from socket.""" self.assertTrue(self.l2gw_ovsdb.read_on) with mock.patch.object(self.l2gw_ovsdb.socket, 'recv', return_value=None) as sock_recv: with mock.patch.object(self.l2gw_ovsdb.socket, 'close') as sock_close: self.l2gw_ovsdb._rcv_thread() self.assertTrue(sock_recv.called) self.assertFalse(self.l2gw_ovsdb.connected) self.assertFalse(self.l2gw_ovsdb.read_on) self.assertTrue(sock_close.called) def test_rcv_thread_exception(self): """Test case to test _rcv_thread with exception.""" with mock.patch.object(self.l2gw_ovsdb.socket, 'recv', side_effect=Exception, return_value=None) as sock_recv, \ mock.patch.object(self.l2gw_ovsdb.socket, 'close') as sock_close, \ mock.patch.object(ovsdb_monitor.LOG, 'exception') as logger_call: self.l2gw_ovsdb._rcv_thread() self.assertTrue(logger_call.called) self.assertTrue(sock_recv.called) self.assertFalse(self.l2gw_ovsdb.connected) self.assertFalse(self.l2gw_ovsdb.read_on) self.assertTrue(sock_close.called) def test_form_ovsdb_data(self): some_value = mock.Mock() expect = {n_const.OVSDB_IDENTIFIER: self.conf.ovsdb_identifier, 'new_logical_switches': some_value, 'new_physical_switches': some_value, 'new_physical_ports': some_value, 'new_physical_locators': some_value, 'new_local_macs': some_value, 'new_remote_macs': some_value, 'new_mlocal_macs': some_value, 'new_locator_sets': some_value, 'deleted_logical_switches': some_value, 'deleted_physical_switches': some_value, 'deleted_physical_ports': some_value, 'deleted_physical_locators': some_value, 'deleted_local_macs': some_value, 'deleted_remote_macs': some_value, 'deleted_mlocal_macs': some_value, 'deleted_locator_sets': some_value, 'modified_logical_switches': some_value, 'modified_physical_switches': some_value, 'modified_physical_ports': some_value, 'modified_physical_locators': some_value, 'modified_local_macs': some_value, 'modified_remote_macs': some_value, 'modified_mlocal_macs': some_value, 'modified_locator_sets': some_value} with mock.patch.object(ovsdb_monitor.OVSDBMonitor, '_get_list', return_value=some_value ): result = self.l2gw_ovsdb._form_ovsdb_data(mock.Mock(), mock.ANY) self.assertEqual(expect, result) def test_process_physical_port(self): """Test case to process new physical_port.""" fake_id = 'fake_id' add = {'new': {'uuid': 'fake_id', 'name': 'fake_name', 'physical_switch_id': 'fake_switch_id', 'port_fault_status': 'fake_status', 'vlan_bindings': [["some"], []]}} delete = {'old': {'uuid': 'fake_id_old', 'name': 'fake_name_old', 'physical_switch_id': 'fake_switch_id_old'}} modify = {} modify.update(add) modify.update(delete) port_map = {'fake_id': 'fake_switch_id'} data_dict = {'new_physical_ports': [], 'modified_physical_ports': [], 'deleted_physical_ports': []} with mock.patch.object(ovsdb_schema, 'PhysicalPort') as phy_port: with mock.patch.object(ovsdb_schema, 'VlanBinding'): # test add self.l2gw_ovsdb._process_physical_port(fake_id, add, port_map, data_dict) phy_port.assert_called_once_with( fake_id, 'fake_name', None, None, 'fake_status') self.assertIn(phy_port.return_value, data_dict.get('new_physical_ports')) # test modify self.l2gw_ovsdb._process_physical_port(fake_id, modify, port_map, data_dict) self.assertIn(phy_port.return_value, data_dict.get('modified_physical_ports')) # test delete self.l2gw_ovsdb._process_physical_port(fake_id, delete, port_map, data_dict) self.assertIn(phy_port.return_value, data_dict.get('deleted_physical_ports')) def test_process_physical_port_with_empty_fault_status(self): """Test case to process new physical_port with empty fault status.""" fake_id = 'fake_id' add = {'new': {'uuid': 'fake_id', 'name': 'fake_name', 'physical_switch_id': 'fake_switch_id', 'port_fault_status': ['set', []], 'vlan_bindings': [["some"], []]}} port_map = {'fake_id': 'fake_switch_id'} data_dict = {'new_physical_ports': [], 'modified_physical_ports': [], 'deleted_physical_ports': []} with mock.patch.object(ovsdb_schema, 'PhysicalPort') as phy_port: with mock.patch.object(ovsdb_schema, 'VlanBinding'): # test add self.l2gw_ovsdb._process_physical_port(fake_id, add, port_map, data_dict) phy_port.assert_called_once_with( fake_id, 'fake_name', None, None, None) self.assertIn(phy_port.return_value, data_dict.get('new_physical_ports')) def test_process_physical_switch(self): """Test case to process new physical_switch.""" port_map = {'fake_id': 'fake_switch_id'} with mock.patch.object(ovsdb_schema, 'PhysicalPort') as phy_port: with mock.patch.object(ovsdb_schema, 'PhysicalSwitch') as phy_switch: physical_port = phy_port.return_value fake_id = 'fake_id' add = {'new': {'uuid': 'fake_id', 'name': 'fake_name', 'tunnel_ips': 'fake_tunnel_ip', 'switch_fault_status': 'fake_status', 'ports': ['set', 'set', physical_port]}} delete = {'old': {'uuid': 'fake_id_old', 'name': 'fake_name_old', 'tunnel_ips': 'fake_tunnel_ip_old', 'ports': ['', physical_port]}} modify = {} modify.update(add) modify.update(delete) data_dict = {'new_physical_switches': [], 'modified_physical_switches': [], 'deleted_physical_switches': [], 'new_physical_ports': []} # test add self.l2gw_ovsdb._process_physical_switch(fake_id, add, port_map, data_dict) self.assertIn(phy_switch.return_value, data_dict['new_physical_switches']) phy_switch.assert_called_once_with( 'fake_id', 'fake_name', 'fake_tunnel_ip', 'fake_status') # test modify self.l2gw_ovsdb._process_physical_switch(fake_id, modify, port_map, data_dict) self.assertIn(phy_switch.return_value, data_dict['modified_physical_switches']) # test delete self.l2gw_ovsdb._process_physical_switch(fake_id, delete, port_map, data_dict) self.assertIn(phy_switch.return_value, data_dict['deleted_physical_switches']) def test_process_physical_switch_with_empty_fault_status(self): """Test case to process new physical_switch with empty fault status.""" port_map = {'fake_id': 'fake_switch_id'} with mock.patch.object(ovsdb_schema, 'PhysicalPort') as phy_port: with mock.patch.object(ovsdb_schema, 'PhysicalSwitch') as phy_switch: physical_port = phy_port.return_value fake_id = 'fake_id' add = {'new': {'uuid': 'fake_id', 'name': 'fake_name', 'tunnel_ips': 'fake_tunnel_ip', 'switch_fault_status': ['set', []], 'ports': ['set', 'set', physical_port]}} data_dict = {'new_physical_switches': [], 'modified_physical_switches': [], 'deleted_physical_switches': [], 'new_physical_ports': []} # test add self.l2gw_ovsdb._process_physical_switch(fake_id, add, port_map, data_dict) self.assertIn(phy_switch.return_value, data_dict['new_physical_switches']) phy_switch.assert_called_once_with('fake_id', 'fake_name', 'fake_tunnel_ip', None) def test_process_logical_switch(self): """Test case to process new logical_switch.""" fake_id = 'fake_id' fake_name = 'fake_name' fake_tunnel_key = 'fake_tunnel_key' add = {'new': {'name': 'fake_name', 'tunnel_key': 'fake_tunnel_key'}} delete = {'old': {'name': 'fake_name_old', 'tunnel_key': 'fake_tunnel_key_old'}} modify = {} modify.update(add) modify.update(delete) data_dict = {'new_logical_switches': [], 'modified_logical_switches': [], 'deleted_logical_switches': []} with mock.patch.object(ovsdb_schema, 'LogicalSwitch') as logical_switch: # test add self.l2gw_ovsdb._process_logical_switch(fake_id, add, data_dict) logical_switch.assert_called_once_with( fake_id, fake_name, fake_tunnel_key, None) self.assertIn(logical_switch.return_value, data_dict['new_logical_switches']) # test modify self.l2gw_ovsdb._process_logical_switch(fake_id, modify, data_dict) self.assertIn(logical_switch.return_value, data_dict['modified_logical_switches']) # test delete self.l2gw_ovsdb._process_logical_switch(fake_id, delete, data_dict) self.assertIn(logical_switch.return_value, data_dict['deleted_logical_switches']) def test_process_ucast_macs_local(self): """Test case to process new ucast_macs_local.""" fake_id = 'fake_id' add = {'new': {'locator': ["uuid", "fake_locator_id"], 'MAC': 'fake_mac', 'logical_switch': ["uuid", "fake_logical_switch_id"], 'ipaddr': 'fake_ip_address'}} delete = {'old': {'MAC': 'fake_mac_old', 'logical_switch': ["uuid", "fake_logical_switch_id_old"]}} modify = {} modify.update(add) modify.update(delete) fake_mac = 'fake_mac' fake_logical_switch_id = 'fake_logical_switch_id' fake_locator_id = 'fake_locator_id' fake_ip_address = 'fake_ip_address' data_dict = {'new_local_macs': [], 'modified_local_macs': [], 'deleted_local_macs': []} with mock.patch.object(ovsdb_schema, 'UcastMacsLocal') as ucast_mac_local: # test add self.l2gw_ovsdb._process_ucast_macs_local(fake_id, add, data_dict) ucast_mac_local.assert_called_once_with(fake_id, fake_mac, fake_logical_switch_id, fake_locator_id, fake_ip_address) self.assertIn(ucast_mac_local.return_value, data_dict['new_local_macs']) # test modify self.l2gw_ovsdb._process_ucast_macs_local(fake_id, modify, data_dict) self.assertIn(ucast_mac_local.return_value, data_dict['modified_local_macs']) # test delete self.l2gw_ovsdb._process_ucast_macs_local(fake_id, delete, data_dict) self.assertIn(ucast_mac_local.return_value, data_dict['deleted_local_macs']) def test_process_ucast_macs_remote(self): """Test case to process new ucast_macs_remote.""" fake_id = 'fake_id' add = {'new': {'locator': ["uuid", "fake_locator_id"], 'MAC': 'fake_mac', 'logical_switch': ["uuid", "fake_logical_switch_id"], 'ipaddr': 'fake_ip_address'}} delete = {'old': {'MAC': 'fake_mac_old', 'logical_switch': ["uuid", "fake_logical_switch_id_old"]}} modify = {} modify.update(add) modify.update(delete) fake_mac = 'fake_mac' fake_logical_switch_id = 'fake_logical_switch_id' fake_locator_id = 'fake_locator_id' fake_ip_address = 'fake_ip_address' data_dict = {'new_remote_macs': [], 'modified_remote_macs': [], 'deleted_remote_macs': []} with mock.patch.object(ovsdb_schema, 'UcastMacsRemote') as ucast_mac_remote: # test add self.l2gw_ovsdb._process_ucast_macs_remote(fake_id, add, data_dict) ucast_mac_remote.assert_called_once_with(fake_id, fake_mac, fake_logical_switch_id, fake_locator_id, fake_ip_address) self.assertIn(ucast_mac_remote.return_value, data_dict['new_remote_macs']) # test modify self.l2gw_ovsdb._process_ucast_macs_remote(fake_id, modify, data_dict) self.assertIn(ucast_mac_remote.return_value, data_dict['modified_remote_macs']) # test delete self.l2gw_ovsdb._process_ucast_macs_remote(fake_id, delete, data_dict) self.assertIn(ucast_mac_remote.return_value, data_dict['deleted_remote_macs']) def test_process_physical_locator(self): """Test case to process new physical locator.""" fake_id = 'fake_id' add = {'new': {"dst_ip": "fake_dst_ip"}} delete = {'old': {"dst_ip": "fake_dst_ip_old"}} modify = {} modify.update(add) modify.update(delete) data_dict = {'new_physical_locators': [], 'modified_physical_locators': [], 'deleted_physical_locators': []} fake_dst_ip = 'fake_dst_ip' with mock.patch.object(ovsdb_schema, 'PhysicalLocator') as phy_locator: # test add self.l2gw_ovsdb._process_physical_locator(fake_id, add, data_dict) phy_locator.assert_called_once_with(fake_id, fake_dst_ip) self.assertIn(phy_locator.return_value, data_dict['new_physical_locators']) # test delete self.l2gw_ovsdb._process_physical_locator(fake_id, delete, data_dict) self.assertIn(phy_locator.return_value, data_dict['deleted_physical_locators']) # test modify self.l2gw_ovsdb._process_physical_locator(fake_id, modify, data_dict) self.assertIn(phy_locator.return_value, data_dict['modified_physical_locators']) def test_process_mcast_macs_local(self): """Test case to process new mcast_macs_local.""" fake_id = 'fake_id' add = {'new': {'locator_set': ["uuid", "fake_locator_id"], 'MAC': 'fake_mac', 'logical_switch': ["uuid", "fake_logical_switch_id"], 'ipaddr': 'fake_ip_address'}} delete = {'old': {'locator': ["uuid", "fake_locator_id"], 'MAC': 'fake_mac', 'logical_switch': ["uuid", "fake_logical_switch_id"], 'ipaddr': 'fake_ip_address'}} fake_mac = 'fake_mac' fake_logical_switch_id = 'fake_logical_switch_id' fake_locator_id = 'fake_locator_id' fake_ip_address = 'fake_ip_address' data_dict = {'new_mlocal_macs': [], 'deleted_mlocal_macs': []} with mock.patch.object(ovsdb_schema, 'McastMacsLocal') as mcast_mac_local: # test add self.l2gw_ovsdb._process_mcast_macs_local(fake_id, add, data_dict) mcast_mac_local.assert_called_once_with(fake_id, fake_mac, fake_logical_switch_id, fake_locator_id, fake_ip_address) self.assertIn(mcast_mac_local.return_value, data_dict['new_mlocal_macs']) # test delete self.l2gw_ovsdb._process_mcast_macs_local(fake_id, delete, data_dict) self.assertTrue(mcast_mac_local.called) self.assertIn(mcast_mac_local.return_value, data_dict['deleted_mlocal_macs']) def test_process_physical_locator_set(self): """Test case to process new physical_locaor_set.""" fake_id = 'fake_id' add = {'new': {'locators': ["set", [["uuid", "fake_id1"], ["uuid", "fake_id2"]]] }} delete = {'old': {'locators': ["set", [["uuid", "fake_id1"], ["uuid", "fake_id2"]]] }} fake_locators = ["fake_id1", "fake_id2"] data_dict = {'new_locator_sets': [], 'deleted_locator_sets': []} with mock.patch.object(ovsdb_schema, 'PhysicalLocatorSet') as phys_loc_set: self.l2gw_ovsdb._process_physical_locator_set(fake_id, add, data_dict) phys_loc_set.assert_called_once_with(fake_id, fake_locators) PhysLocatorSet = phys_loc_set.return_value self.assertIn(PhysLocatorSet, data_dict['new_locator_sets']) self.l2gw_ovsdb._process_physical_locator_set(fake_id, delete, data_dict) self.assertTrue(phys_loc_set.called) PhysLocatorSet = phys_loc_set.return_value self.assertIn(PhysLocatorSet, data_dict['deleted_locator_sets']) class SocketClass(object): def __init__(self, connect_error=None, send_error=None, recv_error=None, rcv_data=None, sock=None, ip_addr=None): self.connect_error = connect_error self.rcv_data = rcv_data self.send_error = send_error self.recv_error = recv_error self.sock = sock self.ip_addr = ip_addr def connect(self, ip_port): if self.connect_error: raise self.connect_error def send(self, data): if self.send_error: raise self.send_error return len(data) def recv(self, length): if self.recv_error: raise self.recv_error return self.rcv_data def bind(self, host_port): pass def listen(self, conn): pass def accept(self): return self.sock, self.ip_addr def setsockopt(self, sock_opt, sock_reuse, mode): pass class TestOVSDBMonitor_with_enable_manager(base.BaseTestCase): def setUp(self): super(TestOVSDBMonitor_with_enable_manager, self).setUp() config.register_ovsdb_opts_helper(cfg.CONF) cfg.CONF.set_override('enable_manager', True, 'ovsdb') self.conf = mock.Mock() self.callback = mock.Mock() self.l2gw_ovsdb = ovsdb_monitor.OVSDBMonitor(mock.Mock(), self.conf, self.callback) def test_init_with_enable_manager(self): self.l2gw_ovsdb.__init__(mock.Mock(), self.conf, self.callback) self.assertTrue(self.l2gw_ovsdb.enable_manager) self.assertFalse(self.l2gw_ovsdb.check_monitor_table_thread) ././@PaxHeader0000000000000000000000000000021200000000000010210 xustar00116 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_ovsdb_writer.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_ovsdb_wri0000664000175000017500000005762014572557755034430 0ustar00jamespagejamespage# 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 random import socket import ssl from unittest import mock from neutron.tests import base from networking_l2gw.services.l2gateway.agent import l2gateway_config as conf from networking_l2gw.services.l2gateway.agent.ovsdb import base_connection from networking_l2gw.services.l2gateway.agent.ovsdb import ovsdb_writer from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway import exceptions from networking_l2gw.tests.unit.services.l2gateway.agent.ovsdb import ( test_base_connection as base_test) from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils LOG = logging.getLogger(__name__) class TestOVSDBWriter(base.BaseTestCase): def setUp(self): super(TestOVSDBWriter, self).setUp() self.conf = mock.patch.object(conf, 'L2GatewayConfig').start() config.register_ovsdb_opts_helper(cfg.CONF) cfg.CONF.set_override('max_connection_retries', 0, 'ovsdb') self.sock = mock.patch('socket.socket').start() self.ssl_sock = mock.patch.object(ssl, 'wrap_socket').start() self.op_id = 'abcd' self.l2gw_ovsdb = ovsdb_writer.OVSDBWriter(mock.Mock(), self.conf) self.fake_message = {'id': self.op_id, 'fake_key': 'fake_value'} self.l2gw_ovsdb.responses = [self.fake_message] def test_process_response(self): """Test case to test _process_response.""" expected_result = {'fake_key': 'fake_value'} with mock.patch.object(base_connection.BaseConnection, '_response', return_value={'fake_key': 'fake_value'} ) as resp: result = self.l2gw_ovsdb._process_response(self.op_id) self.assertEqual(result, expected_result) resp.assert_called_with(self.op_id) def test_process_response_with_error(self): """Test case to test _process_response with error.""" foo_dict = {'fake_key': 'fake_value', 'error': 'fake_error'} with mock.patch.object(base_connection.BaseConnection, '_response', return_value=foo_dict) as resp: self.assertRaises(exceptions.OVSDBError, self.l2gw_ovsdb._process_response, self.op_id) resp.assert_called_with(self.op_id) def test_process_response_with_error1(self): """Test case to test _process_response with errors in the subqueries. """ fake_dict = {"id": "295366252499790541931626006259650283530", "result": [{"uuid": ["uuid", "be236bbf-8f83-4bf0-816b-629c7e5b5609" ]}, {}, {"error": "referential integrity violation", "details": "Table Ucast_Macs_Remote column " "locator row " "be236bbf-8f83-4bf0-816b-629c7e5b5609 " "references nonexistent row " "1b143819-45a6-44ec-826a-ac75243a07ce in " "table Physical_Locator." }], "error": None} with mock.patch.object(base_connection.BaseConnection, '_response', return_value=fake_dict) as resp: self.assertRaises(exceptions.OVSDBError, self.l2gw_ovsdb._process_response, self.op_id) resp.assert_called_with(self.op_id) def test_get_reply(self): """Test case to test _get_reply.""" ret_value = jsonutils.dumps({self.op_id: 'foo_value'}) with mock.patch.object( ovsdb_writer.OVSDBWriter, '_recv_data', return_value=jsonutils.dumps({self.op_id: 'foo_value'})) as recv_data, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_process_response', return_value=(ret_value, None) ) as proc_response, \ mock.patch.object(ovsdb_writer.LOG, 'debug'): self.l2gw_ovsdb._get_reply(self.op_id, mock.ANY) self.assertTrue(recv_data.called) self.assertTrue(proc_response.called) def test_send_and_receive(self): """Test case to test _send_and_receive.""" with mock.patch.object(base_connection.BaseConnection, 'send', return_value=True ) as mock_send: with mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_reply') as mock_reply: self.l2gw_ovsdb._send_and_receive('some_query', self.op_id, mock.ANY, True) mock_send.assert_called_with('some_query', addr=mock.ANY) mock_reply.assert_called_with(self.op_id, mock.ANY) def test_send_and_receive_with_rcv_required_false(self): """Test case to test _send_and_receive.""" with mock.patch.object(base_connection.BaseConnection, 'send', return_value=True ) as mock_send: with mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_reply') as mock_reply: self.l2gw_ovsdb._send_and_receive('some_query', self.op_id, mock.ANY, False) mock_send.assert_called_with('some_query', addr=mock.ANY) mock_reply.assert_not_called() def test_delete_logical_switch(self): """Test case to test delete_logical_switch.""" commit_dict = {"op": "commit", "durable": True} query = {"method": "transact", "params": [n_const.OVSDB_SCHEMA_NAME, {"op": "delete", "table": "Logical_Switch", "where": [["_uuid", "==", ["uuid", 'fake_logical_switch_uuid']]]}, commit_dict], "id": self.op_id} with mock.patch.object(random, 'getrandbits', return_value=self.op_id ) as get_rand: with mock.patch.object(ovsdb_writer.OVSDBWriter, '_send_and_receive' ) as send_n_receive: with mock.patch.object(ovsdb_writer.LOG, 'debug'): self.l2gw_ovsdb.delete_logical_switch( 'fake_logical_switch_uuid', mock.ANY) get_rand.assert_called_with(128) send_n_receive.assert_called_with(query, self.op_id, mock.ANY, True) def test_insert_ucast_macs_remote(self): """Test case to test insert ucast_macs_remote.""" with mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_ucast_macs_remote_dict' ) as get_ucast_mac_remote, \ mock.patch.object(random, 'getrandbits', return_value=self.op_id ) as get_rand, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_send_and_receive' ) as send_n_receive, \ mock.patch.object(ovsdb_writer.LOG, 'debug'), \ mock.patch.object(ovsdb_schema, 'LogicalSwitch'), \ mock.patch.object(ovsdb_schema, 'PhysicalLocator'), \ mock.patch.object(ovsdb_schema, 'UcastMacsRemote'): self.l2gw_ovsdb.insert_ucast_macs_remote(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock.ANY) get_rand.assert_called_with(128) send_n_receive.assert_called_with(mock.ANY, self.op_id, mock.ANY, True) self.assertTrue(get_ucast_mac_remote.called) def test_insert_ucast_macs_remote_with_no_locator_id(self): """Test case to test insert ucast_macs_remote without locator_id and logical_switch_id. """ with mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_ucast_macs_remote_dict' ) as get_ucast_mac_remote, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_physical_locator_dict' ) as get_physical_locator_dict, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_logical_switch_dict' ) as get_logical_switch_dict, \ mock.patch.object(random, 'getrandbits', return_value=self.op_id ), \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_send_and_receive' ), \ mock.patch.object(ovsdb_writer.LOG, 'debug'), \ mock.patch.object(ovsdb_schema, 'LogicalSwitch') as mock_ls, \ mock.patch.object(ovsdb_schema, 'PhysicalLocator') as mock_pl, \ mock.patch.object(ovsdb_schema, 'UcastMacsRemote'): locator = mock_pl.return_value locator.uuid = None ls = mock_ls.return_value ls.uuid = None ls.name = 'ab-cd' self.l2gw_ovsdb.insert_ucast_macs_remote(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock.ANY) self.assertTrue(get_ucast_mac_remote.called) self.assertTrue(get_physical_locator_dict.called) self.assertTrue(get_logical_switch_dict.called) def test_update_ucast_macs_remote(self): """Test case to test update ucast_macs_remote.""" with mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_dict_for_update_ucast_mac_remote' ) as get_update_ucast_mac_remote, \ mock.patch.object(random, 'getrandbits', return_value=self.op_id ) as get_rand, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_send_and_receive' ) as send_n_receive, \ mock.patch.object(ovsdb_writer.LOG, 'debug'), \ mock.patch.object(ovsdb_schema, 'PhysicalLocator'), \ mock.patch.object(ovsdb_schema, 'UcastMacsRemote'): self.l2gw_ovsdb.update_ucast_macs_remote(mock.MagicMock(), mock.MagicMock(), mock.ANY) get_rand.assert_called_with(128) send_n_receive.assert_called_with(mock.ANY, self.op_id, mock.ANY, True) self.assertTrue(get_update_ucast_mac_remote.called) def test_update_ucast_macs_remote_with_no_locator_id(self): """Test case to test update ucast_macs_remote without locator_id and logical_switch_id. """ with mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_dict_for_update_ucast_mac_remote' ) as get_update_ucast_mac_remote, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_physical_locator_dict' ) as get_physical_locator_dict, \ mock.patch.object(random, 'getrandbits', return_value=self.op_id ), \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_send_and_receive' ), \ mock.patch.object(ovsdb_writer.LOG, 'debug'), \ mock.patch.object(ovsdb_schema, 'PhysicalLocator') as mock_pl, \ mock.patch.object(ovsdb_schema, 'UcastMacsRemote'): locator = mock_pl.return_value locator.uuid = None self.l2gw_ovsdb.update_ucast_macs_remote(mock.MagicMock(), mock.MagicMock(), mock.ANY) self.assertTrue(get_update_ucast_mac_remote.called) self.assertTrue(get_physical_locator_dict.called) def test_delete_ucast_macs_remote(self): """Test case to test delete_ucast_macs_remote.""" with mock.patch.object(random, 'getrandbits', return_value=self.op_id ) as get_rand, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_send_and_receive' ) as send_n_receive, \ mock.patch.object(ovsdb_writer.LOG, 'debug'): self.l2gw_ovsdb.delete_ucast_macs_remote(mock.Mock(), mock.MagicMock(), mock.ANY) get_rand.assert_called_with(128) send_n_receive.assert_called_with(mock.ANY, self.op_id, mock.ANY, True) def test_update_connection_to_gateway(self): """Test case to test update_connection_to_gateway.""" with mock.patch.object(ovsdb_writer.OVSDBWriter, '_get_bindings_to_update') as get_bindings, \ mock.patch.object(random, 'getrandbits', return_value=self.op_id ) as get_rand, \ mock.patch.object(ovsdb_writer.OVSDBWriter, '_send_and_receive' ) as send_n_receive, \ mock.patch.object(ovsdb_writer.LOG, 'debug'): self.l2gw_ovsdb.update_connection_to_gateway( mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), mock.ANY, mock.ANY) get_rand.assert_called_with(128) send_n_receive.assert_called_with(mock.ANY, self.op_id, mock.ANY, True) self.assertTrue(get_bindings.called) def test_get_bindings_to_update1(self): """Test case to test _get_bindings_to_update.""" fake_op_method = 'CREATE' with mock.patch.object( ovsdb_writer.OVSDBWriter, '_form_logical_switch') as form_ls, \ mock.patch.object( ovsdb_writer.OVSDBWriter, '_form_physical_locators') as form_pl, \ mock.patch.object( ovsdb_writer.OVSDBWriter, '_form_ports') as form_pp, \ mock.patch.object( ovsdb_schema, 'LogicalSwitch') as mock_ls, \ mock.patch.object( ovsdb_schema, 'PhysicalLocator') as mock_pl, \ mock.patch.object( ovsdb_schema, 'UcastMacsRemote') as mock_ucmr, \ mock.patch.object(ovsdb_schema, 'PhysicalPort') as mock_pp: ls = mock_ls.return_value = ovsdb_schema.LogicalSwitch( 'ls_uuid', 'ls_name', 'ls_key', 'ls_desc') pl = mock_pl.return_value = ovsdb_schema.PhysicalLocator( 'pl_uuid', 'pl_dst_ip') mock_ucmr.return_value = ovsdb_schema.UcastMacsRemote( 'ucmr_uuid', 'ucmr_mac', 'ucmr_ls_id', 'ucmr_pl_id', 'ucmr_ip') pp = mock_pp.return_value = ovsdb_schema.PhysicalPort( 'pp_uuid', 'pp_name', 'pp_ps_id', 'pp_vlan_bindings') form_ls.return_value = ['uuid', ls.uuid] self.l2gw_ovsdb._get_bindings_to_update(mock.MagicMock(), [{'uuid': 'uuid', 'dst_ip': 'dst_ip'}], mock.MagicMock(), [{'uuid': 'uuid', 'name': 'name', 'physical_switch_id': 'physical_switch_id', 'vlan_bindings': 'vlan_bindings', 'port_fault_status': 'port_fault_status'}], fake_op_method) form_ls.assert_called_with(ls, mock.ANY) form_pl.assert_called_with(['uuid', ls.uuid], [pl], mock.ANY, mock.ANY) form_pp.assert_called_with(['uuid', ls.uuid], [pp], mock.ANY, fake_op_method) def test_get_physical_locator_dict(self): """Test case to test _get_physical_locator_dict.""" fake_locator = mock.Mock() fake_locator.uuid = 'fake_uuid' fake_locator.dst_ip = 'fake_dst_ip' phy_loc_dict = self.l2gw_ovsdb._get_physical_locator_dict( fake_locator) self.assertEqual(phy_loc_dict['row']['dst_ip'], fake_locator.dst_ip) self.assertEqual(phy_loc_dict['uuid-name'], fake_locator.uuid) def test_get_logical_switch_dict(self): """Test case to test _get_logical_switch_dict.""" fake_ls = mock.Mock() fake_ls.uuid = 'fake_uuid' fake_ls.description = 'fake_desc' fake_ls.name = 'fake_name' fake_ls.key = 100 logical_switch_dict = self.l2gw_ovsdb._get_logical_switch_dict( fake_ls) self.assertEqual(logical_switch_dict['uuid-name'], fake_ls.uuid) self.assertEqual(logical_switch_dict['row']['description'], fake_ls.description) self.assertEqual(logical_switch_dict['row']['name'], fake_ls.name) self.assertEqual(logical_switch_dict['row']['tunnel_key'], fake_ls.key) def test_get_ucast_macs_remote_dict(self): """Test case to test _get_ucast_macs_remote_dict.""" fake_mac = mock.Mock() fake_mac.mac = 'fake_mac' fake_mac.ip_address = 'fake_ip' locator_list = ['fake_list'] logical_switch_list = ['fake_switch_list'] ucast_mac_remote_dict = self.l2gw_ovsdb._get_ucast_macs_remote_dict( fake_mac, locator_list, logical_switch_list) self.assertEqual(ucast_mac_remote_dict['row']['MAC'], fake_mac.mac) self.assertEqual(ucast_mac_remote_dict['row']['ipaddr'], fake_mac.ip_address) self.assertEqual(ucast_mac_remote_dict['row']['locator'], locator_list) self.assertEqual(ucast_mac_remote_dict['row']['logical_switch'], logical_switch_list) def test_get_dict_for_update_ucast_mac_remote(self): """Test case to test _get_dict_for_update_ucast_mac_remote.""" fake_mac = mock.Mock() fake_mac.uuid = 'fake_uuid' fake_mac.ip_address = 'fake_ip' locator_list = ['fake_list'] temp_method = self.l2gw_ovsdb._get_dict_for_update_ucast_mac_remote ucast_mac_remote_dict = temp_method( fake_mac, locator_list) self.assertEqual(ucast_mac_remote_dict['row']['locator'], locator_list) self.assertEqual(ucast_mac_remote_dict['where'], [["_uuid", "==", ["uuid", fake_mac.uuid]]]) def test_recv_data(self): """Test case to test _recv_data with a valid data.""" fake_data = {"fake_key": "fake_value"} fake_socket = base_test.SocketClass(None, None, None, base_test.FakeDecodeClass( jsonutils.dumps(fake_data))) with mock.patch.object(socket, 'socket', return_value=fake_socket): ovsdb_conf = base_test.FakeConf() l2gw_obj = ovsdb_writer.OVSDBWriter( cfg.CONF.ovsdb, ovsdb_conf) result = l2gw_obj._recv_data(mock.ANY) self.assertEqual(jsonutils.dumps(fake_data), result) def test_recv_data_with_empty_data(self): """Test case to test _recv_data with empty data.""" fake_socket = base_test.SocketClass(None, None, None, '') with mock.patch.object(socket, 'socket', return_value=fake_socket): with mock.patch.object(ovsdb_writer.LOG, 'warning'): ovsdb_conf = base_test.FakeConf() l2gw_obj = ovsdb_writer.OVSDBWriter( cfg.CONF.ovsdb, ovsdb_conf) result = l2gw_obj._recv_data(mock.ANY) self.assertIsNone(result) def test_recv_data_with_socket_error(self): """Test case to test _recv_data with socket error.""" fake_socket = base_test.SocketClass(None, None, socket.error) with mock.patch.object(socket, 'socket', return_value=fake_socket): with mock.patch.object(ovsdb_writer.LOG, 'warning') as fake_warn: ovsdb_conf = base_test.FakeConf() l2gw_obj = ovsdb_writer.OVSDBWriter( cfg.CONF.ovsdb, ovsdb_conf) result = l2gw_obj._recv_data(mock.ANY) self.assertIsNone(result) fake_warn.assert_called_with( "Did not receive any reply from the OVSDB server") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/test_agent_api.py0000664000175000017500000000356614572557755033673 0ustar00jamespagejamespage# 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 networking_l2gw.services.l2gateway.agent import agent_api class L2GatewayAgentApiTestCase(base.BaseTestCase): def setUp(self): self.client_mock_p = mock.patch.object(agent_api.n_rpc, 'get_client') self.client_mock = self.client_mock_p.start() self.topic = 'foo_topic' self.host = 'foo_host' self.agent_rpc = agent_api.L2GatewayAgentApi( self.topic, self.host) super(L2GatewayAgentApiTestCase, self).setUp() def test_update_ovsdb_changes(self): cctxt = mock.Mock() context = mock.Mock() fake_activity = 1 self.agent_rpc.client.prepare.return_value = cctxt self.agent_rpc.update_ovsdb_changes(context, fake_activity, mock.ANY) cctxt.cast.assert_called_with( context, 'update_ovsdb_changes', activity=fake_activity, ovsdb_data=mock.ANY) def test_notify_ovsdb_states(self): cctxt = mock.Mock() context = mock.Mock() self.agent_rpc.client.prepare.return_value = cctxt self.agent_rpc.notify_ovsdb_states(context, mock.ANY) cctxt.cast.assert_called_with( context, 'notify_ovsdb_states', ovsdb_states=mock.ANY) ././@PaxHeader0000000000000000000000000000021200000000000010210 xustar00116 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/test_base_agent_manager.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/test_base_agent_mana0000664000175000017500000001272214572557755034373 0ustar00jamespagejamespage# 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.agent import rpc as agent_rpc from neutron.conf.agent import common as agent_config from neutron.tests import base from oslo_config import cfg from oslo_service import loopingcall from networking_l2gw.services.l2gateway.agent import (base_agent_manager as l2gw_manager) from networking_l2gw.services.l2gateway.agent import agent_api from networking_l2gw.services.l2gateway.common import constants as n_const class TestBaseAgentManager(base.BaseTestCase): def setUp(self): super(TestBaseAgentManager, self).setUp() agent_config.register_agent_state_opts_helper(cfg.CONF) cfg.CONF.set_override('report_interval', 1, 'AGENT') self.context = mock.Mock mock.patch('neutron.agent.rpc.PluginReportStateAPI').start() self.l2gw_agent_manager = l2gw_manager.BaseAgentManager( cfg.CONF) def test_init(self): with mock.patch.object(agent_api, 'L2GatewayAgentApi') as l2_gw_agent_api: with mock.patch.object(l2gw_manager.BaseAgentManager, '_setup_state_rpc') as setup_state_rpc: self.l2gw_agent_manager.__init__(mock.Mock()) self.assertEqual('', self.l2gw_agent_manager.l2gw_agent_type) self.assertTrue(self.l2gw_agent_manager.admin_state_up) self.assertTrue(setup_state_rpc.called) self.assertTrue(l2_gw_agent_api.called) def test_setup_state_rpc(self): cfg.CONF.set_override('report_interval', 1, 'AGENT') with mock.patch.object(agent_rpc, 'PluginReportStateAPI' ) as agent_report_state_rpc: with mock.patch.object(loopingcall, 'FixedIntervalLoopingCall' ) as looping_call: self.l2gw_agent_manager._setup_state_rpc() self.assertTrue(agent_report_state_rpc.called) self.assertTrue(looping_call.called) looping_call.return_value.start.assert_called_with( interval=mock.ANY) def test_report_state(self): with mock.patch.object(self.l2gw_agent_manager, 'state_rpc') as state_api: self.assertTrue(self.l2gw_agent_manager.agent_state['start_flag']) self.l2gw_agent_manager._report_state() self.assertFalse(self.l2gw_agent_manager.agent_state['start_flag']) self.assertTrue(state_api.report_state.called) def test_report_state_exception(self): cfg.CONF.set_override('report_interval', 1, 'AGENT') with mock.patch.object(self.l2gw_agent_manager, 'state_rpc') as state_rpc: with mock.patch.object(l2gw_manager.LOG, 'exception') as exc: with mock.patch.object(self.l2gw_agent_manager, 'handle_report_state_failure' ) as mock_handle_report_state_failure: state_rpc.report_state.side_effect = Exception() self.l2gw_agent_manager._report_state() self.assertTrue(exc.called) self.assertTrue(mock_handle_report_state_failure.called) def test_agent_updated(self): fake_payload = {'fake_key': 'fake_value'} with mock.patch.object(l2gw_manager.LOG, 'info') as logger_call: self.l2gw_agent_manager.agent_updated(mock.Mock(), fake_payload) self.assertEqual(1, logger_call.call_count) def test_set_monitor_agent_type_monitor(self): self.l2gw_agent_manager.l2gw_agent_type = '' self.l2gw_agent_manager.conf.host = 'fake_host' self.l2gw_agent_manager.set_monitor_agent(self.context, 'fake_host') self.assertEqual(n_const.MONITOR, self.l2gw_agent_manager.agent_state. get('configurations')[n_const.L2GW_AGENT_TYPE]) self.assertEqual(n_const.MONITOR, self.l2gw_agent_manager.l2gw_agent_type) def test_set_monitor_agent_type_transact(self): self.l2gw_agent_manager.l2gw_agent_type = '' cfg.CONF.host = 'fake_host' self.l2gw_agent_manager.set_monitor_agent( self.context, 'fake_host1') self.assertNotEqual(n_const.MONITOR, self.l2gw_agent_manager.agent_state. get('configurations')[n_const.L2GW_AGENT_TYPE]) self.assertEqual('', self.l2gw_agent_manager.agent_state. get('configurations')[n_const.L2GW_AGENT_TYPE]) self.assertEqual('', self.l2gw_agent_manager.l2gw_agent_type) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/agent/test_l2gw_agent.py0000664000175000017500000000412014572557755033760 0ustar00jamespagejamespage# 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.common import config as common_config from neutron.tests import base from oslo_config import cfg from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway import l2gw_agent as agent class TestL2gwAgent(base.BaseTestCase): def setUp(self): super(TestL2gwAgent, self).setUp() config.register_ovsdb_opts_helper(cfg.CONF) def test_start(self): with mock.patch.object( agent.n_rpc.Service, 'start' ) as mock_start: mgr = mock.Mock() cfg.CONF.periodic_interval = mock.Mock(return_value=10) agent_service = agent.L2gatewayAgentService('host', 'topic', mgr) agent_service.start() self.assertTrue(mock_start.called) def test_main_l2gw_agent(self): logging_str = 'neutron.conf.agent.common.setup_logging' common_config_str = mock.patch.object(common_config, 'init').start() with mock.patch.object(common_config_str, 'init'), \ mock.patch(logging_str), \ mock.patch.object(agent.service, 'launch') as mock_launch, \ mock.patch('sys.argv'), \ mock.patch.object(agent.manager, 'OVSDBManager'), \ mock.patch.object(cfg.CONF, 'register_opts'): agent.main() mock_launch.assert_called_once_with(cfg.CONF, mock.ANY) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/common/0000775000175000017500000000000014572557757030515 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/common/__init__.py0000664000175000017500000000000014572557755032612 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000021000000000000010206 xustar00114 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/common/test_l2gw_validators.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/common/test_l2gw_validator0000664000175000017500000001133314572557755034416 0ustar00jamespagejamespage# Copyright (c) 2016 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 neutron_lib import exceptions from neutron.tests import base from networking_l2gw.services.l2gateway.common import l2gw_validators class TestL2gwValidators(base.BaseTestCase): def test_validate_gwdevice_list(self): ret_msg = l2gw_validators.validate_gwdevice_list(None) msg = "Cannot create a gateway with an empty device list" self.assertEqual(msg, ret_msg) def test_for_empty_device_name(self): test_device = [{"interfaces": "port"}] ret_msg = l2gw_validators.validate_gwdevice_list(test_device) msg = "Cannot create a gateway with an empty device_name" self.assertEqual(msg, ret_msg) def test_for_empty_interface(self): test_device = [{"device_name": "switch1"}] ret_msg = l2gw_validators.validate_gwdevice_list(test_device) msg = "Cannot create a gateway with an empty interfaces" self.assertEqual(msg, ret_msg) def test_for_interfaces_type_list_of_dicts(self): test_device = [{"device_name": "switch1", "interfaces": "port"}] ret_msg = l2gw_validators.validate_gwdevice_list(test_device) msg = "interfaces format is not a type list of dicts" self.assertEqual(msg, ret_msg) def test_for_interface_type_dict(self): test_device = [{"device_name": "switch1", "interfaces": ["port"]}] ret_msg = l2gw_validators.validate_gwdevice_list(test_device) msg = "interfaces format is not a type dict" self.assertEqual(msg, ret_msg) def test_for_empty_interface_name(self): test_device = [{"device_name": "switch1", "interfaces": [{'name': ""}]}] ret_msg = l2gw_validators.validate_gwdevice_list(test_device) msg = "Cannot create a gateway with an empty interface name" self.assertEqual(msg, ret_msg) def test_segmentation_id_in_interface(self): test_device = [{"device_name": "switch1", "interfaces": [{'name': "i1", 'segmentation_id': ''}]}] ret_msg = l2gw_validators.validate_gwdevice_list(test_device) msg = "segmentation_id_list should not be empty" self.assertEqual(msg, ret_msg) def test_segmentation_id_type(self): test_device = [{"device_name": "switch1", "interfaces": [{'name': "i1", 'segmentation_id': '67'}]}] ret_msg = l2gw_validators.validate_gwdevice_list(test_device) msg = "segmentation_id type should be of list type " self.assertEqual(msg, ret_msg) def test_validate_network_mapping_list_with_seg_id(self): network_mapping = {'segmentation_id': 67} check_vlan = True self.assertRaises(exceptions.InvalidInput, l2gw_validators.validate_network_mapping_list, network_mapping, check_vlan) def test_validate_network_mapping_list_without_seg_id(self): network_mapping = {'segmentation_id': ''} check_vlan = False self.assertRaises(exceptions.InvalidInput, l2gw_validators.validate_network_mapping_list, network_mapping, check_vlan) def test_validate_network_mapping_list_for_empty_network_id(self): network_mapping = {'segmentation_id': '', 'network_id': ''} check_vlan = True self.assertRaises(exceptions.InvalidInput, l2gw_validators.validate_network_mapping_list, network_mapping, check_vlan) def test_is_valid_vlan_id_for_non_integer_value(self): seg_id = 'a' self.assertRaises(exceptions.InvalidInput, l2gw_validators.is_valid_vlan_id, seg_id) def test_is_valid_vlan_id_for_value_less_than_0(self): seg_id = -1 self.assertRaises(exceptions.InvalidInput, l2gw_validators.is_valid_vlan_id, seg_id) def test_is_valid_vlan_id_for_value_greater_than_4095(self): seg_id = 4096 self.assertRaises(exceptions.InvalidInput, l2gw_validators.is_valid_vlan_id, seg_id) ././@PaxHeader0000000000000000000000000000020500000000000010212 xustar00111 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/common/test_tunnel_calls.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/common/test_tunnel_calls.p0000664000175000017500000000506314572557755034422 0ustar00jamespagejamespage# Copyright (c) 2016 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 unittest import mock from neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc from neutron.plugins.ml2.drivers import type_tunnel from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import rpc as rpc from neutron.tests import base from networking_l2gw.services.l2gateway.common import tunnel_calls class TestTunnelCalls(base.BaseTestCase): def setUp(self): super(TestTunnelCalls, self).setUp() mock.patch.object(managers, 'TypeManager').start() self.tunnel_call = tunnel_calls.Tunnel_Calls() self.context = mock.MagicMock() def test_trigger_tunnel_sync(self): with mock.patch.object(rpc, 'RpcCallbacks'), \ mock.patch.object(type_tunnel.TunnelRpcCallbackMixin, 'tunnel_sync') as mock_tunnel_sync: self.tunnel_call.trigger_tunnel_sync(self.context, 'fake_ip') mock_tunnel_sync.assert_called_with( self.context, tunnel_ip='fake_ip', tunnel_type='vxlan') def test_trigger_l2pop_sync(self): fake_fdb_entry = "fake_fdb_entry" with mock.patch.object(l2pop_rpc.L2populationAgentNotifyAPI, 'add_fdb_entries') as (mock_add_fdb): self.tunnel_call.trigger_l2pop_sync(self.context, fake_fdb_entry) mock_add_fdb.assert_called_with(self.context, fake_fdb_entry) def test_trigger_l2pop_delete(self): fake_fdb_entry = "fake_fdb_entry" fake_host = 'fake_host' with mock.patch.object(l2pop_rpc.L2populationAgentNotifyAPI, 'remove_fdb_entries') as (mock_delete_fdb): self.tunnel_call.trigger_l2pop_delete(self.context, fake_fdb_entry, fake_host) mock_delete_fdb.assert_called_with(self.context, fake_fdb_entry, fake_host) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/ovsdb/0000775000175000017500000000000014572557757030342 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/ovsdb/__init__.py0000664000175000017500000000000014572557755032437 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/ovsdb/test_data.py0000664000175000017500000007772014572557755032677 0ustar00jamespagejamespage# Copyright (c) 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. from unittest import mock from neutron import manager from neutron.plugins.ml2 import managers from neutron.tests import base from neutron_lib import context from neutron_lib.plugins import directory from networking_l2gw.db.l2gateway import l2gateway_db from networking_l2gw.db.l2gateway.ovsdb import lib from networking_l2gw.services.l2gateway.common import constants as n_const from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway.common import tunnel_calls from networking_l2gw.services.l2gateway.ovsdb import data from networking_l2gw.services.l2gateway.service_drivers import agent_api class TestL2GatewayOVSDBCallbacks(object): def setUp(self): super(TestL2GatewayOVSDBCallbacks, self).setUp() self.context = context.get_admin_context() def test_update_ovsdb_changes(self): fake_activity = 1 fake_ovsdb_data = {n_const.OVSDB_IDENTIFIER: 'fake_id'} with mock.patch.object(data, 'OVSDBData') as ovs_data: self.l2gw_callbacks.update_ovsdb_changes(self.context, fake_activity, fake_ovsdb_data) ovsdb_return_value = ovs_data.return_value ovsdb_return_value.update_ovsdb_changes.assert_called_with( self.context, fake_activity, fake_ovsdb_data) def test_notify_ovsdb_states(self): fake_ovsdb_states = {'ovsdb1': 'connected'} with mock.patch.object(data, 'OVSDBData') as ovs_data: self.l2gw_callbacks.notify_ovsdb_states(self.context, fake_ovsdb_states) ovsdb_return_value = ovs_data.return_value ovsdb_return_value.notify_ovsdb_states.assert_called_with( self.context, fake_ovsdb_states) def test_get_ovsdbdata_object(self): fake_ovsdb_id = 'fake_ovsdb_id' with mock.patch.object(data, 'OVSDBData') as ovs_data: ret_value = self.l2gw_callbacks.get_ovsdbdata_object( fake_ovsdb_id) ret_value1 = ovs_data.assert_called_with(fake_ovsdb_id) self.assertEqual(ret_value, ret_value1) class TestOVSDBData(base.BaseTestCase): def setUp(self): super(TestOVSDBData, self).setUp() self.context = context.get_admin_context() self.ovsdb_identifier = 'fake_ovsdb_id' mock.patch.object(directory, 'get_plugin').start() mock.patch.object(managers, 'TypeManager').start() self.ovsdb_data = data.OVSDBData(self.ovsdb_identifier) def test_init(self): with mock.patch.object(data.OVSDBData, '_setup_entry_table') as setup_entry_table: self.ovsdb_data.__init__(self.ovsdb_identifier) self.assertEqual('fake_ovsdb_id', self.ovsdb_data.ovsdb_identifier) self.assertTrue(setup_entry_table.called) def test_update_ovsdb_changes(self): fake_dict = {} fake_activity = 1 fake_remote_mac = {'uuid': '123456', 'mac': 'mac123', 'ovsdb_identifier': 'host1', 'logical_switch_id': 'ls123'} fake_new_logical_switches = [fake_dict] fake_new_physical_port = [fake_dict] fake_new_physical_switches = [fake_dict] fake_new_physical_locators = [fake_dict] fake_new_local_macs = [fake_dict] fake_new_remote_macs = [fake_remote_mac] fake_modified_remote_macs = [fake_dict] fake_modified_physical_ports = [fake_dict] fake_modified_local_macs = [fake_dict] fake_deleted_logical_switches = [fake_dict] fake_deleted_physical_ports = [fake_dict] fake_deleted_physical_switches = [fake_dict] fake_deleted_physical_locators = [fake_dict] fake_deleted_local_macs = [fake_dict] fake_deleted_remote_macs = [fake_dict] fake_ovsdb_data = { n_const.OVSDB_IDENTIFIER: 'fake_ovsdb_id', 'new_logical_switches': fake_new_logical_switches, 'new_physical_ports': fake_new_physical_port, 'new_physical_switches': fake_new_physical_switches, 'new_physical_locators': fake_new_physical_locators, 'new_local_macs': fake_new_local_macs, 'new_remote_macs': fake_new_remote_macs, 'modified_remote_macs': fake_modified_remote_macs, 'modified_physical_ports': fake_modified_physical_ports, 'modified_local_macs': fake_modified_local_macs, 'deleted_logical_switches': fake_deleted_logical_switches, 'deleted_physical_switches': fake_deleted_physical_switches, 'deleted_physical_ports': fake_deleted_physical_ports, 'deleted_physical_locators': fake_deleted_physical_locators, 'deleted_local_macs': fake_deleted_local_macs, 'deleted_remote_macs': fake_deleted_remote_macs} with mock.patch.object( self.ovsdb_data, '_process_new_logical_switches' ) as process_new_logical_switches, \ mock.patch.object( self.ovsdb_data, '_process_new_physical_ports' ) as process_new_physical_ports, \ mock.patch.object( self.ovsdb_data, '_process_new_physical_switches' ) as process_new_physical_switches, \ mock.patch.object( self.ovsdb_data, '_process_new_physical_locators' ) as process_new_physical_locators, \ mock.patch.object( self.ovsdb_data, '_process_new_local_macs' ) as process_new_local_macs, \ mock.patch.object( self.ovsdb_data, '_process_new_remote_macs' ) as process_new_remote_macs, \ mock.patch.object( self.ovsdb_data, '_process_modified_remote_macs' ) as process_modified_remote_macs, \ mock.patch.object( self.ovsdb_data, '_process_modified_physical_ports' ) as process_modified_physical_ports, \ mock.patch.object( self.ovsdb_data, '_process_deleted_logical_switches' ) as process_deleted_logical_switches, \ mock.patch.object( self.ovsdb_data, '_process_deleted_physical_switches' ) as process_deleted_physical_switches, \ mock.patch.object( self.ovsdb_data, '_process_deleted_physical_ports' ) as process_deleted_physical_ports, \ mock.patch.object( self.ovsdb_data, '_process_deleted_physical_locators' ) as process_deleted_physical_locators, \ mock.patch.object( self.ovsdb_data, '_process_deleted_local_macs' ) as process_deleted_local_macs, \ mock.patch.object( self.ovsdb_data, '_process_deleted_remote_macs' ) as process_deleted_remote_macs, \ mock.patch.object( self.ovsdb_data, '_handle_l2pop') as mock_handle_l2pop: self.ovsdb_data.entry_table = { 'new_logical_switches': process_new_logical_switches, 'new_physical_ports': process_new_physical_ports, 'new_physical_switches': process_new_physical_switches, 'new_physical_locators': process_new_physical_locators, 'new_local_macs': process_new_local_macs, 'new_remote_macs': process_new_remote_macs, 'modified_remote_macs': process_modified_remote_macs, 'modified_physical_ports': process_modified_physical_ports, 'deleted_logical_switches': process_deleted_logical_switches, 'deleted_physical_switches': process_deleted_physical_switches, 'deleted_physical_ports': process_deleted_physical_ports, 'deleted_physical_locators': process_deleted_physical_locators, 'deleted_local_macs': process_deleted_local_macs, 'deleted_remote_macs': process_deleted_remote_macs} self.ovsdb_data.update_ovsdb_changes( self.context, fake_activity, fake_ovsdb_data) process_new_logical_switches.assert_called_with( self.context, fake_new_logical_switches) process_new_physical_ports.assert_called_with( self.context, fake_new_physical_port) process_new_physical_switches.assert_called_with( self.context, fake_new_physical_switches) process_new_physical_locators.assert_called_with( self.context, fake_new_physical_locators) process_new_local_macs.assert_called_with( self.context, fake_new_local_macs) process_new_remote_macs.assert_called_with( self.context, fake_new_remote_macs) process_modified_remote_macs.assert_called_with( self.context, fake_modified_remote_macs) process_modified_physical_ports.assert_called_with( self.context, fake_modified_physical_ports) process_deleted_logical_switches.assert_called_with( self.context, fake_deleted_logical_switches) process_deleted_physical_switches.assert_called_with( self.context, fake_deleted_physical_switches) process_deleted_physical_ports.assert_called_with( self.context, fake_deleted_physical_ports) process_deleted_physical_locators.assert_called_with( self.context, fake_deleted_physical_locators) process_deleted_local_macs.assert_called_with( self.context, fake_deleted_local_macs) process_deleted_remote_macs.assert_called_with( self.context, fake_deleted_remote_macs) self.assertTrue(mock_handle_l2pop.called) @mock.patch.object(lib, 'get_all_pending_remote_macs_in_asc_order') @mock.patch.object(lib, 'delete_pending_ucast_mac_remote') @mock.patch.object(ovsdb_schema, 'LogicalSwitch') @mock.patch.object(ovsdb_schema, 'PhysicalLocator') @mock.patch.object(ovsdb_schema, 'UcastMacsRemote') @mock.patch.object(agent_api.L2gatewayAgentApi, 'add_vif_to_gateway') @mock.patch.object(agent_api.L2gatewayAgentApi, 'update_vif_to_gateway') @mock.patch.object(agent_api.L2gatewayAgentApi, 'delete_vif_from_gateway') def test_notify_ovsdb_states(self, mock_del_vif, mock_upd_vif, mock_add_vif, mock_ucmr, mock_pl, mock_ls, mock_del_pend_recs, mock_get_pend_recs): fake_ovsdb_states = {'ovsdb1': 'connected'} fake_dict = {'logical_switch_uuid': 'fake_ls_id', 'mac': 'fake_mac', 'locator_uuid': 'fake_loc_id', 'dst_ip': 'fake_dst_ip', 'vm_ip': 'fake_vm_ip'} fake_insert_dict = {'operation': 'insert'} fake_insert_dict.update(fake_dict) fake_update_dict = {'operation': 'update'} fake_update_dict.update(fake_dict) fake_delete_dict = {'operation': 'delete'} fake_delete_dict.update(fake_dict) mock_get_pend_recs.return_value = [fake_insert_dict] self.ovsdb_data.notify_ovsdb_states( self.context, fake_ovsdb_states) self.assertTrue(mock_add_vif.called) mock_get_pend_recs.return_value = [fake_update_dict] self.ovsdb_data.notify_ovsdb_states( self.context, fake_ovsdb_states) self.assertTrue(mock_upd_vif.called) mock_get_pend_recs.return_value = [fake_delete_dict] self.ovsdb_data.notify_ovsdb_states( self.context, fake_ovsdb_states) self.assertTrue(mock_del_vif.called) def test_process_new_logical_switches(self): fake_dict = {} fake_new_logical_switches = [fake_dict] with mock.patch.object(lib, 'get_logical_switch', return_value=None) as get_ls: with mock.patch.object(lib, 'add_logical_switch') as add_ls: self.ovsdb_data._process_new_logical_switches( self.context, fake_new_logical_switches) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) get_ls.assert_called_with(self.context, fake_dict) add_ls.assert_called_with(self.context, fake_dict) def test_process_new_physical_switches(self): fake_dict = {'tunnel_ip': ['set']} fake_new_physical_switches = [fake_dict] with mock.patch.object(lib, 'get_physical_switch', return_value=None) as get_ps: with mock.patch.object(lib, 'add_physical_switch') as add_ps: self.ovsdb_data._process_new_physical_switches( self.context, fake_new_physical_switches) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertIsNone(fake_dict['tunnel_ip']) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) get_ps.assert_called_with(self.context, fake_dict) add_ps.assert_called_with(self.context, fake_dict) @mock.patch.object(lib, 'get_physical_port', return_value=None) @mock.patch.object(lib, 'add_physical_port') @mock.patch.object(lib, 'get_vlan_binding', return_value=None) @mock.patch.object(lib, 'add_vlan_binding') def test_process_new_physical_ports(self, add_vlan, get_vlan, add_pp, get_pp): fake_dict1 = {} fake_dict2 = {'vlan_bindings': [fake_dict1]} fake_new_physical_ports = [fake_dict2] self.ovsdb_data._process_new_physical_ports( self.context, fake_new_physical_ports) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict2) self.assertEqual('fake_ovsdb_id', fake_dict2[n_const.OVSDB_IDENTIFIER]) get_pp.assert_called_with(self.context, fake_dict2) add_pp.assert_called_with(self.context, fake_dict2) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict1) self.assertIn('port_uuid', fake_dict1) get_vlan.assert_called_with(self.context, fake_dict1) add_vlan.assert_called_with(self.context, fake_dict1) def test_process_new_physical_locators(self): fake_dict = {} fake_new_physical_locators = [fake_dict] with mock.patch.object(lib, 'get_physical_locator', return_value=None) as get_pl: with mock.patch.object(lib, 'add_physical_locator') as add_pl: self.ovsdb_data._process_new_physical_locators( self.context, fake_new_physical_locators) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) get_pl.assert_called_with(self.context, fake_dict) add_pl.assert_called_with(self.context, fake_dict) @mock.patch.object(lib, 'get_ucast_mac_local', return_value=None) @mock.patch.object(lib, 'add_ucast_mac_local') def test_process_new_local_macs(self, add_lm, get_lm): fake_dict = {'uuid': '123456', 'mac': 'mac123', 'ovsdb_identifier': 'host1', 'logical_switch_id': 'ls123'} fake_new_local_macs = [fake_dict] self.ovsdb_data._process_new_local_macs( self.context, fake_new_local_macs) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) get_lm.assert_called_with(self.context, fake_dict) add_lm.assert_called_with(self.context, fake_dict) def test_process_new_remote_macs(self): fake_dict = {'logical_switch_id': 'ls123'} fake_new_remote_macs = [fake_dict] with mock.patch.object(lib, 'get_ucast_mac_remote', return_value=None) as get_mr: with mock.patch.object(lib, 'add_ucast_mac_remote') as add_mr: self.ovsdb_data._process_new_remote_macs( self.context, fake_new_remote_macs) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) get_mr.assert_called_with(self.context, fake_dict) add_mr.assert_called_with(self.context, fake_dict) def test_process_modified_remote_macs(self): fake_dict = {'logical_switch_id': 'ls123'} fake_modified_remote_macs = [fake_dict] with mock.patch.object(lib, 'update_ucast_mac_remote') as update_mr: self.ovsdb_data._process_modified_remote_macs( self.context, fake_modified_remote_macs) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) update_mr.assert_called_with(self.context, fake_dict) def test_process_deleted_logical_switches(self): fake_dict = {} fake_deleted_logical_switches = [fake_dict] with mock.patch.object(lib, 'delete_logical_switch') as delete_ls: self.ovsdb_data._process_deleted_logical_switches( self.context, fake_deleted_logical_switches) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) delete_ls.assert_called_with(self.context, fake_dict) def test_get_agent_by_mac(self): fake_mac = {'mac': 'fake_mac_1'} fake_port = [{'binding:host_id': 'fake_host'}] with mock.patch.object(self.ovsdb_data, '_get_port_by_mac', return_value=fake_port) as mock_get_port_mac, \ mock.patch.object( self.ovsdb_data, '_get_agent_details_by_host') as mock_get_agent_detail: self.ovsdb_data._get_agent_by_mac(self.context, fake_mac) mock_get_port_mac.assert_called_with(self.context, 'fake_mac_1') mock_get_agent_detail.assert_called_with(self.context, 'fake_host') def test_get_agent_details_by_host(self): fake_agent = {'configurations': {'tunnel_types': ["vxlan"], 'l2_population': True}} fake_agents = [fake_agent] with mock.patch.object(self.ovsdb_data.core_plugin, 'get_agents', return_value=fake_agents): l2pop_enabled = self.ovsdb_data._get_agent_details_by_host( self.context, 'fake_host') self.assertTrue(l2pop_enabled) def test_process_deleted_physical_switches(self): fake_dict = {} fake_deleted_physical_switches = [fake_dict] fake_ls_dict = {'uuid': 'ls-uuid'} fake_ls_list = [fake_ls_dict] with mock.patch.object(lib, 'delete_physical_switch') as delete_ps, \ mock.patch.object(lib, 'get_all_physical_switches_by_ovsdb_id', return_value=False) as get_ps, \ mock.patch.object(lib, 'get_all_logical_switches_by_ovsdb_id', return_value=fake_ls_list) as get_ls, \ mock.patch.object(agent_api.L2gatewayAgentApi, 'delete_network') as del_network: self.ovsdb_data._process_deleted_physical_switches( self.context, fake_deleted_physical_switches) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) delete_ps.assert_called_with(self.context, fake_dict) get_ps.assert_called_with(self.context, 'fake_ovsdb_id') get_ls.assert_called_with(self.context, 'fake_ovsdb_id') del_network.assert_called_with(self.context, 'fake_ovsdb_id', 'ls-uuid') def test_process_deleted_physical_ports(self): fake_dict = {'name': 'fake_uuid', 'uuid': 'fake_name'} fake_deleted_physical_ports = [fake_dict] fake_physical_port = {'uuid': 'fake_uuid', 'name': 'fake_name'} fake_physical_switch = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id', 'name': 'fake_switch'}, fake_vlan_binding = {'port_uuid:': 'fake_port_uuid', 'vlan': 'fake_vlan', 'logical_switch_uuid': 'fake_switch_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} with mock.patch.object(lib, 'delete_physical_port'), \ mock.patch.object(lib, 'get_physical_port', return_value=fake_physical_port), \ mock.patch.object(lib, 'get_physical_switch', return_vaue=fake_physical_switch), \ mock.patch.object(lib, 'get_all_vlan_bindings_by_physical_port', return_vaue=fake_vlan_binding), \ mock.patch.object(l2gateway_db.L2GatewayMixin, '_get_l2gw_ids_by_interface_switch', return_value=['fake_uuid']), \ mock.patch.object( l2gateway_db.L2GatewayMixin, '_delete_connection_by_l2gw_id') as l2gw_conn_del: self.ovsdb_data._process_deleted_physical_ports( self.context, fake_deleted_physical_ports) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) l2gw_conn_del.assert_called_with(self.context, 'fake_uuid') @mock.patch.object(lib, 'delete_physical_port') @mock.patch.object(lib, 'get_physical_port') @mock.patch.object(lib, 'get_physical_switch') @mock.patch.object(l2gateway_db.L2GatewayMixin, '_get_l2gw_ids_by_interface_switch', return_value=['fake_uuid']) @mock.patch.object(l2gateway_db.L2GatewayMixin, '_delete_connection_by_l2gw_id') @mock.patch.object(lib, 'get_all_vlan_bindings_by_physical_port') @mock.patch.object(lib, 'get_all_vlan_bindings_by_logical_switch') @mock.patch.object(data.OVSDBData, '_delete_macs_from_ovsdb') @mock.patch.object(lib, 'delete_vlan_binding') def test_process_deleted_physical_ports_with_delete_macs( self, del_vlan, del_macs, get_vlan_by_ls, get_vlan_by_pp, l2gw_conn_del, get_l2gw, get_ps, get_pp, delete_pp): fake_dict = {'uuid': 'fake_uuid', 'name': 'fake_name', 'logical_switch_id': 'fake_ls_id', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_deleted_physical_ports = [fake_dict] fake_physical_port = {'uuid': 'fake_uuid', 'name': 'fake_name', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_physical_switch = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id', 'name': 'fake_switch'} vlan_binding_dict = {'logical_switch_uuid': 'fake_ls_id', 'ovsdb_identifier': 'fake_ovsdb_id', 'port_uuid': 'fake_uuid', 'vlan': 'fake_vlan', 'logical_switch_id': 'fake_ls_id'} fake_vlan_binding_list = [vlan_binding_dict] fake_binding_list = [vlan_binding_dict] get_pp.return_value = fake_physical_port get_ps.return_vaue = fake_physical_switch get_vlan_by_pp.return_value = fake_vlan_binding_list get_vlan_by_ls.return_value = fake_binding_list self.ovsdb_data._process_deleted_physical_ports( self.context, fake_deleted_physical_ports) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) l2gw_conn_del.assert_called_with(self.context, 'fake_uuid') get_vlan_by_pp.assert_called_with(self.context, fake_dict) del_vlan.assert_called_with(self.context, vlan_binding_dict) get_vlan_by_ls.assert_called_with(self.context, vlan_binding_dict) del_macs.assert_called_with(self.context, 'fake_ls_id', 'fake_ovsdb_id') del_vlan.assert_called_with(self.context, vlan_binding_dict) delete_pp.assert_called_with(self.context, fake_dict) @mock.patch.object(data.OVSDBData, '_get_logical_switch_ids', return_value=['1']) @mock.patch.object(lib, 'get_all_physical_switches_by_ovsdb_id', return_value=[{'tunnel_ip': '3.3.3.3'}]) @mock.patch.object(data.OVSDBData, '_get_fdb_entries') @mock.patch.object(lib, 'delete_physical_locator') @mock.patch.object(data.OVSDBData, '_get_agent_ips', return_value={'1.1.1.1': 'hostname'}) @mock.patch.object(tunnel_calls.Tunnel_Calls, 'trigger_l2pop_delete') def test_process_deleted_physical_locators( self, trig_l2pop, get_agent_ips, delete_pl, get_fdb, get_all_ps, get_ls): """Test case to test _process_deleted_physical_locators. for unicast rpc to the L2 agent """ fake_dict1 = {'dst_ip': '1.1.1.1'} fake_dict2 = {'dst_ip': '2.2.2.2'} fake_deleted_physical_locators = [fake_dict2, fake_dict1] mock.patch.object(manager, 'NeutronManager').start() self.ovsdb_data._process_deleted_physical_locators( self.context, fake_deleted_physical_locators) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict1) self.assertTrue(get_ls.called) self.assertTrue(get_all_ps.called) self.assertTrue(get_fdb.called) self.assertEqual('fake_ovsdb_id', fake_dict1[n_const.OVSDB_IDENTIFIER]) delete_pl.assert_called_with(self.context, fake_dict1) self.assertTrue(get_agent_ips.called) trig_l2pop.assert_called_with(self.context, mock.ANY, 'hostname') @mock.patch.object(data.OVSDBData, '_get_logical_switch_ids', return_value=['1']) @mock.patch.object(lib, 'get_all_physical_switches_by_ovsdb_id', return_value=[{'tunnel_ip': '3.3.3.3'}]) @mock.patch.object(data.OVSDBData, '_get_fdb_entries') @mock.patch.object(lib, 'delete_physical_locator') @mock.patch.object(data.OVSDBData, '_get_agent_ips', return_value={'2.2.2.2': 'hostname'}) @mock.patch.object(tunnel_calls.Tunnel_Calls, 'trigger_l2pop_delete') def test_process_deleted_physical_locators1( self, trig_l2pop, get_agent_ips, delete_pl, get_fdb, get_all_ps, get_ls): """Test case to test _process_deleted_physical_locators. for broadcast rpc to the L2 agents """ fake_dict1 = {'dst_ip': '1.1.1.1'} fake_deleted_physical_locators = [fake_dict1] mock.patch.object(manager, 'NeutronManager').start() self.ovsdb_data._process_deleted_physical_locators( self.context, fake_deleted_physical_locators) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict1) self.assertTrue(get_ls.called) self.assertTrue(get_all_ps.called) self.assertTrue(get_fdb.called) self.assertEqual('fake_ovsdb_id', fake_dict1[n_const.OVSDB_IDENTIFIER]) delete_pl.assert_called_once_with(self.context, fake_dict1) self.assertTrue(get_agent_ips.called) trig_l2pop.assert_called_with(self.context, mock.ANY) def test_process_deleted_local_macs(self): fake_dict = {'uuid': '123456', 'mac': 'mac123', 'ovsdb_identifier': 'host1', 'logical_switch_id': 'ls123'} fake_deleted_local_macs = [fake_dict] with mock.patch.object(lib, 'delete_ucast_mac_local') as delete_ml: with mock.patch.object(lib, 'get_ucast_mac_remote_by_mac_and_ls', return_value=True): self.ovsdb_data._process_deleted_local_macs( self.context, fake_deleted_local_macs) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) delete_ml.assert_called_with(self.context, fake_dict) def test_process_deleted_remote_macs(self): fake_dict = {} fake_deleted_remote_macs = [fake_dict] with mock.patch.object(lib, 'delete_ucast_mac_remote') as delete_mr: self.ovsdb_data._process_deleted_remote_macs( self.context, fake_deleted_remote_macs) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict) self.assertEqual('fake_ovsdb_id', fake_dict[n_const.OVSDB_IDENTIFIER]) delete_mr.assert_called_with(self.context, fake_dict) @mock.patch.object(lib, 'get_physical_port') @mock.patch.object(lib, 'add_physical_port') @mock.patch.object(lib, 'get_all_vlan_bindings_by_physical_port') @mock.patch.object(lib, 'add_vlan_binding') @mock.patch.object(lib, 'update_physical_ports_status') def test_process_modified_physical_ports(self, update_pp_status, add_vlan, get_vlan, add_pp, get_pp): fake_dict1 = {} fake_dict2 = {'vlan_bindings': [fake_dict1], 'uuid': 'fake_uuid'} fake_modified_physical_ports = [fake_dict2] self.ovsdb_data._process_modified_physical_ports( self.context, fake_modified_physical_ports) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict2) self.assertEqual('fake_ovsdb_id', fake_dict2[n_const.OVSDB_IDENTIFIER]) get_pp.assert_called_with(self.context, fake_dict2) update_pp_status.assert_called_with(self.context, fake_dict2) self.assertFalse(add_pp.called) get_vlan.assert_called_with(self.context, fake_dict2) self.assertIn(n_const.OVSDB_IDENTIFIER, fake_dict1) self.assertIn('port_uuid', fake_dict1) add_vlan.assert_called_with(self.context, fake_dict1) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/service_drivers/0000775000175000017500000000000014572557757032423 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000020500000000000010212 xustar00111 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/service_drivers/__init__.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/service_drivers/__init__.p0000664000175000017500000000000014572557755034327 0ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000021300000000000010211 xustar00117 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/service_drivers/test_agent_api.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/service_drivers/test_agent0000664000175000017500000001575214572557755034513 0ustar00jamespagejamespage# Copyright (c) 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. from unittest import mock from neutron.tests import base from neutron_lib import rpc as n_rpc from networking_l2gw.services.l2gateway import exceptions as l2gw_exc from networking_l2gw.services.l2gateway.service_drivers import agent_api import oslo_messaging as messaging class TestL2GatewayAgentApi(base.BaseTestCase): def setUp(self): self.client_mock_p = mock.patch.object(n_rpc, 'get_client') self.client_mock = self.client_mock_p.start() self.context = mock.ANY self.topic = 'foo_topic' self.host = 'foo_host' self.plugin_rpc = agent_api.L2gatewayAgentApi( self.topic, self.host) super(TestL2GatewayAgentApi, self).setUp() def test_set_monitor_agent(self): cctxt = mock.Mock() fake_hostname = 'fake_hostname' self.plugin_rpc.client.prepare.return_value = cctxt self.plugin_rpc.set_monitor_agent( self.context, fake_hostname) cctxt.cast.assert_called_with( self.context, 'set_monitor_agent', hostname=fake_hostname) def test_add_vif_to_gateway(self): cctxt = mock.Mock() fake_ovsdb_identifier = 'fake_ovsdb_id' fake_logical_switch = {} fake_physical_locator = {} fake_mac_remote = {} self.plugin_rpc.client.prepare.return_value = cctxt self.plugin_rpc.add_vif_to_gateway( self.context, fake_ovsdb_identifier, fake_logical_switch, fake_physical_locator, fake_mac_remote) cctxt.call.assert_called_with( self.context, 'add_vif_to_gateway', ovsdb_identifier=fake_ovsdb_identifier, logical_switch_dict=fake_logical_switch, locator_dict=fake_physical_locator, mac_dict=fake_mac_remote) def test_update_vif_to_gateway(self): cctxt = mock.Mock() fake_ovsdb_identifier = 'fake_ovsdb_id' fake_physical_locator = {} fake_mac_remote = {} self.plugin_rpc.client.prepare.return_value = cctxt self.plugin_rpc.update_vif_to_gateway( self.context, fake_ovsdb_identifier, fake_physical_locator, fake_mac_remote) cctxt.call.assert_called_with( self.context, 'update_vif_to_gateway', ovsdb_identifier=fake_ovsdb_identifier, locator_dict=fake_physical_locator, mac_dict=fake_mac_remote) def test_delete_vif_from_gateway(self): cctxt = mock.Mock() self.plugin_rpc.client.prepare.return_value = cctxt self.plugin_rpc.delete_vif_from_gateway(self.context, mock.ANY, mock.ANY, mock.ANY) cctxt.call.assert_called_with( self.context, 'delete_vif_from_gateway', ovsdb_identifier=mock.ANY, logical_switch_uuid=mock.ANY, mac=mock.ANY) def test_delete_network(self): cctxt = mock.Mock() self.plugin_rpc.client.prepare.return_value = cctxt self.plugin_rpc.delete_network(self.context, mock.ANY, mock.ANY) cctxt.cast.assert_called_with( self.context, 'delete_network', ovsdb_identifier=mock.ANY, logical_switch_uuid=mock.ANY) def test_validate_request_op_method(self): self.assertRaises(l2gw_exc.InvalidMethod, self.plugin_rpc._validate_request_op_method, self.context, 'fake_op_method') def test_update_connection_to_gateway(self): cctxt = mock.Mock() fake_ovsdb_identifier = 'fake_ovsdb_id' fake_logical_switch = {} fake_physical_locator_list = [] fake_mac_dicts = [{}] fake_port_dicts = [{}] fake_op_method = 'DELETE' self.plugin_rpc.client.prepare.return_value = cctxt self.plugin_rpc.update_connection_to_gateway( self.context, fake_ovsdb_identifier, fake_logical_switch, fake_physical_locator_list, fake_mac_dicts, fake_port_dicts, fake_op_method) cctxt.call.assert_called_with( self.context, 'update_connection_to_gateway', ovsdb_identifier=fake_ovsdb_identifier, logical_switch_dict=fake_logical_switch, locator_dicts=fake_physical_locator_list, mac_dicts=fake_mac_dicts, port_dicts=fake_port_dicts, op_method=fake_op_method) def test_update_connection_to_gateway_with_invalid_op_method(self): cctxt = mock.Mock() fake_ovsdb_identifier = 'fake_ovsdb_id' fake_logical_switch = {} fake_physical_locator_list = [] fake_mac_dicts = [{}] fake_port_dicts = [{}] fake_op_method = 'create_delete_op' self.plugin_rpc.client.prepare.return_value = cctxt self.assertRaises( l2gw_exc.InvalidMethod, self.plugin_rpc.update_connection_to_gateway, self.context, fake_ovsdb_identifier, fake_logical_switch, fake_physical_locator_list, fake_mac_dicts, fake_port_dicts, fake_op_method) def test_update_connection_to_gateway_with_error(self): cctxt = mock.Mock() fake_ovsdb_identifier = 'fake_ovsdb_id' fake_logical_switch = {} fake_physical_locator_list = [] fake_mac_dicts = [{}] fake_port_dicts = [{}] fake_op_method = 'CREATE' self.plugin_rpc.client.prepare.return_value = cctxt # Test with a timeout exception with mock.patch.object(cctxt, 'call', side_effect=messaging.MessagingTimeout): self.assertRaises( l2gw_exc.OVSDBError, self.plugin_rpc.update_connection_to_gateway, self.context, fake_ovsdb_identifier, fake_logical_switch, fake_physical_locator_list, fake_mac_dicts, fake_port_dicts, fake_op_method) # Test with a remote exception with mock.patch.object(cctxt, 'call', side_effect=Exception): self.assertRaises( l2gw_exc.OVSDBError, self.plugin_rpc.update_connection_to_gateway, self.context, fake_ovsdb_identifier, fake_logical_switch, fake_physical_locator_list, fake_mac_dicts, fake_port_dicts, fake_op_method) ././@PaxHeader0000000000000000000000000000021200000000000010210 xustar00116 path=networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/service_drivers/test_rpc_l2gw.py 22 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/service_drivers/test_rpc_l0000664000175000017500000025503614572557755034515 0ustar00jamespagejamespage# Copyright (c) 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. from unittest import mock from neutron.db import agents_db from neutron.tests.unit.plugins.ml2 import test_plugin from neutron_lib import context as ctx from neutron_lib import rpc as n_rpc from networking_l2gw.db.l2gateway.ovsdb import lib as db from networking_l2gw.services.l2gateway.common import l2gw_validators from networking_l2gw.services.l2gateway.common import ovsdb_schema from networking_l2gw.services.l2gateway import exceptions as l2gw_exc from networking_l2gw.services.l2gateway.ovsdb import data from networking_l2gw.services.l2gateway.service_drivers import agent_api from networking_l2gw.services.l2gateway.service_drivers import rpc_l2gw from oslo_utils import importutils class TestL2gwRpcDriver(test_plugin.Ml2PluginV2TestCase): def setUp(self): super(TestL2gwRpcDriver, self).setUp() self.service_plugin = mock.MagicMock() load_driver = mock.MagicMock() self.service_plugin._load_drivers.return_value = load_driver self.service_plugin._get_driver_for_provider.return_value = load_driver self.plugin = rpc_l2gw.L2gwRpcDriver(self.service_plugin) self.plugin.agent_rpc = mock.MagicMock() self.ovsdb_identifier = 'fake_ovsdb_id' self.context = mock.ANY @mock.patch.object(importutils, 'import_object') @mock.patch.object(agents_db, 'AgentExtRpcCallback') @mock.patch.object(n_rpc, 'Connection') @mock.patch.object(n_rpc.Connection, 'consume_in_threads') @mock.patch.object(rpc_l2gw.LOG, 'debug') @mock.patch.object(rpc_l2gw.L2gwRpcDriver, 'start_l2gateway_agent_scheduler') def test_l2rpcdriver_init(self, scheduler, debug, consume_in_thread, create_conn, agent_calback, import_obj): rpc_l2gw.L2gwRpcDriver(self.service_plugin) rpc_conn = create_conn.return_value rpc_conn.create_consumer.assert_called_with( mock.ANY, [mock.ANY, mock.ANY], fanout=mock.ANY) rpc_conn.consume_in_threads.assert_called_with() self.assertTrue(import_obj.called) self.assertTrue(agent_calback.called) self.assertTrue(create_conn.called) self.assertTrue(debug.called) self.assertTrue(scheduler.called) def test_validate_connection(self): fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_net_seg_list = [{'id': 'fake_id', 'network_type': 'fake_vxlan', 'physical_network': 'fake_phy_net', 'segmentation_id': 100}] fake_l2_gw = {'id': 'fake_l2gw_id'} fake_tenant_id = 'fake_tenant_id' fake_filters = {'network_id': ['fake_network_id'], 'tenant_id': [fake_tenant_id], 'l2_gateway_id': ['fake_l2gw_id']} with mock.patch.object(self.service_plugin, '_is_vlan_configured_on_any_interface_for_l2gw', return_value=False) as is_vlan, \ mock.patch.object(l2gw_validators, 'validate_network_mapping_list', return_value='fake_network_id') as val_ntwk, \ mock.patch.object(self.service_plugin, '_get_network_segments', return_value=fake_net_seg_list), \ mock.patch.object(self.service_plugin, '_get_network', return_value=True) as get_network, \ mock.patch.object(self.service_plugin, '_get_l2_gateway', return_value=fake_l2_gw) as get_l2gw, \ mock.patch.object(self.service_plugin, '_retrieve_gateway_connections', return_value=False) as ret_gw_conn, \ mock.patch.object(self.service_plugin, '_get_tenant_id_for_create', return_value=fake_tenant_id) as get_ten_id, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=False) as get_l2_gw_conn, \ mock.patch.object( self.plugin, '_check_port_fault_status_and_switch_fault_status') as pf_sf: self.plugin._validate_connection(self.context, fake_connection) is_vlan.assert_called_with(self.context, 'fake_l2gw_id') val_ntwk.assert_called_with(fake_connection, False) get_network.assert_called_with(self.context, 'fake_network_id') get_l2gw.assert_called_with(self.context, 'fake_l2gw_id') pf_sf.assert_called_with(self.context, 'fake_l2gw_id') ret_gw_conn.assert_called_with(self.context, 'fake_l2gw_id', fake_connection) get_ten_id.assert_called_with(self.context, fake_l2_gw) get_l2_gw_conn.assert_called_with(self.context, filters=fake_filters) @mock.patch.object(db, 'get_physical_switch_by_name') @mock.patch.object(db, 'get_logical_switch_by_name') @mock.patch.object(db, 'get_physical_port_by_name_and_ps') def test_process_port_list(self, get_pp, get_ls, get_ps): fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_device = {'id': 'fake_device_id', 'device_name': 'fake_device_name'} fake_method = 'CREATE' fake_interface = {'interface_name': 'fake_interface_name'} fake_interface_list = [fake_interface] fake_physical_switch = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_pp_dict = {'interface_name': 'fake_interface_name', 'ovsdb_identifier': 'fake_ovsdb_id', 'physical_switch_id': 'fake_uuid', 'logical_switch_name': 'fake_network_id', 'uuid': 'fake_uuid', 'name': 'fake_name'} fake_logical_switch = {'uuid': 'fake_uuid', 'name': 'fake_network_id'} fake_physical_port = {'uuid': 'fake_uuid', 'name': 'fake_name'} get_ps.return_value = fake_physical_switch get_ls.return_value = fake_logical_switch get_pp.return_value = fake_physical_port with mock.patch.object( self.service_plugin, 'get_l2gateway_interfaces_by_device_id', return_value=fake_interface_list) as get_intf, \ mock.patch.object(self.plugin, '_generate_port_list') as gen_port_list: self.plugin._process_port_list(self.context, fake_device, fake_connection, fake_method) get_intf.assert_called_with(self.context, 'fake_device_id') get_ps.assert_called_with(self.context, 'fake_device_name') get_pp.assert_called_with(self.context, fake_pp_dict) get_ls.assert_called_with(self.context, fake_pp_dict) gen_port_list.assert_called_with( self.context, fake_method, 100, fake_interface, fake_pp_dict, 'fake_uuid', fake_connection) def test_generate_port_list_for_create(self): fake_method = 'CREATE' fake_interface = {'interface_name': 'fake_interface_name'} fake_pp_dict = {'interface_name': 'fake_interface_name', 'ovsdb_identifier': 'fake_ovsdb_id', 'physical_switch_id': 'fake_uuid', 'logical_switch_name': 'fake_network_id', 'uuid': 'fake_uuid', 'name': 'fake_name'} fake_vlan_binding = {'vlan': 100, 'logical_switch_uuid': 'fake_uuid'} fake_vlan_binding_list = [fake_vlan_binding] with mock.patch.object( db, 'get_all_vlan_bindings_by_physical_port', return_value=fake_vlan_binding_list) as ( get_vlan): self.plugin._generate_port_list( self.context, fake_method, 101, fake_interface, fake_pp_dict, 'fake_uuid') get_vlan.assert_called_with(self.context, fake_pp_dict) def test_generate_port_list_for_create_for_duplicate_seg_id(self): fake_method = 'CREATE' fake_interface = {'interface_name': 'fake_interface_name'} fake_pp_dict = {'interface_name': 'fake_interface_name', 'ovsdb_identifier': 'fake_ovsdb_id', 'physical_switch_id': 'fake_uuid', 'logical_switch_name': 'fake_network_id', 'uuid': 'fake_uuid', 'name': 'fake_name'} fake_vlan_binding = {'vlan': 100, 'logical_switch_uuid': 'fake_uuid'} fake_vlan_binding_list = [fake_vlan_binding] with mock.patch.object( db, 'get_all_vlan_bindings_by_physical_port', return_value=fake_vlan_binding_list) as ( get_vlan): self.assertRaises(l2gw_exc.L2GatewayDuplicateSegmentationID, self.plugin._generate_port_list, self.context, fake_method, 100, fake_interface, fake_pp_dict, 'fake_uuid') get_vlan.assert_called_with(self.context, fake_pp_dict) def test_generate_port_list_for_delete(self): fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_method = 'DELETE' fake_interface = {'interface_name': 'fake_interface_name'} fake_pp_dict = {'interface_name': 'fake_interface_name', 'ovsdb_identifier': 'fake_ovsdb_id', 'physical_switch_id': 'fake_uuid', 'logical_switch_name': 'fake_network_id', 'uuid': 'fake_uuid'} fake_vlan_binding = {'vlan': 100, 'logical_switch_uuid': 'fake_uuid'} fake_vlan_binding_list = [fake_vlan_binding] vlan_dict = {'vlan': 100, 'logical_switch_uuid': 'fake_uuid'} physical_port = ovsdb_schema.PhysicalPort( uuid='fake_uuid', name='fake_interface_name', phys_switch_id='fake_uuid', vlan_binding_dicts=None, port_fault_status=None) phys_port_dict = physical_port.__dict__ phys_port_dict['vlan_bindings'] = [vlan_dict] with mock.patch.object( db, 'get_all_vlan_bindings_by_physical_port', return_value=fake_vlan_binding_list) as ( get_vlan): port = self.plugin._generate_port_list( self.context, fake_method, 100, fake_interface, fake_pp_dict, 'fake_uuid', fake_connection) get_vlan.assert_called_with(self.context, fake_pp_dict) self.assertEqual(phys_port_dict, port) def test_get_ip_details(self): fake_port = {'binding:host_id': 'fake_host', 'fixed_ips': [{'ip_address': 'fake_ip'}]} fake_agent = {'configurations': {'tunneling_ip': 'fake_tun_ip'}} with mock.patch.object(self.plugin, '_get_agent_details', return_value=fake_agent) as get_agent: (ret_dst_ip, ret_ip_add) = self.plugin._get_ip_details( self.context, fake_port) get_agent.assert_called_with(self.context, 'fake_host') self.assertEqual('fake_tun_ip', ret_dst_ip) self.assertEqual('fake_ip', ret_ip_add) def test_get_agent_details_for_no_ovs_agent(self): core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_agents. return_value) = [] self.assertRaises(l2gw_exc.L2AgentNotFoundByHost, self.plugin._get_agent_details, self.context, 'fake_host') def test_get_network_details(self): fake_network = {'id': 'fake_network_id', 'name': 'fake_network_name', 'provider:segmentation_id': 'fake_key'} core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_network. return_value) = fake_network def test_get_port_details(self): fake_port = {'binding:host_id': 'fake_host', 'fixed_ips': [{'ip_address': 'fake_ip'}], 'mac_address': 'fake_mac_add'} fake_port_list = [fake_port] core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_ports. return_value) = fake_port_list def test_get_agent_details(self): fake_agent = [{'configurations': {'tunneling_ip': 'fake_tun_ip'}}] core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_agents.return_value) = fake_agent def test_get_logical_switch_dict(self): fake_logical_switch = {'uuid': 'fake_uuid', 'name': 'fake_network_id'} fake_ls = None fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_network = {'id': 'fake_network_id', 'name': 'fake_network_name', 'provider:network_type': 'vxlan', 'provider:segmentation_id': 'fake_key'} fake_ls_dict = {'uuid': 'fake_uuid', 'name': 'fake_network_id', 'description': 'fake_network_name', 'key': 'fake_key'} fake_ls_dict_without_ls = {'uuid': None, 'name': 'fake_network_id', 'description': 'fake_network_name', 'key': 'fake_key'} with mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network) as get_network: ret_ls_dict = self.plugin._get_logical_switch_dict( self.context, fake_logical_switch, fake_connection) ret_ls_dict_without_ls = self.plugin._get_logical_switch_dict( self.context, fake_ls, fake_connection) get_network.assert_called_with(self.context, 'fake_network_id') self.assertEqual(fake_ls_dict, ret_ls_dict) self.assertEqual(fake_ls_dict_without_ls, ret_ls_dict_without_ls) def test_get_logical_switch_dict_for_multi_segment_network(self): fake_logical_switch = {'uuid': 'fake_uuid', 'name': 'fake_network_id'} fake_ls = None fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_network = {'id': 'fake_network_id', 'name': 'fake_network_name', 'segments': [{"provider:network_type": "vxlan", "provider:segmentation_id": 'fake_key'}, {"provider:network_type": "vlan", "provider:segmentation_id": 'fake_key'}]} fake_ls_dict = {'uuid': 'fake_uuid', 'name': 'fake_network_id', 'description': 'fake_network_name', 'key': 'fake_key'} fake_ls_dict_without_ls = {'uuid': None, 'name': 'fake_network_id', 'description': 'fake_network_name', 'key': 'fake_key'} with mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network) as get_network: ret_ls_dict = self.plugin._get_logical_switch_dict( self.context, fake_logical_switch, fake_connection) ret_ls_dict_without_ls = self.plugin._get_logical_switch_dict( self.context, fake_ls, fake_connection) get_network.assert_called_with(self.context, 'fake_network_id') self.assertEqual(fake_ls_dict, ret_ls_dict) self.assertEqual(fake_ls_dict_without_ls, ret_ls_dict_without_ls) def test_get_logical_switch_dict_for_non_Vxlan_networks(self): fake_logical_switch = {'uuid': 'fake_uuid', 'name': 'fake_network_id'} fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_network = {'id': 'fake_network_id', 'name': 'fake_network_name', 'segments': [{"provider:network_type": "vlan", "provider:segmentation_id": 'fake_key'}, {"provider:network_type": "gre", "provider:segmentation_id": 'fake_key'}]} with mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network) as get_network: self.assertRaises(l2gw_exc.VxlanSegmentationIDNotFound, self.plugin._get_logical_switch_dict, self.context, fake_logical_switch, fake_connection) get_network.assert_called_with(self.context, 'fake_network_id') def test_get_logical_switch_dict_for_multiple_vxlan_segments(self): fake_logical_switch = {'uuid': 'fake_uuid', 'name': 'fake_network_id'} fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_network = {'id': 'fake_network_id', 'name': 'fake_network_name', 'segments': [{"provider:network_type": "vxlan", "provider:segmentation_id": 'seg_1'}, {"provider:network_type": "vxlan", "provider:segmentation_id": 'seg_2'}]} with mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network) as get_network: self.assertRaises(l2gw_exc.MultipleVxlanSegmentsFound, self.plugin._get_logical_switch_dict, self.context, fake_logical_switch, fake_connection) get_network.assert_called_with(self.context, 'fake_network_id') @mock.patch.object(db, 'get_physical_locator_by_dst_ip') def test_get_locator_list(self, get_pl_by_dst_ip): fake_dst_ip = 'fake_tun_ip' fake_ovsdb_id = 'fake_ovsdb_id' fake_mac_list = [] fake_locator_list = [] fake_locator_dict = {'uuid': 'fake_uuid', 'dst_ip': 'fake_tun_ip', 'ovsdb_identifier': 'fake_ovsdb_id', 'macs': []} fake_pl = {'uuid': 'fake_uuid', 'dst_ip': 'fake_tun_ip'} fale_pl_list = [fake_locator_dict] get_pl_by_dst_ip.return_value = fake_pl with mock.patch.object( self.plugin, '_get_physical_locator_dict', return_value=fake_locator_dict) as get_pl_dict: ret_pl_list = self.plugin._get_locator_list( self.context, fake_dst_ip, fake_ovsdb_id, fake_mac_list, fake_locator_list) get_pl_by_dst_ip.assert_called_with(self.context, fake_locator_dict) get_pl_dict.assert_called_with(fake_dst_ip, 'fake_uuid', fake_mac_list) self.assertEqual(fale_pl_list, ret_pl_list) @mock.patch.object(db, 'get_physical_switch_by_name') def test_get_identifer_list(self, get_ps): fake_connection = {'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_device = {'id': 'fake_device_id', 'device_name': 'fake_device_name'} fake_device_list = [fake_device] fake_physical_switch = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_identifier_set = set(['fake_ovsdb_id']) get_ps.return_value = fake_physical_switch with mock.patch.object(self.service_plugin, 'get_l2gateway_devices_by_gateway_id', return_value=fake_device_list): ret_value = self.plugin._get_identifer_list(self.context, fake_connection) (self.service_plugin.get_l2gateway_devices_by_gateway_id. assert_called_with(mock.ANY, mock.ANY)) db.get_physical_switch_by_name.assert_called_with( self.context, 'fake_device_name') self.assertEqual(fake_identifier_set, ret_value) def test_get_set_of_ovsdb_ids(self): fake_connection = {'id': 'fake_id', 'l2_gateway_id': 'fake_l2gw_id', 'network_id': 'fake_network_id', 'segmentation_id': 100} fake_gw_conn_ovsdb_set = set(['fake_ovsdb_id']) fake_connection_list = [fake_connection] with mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=fake_connection_list): ret_value = self.plugin._get_set_of_ovsdb_ids( self.context, fake_connection, fake_gw_conn_ovsdb_set) self.service_plugin.get_l2_gateway_connections.assert_called_with( mock.ANY, filters=mock.ANY) self.assertEqual(fake_gw_conn_ovsdb_set, ret_value) def test_remove_vm_macs(self): fake_network_id = 'fake_network_id' fake_ovsdb_id_set = set(['fake_ovsdb_id']) fake_port = {'binding:host_id': 'fake_host', 'fixed_ips': [{'ip_address': 'fake_ip'}], 'mac_address': 'fake_mac_add'} fake_port_list = [fake_port] with mock.patch.object(self.plugin, '_get_port_details', return_value=fake_port_list) as get_port, \ mock.patch.object(self.plugin, 'delete_port_mac') as delete_mac: self.plugin._remove_vm_macs(self.context, fake_network_id, fake_ovsdb_id_set) get_port.assert_called_with(self.context, fake_network_id) delete_mac.assert_called_with(self.context, fake_port_list) def test_add_port_mac(self): fake_ip1 = "fake_ip1" fake_ip2 = "fake_ip2" fake_network_dict = {'provider:segmentation_id': 100, 'name': 'fake_name', 'id': 'fake_network_id'} fake_conn_dict = {'id': 'fake_conn_id', 'l2_gateway_id': 'fake_gateway_id'} fake_conn_list = [fake_conn_dict] fake_logical_switch = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_network_id', 'description': 'fake_name'} fake_logical_switch_list = [fake_logical_switch] fake_locator_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_locator_id', 'dst_ip': 'fake_ip2'} fake_pl_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'dst_ip': 'fake_ip1'} fake_mac_dict = {'mac': 'fake_mac', 'logical_switch_id': 'fake_network_id', 'physical_locator_id': 'fake_locator_id', 'ip_address': 'fake_ip1'} fake_dict = fake_mac_dict fake_dict['ovsdb_identifier'] = 'fake_ovsdb_id' core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_port. return_value) = {'device_owner': 'fake_owner', 'network_id': 'fake_network_id', 'mac_address': 'fake_mac'} ovsdb_identifier = 'fake_ovsdb_id' with mock.patch.object(self.plugin, '_get_ip_details', return_value=(fake_ip1, fake_ip2)), \ mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network_dict) as get_network, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=fake_conn_list) as get_l2gw_conn, \ mock.patch.object(self.plugin, '_form_physical_locator_schema', return_value=fake_locator_dict) as get_pl, \ mock.patch.object(ovsdb_schema, 'UcastMacsRemote') as mock_ucmr, \ mock.patch.object(self.plugin, '_get_dict', return_value=fake_dict) as get_dict, \ mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=False) as get_ucast_mac, \ mock.patch.object(self.plugin.agent_rpc, 'add_vif_to_gateway') as add_rpc, \ mock.patch.object(self.plugin.agent_rpc, 'update_vif_to_gateway') as update_rpc, \ mock.patch.object(db, 'get_all_logical_switches_by_name', return_value=fake_logical_switch_list): remote_mac = ovsdb_schema.UcastMacsRemote( None, fake_dict['mac'], fake_logical_switch['uuid'], fake_locator_dict['uuid'], fake_ip2) mock_ucmr.return_value = remote_mac self.plugin.add_port_mac(self.context, fake_dict) get_network.assert_called_with(self.context, 'fake_network_id') get_l2gw_conn.assert_called_with( self.context, filters={'network_id': ['fake_network_id']}) get_pl.assert_called_with(self.context, fake_pl_dict) get_ucast_mac.assert_called_with(self.context, fake_dict) get_dict.assert_called_with(remote_mac) mock_ucmr.assert_called_with( uuid=None, mac=fake_dict['mac'], logical_switch_id=fake_logical_switch['uuid'], physical_locator_id=fake_locator_dict['uuid'], ip_address=fake_ip2) add_rpc.assert_called_with( self.context, ovsdb_identifier, fake_logical_switch, fake_locator_dict, fake_dict) self.assertFalse(update_rpc.called) @mock.patch.object(db, 'get_all_logical_switches_by_name') @mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=True) @mock.patch.object(agent_api.L2gatewayAgentApi, 'delete_vif_from_gateway') @mock.patch.object(db, 'get_logical_switch_by_name') def test_delete_port_mac_for_multiple_vlan_bindings( self, get_ls, delete_rpc, get_mac, get_all_ls): fake_port_list = [{'network_id': 'fake_network_id', 'device_owner': 'fake_owner', 'mac_address': 'fake_mac', 'ovsdb_identifier': 'fake_ovsdb_id'}] fake_logical_switch_dict = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_logical_switch_list = [fake_logical_switch_dict] lg_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} get_all_ls.return_value = fake_logical_switch_list get_ls.return_value = fake_logical_switch_dict with mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=[1, 2]): self.plugin.delete_port_mac(self.context, fake_port_list) self.assertFalse(get_all_ls.called) get_ls.assert_called_with(self.context, lg_dict) self.assertFalse(get_mac.called) self.assertFalse(delete_rpc.called) def test_add_port_mac_with_ovsdb_server_down(self): "Test case to test add_port_mac when the OVSDB server is down." fake_ip1 = "fake_ip1" fake_ip2 = "fake_ip2" fake_network_dict = {'provider:segmentation_id': 100, 'name': 'fake_name', 'id': 'fake_network_id'} fake_conn_dict = {'id': 'fake_conn_id', 'l2_gateway_id': 'fake_gateway_id'} fake_conn_list = [fake_conn_dict] fake_logical_switch = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_network_id', 'description': 'fake_name'} fake_logical_switch_list = [fake_logical_switch] fake_locator_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_locator_id', 'dst_ip': 'fake_ip2'} fake_pl_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'dst_ip': 'fake_ip1'} fake_mac_dict = {'mac': 'fake_mac', 'logical_switch_id': 'fake_network_id', 'physical_locator_id': 'fake_locator_id', 'ip_address': 'fake_ip1'} fake_dict = fake_mac_dict fake_dict['ovsdb_identifier'] = 'fake_ovsdb_id' core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_port. return_value) = {'device_owner': 'fake_owner', 'network_id': 'fake_network_id', 'mac_address': 'fake_mac'} ovsdb_identifier = 'fake_ovsdb_id' with mock.patch.object(self.plugin, '_get_ip_details', return_value=(fake_ip1, fake_ip2)), \ mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network_dict) as get_network, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=fake_conn_list), \ mock.patch.object(self.plugin, '_form_physical_locator_schema', return_value=fake_locator_dict) as get_pl, \ mock.patch.object(ovsdb_schema, 'UcastMacsRemote') as mock_ucmr, \ mock.patch.object(self.plugin, '_get_dict', return_value=fake_dict) as get_dict, \ mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=False) as get_ucast_mac, \ mock.patch.object(self.plugin.agent_rpc, 'add_vif_to_gateway', side_effect=RuntimeError) as add_rpc, \ mock.patch.object(self.plugin.agent_rpc, 'update_vif_to_gateway') as update_rpc, \ mock.patch.object( db, 'add_pending_ucast_mac_remote') as add_pending_mac, \ mock.patch.object(db, 'get_all_logical_switches_by_name', return_value=fake_logical_switch_list): remote_mac = ovsdb_schema.UcastMacsRemote( None, fake_dict['mac'], fake_logical_switch['uuid'], fake_locator_dict['uuid'], fake_ip2) mock_ucmr.return_value = remote_mac self.plugin.add_port_mac(self.context, fake_dict) get_network.assert_called_with(mock.ANY, mock.ANY) self.service_plugin.get_l2_gateway_connections.assert_called_with( mock.ANY, filters=mock.ANY) get_pl.assert_called_with(self.context, fake_pl_dict) get_dict.assert_called_with(remote_mac) get_ucast_mac.assert_called_with(self.context, fake_dict) add_rpc.assert_called_with( self.context, ovsdb_identifier, fake_logical_switch, fake_locator_dict, fake_dict) self.assertFalse(update_rpc.called) self.assertTrue(add_pending_mac.called) def test_add_port_mac_vm_migrate(self): fake_ip1 = "fake_ip1" fake_ip2 = "fake_ip2" fake_network_dict = {'provider:segmentation_id': 100, 'name': 'fake_name', 'id': 'fake_network_id'} fake_conn_dict = {'id': 'fake_conn_id', 'l2_gateway_id': 'fake_gateway_id'} fake_conn_list = [fake_conn_dict] fake_logical_switch = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_network_id', 'description': 'fake_name'} fake_logical_switch_list = [fake_logical_switch] fake_locator_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_locator_id', 'dst_ip': 'fake_ip2'} fake_pl_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'dst_ip': 'fake_ip1'} fake_mac_dict = {'mac': 'fake_mac', 'uuid': 'fake_mac_id', 'locator': 'fake_locator_id2', 'logical_switch_id': 'fake_network_id', 'physical_locator_id': 'fake_locator_id1', 'ip_address': 'fake_ip1'} fake_dict = fake_mac_dict fake_dict['ovsdb_identifier'] = 'fake_ovsdb_id' core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_port. return_value) = {'device_owner': 'fake_owner', 'network_id': 'fake_network_id', 'mac_address': 'fake_mac'} ovsdb_identifier = 'fake_ovsdb_id' with mock.patch.object(self.plugin, '_get_ip_details', return_value=(fake_ip1, fake_ip2)), \ mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network_dict) as get_network, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=fake_conn_list) as get_l2gw_conn, \ mock.patch.object(self.plugin, '_form_physical_locator_schema', return_value=fake_locator_dict) as get_pl, \ mock.patch.object(self.plugin, '_get_dict', return_value=fake_dict), \ mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=fake_mac_dict) as get_ucast_mac, \ mock.patch.object(self.plugin.agent_rpc, 'add_vif_to_gateway') as add_rpc, \ mock.patch.object(self.plugin.agent_rpc, 'update_vif_to_gateway') as update_rpc, \ mock.patch.object(db, 'get_all_logical_switches_by_name', return_value=fake_logical_switch_list): self.plugin.add_port_mac(self.context, fake_dict) get_network.assert_called_with(self.context, 'fake_network_id') get_l2gw_conn.assert_called_with( self.context, filters={'network_id': ['fake_network_id']}) get_pl.assert_called_with(self.context, fake_pl_dict) get_ucast_mac.assert_called_with(self.context, fake_dict) self.assertFalse(add_rpc.called) update_rpc.assert_called_with( self.context, ovsdb_identifier, fake_locator_dict, fake_dict) def test_add_port_mac_vm_migrate_with_ovsdb_server_down(self): "Test case to test update_port_mac when the OVSDB server is down." fake_ip1 = "fake_ip1" fake_ip2 = "fake_ip2" fake_network_dict = {'provider:segmentation_id': 100, 'name': 'fake_name', 'id': 'fake_network_id'} fake_conn_dict = {'id': 'fake_conn_id', 'l2_gateway_id': 'fake_gateway_id'} fake_conn_list = [fake_conn_dict] fake_logical_switch = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_network_id', 'description': 'fake_name'} fake_logical_switch_list = [fake_logical_switch] fake_locator_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_locator_id', 'dst_ip': 'fake_ip2'} fake_pl_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'dst_ip': 'fake_ip1'} fake_mac_dict = {'mac': 'fake_mac', 'uuid': 'fake_mac_id', 'locator': 'fake_locator_id2', 'logical_switch_id': 'fake_network_id', 'physical_locator_id': 'fake_locator_id1', 'ip_address': 'fake_ip1'} fake_dict = fake_mac_dict fake_dict['ovsdb_identifier'] = 'fake_ovsdb_id' core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_port. return_value) = {'device_owner': 'fake_owner', 'network_id': 'fake_network_id', 'mac_address': 'fake_mac'} with mock.patch.object(self.plugin, '_get_ip_details', return_value=(fake_ip1, fake_ip2)), \ mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network_dict) as get_network, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=fake_conn_list) as get_l2gw_conn, \ mock.patch.object(self.plugin, '_form_physical_locator_schema', return_value=fake_locator_dict) as get_pl, \ mock.patch.object(self.plugin, '_get_dict', return_value=fake_dict), \ mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=fake_mac_dict) as get_ucast_mac, \ mock.patch.object(self.plugin.agent_rpc, 'add_vif_to_gateway') as add_rpc, \ mock.patch.object(self.plugin.agent_rpc, 'update_vif_to_gateway', side_effect=RuntimeError) as update_rpc, \ mock.patch.object( db, 'add_pending_ucast_mac_remote') as add_pending_mac, \ mock.patch.object(db, 'get_all_logical_switches_by_name', return_value=fake_logical_switch_list): self.plugin.add_port_mac(self.context, fake_dict) get_network.assert_called_with(self.context, 'fake_network_id') get_l2gw_conn.assert_called_with( self.context, filters={'network_id': ['fake_network_id']}) get_pl.assert_called_with(self.context, fake_pl_dict) get_ucast_mac.assert_called_with(self.context, fake_dict) self.assertFalse(add_rpc.called) self.assertTrue(update_rpc.called) self.assertTrue(add_pending_mac.called) def test_add_port_mac_tunnel_recreation(self): "Test case to test recreation of tunnels" "when the openvswitch agent is restarted." fake_ip1 = "fake_ip1" fake_ip2 = "fake_ip2" fake_network_dict = {'provider:segmentation_id': 100, 'name': 'fake_name', 'id': 'fake_network_id'} fake_conn_dict = {'id': 'fake_conn_id', 'l2_gateway_id': 'fake_gateway_id'} fake_conn_list = [fake_conn_dict] fake_logical_switch = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_network_id', 'description': 'fake_name'} fake_logical_switch_list = [fake_logical_switch] fake_locator_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'uuid': 'fake_locator_id', 'dst_ip': 'fake_ip2'} fake_pl_dict = {'ovsdb_identifier': 'fake_ovsdb_id', 'dst_ip': 'fake_ip1'} fake_mac_dict = {'mac': 'fake_mac', 'uuid': 'fake_mac_id', 'locator': 'fake_locator_id', 'logical_switch_id': 'fake_network_id', 'physical_locator_id': 'fake_locator_id', 'ip_address': 'fake_ip1'} fake_dict = fake_mac_dict fake_dict['ovsdb_identifier'] = 'fake_ovsdb_id' core_plugin = mock.PropertyMock() type(self.service_plugin)._core_plugin = core_plugin (self.service_plugin._core_plugin.get_port. return_value) = {'device_owner': 'fake_owner', 'network_id': 'fake_network_id', 'mac_address': 'fake_mac'} with mock.patch.object(self.plugin, '_get_ip_details', return_value=(fake_ip1, fake_ip2)), \ mock.patch.object(self.plugin, '_get_network_details', return_value=fake_network_dict) as get_network, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=fake_conn_list) as get_l2gw_conn, \ mock.patch.object(self.plugin, '_form_physical_locator_schema', return_value=fake_locator_dict) as get_pl, \ mock.patch.object(self.plugin, '_get_dict', return_value=fake_dict), \ mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=fake_mac_dict) as get_ucast_mac, \ mock.patch.object(agent_api.L2gatewayAgentApi, 'add_vif_to_gateway') as add_rpc, \ mock.patch.object(data.L2GatewayOVSDBCallbacks, 'get_ovsdbdata_object') as get_ovsdbdata_obj, \ mock.patch.object(db, 'get_all_logical_switches_by_name', return_value=fake_logical_switch_list): self.plugin.add_port_mac(self.context, fake_dict) get_network.assert_called_with(self.context, 'fake_network_id') get_l2gw_conn.assert_called_with( self.context, filters={'network_id': ['fake_network_id']}) get_pl.assert_called_with(self.context, fake_pl_dict) get_ucast_mac.assert_called_with(self.context, fake_dict) self.assertFalse(add_rpc.called) self.assertTrue(get_ovsdbdata_obj.called) @mock.patch.object(db, 'get_all_logical_switches_by_name') @mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=True) def test_delete_port_mac_with_list(self, get_mac, get_ls): network_id = 'fake_network_id' fake_port_dict = {'network_id': 'fake_network_id', 'device_owner': 'fake_owner', 'mac_address': 'fake_mac', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_logical_switch_dict = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_logical_switch_list = [fake_logical_switch_dict] fake_dict = {'mac': 'fake_mac', 'logical_switch_uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} get_ls.return_value = fake_logical_switch_list with mock.patch.object(self.plugin.agent_rpc, 'delete_vif_from_gateway') as delete_rpc, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=True): self.plugin.delete_port_mac(self.context, fake_port_dict) get_ls.assert_called_with(self.context, network_id) get_mac.assert_called_with(self.context, fake_dict) delete_rpc.assert_called_with( self.context, 'fake_ovsdb_id', 'fake_uuid', ['fake_mac']) @mock.patch.object(db, 'get_logical_switch_by_name') @mock.patch.object(db, 'get_all_logical_switches_by_name') @mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=True) def test_delete_port_mac_for_single_l2gw_connection(self, get_mac, get_ls, get_ls_by_name): fake_port_dict = {'network_id': 'fake_network_id', 'device_owner': 'fake_owner', 'mac_address': 'fake_mac', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_port_list = [fake_port_dict] fake_rec_dict = {'uuid': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_ucast_mac_and_ls = {'mac': 'fake_mac', 'logical_switch_uuid': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} get_ls_by_name.return_value = fake_rec_dict with mock.patch.object(self.plugin.agent_rpc, 'delete_vif_from_gateway') as delete_rpc, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=[1]): self.plugin.delete_port_mac(self.context, fake_port_list) get_ls_by_name.assert_called_with(self.context, fake_dict) get_ls.assert_not_called() get_mac.assert_called_with(self.context, fake_ucast_mac_and_ls) delete_rpc.assert_called_with( self.context, 'fake_ovsdb_id', 'fake_network_id', ['fake_mac']) @mock.patch.object(db, 'get_logical_switch_by_name') @mock.patch.object(db, 'get_all_logical_switches_by_name') @mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=True) def test_delete_port_mac_for_multiple_l2gw_connection(self, get_mac, get_ls, get_ls_by_name): fake_port_dict = {'network_id': 'fake_network_id', 'device_owner': 'fake_owner', 'mac_address': 'fake_mac', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_port_list = [fake_port_dict] fake_rec_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} with mock.patch.object(self.plugin.agent_rpc, 'delete_vif_from_gateway') as delete_rpc, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connections', return_value=[1, 2]): self.plugin.delete_port_mac(self.context, fake_port_list) get_ls_by_name.assert_called_with(self.context, fake_rec_dict) get_ls.assert_not_called() get_mac.assert_not_called() delete_rpc.assert_not_called() @mock.patch.object(db, 'get_all_logical_switches_by_name') @mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=True) @mock.patch.object(db, 'get_all_vlan_bindings_by_logical_switch', return_value=[1]) @mock.patch.object(db, 'get_logical_switch_by_name') def test_delete_port_mac(self, get_ls, get_vlan_binding, get_mac, get_all_ls): fake_port_list = [{'network_id': 'fake_network_id', 'device_owner': 'fake_owner', 'mac_address': 'fake_mac', 'allowed_address_pairs': [{'mac_address': 'fake_pairs'}], 'ovsdb_identifier': 'fake_ovsdb_id'}] fake_logical_switch_dict = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} lg_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_dict = {'mac': 'fake_mac', 'logical_switch_uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} get_all_ls.return_value = fake_logical_switch_dict get_ls.return_value = fake_logical_switch_dict with mock.patch.object(self.plugin.agent_rpc, 'delete_vif_from_gateway') as delete_rpc: self.plugin.delete_port_mac(self.context, fake_port_list) self.assertFalse(get_all_ls.called) get_ls.assert_called_with(self.context, lg_dict) get_mac.assert_called_with(self.context, fake_dict) delete_rpc.assert_called_with( self.context, 'fake_ovsdb_id', 'fake_uuid', ['fake_pairs', 'fake_mac']) @mock.patch.object(db, 'get_all_logical_switches_by_name') @mock.patch.object(db, 'get_ucast_mac_remote_by_mac_and_ls', return_value=True) @mock.patch.object(db, 'add_pending_ucast_mac_remote') @mock.patch.object(db, 'get_logical_switch_by_name') def test_delete_port_mac_with_ovsdb_server_down(self, get_ls, add_pending_mac, get_mac, get_all_ls): "Test case to test delete_port_mac when the OVSDB server is down." fake_port_list = [{'network_id': 'fake_network_id', 'device_owner': 'fake_owner', 'mac_address': 'fake_mac', 'ovsdb_identifier': 'fake_ovsdb_id'}] fake_logical_switch_dict = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} lg_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_dict = {'mac': 'fake_mac', 'logical_switch_uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} get_all_ls.return_value = fake_logical_switch_dict get_ls.return_value = fake_logical_switch_dict with mock.patch.object(self.plugin.agent_rpc, 'delete_vif_from_gateway', side_effect=RuntimeError) as delete_rpc: self.plugin.delete_port_mac(self.context, fake_port_list) self.assertFalse(get_all_ls.called) get_ls.assert_called_with(self.context, lg_dict) get_mac.assert_called_with(self.context, fake_dict) delete_rpc.assert_called_with( self.context, 'fake_ovsdb_id', 'fake_uuid', ['fake_mac']) self.assertTrue(add_pending_mac.called) def test_delete_l2_gateway_connection(self): self.db_context = ctx.get_admin_context() fake_conn_dict = {'l2_gateway_id': 'fake_l2gw_id', 'ovsdb_identifier': 'fake_ovsdb_id', 'network_id': 'fake_network_id'} ovsdb_id = 'fake_ovsdb_id' logical_switch = {'uuid': 'fake_uuid', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_device_dict = {'id': 'fake_device_id', 'device_name': 'fake_device_name'} fake_device_list = [fake_device_dict] fake_identifier_list = ['fake_ovsdb_id'] fake_ovsdb_list = ['fake_ovsdb_id'] fake_port_dict = {'network_id': 'fake_network_id', 'device_owner': 'fake_owner', 'mac_address': 'fake_mac', 'ovsdb_identifier': 'fake_ovsdb_id'} DELETE = "DELETE" with mock.patch.object(self.service_plugin, '_admin_check', return_value=True) as admin_check, \ mock.patch.object(self.service_plugin, 'get_l2_gateway_connection', return_value=fake_conn_dict) as get_con, \ mock.patch.object( self.plugin, '_get_identifer_list', return_value=fake_identifier_list) as get_id_list, \ mock.patch.object(self.plugin, '_get_set_of_ovsdb_ids', return_value=fake_ovsdb_list) as get_ovsdb_id, \ mock.patch.object(self.service_plugin, 'get_l2gateway_devices_by_gateway_id', return_value=fake_device_list) as get_devices, \ mock.patch.object(self.plugin, '_process_port_list', return_value=(ovsdb_id, logical_switch, fake_port_dict)) as port_list, \ mock.patch.object(self.plugin.agent_rpc, 'update_connection_to_gateway') as update_rpc, \ mock.patch.object(self.plugin, '_remove_vm_macs') as remove_vm_mac: self.plugin.delete_l2_gateway_connection(self.context, fake_conn_dict) admin_check.assert_called_with(self.context, 'DELETE') get_con.assert_called_with(self.context, fake_conn_dict) get_id_list.assert_called_with(self.context, fake_conn_dict) get_ovsdb_id.assert_called_with( self.context, fake_conn_dict, fake_identifier_list) get_devices.assert_called_with(self.context, 'fake_l2gw_id') port_list.assert_called_with( self.context, fake_device_dict, fake_conn_dict, DELETE, fake_identifier_list) self.assertTrue(update_rpc.called) remove_vm_mac.assert_called_with( self.context, 'fake_network_id', fake_ovsdb_list) def test_create_l2gateway_connection_with_switch_fault_status_down(self): self.db_context = ctx.get_admin_context() fake_l2gw_conn_dict = {'l2_gateway_connection': { 'id': 'fake_id', 'network_id': 'fake_network_id', 'l2_gateway_id': 'fake_l2gw_id'}} fake_device = {'devices': [{'device_name': 'fake_device', 'interfaces': [{'name': 'fake_interface'}]}]} fake_physical_port = {'uuid': 'fake_id', 'name': 'fake_name', 'physical_switch_id': 'fake_switch1', 'port_fault_status': 'UP'} fake_physical_switch = {'uuid': 'fake_id', 'name': 'fake_name', 'tunnel_ip': 'fake_tunnel_ip', 'ovsdb_identifier': 'fake_ovsdb_id', 'switch_fault_status': 'DOWN'} with mock.patch.object(self.service_plugin, '_admin_check', return_value=True), \ mock.patch.object(self.service_plugin, 'get_l2_gateway', return_value=fake_device), \ mock.patch.object(db, 'get_physical_port_by_name_and_ps', return_value=fake_physical_port), \ mock.patch.object(db, 'get_physical_switch_by_name', return_value=fake_physical_switch), \ mock.patch.object(self.service_plugin, '_get_network', return_value=True), \ mock.patch.object(self.service_plugin, '_get_l2_gateway', return_value=True): self.assertRaises(l2gw_exc.L2GatewayPhysicalSwitchFaultStatus, self.plugin.create_l2_gateway_connection, self.db_context, fake_l2gw_conn_dict) def test_create_l2gateway_connection_with_port_fault_status_down(self): self.db_context = ctx.get_admin_context() fake_l2gw_conn_dict = {'l2_gateway_connection': { 'id': 'fake_id', 'network_id': 'fake_network_id', 'l2_gateway_id': 'fake_l2gw_id'}} fake_device = {'devices': [{'device_name': 'fake_device', 'interfaces': [{'name': 'fake_interface'}]}]} fake_physical_port = {'uuid': 'fake_id', 'name': 'fake_name', 'physical_switch_id': 'fake_switch1', 'port_fault_status': 'DOWN'} fake_physical_switch = {'uuid': 'fake_id', 'name': 'fake_name', 'tunnel_ip': 'fake_tunnel_ip', 'ovsdb_identifier': 'fake_ovsdb_id', 'switch_fault_status': 'UP'} with mock.patch.object(self.service_plugin, '_admin_check', return_value=True), \ mock.patch.object(self.service_plugin, 'get_l2_gateway', return_value=fake_device), \ mock.patch.object(db, 'get_physical_port_by_name_and_ps', return_value=fake_physical_port), \ mock.patch.object(db, 'get_physical_switch_by_name', return_value=fake_physical_switch), \ mock.patch.object(self.service_plugin, '_get_network', return_value=True), \ mock.patch.object(self.service_plugin, '_get_l2_gateway', return_value=True): self.assertRaises(l2gw_exc.L2GatewayPhysicalPortFaultStatus, self.plugin.create_l2_gateway_connection, self.db_context, fake_l2gw_conn_dict) @mock.patch.object(db, 'get_physical_port_by_name_and_ps') @mock.patch.object(db, 'get_physical_switch_by_name') def test_check_port_fault_status_and_switch_fault_status( self, phy_switch, phy_port): fake_device = {'devices': [{'device_name': 'fake_device', 'interfaces': [{'name': 'fake_interface'}]}]} self.db_context = ctx.get_admin_context() fake_device = {'devices': [{'device_name': 'fake_device', 'interfaces': [{'name': 'fake_interface'}]}]} fake_physical_port = {'uuid': 'fake_id', 'name': 'fake_name', 'physical_switch_id': 'fake_switch1', 'port_fault_status': None} fake_physical_switch = {'uuid': 'fake_id', 'name': 'fake_name', 'tunnel_ip': 'fake_tunnel_ip', 'ovsdb_identifier': 'fake_ovsdb_id', 'switch_fault_status': None} phy_port.return_value = fake_physical_port phy_switch.return_value = fake_physical_switch with mock.patch.object(self.service_plugin, 'get_l2_gateway', return_value=fake_device) as get_l2gw: self.plugin._check_port_fault_status_and_switch_fault_status( mock.Mock(), mock.Mock()) self.assertTrue(get_l2gw.called) self.assertTrue(phy_port.called) self.assertTrue(phy_switch.called) def test_create_l2_gateway_connection(self): self.db_context = ctx.get_admin_context() fake_l2gw_conn_dict = {'l2_gateway_connection': { 'id': 'fake_id', 'network_id': 'fake_network_id', 'l2_gateway_id': 'fake_l2gw_id'}} fake_port = {'device_owner': 'fake_owner', 'network_id': 'fake_network_id', 'mac_address': 'fake_mac', 'ip_address': 'fake_ip2', 'allowed_address_pairs': [{'ip_address': 'fake_ip2', 'mac_address': 'fake_mac2'}], } fake_port_list = [fake_port] fake_conn_dict = fake_l2gw_conn_dict.get('l2_gateway_connection') ovsdb_id = 'fake_ovsdb_id' logical_switch = {'uuid': 'fake_id'} fake_device_dict = {'device_name': 'fake_device_name'} fake_device_list = [fake_device_dict] fake_ls_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_pl_dict = {'uuid': 'fake_uuid', 'dst_ip': 'fake_ip1', 'ovsdb_identifier': 'fake_ovsdb_id', 'macs': [fake_port]} fake_pl_list = [fake_pl_dict] with mock.patch.object(self.service_plugin, '_admin_check', return_value=True) as admin_check, \ mock.patch.object(self.plugin, '_validate_connection'), \ mock.patch.object(self.service_plugin, 'get_l2gateway_devices_by_gateway_id', return_value=fake_device_list) as get_devices, \ mock.patch.object(self.plugin, '_process_port_list', return_value=(ovsdb_id, logical_switch, fake_port)) as port_list, \ mock.patch.object(self.plugin, '_get_logical_switch_dict', return_value=fake_ls_dict) as get_ls, \ mock.patch.object(self.plugin, '_get_port_details', return_value=fake_port_list) as get_port, \ mock.patch.object( self.plugin, '_get_ip_details', return_value=('fake_ip1', 'fake_ip2')) as get_ip, \ mock.patch.object(self.plugin, '_get_dict', return_value=mock.ANY) as get_dict, \ mock.patch.object( db, 'get_ucast_mac_remote_by_mac_and_ls') as get_ucast_mac, \ mock.patch.object(self.plugin, '_get_locator_list', return_value=fake_pl_list) as get_pl, \ mock.patch.object(self.plugin.agent_rpc, 'update_connection_to_gateway') as update_rpc: self.plugin.create_l2_gateway_connection(self.db_context, fake_l2gw_conn_dict) admin_check.assert_called_with(self.db_context, 'CREATE') get_devices.assert_called_with(self.db_context, 'fake_l2gw_id') port_list.assert_called_with( self.db_context, fake_device_dict, fake_conn_dict, "CREATE") get_ls.assert_called_with(self.db_context, logical_switch, fake_conn_dict) get_port.assert_called_with(self.db_context, 'fake_network_id') self.assertTrue(get_ip.called) self.assertTrue(get_dict.called) self.assertEqual(get_ucast_mac.call_count, 2) self.assertTrue(get_pl.called) self.assertTrue(update_rpc.called) @mock.patch.object(db, 'get_physical_switch_by_name') def test_create_l2gateway_connection_with_invalid_device(self, phy_switch): self.db_context = ctx.get_admin_context() fake_l2gw_conn_dict = {'l2_gateway_connection': { 'id': 'fake_id', 'network_id': 'fake_network_id', 'l2_gateway_id': 'fake_l2gw_id'}} fake_device = {'devices': [{'device_name': 'fake_device', 'interfaces': [{'name': 'fake_interface'}]}]} fake_physical_switch = None phy_switch.return_value = fake_physical_switch with mock.patch.object(self.service_plugin, '_admin_check', return_value=True), \ mock.patch.object(self.service_plugin, 'get_l2_gateway', return_value=fake_device), \ mock.patch.object(self.service_plugin, '_get_network', return_value=True), \ mock.patch.object(self.service_plugin, '_get_l2_gateway', return_value=True): self.assertRaises(l2gw_exc.L2GatewayDeviceNotFound, self.plugin.create_l2_gateway_connection, self.db_context, fake_l2gw_conn_dict) @mock.patch.object(db, 'get_physical_switch_by_name') @mock.patch.object(db, 'get_physical_port_by_name_and_ps') def test_create_l2gateway_connection_with_invalid_interface( self, phy_port, phy_switch): self.db_context = ctx.get_admin_context() fake_l2gw_conn_dict = {'l2_gateway_connection': { 'id': 'fake_id', 'network_id': 'fake_network_id', 'l2_gateway_id': 'fake_l2gw_id'}} fake_device = {'devices': [{'device_name': 'fake_device', 'interfaces': [{'name': 'fake_interface'}]}]} fake_physical_port = None fake_physical_switch = {'uuid': 'fake_id', 'name': 'fake_device', 'tunnel_ip': 'fake_tunnel_ip', 'ovsdb_identifier': 'fake_ovsdb_id', 'switch_fault_status': None} phy_switch.return_value = fake_physical_switch phy_port.return_value = fake_physical_port with mock.patch.object(self.service_plugin, '_admin_check', return_value=True), \ mock.patch.object(self.service_plugin, 'get_l2_gateway', return_value=fake_device), \ mock.patch.object(self.service_plugin, '_get_network', return_value=True), \ mock.patch.object(self.service_plugin, '_get_l2_gateway', return_value=True): self.assertRaises(l2gw_exc.L2GatewayInterfaceNotFound, self.plugin.create_l2_gateway_connection, self.db_context, fake_l2gw_conn_dict) def test_validate_gateway_for_update_with_invalid_device(self): self.db_context = ctx.get_admin_context() fake_l2gw_dict = {"l2_gateway": {"name": "fake_name", "devices": [{"interfaces": [{"name": "port1", "segmentation_id": [ "111"]}], "device_name": "device_name"}]}} with mock.patch.object(db, 'get_physical_switch_by_name', return_value=None): self.assertRaises(l2gw_exc.L2GatewayDeviceNotFound, self.plugin._validate_gateway_for_update, self.db_context, fake_l2gw_dict) def test_validate_gateway_for_update_with_invalid_port(self): self.db_context = ctx.get_admin_context() fake_l2gw_dict = {"l2_gateway": {"name": "fake_name", "devices": [{"interfaces": [{"name": "port1", "segmentation_id": [ "111"]}], "device_name": "device_name"}]}} fake_physical_switch = {'uuid': 'fake_id', 'name': 'fake_device_name', 'tunnel_ip': 'fake_tunnel_ip', 'ovsdb_identifier': 'fake_ovsdb_id', 'switch_fault_status': None} with mock.patch.object(db, 'get_physical_switch_by_name', return_value=fake_physical_switch), \ mock.patch.object(db, 'get_physical_port_by_name_and_ps', return_value=None): self.assertRaises(l2gw_exc.L2GatewayPhysicalPortNotFound, self.plugin._validate_gateway_for_update, self.db_context, fake_l2gw_dict) def test_validate_gateway_for_update_without_device(self): self.db_context = ctx.get_admin_context() fake_l2gw_dict = {"l2_gateway": {"name": "fake_name"}} with mock.patch.object(db, 'get_physical_switch_by_name', return_value=None): self.plugin._validate_gateway_for_update(self.db_context, fake_l2gw_dict) def test_update_l2_gateway(self): self.db_context = ctx.get_admin_context() fake_l2gw_dict = {'id': 'fake_l2gw_id', 'tenant_id': 'fake_tenant_id', "name": "fake_l2gw_name", "devices": [{"interfaces": [{"name": "port1", "segmentation_id": ["111"]}], "device_name": "fake_device_name", 'id': 'fake_device_id'}]} fake_l2gw_id = 'fake_l2gw_id' fake_device_dict = {'devices': [{'device_name': 'fake_device', 'interfaces': [{'name': 'fake_interface'}]}]} fake_device_list = [fake_device_dict] fake_conn_dict = {'l2_gateway_id': 'fake_l2gw_id', 'ovsdb_identifier': 'fake_ovsdb_id', 'network_id': 'fake_network_id'} fake_conn_list = [fake_conn_dict] fake_vlan_dict = {'vlan': 100, 'logical_switch_uuid': 'fake_uuid'} fake_physical_port = ovsdb_schema.PhysicalPort( uuid='fake_uuid', name='fake_interface_name', phys_switch_id='fake_uuid', vlan_binding_dicts=None, port_fault_status=None) fake_phys_port_dict = fake_physical_port.__dict__ fake_phys_port_dict['vlan_bindings'] = [fake_vlan_dict] fake_port_list = [fake_phys_port_dict] ovsdb_id = 'fake_ovsdb_id' logical_switch = {'uuid': 'fake_id'} with mock.patch.object(self.service_plugin, '_admin_check', return_value=True) as admin_check, \ mock.patch.object( self.plugin, '_validate_gateway_for_update') as validate_gateway, \ mock.patch.object(self.service_plugin, 'get_l2gateway_devices_by_gateway_id', return_value=fake_device_list) as device_list, \ mock.patch.object(self.service_plugin, '_get_l2_gateway_connections', return_value=fake_conn_list) as conn_list, \ mock.patch.object(self.plugin, '_process_port_list', return_value=(ovsdb_id, logical_switch, fake_port_list)) as port_list: self.plugin.update_l2_gateway( self.db_context, fake_l2gw_id, fake_l2gw_dict) admin_check.assert_called_with(self.db_context, 'UPDATE') self.assertTrue(validate_gateway.called) device_list.assert_called_with(self.db_context, 'fake_l2gw_id') conn_list.assert_called_with(self.db_context) port_list.assert_called_with( self.db_context, fake_device_dict, fake_conn_dict, "UPDATE") self.assertEqual(self.plugin.port_dict_before_update, fake_port_list) def test_update_l2_gateway_postcommit_with_add_port(self): self.db_context = ctx.get_admin_context() fake_l2gw_dict = {'id': 'fake_l2gw_id', 'tenant_id': 'fake_tenant_id', "name": "fake_l2gw_name", "devices": [{"interfaces": [{"name": "port1", "segmentation_id": ["111"]}], "device_name": "fake_device_name", 'id': 'fake_device_id'}]} fake_device_dict = {'devices': [{'device_name': 'fake_device_name', 'interfaces': [{'name': 'port1'}]}]} fake_device_list = [fake_device_dict] fake_conn_dict = {'l2_gateway_id': 'fake_l2gw_id', 'ovsdb_identifier': 'fake_ovsdb_id', 'network_id': 'fake_network_id'} fake_conn_list = [fake_conn_dict] fake_vlan_dict = {'vlan': 100, 'logical_switch_uuid': 'fake_ls_id'} fake_physical_port_1 = ovsdb_schema.PhysicalPort( uuid='fake_uuid_1', name='fake_interface_name_1', phys_switch_id='fake_uuid_1', vlan_binding_dicts=None, port_fault_status=None) fake_physical_port_2 = ovsdb_schema.PhysicalPort( uuid='fake_uuid_2', name='fake_interface_name_2', phys_switch_id='fake_uuid_2', vlan_binding_dicts=None, port_fault_status=None) fake_phys_port_dict_1 = fake_physical_port_1.__dict__ fake_phys_port_dict_1['vlan_bindings'] = [fake_vlan_dict] fake_phys_port_dict_2 = fake_physical_port_2.__dict__ fake_phys_port_dict_2['vlan_bindings'] = [fake_vlan_dict] fake_port_list_before_update = [fake_phys_port_dict_1] fake_port_list_after_update = [fake_phys_port_dict_1, fake_phys_port_dict_2] port_list_add = [fake_phys_port_dict_2] fake_port = {'device_owner': 'fake_owner', 'network_id': 'fake_network_id', 'mac_address': 'fake_mac', 'ip_address': 'fake_ip', 'allowed_address_pairs': [{'ip_address': 'fake_ip', 'mac_address': 'fake_mac'}]} fake_port_list = [fake_port] ovsdb_id = 'fake_ovsdb_id' logical_switch = {'uuid': 'fake_ls_id'} fake_ls_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} fake_pl_dict = {'uuid': 'fake_uuid', 'dst_ip': 'fake_ip1', 'ovsdb_identifier': 'fake_ovsdb_id', 'macs': [fake_port]} mac_dict = {'fake_ip1': fake_pl_dict['macs']} fake_pl_list = [fake_pl_dict] with mock.patch.object(self.plugin, 'port_dict_before_update', fake_port_list_before_update), \ mock.patch.object(self.service_plugin, '_admin_check', return_value=True) as admin_check, \ mock.patch.object(self.service_plugin, 'get_l2gateway_devices_by_gateway_id', return_value=fake_device_list) as device_list, \ mock.patch.object(self.service_plugin, '_get_l2_gateway_connections', return_value=fake_conn_list) as conn_list, \ mock.patch.object( self.plugin, '_process_port_list', return_value=(ovsdb_id, logical_switch, fake_port_list_after_update)) as port_list, \ mock.patch.object(self.plugin, '_get_logical_switch_dict', return_value=fake_ls_dict) as get_ls, \ mock.patch.object(self.plugin, '_get_port_details', return_value=fake_port_list) as get_port, \ mock.patch.object(self.plugin, '_get_ip_details', return_value=('fake_ip1', 'fake_ip2')) as get_ip, \ mock.patch.object(self.plugin, '_get_dict', return_value=mock.ANY) as get_dict, \ mock.patch.object( db, 'get_ucast_mac_remote_by_mac_and_ls') as get_ucast_mac, \ mock.patch.object(self.plugin, '_get_locator_list', return_value=fake_pl_list) as get_pl, \ mock.patch.object(self.plugin.agent_rpc, 'update_connection_to_gateway') as update_rpc: self.plugin.update_l2_gateway_postcommit(self.db_context, fake_l2gw_dict) admin_check.assert_called_with(self.db_context, 'UPDATE') device_list.assert_called_with(self.db_context, 'fake_l2gw_id') conn_list.assert_called_with(self.db_context) port_list.assert_called_with( self.db_context, fake_device_dict, fake_conn_dict, "UPDATE") get_ls.assert_called_with(self.db_context, logical_switch, fake_conn_dict) get_port.assert_called_with(self.db_context, 'fake_network_id') self.assertTrue(get_ip.called) self.assertTrue(get_dict.called) self.assertEqual(get_ucast_mac.call_count, 2) self.assertTrue(get_pl.called) update_rpc.assert_called_with(self.db_context, ovsdb_id, fake_ls_dict, fake_pl_list, mac_dict, port_list_add, 'CREATE') def test_update_l2_gateway_postcommit_with_del_port(self): self.db_context = ctx.get_admin_context() fake_l2gw_dict = {'id': 'fake_l2gw_id', 'tenant_id': 'fake_tenant_id', "name": "fake_l2gw_name", "devices": [{"interfaces": [{"name": "port1", "segmentation_id": ["111"]}], "device_name": "fake_device_name", 'id': 'fake_device_id'}]} fake_device_dict = {'devices': [{'device_name': 'fake_device_name', 'interfaces': [{'name': 'port1'}]}]} fake_device_list = [fake_device_dict] fake_conn_dict = {'l2_gateway_id': 'fake_l2gw_id', 'ovsdb_identifier': 'fake_ovsdb_id', 'network_id': 'fake_network_id'} fake_conn_list = [fake_conn_dict] fake_vlan_dict = {'vlan': 100, 'logical_switch_uuid': 'fake_ls_id'} fake_physical_port_1 = ovsdb_schema.PhysicalPort( uuid='fake_uuid_1', name='fake_interface_name_1', phys_switch_id='fake_uuid_1', vlan_binding_dicts=None, port_fault_status=None) fake_physical_port_2 = ovsdb_schema.PhysicalPort( uuid='fake_uuid_2', name='fake_interface_name_2', phys_switch_id='fake_uuid_2', vlan_binding_dicts=None, port_fault_status=None) fake_phys_port_dict_1 = fake_physical_port_1.__dict__ fake_phys_port_dict_1['vlan_bindings'] = [fake_vlan_dict] fake_phys_port_dict_2 = fake_physical_port_2.__dict__ fake_phys_port_dict_2['vlan_bindings'] = [fake_vlan_dict] fake_port_list_before_update = [fake_phys_port_dict_1, fake_phys_port_dict_2] fake_port_list_after_update = [fake_phys_port_dict_1] port_list_del = [fake_phys_port_dict_2] ovsdb_id = 'fake_ovsdb_id' logical_switch = {'uuid': 'fake_ls_id'} fake_ls_dict = {'logical_switch_name': 'fake_network_id', 'ovsdb_identifier': 'fake_ovsdb_id'} mac_dict = {} fake_pl_list = [] with mock.patch.object(self.plugin, 'port_dict_before_update', fake_port_list_before_update), \ mock.patch.object(self.service_plugin, '_admin_check', return_value=True) as admin_check, \ mock.patch.object(self.service_plugin, 'get_l2gateway_devices_by_gateway_id', return_value=fake_device_list) as device_list, \ mock.patch.object(self.service_plugin, '_get_l2_gateway_connections', return_value=fake_conn_list) as conn_list, \ mock.patch.object( self.plugin, '_process_port_list', return_value=(ovsdb_id, logical_switch, fake_port_list_after_update)) as port_list, \ mock.patch.object(self.plugin, '_get_logical_switch_dict', return_value=fake_ls_dict) as get_ls, \ mock.patch.object(self.plugin.agent_rpc, 'update_connection_to_gateway') as update_rpc: self.plugin.update_l2_gateway_postcommit(self.db_context, fake_l2gw_dict) admin_check.assert_called_with(self.db_context, 'UPDATE') device_list.assert_called_with(self.db_context, 'fake_l2gw_id') conn_list.assert_called_with(self.db_context) port_list.assert_called_with( self.db_context, fake_device_dict, fake_conn_dict, "UPDATE") get_ls.assert_called_with(self.db_context, logical_switch, fake_conn_dict) update_rpc.assert_called_with(self.db_context, ovsdb_id, fake_ls_dict, fake_pl_list, mac_dict, port_list_del, 'DELETE') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/test_agent_scheduler.py0000664000175000017500000001575514572557755034005 0ustar00jamespagejamespage# Copyright (c) 2015 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 import datetime from unittest import mock from oslo_config import cfg from oslo_service import loopingcall from oslo_utils import timeutils from neutron.agent.common import utils from neutron.db import agents_db from neutron import manager from neutron.plugins.ml2 import rpc from neutron.tests import base from neutron_lib.agent import topics from neutron_lib import context as neutron_context from networking_l2gw.services.l2gateway import agent_scheduler from networking_l2gw.services.l2gateway.common import config as l2gw_config from networking_l2gw.services.l2gateway.common import constants as srv_const from networking_l2gw.services.l2gateway.common import topics as l2gw_topics from networking_l2gw.services.l2gateway.service_drivers import agent_api def make_active_agent(fake_id, fake_agent_type, config=None): agent_dict = dict(id=fake_id, agent_type=fake_agent_type, host='localhost_' + str(fake_id), heartbeat_timestamp=timeutils.utcnow(), started_at=timeutils.utcnow(), configurations=config) return agent_dict def make_inactive_agent(fake_id, fake_agent_type, delta, config=None): agent_dict = dict(id=fake_id, agent_type=fake_agent_type, host='remotehost_' + str(fake_id), heartbeat_timestamp=(timeutils.utcnow() - datetime. timedelta(delta)), configurations=config) return agent_dict class FakePlugin(agents_db.AgentDbMixin): def __init__(self): self.notifier = rpc.AgentNotifierApi(topics.AGENT) class TestAgentScheduler(base.BaseTestCase): fake_a_agent_list = [] fake_i_agent_list = [] def setUp(self): super(TestAgentScheduler, self).setUp() l2gw_config.register_l2gw_opts_helper() cfg.CONF.set_override('core_plugin', "neutron.plugins.ml2.plugin.Ml2Plugin") self.plugin = FakePlugin() self.agent_rpc = agent_api.L2gatewayAgentApi( l2gw_topics.L2GATEWAY_AGENT, cfg.CONF.host) self.context = neutron_context.get_admin_context() cfg.CONF.set_override('agent_down_time', 10) cfg.CONF.set_override('periodic_monitoring_interval', 5) self.agentsch = agent_scheduler.L2GatewayAgentScheduler(self.agent_rpc, cfg.CONF) self.agentsch._plugin = self.plugin self.agentsch.context = self.context self.agentsch.agent_ext_support = True self.LOG = agent_scheduler.LOG def populate_agent_lists(self, config=None): self.fake_a_agent_list = [] self.fake_a_agent_list.append(make_active_agent( '1000', srv_const.AGENT_TYPE_L2GATEWAY, config)) self.fake_i_agent_list = [] self.fake_i_agent_list.append(make_inactive_agent( '2000', srv_const.AGENT_TYPE_L2GATEWAY, 52, config)) @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') @mock.patch.object(agent_scheduler.LOG, 'debug') @mock.patch.object(agent_scheduler.LOG, 'error') def test_initialize_thread(self, err, debug, loop_call): self.agentsch.initialize_thread() self.assertTrue(loop_call.called) self.assertTrue(debug.called) self.assertFalse(err.called) @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall', side_effect=RuntimeError) def test_initialize_thread_loop_call_exception(self, loop_call): with mock.patch.object(self.LOG, 'error') as log_err: self.agentsch.initialize_thread() self.assertTrue(loop_call.called) self.assertTrue(log_err.called) @mock.patch.object(manager, 'NeutronManager') def test_select_agent_type_one_active(self, mgr): config = {srv_const.L2GW_AGENT_TYPE: ''} self.populate_agent_lists(config) with mock.patch.object(self.LOG, 'exception'): self.agentsch._l2gwplugin = mock.Mock() self.agentsch._select_agent_type(self.context, self.fake_a_agent_list) self.agentsch.l2gwplugin.agent_rpc.set_monitor_agent_called_with( self.context, self.fake_a_agent_list[0]['host']) @mock.patch.object(manager, 'NeutronManager') def test_select_agent_type_multiple_active(self, mgr): config = {srv_const.L2GW_AGENT_TYPE: ''} self.populate_agent_lists(config) self.fake_a_agent_list.append(make_active_agent( '1001', srv_const.AGENT_TYPE_L2GATEWAY, config)) self.agentsch._l2gwplugin = mock.Mock() with mock.patch.object(self.LOG, 'exception'): self.agentsch._select_agent_type(self.context, self.fake_a_agent_list) self.agentsch.l2gwplugin.agent_rpc.set_monitor_agent_called_with( self.context, self.fake_a_agent_list[0]['host']) def test_monitor_agent_state(self): config = {srv_const.L2GW_AGENT_TYPE: ''} self.populate_agent_lists(config) fake_all_agent_list = copy.deepcopy(self.fake_i_agent_list) fake_all_agent_list.extend(self.fake_a_agent_list) self.fake_a_agent_list.append(make_active_agent( '1001', srv_const.AGENT_TYPE_L2GATEWAY, config)) with mock.patch.object(self.agentsch, '_select_agent_type') as select_agent, \ mock.patch.object( self.plugin, 'get_agents', return_value=fake_all_agent_list) as get_agent_list, \ mock.patch.object(utils, 'is_agent_down', return_value=False) as is_agt: self.agentsch.monitor_agent_state() self.assertTrue(get_agent_list.called) self.assertTrue(select_agent.called) self.assertTrue(is_agt.called) def test_monitor_agent_state_exception_get_agents(self): with mock.patch.object( self.plugin, 'get_agents', side_effect=Exception) as get_agent_list, \ mock.patch.object(self.LOG, 'exception') as exception_log: self.agentsch.monitor_agent_state() self.assertTrue(get_agent_list.called) self.assertTrue(exception_log.called) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/networking_l2gw/tests/unit/services/l2gateway/test_plugin.py0000664000175000017500000002225714572557755032142 0ustar00jamespagejamespage# Copyright (c) 2016 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 unittest import mock from neutron.tests import base from networking_l2gw.db.l2gateway import l2gateway_db from networking_l2gw.services.l2gateway.common import config from networking_l2gw.services.l2gateway import plugin as l2gw_plugin class TestL2GatewayPlugin(base.BaseTestCase): def setUp(self): super(TestL2GatewayPlugin, self).setUp() mock.patch.object(config, 'register_l2gw_opts_helper') self.driver = mock.MagicMock() mock.patch('neutron.services.service_base.load_drivers', return_value=({'dummyprovider': self.driver}, 'dummyprovider')).start() mock.patch.object(l2gateway_db.L2GatewayMixin, '__init__'), mock.patch.object(l2gateway_db, 'subscribe') mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=mock.MagicMock()).start() self.context = mock.MagicMock() self.plugin = l2gw_plugin.L2GatewayPlugin() self.ovsdb_identifier = 'fake_ovsdb_id' def _get_fake_l2_gateway(self): fake_l2_gateway_id = "5227c228-6bba-4bbe-bdb8-6942768ff0f1" fake_l2_gateway = { "tenant_id": "de0a7495-05c4-4be0-b796-1412835c6820", "id": "5227c228-6bba-4bbe-bdb8-6942768ff0f1", "name": "test-gateway", "devices": [ { "device_name": "switch1", "interfaces": [ { "name": "port1", "segmentation_id": [100] }, { "name": "port2", "segmentation_id": [151, 152] } ] } ] } return fake_l2_gateway_id, fake_l2_gateway def _get_fake_l2_gateway_connection(self): fake_l2_gateway_conn_id = "5227c228-6bba-4bbe-bdb8-6942768ff02f" fake_l2_gateway_conn = { "tenant_id": "de0a7495-05c4-4be0-b796-1412835c6820", "id": "5227c228-6bba-4bbe-bdb8-6942768ff02f", "default_segmentation_id": 77, "network_id": "5227c228-6bba-4bbe-bdb8-6942768ff077", "l2_gateway_id": "4227c228-6bba-4bbe-bdb8-6942768ff088" } return fake_l2_gateway_conn_id, fake_l2_gateway_conn def test_add_port_mac(self): self.plugin.add_port_mac(self.context, {}) self.driver.add_port_mac.assert_called_once_with(self.context, {}) def test_delete_port_mac(self): self.plugin.delete_port_mac(self.context, {}) self.driver.delete_port_mac.assert_called_once_with(self.context, {}) @mock.patch.object(l2gateway_db.L2GatewayMixin, 'validate_l2_gateway_for_create') @mock.patch.object(l2gateway_db.L2GatewayMixin, 'create_l2_gateway') def test_create_l2_gateway(self, mock_create_l2gw_db, mock_validate_for_create): fake_l2gw_id, fake_l2gw = self._get_fake_l2_gateway() mock_create_l2gw_db.return_value = fake_l2gw self.plugin.create_l2_gateway(self.context, fake_l2gw) mock_validate_for_create.assert_called_with(self.context, fake_l2gw) mock_create_l2gw_db.assert_called_with(self.context, fake_l2gw) self.driver.create_l2_gateway.assert_called_once_with(self.context, fake_l2gw) self.driver.create_l2_gateway_precommit.assert_called_once_with( self.context, fake_l2gw) self.driver.create_l2_gateway_postcommit.assert_called_once_with( self.context, fake_l2gw) @mock.patch.object(l2gateway_db.L2GatewayMixin, 'validate_l2_gateway_for_delete') @mock.patch.object(l2gateway_db.L2GatewayMixin, 'delete_l2_gateway') def test_delete_l2_gateway(self, mock_delete_l2gw_db, mock_validate_for_delete): fake_l2gw_id, fake_l2gw = self._get_fake_l2_gateway() self.plugin.delete_l2_gateway(self.context, fake_l2gw_id) mock_validate_for_delete.assert_called_with(self.context, fake_l2gw_id) mock_delete_l2gw_db.assert_called_with(self.context, fake_l2gw_id) self.driver.delete_l2_gateway.assert_called_once_with(self.context, fake_l2gw_id) self.driver.delete_l2_gateway_precommit.assert_called_once_with( self.context, fake_l2gw_id) self.driver.delete_l2_gateway_postcommit.assert_called_once_with( self.context, fake_l2gw_id) @mock.patch.object(l2gateway_db.L2GatewayMixin, 'validate_l2_gateway_for_update') @mock.patch.object(l2gateway_db.L2GatewayMixin, 'update_l2_gateway') def test_update_l2_gateway(self, mock_update_l2gw_db, mock_validate_for_update): fake_l2gw_id, fake_l2gw = self._get_fake_l2_gateway() mock_update_l2gw_db.return_value = fake_l2gw self.plugin.update_l2_gateway(self.context, fake_l2gw_id, fake_l2gw) mock_validate_for_update.assert_called_with(self.context, fake_l2gw_id, fake_l2gw) mock_update_l2gw_db.assert_called_with(self.context, fake_l2gw_id, fake_l2gw) self.driver.update_l2_gateway.assert_called_once_with(self.context, fake_l2gw_id, fake_l2gw) self.driver.update_l2_gateway_precommit.assert_called_once_with( self.context, fake_l2gw) self.driver.update_l2_gateway_postcommit.assert_called_once_with( self.context, fake_l2gw) @mock.patch.object(l2gateway_db.L2GatewayMixin, 'validate_l2_gateway_connection_for_create') @mock.patch.object(l2gateway_db.L2GatewayMixin, 'create_l2_gateway_connection') def test_create_l2_gateway_connection(self, mock_conn_create_l2gw_db, mock_validate_for_conn_create): fake_l2gw_conn_id, fake_l2gw_conn = ( self._get_fake_l2_gateway_connection()) mock_conn_create_l2gw_db.return_value = fake_l2gw_conn self.plugin.create_l2_gateway_connection(self.context, fake_l2gw_conn) mock_validate_for_conn_create.assert_called_with(self.context, fake_l2gw_conn) mock_conn_create_l2gw_db.assert_called_with(self.context, fake_l2gw_conn) self.driver.create_l2_gateway_connection.assert_called_once_with( self.context, fake_l2gw_conn) (self.driver.create_l2_gateway_connection_precommit. assert_called_once_with(self.context, fake_l2gw_conn)) (self.driver.create_l2_gateway_connection_postcommit. assert_called_once_with(self.context, fake_l2gw_conn)) @mock.patch.object(l2gateway_db.L2GatewayMixin, 'validate_l2_gateway_connection_for_delete') @mock.patch.object(l2gateway_db.L2GatewayMixin, 'delete_l2_gateway_connection') def test_delete_l2_gateway_connection(self, mock_conn_delete_l2gw_db, mock_validate_for_conn_delete): fake_l2gw_conn_id, fake_l2gw_conn = ( self._get_fake_l2_gateway_connection()) self.plugin.delete_l2_gateway_connection(self.context, fake_l2gw_conn_id) mock_validate_for_conn_delete.assert_called_with(self.context, fake_l2gw_conn_id) mock_conn_delete_l2gw_db.assert_called_with(self.context, fake_l2gw_conn_id) self.driver.delete_l2_gateway_connection.assert_called_once_with( self.context, fake_l2gw_conn_id) (self.driver.delete_l2_gateway_connection_precommit. assert_called_once_with(self.context, fake_l2gw_conn_id)) (self.driver.delete_l2_gateway_connection_postcommit. assert_called_once_with(self.context, fake_l2gw_conn_id)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9399855 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/0000775000175000017500000000000014572557757023054 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/PKG-INFO0000664000175000017500000001532214572557756024153 0ustar00jamespagejamespageMetadata-Version: 2.1 Name: networking-l2gw Version: 20.0.1.dev5 Summary: APIs and implementations to support L2 Gateways in Neutron. Home-page: https://opendev.org/x/networking-l2gw Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 Requires-Python: >=3.8 License-File: LICENSE =============== networking-l2gw =============== API's and implementations to support L2 Gateways in Neutron. * Free software: Apache license * Source: https://opendev.org/x/networking-l2gw L2 Gateways ----------- This project proposes a Neutron API extension that can be used to express and manage L2 Gateway components. In the simplest terms L2 Gateways are meant to bridge two or more networks together to make them look at a single L2 broadcast domain. Initial implementation ---------------------- There are a number of use cases that can be addressed by an L2 Gateway API. Most notably in cloud computing environments, a typical use case is bridging the virtual with the physical. Translate this to Neutron and the OpenStack world, and this means relying on L2 Gateway capabilities to extend Neutron logical (overlay) networks into physical (provider) networks that are outside the OpenStack realm. These networks can be, for instance, VLAN's that may or may not be managed by OpenStack. More information ---------------- For help using or hacking on L2GW, you can send an email to the `OpenStack Discuss Mailing List `; please use the [L2-Gateway] Tag in the subject. Most folks involved hang out on the IRC channel #openstack-neutron. Getting started --------------- To get started you have to install the l2gw plugin software on the Controller node where you are already running the Neutron server. Then you need a new node, that we call the l2gw node, where you do the actual bridging between a vxlan tenant network and a physical network. The l2gw node could be a bare metal switch that supports the OVSDB schema, or a server with OVS installed. In this example we are going to use a server. In this example the l2gw node has a `ens5` interface attached to a physical segment, and a management interface with IP 10.225.0.27. :: ip link set up dev ens5 apt-get update apt-get install openvswitch-vtep ovsdb-tool create /etc/openvswitch/vtep.db /usr/share/openvswitch/vtep.ovsschema ovsdb-tool create /etc/openvswitch/vswitch.db /usr/share/openvswitch/vswitch.ovsschema # Stop OVS services started by the installer. systemctl is-active --quiet ovs-vswitchd && systemctl stop ovs-vswitchd systemctl is-active --quiet ovsdb-server && systemctl stop ovsdb-server mkdir -p /var/run/openvswitch/ ovsdb-server --pidfile --detach --log-file --remote ptcp:6632:10.225.0.27 \ --remote punix:/var/run/openvswitch/db.sock --remote=db:hardware_vtep,Global,managers \ /etc/openvswitch/vswitch.db /etc/openvswitch/vtep.db ovs-vswitchd --log-file --detach --pidfile unix:/var/run/openvswitch/db.sock ovs-vsctl add-br myphyswitch vtep-ctl add-ps myphyswitch vtep-ctl set Physical_Switch myphyswitch tunnel_ips=10.225.0.27 ovs-vsctl add-port myphyswitch ens5 vtep-ctl add-port myphyswitch ens5 /usr/share/openvswitch/scripts/ovs-vtep \ --log-file=/var/log/openvswitch/ovs-vtep.log \ --pidfile=/var/run/openvswitch/ovs-vtep.pid \ --detach myphyswitch At this point your l2gw node is running. For the configuration of the Openstack control plane you have to check three files: ``neutron.conf``, `l2gw_plugin.ini `__, and `l2gateway_agent.ini `__ Edit your ``neutron.conf`` on the controller node and make sure that in the ``service_plugins`` you have the string ``networking_l2gw.services.l2gateway.plugin.L2GatewayPlugin``. You can add it with: :: sudo sed -ri 's/^(service_plugins.*)/\1,networking_l2gw.services.l2gateway.plugin.L2GatewayPlugin/' \ /etc/neutron/neutron.conf Make sure the neutron-server runs with ``--config-file=/etc/neutron/l2gw_plugin.ini``. The default for the l2gw_plugin.ini file should be okay. Now you are ready to create the database tables for the neutron l2gw plugin using the command: ``neutron-db-manage upgrade heads`` The file `l2gateway_agent.ini `__ is used to configure the neutron-l2gateway agent. The agent is the piece of software that will configure the l2gw node when you interact with the Openstack API. Here it is important to give the pointer to the switch. ``ovsdb_hosts = 'ovsdb1:10.225.0.27:6632'`` The name ``ovsdb1`` is just a name that will be used in the Openstack database to identify this switch. Now that both the l2gw node and the Openstack control plane are configured, we can use the API service to bridge a VXLAN tenant network to a physical interface of the l2gw node. First let's create in Openstack a l2-gateway object. We need to give the interface names and the name of the bridge that we used before in the OVS commands. ``l2-gateway-create --device name="myphyswitch",interface_names="ens5" openstackname`` Use the just created to feed the second command where you do the actual bridging between the VXLAN tenant network and the Physical L2 network. ``l2-gateway-connection-create `` Now let's see what happened. On the l2gw node you can do the commands: :: ovs-vsctl show vtep-ctl show You should see some VXLAN tunnels are created. You will see a vxlan tunnel to each compute node that is hosting an instance attached to the tenant network that you bridge. If there is also a router in this tenant network, you will find a VXLAN tunnel also to the network node. References: * http://networkop.co.uk/blog/2016/05/21/neutron-l2gw/ * http://kimizhang.com/neutron-l2-gateway-hp-5930-switch-ovsdb-integration/ * http://openvswitch.org/support/dist-docs-2.5/vtep/README.ovs-vtep.md.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/SOURCES.txt0000664000175000017500000001752214572557756024746 0ustar00jamespagejamespage.coveragerc .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst bindep.txt lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini contrib/README_install_and_config_l2gateway_plugin.rst contrib/README_install_l2gateway_agent.rst contrib/install_and_config_l2gateway_plugin.sh contrib/install_l2gateway_agent.sh contrib/neutron-l2gateway-agent.conf debian/changelog debian/compat debian/control debian/copyright debian/rules devstack/README.rst devstack/devstackgaterc devstack/plugin.sh devstack/settings doc/requirements.txt doc/source/conf.py doc/source/contributing.rst doc/source/index.rst doc/source/installation.rst doc/source/readme.rst doc/source/usage.rst doc/source/images/L2GW_deployment.png etc/l2gateway_agent.ini etc/l2gw_plugin.ini etc/policy.json networking_l2gw/__init__.py networking_l2gw/_i18n.py networking_l2gw.egg-info/PKG-INFO networking_l2gw.egg-info/SOURCES.txt networking_l2gw.egg-info/dependency_links.txt networking_l2gw.egg-info/entry_points.txt networking_l2gw.egg-info/not-zip-safe networking_l2gw.egg-info/pbr.json networking_l2gw.egg-info/requires.txt networking_l2gw.egg-info/top_level.txt networking_l2gw/cmd/__init__.py networking_l2gw/cmd/eventlet/__init__.py networking_l2gw/cmd/eventlet/agent.py networking_l2gw/db/__init__.py networking_l2gw/db/l2gateway/__init__.py networking_l2gw/db/l2gateway/db_query.py networking_l2gw/db/l2gateway/head.py networking_l2gw/db/l2gateway/l2gateway_db.py networking_l2gw/db/l2gateway/l2gateway_models.py networking_l2gw/db/l2gateway/ovsdb/__init__.py networking_l2gw/db/l2gateway/ovsdb/lib.py networking_l2gw/db/l2gateway/ovsdb/models.py networking_l2gw/db/migration/__init__.py networking_l2gw/db/migration/alembic_migrations/README networking_l2gw/db/migration/alembic_migrations/__init__.py networking_l2gw/db/migration/alembic_migrations/env.py networking_l2gw/db/migration/alembic_migrations/script.py.mako networking_l2gw/db/migration/alembic_migrations/versions/42438454c556_l2gateway_models.py networking_l2gw/db/migration/alembic_migrations/versions/54c9c8fe22bf_db_models_for_ovsdb_hardware_vtep_schema.py networking_l2gw/db/migration/alembic_migrations/versions/CONTRACT_HEAD networking_l2gw/db/migration/alembic_migrations/versions/EXPAND_HEAD networking_l2gw/db/migration/alembic_migrations/versions/__init__.py networking_l2gw/db/migration/alembic_migrations/versions/kilo_release.py networking_l2gw/db/migration/alembic_migrations/versions/start_networking_l2gw.py networking_l2gw/db/migration/alembic_migrations/versions/liberty/contract/79919185aa99_initial_contract.py networking_l2gw/db/migration/alembic_migrations/versions/liberty/expand/60019185aa99_initial_expand.py networking_l2gw/db/migration/alembic_migrations/versions/newton/contract/2f533f7705dd_rename_tenant_to_project.py networking_l2gw/db/migration/alembic_migrations/versions/newton/expand/49ce408ac349_add_indexes_to_tenant_id.py networking_l2gw/db/migration/alembic_migrations/versions/queens/contract/0fb45e525aa9_add_standard_attribute_id_for_l2gw.py networking_l2gw/db/migration/alembic_migrations/versions/queens/expand/8d7d772eafcf_add_standard_attribute_id_for_l2gw.py networking_l2gw/extensions/__init__.py networking_l2gw/extensions/l2gateway.py networking_l2gw/extensions/l2gatewayconnection.py networking_l2gw/l2gatewayclient/__init__.py networking_l2gw/l2gatewayclient/osc/__init__.py networking_l2gw/l2gatewayclient/osc/l2gw.py networking_l2gw/l2gatewayclient/osc/l2gw_connection.py networking_l2gw/services/__init__.py networking_l2gw/services/l2gateway/__init__.py networking_l2gw/services/l2gateway/agent_scheduler.py networking_l2gw/services/l2gateway/exceptions.py networking_l2gw/services/l2gateway/l2gw_agent.py networking_l2gw/services/l2gateway/plugin.py networking_l2gw/services/l2gateway/agent/__init__.py networking_l2gw/services/l2gateway/agent/agent_api.py networking_l2gw/services/l2gateway/agent/base_agent_manager.py networking_l2gw/services/l2gateway/agent/l2gateway_config.py networking_l2gw/services/l2gateway/agent/ovsdb/README.rst networking_l2gw/services/l2gateway/agent/ovsdb/__init__.py networking_l2gw/services/l2gateway/agent/ovsdb/api.py networking_l2gw/services/l2gateway/agent/ovsdb/base_connection.py networking_l2gw/services/l2gateway/agent/ovsdb/impl_idl.py networking_l2gw/services/l2gateway/agent/ovsdb/manager.py networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_common_class.py networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_model.py networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_monitor.py networking_l2gw/services/l2gateway/agent/ovsdb/ovsdb_writer.py networking_l2gw/services/l2gateway/agent/ovsdb/native/__init__.py networking_l2gw/services/l2gateway/agent/ovsdb/native/commands.py networking_l2gw/services/l2gateway/agent/ovsdb/native/connection.py networking_l2gw/services/l2gateway/agent/ovsdb/vtep/vtep.ovsschema networking_l2gw/services/l2gateway/common/__init__.py networking_l2gw/services/l2gateway/common/config.py networking_l2gw/services/l2gateway/common/constants.py networking_l2gw/services/l2gateway/common/l2gw_validators.py networking_l2gw/services/l2gateway/common/ovsdb_schema.py networking_l2gw/services/l2gateway/common/topics.py networking_l2gw/services/l2gateway/common/tunnel_calls.py networking_l2gw/services/l2gateway/ovsdb/__init__.py networking_l2gw/services/l2gateway/ovsdb/data.py networking_l2gw/services/l2gateway/service_drivers/__init__.py networking_l2gw/services/l2gateway/service_drivers/agent_api.py networking_l2gw/services/l2gateway/service_drivers/rpc_l2gw.py networking_l2gw/tests/__init__.py networking_l2gw/tests/contrib/gate_hook.sh networking_l2gw/tests/contrib/post_test_hook.sh networking_l2gw/tests/unit/__init__.py networking_l2gw/tests/unit/db/__init__.py networking_l2gw/tests/unit/db/test_l2gw_db.py networking_l2gw/tests/unit/db/test_migrations.py networking_l2gw/tests/unit/db/ovsdb/__init__.py networking_l2gw/tests/unit/db/ovsdb/test_idl_impl.py networking_l2gw/tests/unit/db/ovsdb/test_lib.py networking_l2gw/tests/unit/l2gatewayclient/__init__.py networking_l2gw/tests/unit/l2gatewayclient/osc/__init__.py networking_l2gw/tests/unit/l2gatewayclient/osc/fakes.py networking_l2gw/tests/unit/l2gatewayclient/osc/test_osc_l2gw.py networking_l2gw/tests/unit/l2gatewayclient/osc/test_osc_l2gw_connection.py networking_l2gw/tests/unit/services/__init__.py networking_l2gw/tests/unit/services/l2gateway/__init__.py networking_l2gw/tests/unit/services/l2gateway/test_agent_scheduler.py networking_l2gw/tests/unit/services/l2gateway/test_plugin.py networking_l2gw/tests/unit/services/l2gateway/agent/__init__.py networking_l2gw/tests/unit/services/l2gateway/agent/test_agent_api.py networking_l2gw/tests/unit/services/l2gateway/agent/test_base_agent_manager.py networking_l2gw/tests/unit/services/l2gateway/agent/test_l2gw_agent.py networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/__init__.py networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_base_connection.py networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_manager.py networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_ovsdb_monitor.py networking_l2gw/tests/unit/services/l2gateway/agent/ovsdb/test_ovsdb_writer.py networking_l2gw/tests/unit/services/l2gateway/common/__init__.py networking_l2gw/tests/unit/services/l2gateway/common/test_l2gw_validators.py networking_l2gw/tests/unit/services/l2gateway/common/test_tunnel_calls.py networking_l2gw/tests/unit/services/l2gateway/ovsdb/__init__.py networking_l2gw/tests/unit/services/l2gateway/ovsdb/test_data.py networking_l2gw/tests/unit/services/l2gateway/service_drivers/__init__.py networking_l2gw/tests/unit/services/l2gateway/service_drivers/test_agent_api.py networking_l2gw/tests/unit/services/l2gateway/service_drivers/test_rpc_l2gw.py specs/kilo/l2-gateway-api-implementation.rst specs/kilo/l2-gateway-api.rst specs/newton/l2-border-gateway-api.rst specs/newton/l2-border-gateway-implementation.rst././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/dependency_links.txt0000664000175000017500000000000114572557756027121 0ustar00jamespagejamespage ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/entry_points.txt0000664000175000017500000000176314572557756026360 0ustar00jamespagejamespage[console_scripts] neutron-l2gateway-agent = networking_l2gw.cmd.eventlet.agent:main [neutron.db.alembic_migrations] networking-l2gw = networking_l2gw.db.migration:alembic_migrations [neutron.service_plugins] l2gw = networking_l2gw.services.l2gateway.plugin:L2GatewayPlugin [openstack.neutronclient.v2] l2gw_connection_create = networking_l2gw.l2gatewayclient.osc.l2gw_connection:CreateL2gwConnection l2gw_connection_delete = networking_l2gw.l2gatewayclient.osc.l2gw_connection:DeleteL2gwConnection l2gw_connection_list = networking_l2gw.l2gatewayclient.osc.l2gw_connection:ListL2gwConnection l2gw_connection_show = networking_l2gw.l2gatewayclient.osc.l2gw_connection:ShowL2gwConnection l2gw_create = networking_l2gw.l2gatewayclient.osc.l2gw:CreateL2gw l2gw_delete = networking_l2gw.l2gatewayclient.osc.l2gw:DeleteL2gw l2gw_list = networking_l2gw.l2gatewayclient.osc.l2gw:ListL2gw l2gw_show = networking_l2gw.l2gatewayclient.osc.l2gw:ShowL2gw l2gw_update = networking_l2gw.l2gatewayclient.osc.l2gw:UpdateL2gw ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/not-zip-safe0000664000175000017500000000000114572557756025301 0ustar00jamespagejamespage ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/pbr.json0000664000175000017500000000005714572557756024533 0ustar00jamespagejamespage{"git_version": "c556d24", "is_release": false}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/requires.txt0000664000175000017500000000013314572557756025450 0ustar00jamespagejamespageneutron-lib>=3.1.0 neutron>=21.0.0 ovsdbapp>=1.16.0 pbr>=4.0.0 python-neutronclient>=7.8.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891566.0 networking-l2gw-20.0.1.dev5/networking_l2gw.egg-info/top_level.txt0000664000175000017500000000002014572557756025575 0ustar00jamespagejamespagenetworking_l2gw ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/requirements.txt0000664000175000017500000000072014572557755021521 0ustar00jamespagejamespagepbr>=4.0.0 # Apache-2.0 neutron-lib>=3.1.0 # Apache-2.0 python-neutronclient>=7.8.0 # Apache-2.0 ovsdbapp>=1.16.0 # Apache-2.0 neutron>=21.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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/setup.cfg0000664000175000017500000000370014572557757020061 0ustar00jamespagejamespage[metadata] name = networking-l2gw summary = APIs and implementations to support L2 Gateways in Neutron. description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://opendev.org/x/networking-l2gw 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 [files] packages = networking_l2gw data_files = etc/neutron = etc/l2gateway_agent.ini etc/l2gw_plugin.ini [entry_points] console_scripts = neutron-l2gateway-agent = networking_l2gw.cmd.eventlet.agent:main neutron.db.alembic_migrations = networking-l2gw = networking_l2gw.db.migration:alembic_migrations openstack.neutronclient.v2 = l2gw_create = networking_l2gw.l2gatewayclient.osc.l2gw:CreateL2gw l2gw_list = networking_l2gw.l2gatewayclient.osc.l2gw:ListL2gw l2gw_show = networking_l2gw.l2gatewayclient.osc.l2gw:ShowL2gw l2gw_delete = networking_l2gw.l2gatewayclient.osc.l2gw:DeleteL2gw l2gw_update = networking_l2gw.l2gatewayclient.osc.l2gw:UpdateL2gw l2gw_connection_create = networking_l2gw.l2gatewayclient.osc.l2gw_connection:CreateL2gwConnection l2gw_connection_list = networking_l2gw.l2gatewayclient.osc.l2gw_connection:ListL2gwConnection l2gw_connection_show = networking_l2gw.l2gatewayclient.osc.l2gw_connection:ShowL2gwConnection l2gw_connection_delete = networking_l2gw.l2gatewayclient.osc.l2gw_connection:DeleteL2gwConnection neutron.service_plugins = l2gw = networking_l2gw.services.l2gateway.plugin:L2GatewayPlugin [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/setup.py0000664000175000017500000000127114572557755017751 0ustar00jamespagejamespage# 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9319854 networking-l2gw-20.0.1.dev5/specs/0000775000175000017500000000000014572557757017355 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/specs/kilo/0000775000175000017500000000000014572557757020313 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/specs/kilo/l2-gateway-api-implementation.rst0000664000175000017500000004456214572557755026624 0ustar00jamespagejamespage.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode =========================================================== Implementation of L2 gateway APIs using OVSDB =========================================================== Launchpad blueprint: https://blueprints.launchpad.net/neutron/+spec/l2-gateway-api-implementation The blueprint discloses one implementation of the L2 gateway APIs described in https://review.openstack.org/144173 using the standard OVSDB hardware_vtep schema. Problem Description =================== Per the above specified URL, there is a proposal to represent an L2 gateway (hardware or software) with logical resources in Neutron. It also defines REST APIs in Neutron which will manage these resources. The current spec discloses an implementation of these APIs. Proposed Change =============== The current spec proposes one implementation of the said REST APIs. It makes use of the open standard OVSDB server underneath which provides hardware_vtep schema to provision an L2 gateway. The diagram below provides a high level overview of how the entire system works. Flows:: +–––––––––––––––––––––––––+ | | | | | Neutron Server | | | +-------+ +-----+ | | | | | | | | | | | | | +–––––––––––––––––––––––––+ | | | | | +–––––––––+–––––––––––––––+ +––––––––––––+––––––––––––+ | | | | | | | | | ML2 Plugin | | L2 Gateway | | | +------+ Service | | | | | Plugin | | | | | | | | | | | +–––––––––+–––––––––––––––+ | +–––––––––––––––––––––––––+ | | | | | | | | | | | | +––––––––––+––––––––––––––+ | +–––––––––––––––––––––––+ | | | | Monitoring | | Compute/Network Node | +--------+ L2 gateway agent | | | | | +---+ | | | +––––––––––––––––––––––+ | | | | | | | | | +–––––––––––––––––––––––––+ +-----+ Transact |–––+ | | L2 gateway agent | | | +--+ | | | | | +––––––––––––––––––––––+ | | | | | | +––––––––––––––––––––––+ | | | | | | | +--+ | | OVSDB | | | | +--|----+ | | | | | | | | +––––––––––––––––––––––+ | | | | | | | | | |––––+ | | | | | | | OVSDB +-------+ | | | | | | | | +------------+ +––––––––––––––––––––––+ The L2 gateway service plugin transforms the REST APIs into Neutron RPCs over the RabbitMQ bus to the proposed L2 gateway agents. The RPCs from the plugin to an L2 gateway agent may include: - A cast message to send details of virtual machines, compute node on which the virtual machines are hosted and network details to the agent. - A cast message to send the mapping of VXLAN segmentation ID of the specified virtual network and VLAN ID on the physical network to the agent so that the binding is created on the gateway device - A cast message to send the mapping of VXLAN segmentation ID of the specified virtual network and VLAN ID on the physical network to the agent so that the binding is destroyed on the gateway device An L2 gateway may be a physical switch, or a server with two NICs, or a virtual machine with two virtual interfaces. In the first iteration, we will support only a physical switch. Once the implementation is mature and most of the use cases work, then anyone can enhance it for other models. An L2 gateway agent communicates with an OVSDB server over the OVSDB protocol. The OVSDB server supports hardware_vtep database schema [2]. It does not matter for the agent where the OVSDB server runs, or the physical gateway device runs. Location of an L2 gateway agent is not tied to any node. It can run on any system that is reachable by the neutron server and the OVSDB server. Only one instance of L2 gateway agent can run on a node. L2 gateway agent configuration ------------------------------ A configuration file, /etc/neutron/l2gateway_agent.ini will be placed on the node on which the L2 gateway agent runs. This file contains the information about which L2 gateway the agent is associated to (as shown below). # List of tuple ovsdb_name:ip:port # ovsdb_hosts = foo1:foo_ip1:foo_port1, foo2:foo_ip2:foo_port2 # Below variables need to be set if secure connection is required # between the L2 gateway agent and OVSDB server. # If the agent wants a secure communication with the OVSDB server, # then the following attributes are to be set # Base path to private key file(s). # Agent will find key file named # $l2_gw_agent_priv_key_base_path/$ovsdb_name.key l2_gw_agent_priv_key_base_path = # Base path to cert file(s). # Agent will find cert file named # $l2_gw_agent_cert_base_path/$ovsdb_name.cert l2_gw_agent_cert_base_path = # Base path to ca cert file(s). # Agent will find ca cert file named # $l2_gw_agent_ca_cert_base_path/$ovsdb_name.ca_cert l2_gw_agent_ca_cert_base_path = foo_ip1 and foo_ip2 represent the IP addresses of the OVSDB servers/L2 gateways that are to be managed by this agent. foo_port1 and foo_port2 are the TCP ports on which the OVSDB servers are listening to. High Availability ----------------- In order to support scale out model, multiple such L2 gateway agents may run on different nodes and send their heartbeats to the neutron server. Any agent can 'transact' with the OVSDB servers (active/active replication). In this model, messages that come from the neutron server are casted to any available agent at any given time. Conversely, an agent needs to be notified by the OVSDB servers of events that happen in the physical space. If all agents listened, there will be duplicates, and therefore only have one agent can 'monitor' the OVSDB servers at any given time (active/passive replication). The service plugin scheduler component determines one of these agents as the "monitoring" agent and the rest of the agents as "transact" agents. The monitoring agent listens to the OVSDB server state change notifications over the TCP socket specified in the l2gateway_agent.ini file. The OVSDB notifications may include information of the L2 gateway device and MAC addresses of the bare metal hosts that are learnt by the L2 gateway device. The agent converts these notifications into RabbitMQ messages (cast RPC) that are sent to the service plugin. The service plugin may write the required information to the Neutron database, and sends RPC messages (add/delete_fdb_entries) to the L2 agent on compute and network nodes. This results in VXLAN tunnels from compute/network nodes to the L2 gateway devices. When a logical gateway is bound to a given virtual network in the command: neutron l2-gateway-connection-create [--default-segmentation-id=] the outcome depends upon the following two scenarios described. Scenario 1: The command is issued when virtual machines exist in the network ---------------------------------------------------------------------------- The service plugin prepares a list of MACs of all the virtual machines that belong to the network, VMs' IPs, compute nodes' VTEP IPs and makes an RPC call to the L2 gateway agents over the RabbitMQ message bus. In the RPC call, it also sends the VXLAN ID-to-VLAN mapping. One of the "transact" L2 gateway agents consumes this message from the bus. The agent, in turn, updates the OVSDB tables with the binding information (VXLAN ID-to-VLAN mapping in case of VXLAN networks), the new MAC addresses (virtual machines) and the remote compute/network node IP which acts as a remote VTEP IP. The L2 gateway, then creates a reverse VXLAN tunnel to the compute nodes/network node's VTEP IP. Scenario 2: The command is issued when the network does not have virtual machines ------------------------------------------------------------------------ The service plugin sends the VXLAN ID-to-VLAN mapping in an RPC call over the RabbitMQ bus. One of the "transact" L2 gateway agents consumes this message from the bus. The agent, in turn, updates the OVSDB tables with the binding information (VXLAN ID-to-VLAN mapping in case of VXLAN networks). Later, when a virtual machine is spawned on this network, the service plugin sends the MAC address of this VM along with its IP address, compute node's VTEP IP in an RPC to the transact agents. One of the transact agents consumes this message and updates the OVSDB tables with the information. The L2 gateway, then creates a reverse VXLAN tunnel to the compute node. Note: The plugin can either send one bulk message to a transact agent to process, or split a request into multiple RPCs to the agents. This will be taken care in the implementation. L2 gateways are configured based on the information present in the OVSDB tables. This is left to each vendor how to configure the gateway based on the information in the OVSDB tables. If the "monitoring" agent dies due to some reason, the heartbeats from the agent stop arriving at the neutron server. The service plugin, then makes the L2 gateway agent that has sent the latest heartbeat as the "monitoring" agent. Note that the monitoring agent will read the entire OVSDB after it is elected as the monitoring agent so that events are not missed. Most typical agent failure modes will be kept in mind during the implementation. L2 gateway agents will be listed in the neutron agent-list command output. Data Model Impact ----------------- New tables representing OVSDB tables (hardware_vtep schema) [2] may be added in the neutron DB: - Ucast_Macs_Local table will be added that will represent details of bare metal server on the physical side. - Ucast_Macs_Remote table will be added that will represent details of the virtual machines on the virtual side. - Physical_Locator table will be added that will represent details of the VTEPs (compute node, network node and physical side VTEPs aka gateway devices) - Physical_Switch table will be added that will represent details of the gateway devices on the physical side - Physical_Port table will be added that will represent details of the physical interfaces of the gateway devices - Logical_Switch table will be added that will represent details of the virtual network (this is just a placeholder and we may not reqruire the table when we come to the implementation as network information is already kept inside the neutron tables) L2 gateway agent information will be stored in the existing Agent table model. REST API Impact --------------- None. Security Impact --------------- None. Notifications Impact -------------------- A cast message from the plugin to L2 gateway agents to send details of virtual machines, compute node on which the virtual machines are hosted and network details to the agent. A cast message from the plugin to L2 gateway agents to send the mapping of VXLAN segmentation ID of the specified virtual network and VLAN ID on the physical network to the agent so that the binding is created on the gateway device A cast message from the plugin to L2 gateway agents to send the mapping of VXLAN segmentation ID of the specified virtual network and VLAN ID on the physical network to the agent so that the binding is destroyed on the gateway device A call message from the plugin to an L2 gateway agent to elect it as the monitoring agent A cast message from the monitoring agent to the plugin to notify OVSDB state changes. Other End User Impact --------------------- The L2 gateway agent will be listed along with other agents in "neutron agent-list" command output. Performance Impact ------------------ None IPv6 Impact ----------- None Other Deployer Impact --------------------- There is no change in the existing compute node based L2 OpenVSwitch agent. The proposed L2 gateway agent and existing L2 OpenVSwitch agent are two different agents. The L2 gateway agent does not necessarily require OpenVSwitch installation on that node. It may run on any node. However, it interacts with a remote OVSDB server 2.3.x which supports hardware_vtep schema. If L2 gateway service is to be enabled, then it is required to configure the L2 gateway service plugin and L2 gateway agent. /etc/neutron.conf: service_plugins=l2gw Provider driver may be specified optionally, service_provider=L2GW:l2gw: Developer Impact ---------------- None. Community Impact ---------------- With this approach, different vendors can implement the support for their gateways as the implementation is solely based on a standard hardware_vtep schema supported by the OVSDB. With this spec, we can bring legacy VLAN networks into cloud which will help the community. Alternatives ------------ An alternative solution would be to develop a mechanism with which the service plugin can interact with a gateway over NetConf or similar configuration protocols. However, the benefit cannot be leveraged by all the vendors. In the proposed architecture, as the open standard OVSDB "hardware_vtep" schema is used, everyone's needs may be satisfied. Implementation ============== Assignee(s) ----------- Maruti Kamat (marutik) Selvakumar S (selvakumar-s2) Vivekanandan Narasimhan (vivekanandan-narasimhan) Phani Pawan (ppawan) Koteswara Rao Kelam (koti-kelam) Manjunath Patil (mpatil) Vikas D M (vikas-d-m) Ashish Kumar Gupta (ashish-kumar-gupta) Alok Kumar Maurya (alok-kumar-maurya) Preeti Mirji (preeti-mirji) Work Items ---------- The work is split up into three parts: 1. Initiating L2 gateway RPCs by/to the service plugin over the RabbitMQ message bus. 2. Implementation of the L2 gateway agent * This will require development of L2 gateway agent that will communicate with the OVSDB server so as to integrate with the L2 gateway. The agent will communicate with the OVSDB server over a TCP socket. The L2 gateway agent will manage the L2 gateways as specified in the configuration (l2gateway_agent.ini). Future work: - Instead of configuring the IP addresses of the L2 gateways inside the l2gateway_agent.ini file, a REST interface may be provided to the neutron server. This way, an administrator can dynamically specify the mapping (in JSON format) of which L2 gateways are to be managed by which L2 gateway agents. 3. Packaging of the implemented software and its deployment Dependencies ============ * L2 gateway APIs https://review.openstack.org/144173 Testing ======= Tempest Tests ------------- Tempest test cases will be added. Functional Tests ---------------- The testing will be performed in a setup with an OpenStack deployment (devstack) connected to a L2 gateway agent that reads/writes into the OVSDB hardware_vtep schema. Hardware based switches can be tested by a third party CI infrastructure. Every vendor that supports hardware_vtep schema should be able to validate the solution independently irrespective of whether it is a software or a hardware gateway. API Tests --------- None Documentation Impact ==================== User Documentation ------------------ Functionality and configuration details will be documented Developer Documentation ----------------------- OpenStack Neutron wiki needs to be updated References ========== [1] L2 gateway APIs https://review.openstack.org/144173 [2] OVSDB hardware_vtep schema http://openvswitch.org/docs/vtep.5.pdf [3] VTEP emulator https://github.com/openvswitch/ovs/blob/master/vtep/README.ovs-vtep.md ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/specs/kilo/l2-gateway-api.rst0000664000175000017500000011765214572557755023602 0ustar00jamespagejamespage.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode ============== L2 Gateway API ============== This 'manifesto' introduces a Neutron API extension that can be used to express and manage L2 Gateway components. In the simplest terms L2 Gateways are meant to bridge two or more networks together to make them look at a single L2 broadcast domain. The are a number of use cases that can be addressed by an L2 Gateway API. Most notably in cloud computing environments, a typical use case is bridging the virtual with the physical. Translate this to Neutron and the OpenStack world, and this means relying on L2 Gateway capabilities to extend Neutron logical (overlay) networks into physical (provider) networks that are outside the OpenStack realm. These networks can be, for instance, VLAN's that may or may not be managed by OpenStack. To fix ideas, we are going to present this API assuming the afore-mentioned use case is going to be tackled initially, and we are going to use VXLAN and VLAN as the two L2 segmentation technologies being bridged by an L2 Gateway. How these L2 Gateway components are going to be implemented (either in software or hardware) is outside the scope of this document, and will be discussed elsewhere. Proposed Approach ================= This specification tries to expose enough details so that API is flexible enough to allow implementers to map logical gateways to the physical ones the way they see fit. This implies that the API could be implemented with physical gateway, software gateway, and/or different bridging mechanisms, details of which are deferred to the implementation. The below diagram depicts that the API will be implemented in the proposed service plugin just like other Neutron API extensions. diagram:: +–––––––––––––––––––––––––+ | | | | | Neutron Server +-----+ | | | | | | | | | +–––––––––––––––––––––––––+ | | | +––––––––––––+––––––––––––+ | | | L2 Gateway | | Service | | Plugin | | | | | +–––––––––––––––––––––––––+ L2 gateway service plugin is proposed, which provides REST interfaces to support the following use cases: Note: These commands/APIs can be executed only by the admin users. 1. Creating an abstraction of an L2 gateway with its interface(s). That is, creation of a logical gateway. e.g., neutron l2-gateway-create --device name=,interface_names=[:]; [:,] --device name=,interface_names=[:]; [:,] where seg-id is optional, gateway-name is a descriptive name for the logical gateway, device-name1 and device-name2 are the names or identifiers of the L2 gateways. interface-name1 and interface-name2 ... interface-nameN are interfaces on the gateways. seg-id indicates the segmentation identifier of the physical network to which the interfaces belong to. 2. Updating a logical gateway neutron l2-gateway-update --device name= [--add-interface=:] [--remove-interface=] 3. Deletion of a logical gateway neutron l2-gateway-delete 4. List all the logical gateways neutron l2-gateway-list 5. Show details of a logical gateway neutron l2-gateway-show 6. Binding a logical gateway to an overlay network. neutron l2-gateway-connection-create [--default-segmentation-id=] Note: a. We will support specifying a list of networks in this command in future. b. We will add --segmentation-type option in future to support other bridging mechanisms like FLAT is the name of the neutron network. --default-segmentation-id indicates the default segmentation-id that will be applied to the interfaces for which segmentation id was not specified in l2-gateway-create command. Outcome: Support for multi-segmented networks is out of scope of this spec. For the time being, if a network consists of more than one segment, then it will throw an error. 7. Listing connections neutron l2-gateway-connection-list 8. Show details of a connection neutron l2-gateway-connection-show 9. Destroying binding between neutron network and the VLAN neutron l2-gateway-connection-delete Data Model Impact ----------------- The following four tables will be introduced. l2gateways: +-----------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------+--------------+------+-----+---------+-------+ | id | varchar(36) | NO | PRI | NULL | | | name | varchar(255) | YES | | NULL | | | tenant_id | varchar(36) | YES | | NULL | | +-----------+--------------+------+-----+---------+-------+ l2gatewaydevices: +--------------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------+-------------+------+-----+---------+-------+ | id | varchar(36) | NO | PRI | NULL | | | device_name | varchar(36) | NO | PRI | NULL | | | l2_gateway_id | varchar(36) | NO | FOR | NULL | | +--------------------+-------------+------+-----+---------+-------+ l2gatewayinterfaces: +--------------------+-------------+------+----------+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------+-------------+------+----------+---------+-------+ | id | varchar(36) | NO | PRI | NULL | | | interface_name | varchar(36) | NO | MUL | NULL | | | device_id | varchar(36) | NO | FOR, MUL | NULL | | | segmentation_id | int(11) | YES | | NULL | | +--------------------+-------------+------+----------+---------+-------+ networkconnections: +--------------------+---------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------+---------------------+------+-----+---------+-------+ | id | varchar(36) | NO | PRI | NULL | | | tenant_id | varchar(255) | YES | | NULL | | | l2_gateway_id | varchar(36) | YES | MUL | NULL | | | network_id | varchar(36) | YES | MUL | NULL | | | port_id | varchar(36) | NO | PRI | NULL | | +--------------------+---------------------+------+-----+---------+-------+ REST API Impact --------------- New REST resources are shown below. l2gateways: +-----------+--------------+---------+---------+--------------+ |Attribute |Type |Access |Default |Description | |Name | | |Value | | +===========+==============+=========+=========+==============+ |id |string |CRD |generated|identity | | |(UUID) | | | | +-----------+--------------+---------+---------+--------------+ |tenant id |string |CRUD | | | | |(UUID) | | | | +-----------+--------------+---------+---------+--------------+ |name |string |CRUD |'' | | | | | | | | +-----------+--------------+---------+---------+--------------+ |devices |list of |CRUD |[] | | | |dicts | | | | | |for devices | | | | | |and interfaces| | | | | | | | | | +-----------+--------------+---------+---------+--------------+ Note: In "devices" attribute, existing device can be updated to add/remove interface only. networkconnections: +-------------------+-------+---------+---------+--------------+ |Attribute |Type |Access |Default |Description | |Name | | |Value | | +===================+=======+=========+=========+==============+ |id |string |CRD |generated|connectionuuid| | |(UUID) | | | | +-------------------+-------+---------+---------+--------------+ |l2 |string |CRD | | | |gateway id |(UUID) | | | | +-------------------+-------+---------+---------+--------------+ |network id |string | | | | | | (UUID)|CRD | | | +-------------------+-------+---------+---------+--------------+ |port_id |UUID |CRD | | | +-------------------+-------+---------+---------+--------------+ |default | int |C | | | |segmentation_id | | | | | +-------------------+-------+---------+---------+--------------+ The following new REST APIs will be introduced. 1. neutron l2-gateway-create --device name=,interface_names=[:]; [:,] --device name=,interface_names=[:]; [:,] JSON Request :: POST /v2/l2-gateways Content-Type: application/json {"l2_gateway": {"name": "", "devices": [{"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }, {"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }]}} Response: :: {"l2_gateway": {"name": "", "tenant_id": "7ea656c7c9b8447494f33b0bc741d9e6", "devices": [{"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }, {"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }], "id": "d3590f37-b072-4358-9719-71964d84a31c"}} Normal Response Code(s): Created (201) Error Response Code(s): Standard http error codes 2. neutron l2-gateway-update --device name= [--add-interface=:] [--remove-interface=] JSON Request :: POST /v2/l2-gateways Content-Type: application/json {"l2_gateway": {"name": "", "devices": [{"device_name": "", "new_interfaces": [{"name":"", "segmentation-id":[]}] }, "deleted_interfaces": [{"name":""}] ]}} Response: :: {"l2_gateway": {"name": "", "tenant_id": "7ea656c7c9b8447494f33b0bc741d9e6", "devices": [{"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }, {"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }], "id": "d3590f37-b072-4358-9719-71964d84a31c"}} Normal Response Code(s): Created (200) Error Response Code(s): Standard http error codes 2. neutron l2-gateway-connection-create [--default-segmentation-id=] :: JSON Request POST /v2/l2-gateway-connections Content-Type: application/json {"network_id": "591ffe08-f8f5-44c1-85c1-1026878f69bd", "default_segmentation_id": , "gateway_id": "d3590f37-b072-4358-9719-71964d84a31c" } Response: {"tenant_id": "7ea656c7c9b8447494f33b0bc741d9e6", "connection_id": "", "network_id": "591ffe08-f8f5-44c1-85c1-1026878f69bd", "default_segmentation_id": , "gateway_id": "d3590f37-b072-4358-9719-71964d84a31c", "port_id": "9ea656c7c9b8447494f33b0bc741d9a9" } Normal Response Code(s): Created (201) Error Response Code(s): Standard http error codes 3. neutron l2-gateway-connection-list :: JSON Request GET /v2/l2-gateway-connections Content-Type: application/json Response: {"l2_gateway_connections": [{"connection_id": "", "tenant_id": "7ea656c7c9b8447494f33b0bc741d9e6", "network_id": "e5062ab3-b120-41b2-b138-dc5d2fcaf216", "default_segmentation_id": , "gateway_id": "d3590f37-b072-4358-9719-71964d84a31c", "port_id": "9ea656c7c9b8447494f33b0bc741d9a9"}] } Normal Response Code(s): OK (200) Error Response Code(s): Standard http error codes 4. neutron l2-gateway-connection-show :: JSON Request GET /v2/l2-gateway-connections/ Content-Type: application/json Response: {"connection_id" : "", "tenant_id": "7ea656c7c9b8447494f33b0bc741d9e6", "network_id": "e5062ab3-b120-41b2-b138-dc5d2fcaf216", "default_segmentation_id": , "gateway_id": "d3590f37-b072-4358-9719-71964d84a31c", "port_id": "9ea656c7c9b8447494f33b0bc741d9a9" } Normal Response Code(s): OK (200) Error Response Code(s): Standard http error codes 5. neutron l2-gateway-list :: JSON Request GET /v2/l2-gateways Content-Type: application/json Response: {"l2_gateways": [{"name": "", "tenant_id": "7ea656c7c9b8447494f33b0bc741d9e6", "devices": [{"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }, {"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }], "id": "d3590f37-b072-4358-9719-71964d84a31c"}]} Normal Response Code(s): OK (200) Error Response Code(s): Standard http error codes 6. neutron l2-gateway-show :: JSON Request GET /v2/l2-gateways/ Content-Type: application/json Response: {"l2_gateway": {"name": "", "tenant_id": "7ea656c7c9b8447494f33b0bc741d9e6", "devices": [{"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }, {"device_name": "", "interfaces": [{"name":"", "segmentation-id":[]}, {"name":"", "segmentation-id":[, ]}] }], "id": "d3590f37-b072-4358-9719-71964d84a31c"} } Normal Response Code(s): OK (200) Error Response Code(s): Standard http error codes 7. neutron l2-gateway-connection-delete :: JSON Request DELETE /v2/l2-gateway-connections/ Content-Type: application/json Response: null Normal Response Code(s): No content (204) Error Response Code(s): Standard http error codes 8. neutron l2-gateway-delete :: JSON Request DELETE /v2/l2-gateways/ Content-Type: application/json Response: null Normal Response Code(s): No content (204) Error Response Code(s): Standard http error codes Typical workflow using the proposed REST APIs --------------------------------------------- Consider a cloud administrator has identified a physical gateway with hostname 'gatewayhost' with physical interfaces port1, port2, .... portN which s/he can use to leverage services like a legacy database server, an edge firewall, etc. residing on bare metal hosts. Consider that port1 and port2 belong to VLAN 100 on the physical side, to which bare metal hosts BM1 and BM2 are connected. The administrator can then execute the following commands to interconnect the existing virtual machines in the cloud with the bare metal hosts. 1. The administrator creates a logical gateway 'gw1' representing the hardware gateway device 'gatewayhost' and its interfaces port1 and port2. neutron l2-gateway-create gw1 --device name=gatewayhost,interface_names=port1;port2 This just creates an entry in the Neutron database. Flow:: +–––––––––––––––––––––––––+ | | | | | Neutron Server +-----+ | | | | | | | | | +–––––––––––––––––––––––––+ | | \|/ +––––––––––––+––––––––––––+ | | | L2 Gateway | | Service | | Plugin | | | | | +––––––––––––+––––––––––––+ | \|/ +––––––––––––+––––––––––––+ | | | Neutron DB | | | | | +–––––––––––––––––––––––––+ Note: From steps 2 to 8, Neutron server is not shown for convenience. 2. The administrator binds an existing VXLAN network 'net1' with the VXLAN ID 1000 (I.e. provider:network_type=VXLAN, provider:segmentation_id=1000) with this logical gateway gw1. neutron l2-gateway-connection-create gw1 net1 --default-segmentation-id=100 As the segmentation ID was not specified in the gateway creation time, the default segmentation ID 100 is used for both the interfaces, port1 and port2. The service plugin builds the following: - MAC addresses of all the virtual machines of the network net1 - IP addresses of the virtual machines - VTEP IP of the compute nodes which host the virtual machines - VXLAN-VLAN binding, that is 1000=100 and sends it to the underlying implementation. The underlying implementation configures the physical gateway with the above information. Flow:: +–––––––––––––––––––––––––+ | | | L2 Gateway | | Service | | Plugin | | | | | +––––––––––––+––––––––––––+ | | +-------------------------------------+ | \|/ +––––––––––––+––––––––––––+ | | | Physical Gateway | | | | | +–––––––––––––––––––––––––+ As the physical gateway now knows the VTEP IP of the compute nodes, it creates VXLAN tunnels to the compute nodes. Flow:: +––––––––––––+––––––––––––+ +––––––––––––+––––––––––––+ | |/ | | | Compute Node +--------------------+ Physical Gateway | | |\ | | | | | | +–––––––––––––––––––––––––+ +–––––––––––––––––––––––––+ 3. The underlying implementation sends information of the physical gateway's VTEP IP address, MAC addresses of the bare metal hosts and their IP addresses to the service plugin. Flow:: +–––––––––––––––––––––––––+ | | | L2 Gateway | | Service | | Plugin | | | | | +––––––––––––+––––––––––––+ /|\ | +-------------------------------------+ | | +––––––––––––+––––––––––––+ | | | Physical Gateway | | | | | +–––––––––––––––––––––––––+ 4. The service plugin sends this information to the compute nodes. Flow:: +–––––––––––––––––––––––––+ | | | L2 Gateway | | Service | | Plugin | | | | | +––––––––––––+––––––––––––+ | | +-----+ | \|/ +––––––––––––+––––––––––––+ | | | Compute Node | | | | | +–––––––––––––––––––––––––+ 5. The compute nodes create reverse VXLAN tunnels to the physical gateway. Flow:: +–––––––––––––––––––––––––+ +–––––––––––––––––––––––––+ | |/ | | | +--------------------+ | | |\ \| | Compute Node +--------------------+ Physical Gateway | | | /| | | | | | +–––––––––––––––––––––––––+ +–––––––––––––––––––––––––+ 6. Hereafter, any number of new virtual machines that are created on this compute node on this network (net1), do not impact the VXLAN tunnel that originated from the gateway and terminated at the compute node. They can use the existing the tunnel to send/receive the data traffic. 7. Similarly, any number of new bare metal servers connected to the interfaces port1 and port2 do not impact the VXLAN tunnel that originated from the compute node and terminated at the gateway. They can use the existing the tunnel to send/receive the data traffic. 8. Only when the last virtual machine on the compute node for the network net1 is destroyed, the plugin instructs the gateway to destroy the VXLAN tunnel to the compute node as it is no longer needed. 9. Similarly, if all the bare metal servers connected to interfaces port1 and port2 are disconnected, then the plugin instructs the compute node to destroy the VXLAN tunnel to the gateway as it is no longer needed. 10. In a case where the VXLAN tunnel exists between the compute node and the gateway when there is at least one virtual machine on the compute node on network net1 and at least one bare metal server on the gateway, the administrator may still want to destroy the VXLAN tunnel between the compute node and the gateway. This can be done using the below command. neutron l2-gateway-connection-delete connection-uuid The underlying implementation deletes the below information from the physical gateway: - MAC addresses of all the virtual machines of the network net1 - IP addresses of the virtual machines - VTEP IP of the compute nodes which host the virtual machines - VXLAN-VLAN binding, that is 1000=100. Flow:: +–––––––––––––––––––––––––+ | | | L2 Gateway | | Service | | Plugin | | | | | +––––––––––––+––––––––––––+ | | +-------------------------------------+ | \|/ +––––––––––––+––––––––––––+ | | | Physical Gateway | | | | | +–––––––––––––––––––––––––+ 11. As the binding is destroyed, the physical gateway destroys the VXLAN tunnels to the compute nodes. Flow:: +–––––––––––––––––––––––––+ +–––––––––––––––––––––––––+ | | | | | | | | | | \| | | Compute Node +--------------------+ Physical Gateway | | | /| | | | | | +–––––––––––––––––––––––––+ +–––––––––––––––––––––––––+ 12. The service plugin informs the compute nodes to destroy the VXLAN tunnels to the physical gateway Flow:: +–––––––––––––––––––––––––+ +–––––––––––––––––––––––––+ | | | | | | | | | | | | | Compute Node | | Physical Gateway | | | | | | | | | +–––––––––––––––––––––––––+ +–––––––––––––––––––––––––+ 13. The administrator deletes the logical gateway gw1 if it is not required any longer. neutron l2-gateway-delete gw1 This removes entry of gw1 from the Neutron database. Flow:: +–––––––––––––––––––––––––+ | | | | | Neutron Server +-----+ | | | | | | | | | +–––––––––––––––––––––––––+ | | \|/ +––––––––––––+––––––––––––+ | | | L2 Gateway | | Service | | Plugin | | | | | +––––––––––––+––––––––––––+ | \|/ +––––––––––––+––––––––––––+ | | | Neutron DB | | | | | +–––––––––––––––––––––––––+ Security Impact --------------- None Notifications Impact -------------------- Impact depends upon the underlying implementation of the REST APIs. Other End User Impact --------------------- Python-neutronclient will invoke the APIs. Performance Impact ------------------ None IPv6 Impact ----------- None Other Deployer Impact --------------------- If L2 gateway service is to be enabled, then it is required to configure the L2 gateway service plugin in neutron.conf. /etc/neutron.conf: service_plugins=l2gw Provider driver may be specified optionally, service_provider=L2GW:l2gw: Developer Impact ---------------- None Community Impact ---------------- The spec does not impose a restriction on the implementation. It is left to the party who wants to support its own gateway (hardware or software) with whatever mechanism it wants to implement. This way, we can bring legacy VLAN networks into cloud which will help the community. Alternatives ------------ An alternative solution would be to develop a monolithic vendor plugin. However, the benefit cannot be leveraged by all the vendors. Another advantage is that no major change in the existing ML2 plugin is required. Other alternatives: 1. https://review.openstack.org/#/c/93613 This can be achieved by the APIs proposed in the current spec by providing an option to specify other segmentation types in future. 2. https://review.openstack.org/#/c/136555 The spec does not support different segmentation types. On the other hand, the current spec can add an option in future to support different segmentation types. With the current spec, it is possible to connect a virtual network to multiple gateways. The current spec also addresses a problem where different overlay networks like VXLAN, GRE, etc. can communicate with VLAN networks. This helps in intercommunication between two different overlay network types with an L2 gateway in between. Another advantage of the current spec is that with the same set of APIs, it can support both the types of gateways (hardware as well as software). Implementation ============== Assignee(s) ----------- Maruti Kamat (marutik) Selvakumar S (selvakumar-s2) Vivekanandan Narasimhan (vivekanandan-narasimhan) Phani Pawan (ppawan) Koteswara Rao Kelam (koti-kelam) Manjunath Patil (mpatil) Vikas D M (vikas-d-m) Ashish Kumar Gupta (ashish-kumar-gupta) Alok Kumar Maurya (alok-kumar-maurya) Preeti Mirji (preeti-mirji) (Please add your name and launchpad ID if you are interested in contributing to this spec - CLI, APIs and the service plugin) Work Items ---------- The work is split into multiple parts: 1. Implementation of the service plugin * This will require supporting the REST calls described above. * Implementation of the proposed DB model. * Definition of RPCs for the underlying implementation. 2. Implementation of new CLIs in a client 3. Packaging of the implemented software and its deployment Dependencies ============ None Testing ======= Tempest Tests ------------- None Functional Tests ---------------- None API Tests --------- The following tempest API tests will be added: 1. CRUD operation of an L2 gateway 2. CRD connection of an L2 gateway with a neutron network Documentation Impact ==================== User Documentation ------------------ Functionality and configuration details will be documented Developer Documentation ----------------------- OpenStack wiki needs to be updated References ========== [1] NSX plugin https://github.com/openstack/neutron/blob/master/neutron/plugins/vmware/plugins/base.py#L88 [2] Connecting neutron networks with external networks at the layer-2 level https://review.openstack.org/#/c/100278 [3] Support for extensions in ML2 using Extension Mechanism Manager https://review.openstack.org/#/c/89211 [4] Support for external attachment type validators https://review.openstack.org/#/c/87825 [5] Service API for L2 bridging tenants/provider networks https://review.openstack.org/#/c/93613 [6] Paris summit Neutron lightning talks https://etherpad.openstack.org/p/neutron-kilo-lightning-talks https://drive.google.com/file/d/0B6wARyYJHf0ZRDJvdkJYVjVLVzQ/view ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1709891566.9479856 networking-l2gw-20.0.1.dev5/specs/newton/0000775000175000017500000000000014572557757020667 5ustar00jamespagejamespage././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/specs/newton/l2-border-gateway-api.rst0000664000175000017500000002123114572557755025414 0ustar00jamespagejamespage ============== L2 Gateway API ============== Until this version, the L2GW was meant to connect overlay Neutron network to bare metal servers via hardware switches. This was done by connecting the hardware switch to the local Compute Nodes using VXLAN and connecting the bare metal servers to the hardware switches physical ports. To bind the proper overlay network to the bare metal server the L2GW uses the overlay network's segmentation-id as VXLAN tunnel id and VLANs on the hardware switch. It is our intention to use this project to also support the connection of the local overlay networks with remote overlay networks. One possible scenario is to connect multiple OpenStack clouds in a way that the overlay networks could be connected and act as local networks that are connected via WAN link (inter-cloud connection). Currently the L2GW support L2 connection only but this can also be changed in the future to add L3 connection as well. To support the inter-cloud connection, the L2GW API is extended to provide a way to stretch the network between the local and the remote clouds. Note: the proposed API is an extension of the existing API and can be executed by admin users only, as the previous one. ================ New API Commands ================ 1. Remote Gateway Remote Gateway is an entity that connects to a local Gateway using tunnel to enable connection between local and remote networks. Different networks are connected using different segmentation id. The segmentation id is implementation specific. Different implementation can use different tunnel protocol with its own tunnel key to be used as the segmentation id (VxLAN, GRE, Geneve, Etc.). Single Remote Gateway can connect to multiple local Gateways and the local networks that are attached to them. Also Local Gateway can connect to multiple Remote Gateways using multiple Remote Gateway Connections, each connection to a different Remote Gateway. 1.1. Remote Gateway creation: usage: l2-remote-gateway-create where is a logical name for the remote gateway and is the IP address of the remote gateway which will be the end point of the tunnel. 1.2. Remote Gateway update: usage: l2-remote-gateway-update After creating a Remote Gateway, one can use this command to update the IP address or/and the gateway's logical name. 1.3. Remote Gateway deletion: usage: l2-remote-gateway-delete Use this command to delete Remote Gateway using its logical name or UUID. 1.4. Remote Gateway list: usage: l2-remote-gateway-list This command lists all the Remote Gateways. A response will look like the following: +--------------------------------------+------+----------+ | id | name | ipaddr | +--------------------------------------+------+----------+ | 63821629-6cfd-44c8-8c4a-14c4b5fe28ea | rgw1 | 10.0.0.1 | +--------------------------------------+------+----------+ 1.5. Remote Gateway show: usage: l2-remote-gateway-show Get information of specific Remote Gateway using its logical name or UUID. Example of response: +--------+--------------------------------------+ | Field | Value | +--------+--------------------------------------+ | id | 63821629-6cfd-44c8-8c4a-14c4b5fe28ea | | ipaddr | 10.0.0.1 | | name | rgw1 | +--------+--------------------------------------+ 2. Remote Gateway Connection Remote Gateway Connection is a connection between local and remote networks. Currently the connections are implemented using VXLAN between the local and remote switches. 2.1 Remote Gateway Connection creation: Use this command to create a connection between local and remote network. usage: l2-remote-gateway-connection-create is UUID or logical name of previously created local gateway. is UUID or logical name of tenant network that was previously added to local gateway using l2-gateway-connection-create command. is UUID or logical name of the Remote Gateway that the remote network is connected to. --seg-id is an optional parameter that will be used as tunnel key for the connection between the local and remote Gateways. This parameter is optional and in case it will not be used, the tunnel key that will be used is the overlay network segmentation id. --flood is an optional string of True/False values that state if the local switch should flood unknown MACs or broadcast MAC to the remote connection. 2.2 Remote Gateway Connection delete: usage: l2-remote-gateway-connection-delete use Remote Gateway UUID to delete connection to remote network. 2.3 Remote Gateway Connection list: usage: l2-remote-gateway-connection-list Lists all the connection to Remote Gateways. example of response: +------------+-------------------+-----------------+-----------------+ | id | l2_gateway_id | network_id | segmentation_id | +------------+-------------------+-----------------+-----------------+ | 31c204.... | c8da3e74-fde9-... | d90db94a-2b3... | 9765 | +------------+-------------------+-----------------+-----------------+ 2.4 Remote Gateway Connection show: Show information of a single connection to a Remote Gateway using the connection UUID. example: l2-remote-gateway-connection-show 31c20418-9cf2-4d47-b17e-d92906e3f248 +-----------------+--------------------------------------+ | Field | Value | +-----------------+--------------------------------------+ | id | 31c20418-9cf2-4d47-b17e-d92906e3f248 | | l2_gateway_id | c8da3e74-fde9-48e3-81f2-5fee756dd9de | | network_id | d90db94a-2b3c-4415-971f-967e2f52248d | | segmentation_id | 9765 | | tenant_id | 84429618ed684296bc48eb120acf57bc | +-----------------+--------------------------------------+ 3. Remote MAC The following command could be used by orchestration application to provide information on remote hosts - their MAC address, IP address (for ARP suppression) and link them to Remote Gateway Connection for the local switch to know where to switch the packets to. 3.1 Remote MAC creation: usage: l2-remote-mac-create is a tuple of remote host MAC address and optional IP address. The format is as followed: [;] Where: is the MAC address of the remote host in the form of 00:00:00:00:00:00 IP address of the remote host (optional). the UUID of the Remote Gateway Connection that will lead to the network where the remote host is located. REST message format: { "remote-mac" : { "mac": "00:11:22:33:44:55', "ip": "1.2.3.4" } } 3.2 Bulk Remote MAC creation To send bulk remote MAC creation to the server, the following REST message should be used: { "remote-macs": [ { "mac": "00:11:22:33:44:55', "ip": "1.2.3.5" }, { "mac": "00:11:22:33:44:66', "ip": "1.2.3.6" } ] } 3.3 Remote MAC delete: usage: l2-remote-mac-delete L2_REMOTE_MAC is the UUID of the MAC address to be deleted. 3.4 Remote MAC list: usage: l2-remote-mac-list [--remote-connection UUID] Lists all the remote MAC addresses. example of a response: +--------------+-------------------+--------------------------------------+ | uuid | mac | ip_addr | rgw_connection | +--------------+-------------------+--------------------------------------+ | b59584eb-... | 00:11:22:33:44:55 | 192.168.10.23 | a60dc097-... | +--------------+-------------------+--------------------------------------+ if the optional remote-connection is provided, only MACs that are configured on this connection will be displayed. 3.4 Remote MAC show: usage: l2-remote-mac-show show information of a specific MAC address using its UUID. example of response: l2-remote-mac-show b59584eb-432a-4dba-9a09-f929e77da0c7 +----------------+--------------------------------------+ | Field | Value | +----------------+--------------------------------------+ | ipaddr | 3.3.3.3 | | mac | 00:11:22:33:44:55 | | rgw_connection | a60dc097-13d7-4a9a-9842-117440911eb9 | | uuid | b59584eb-432a-4dba-9a09-f929e77da0c7 | +----------------+--------------------------------------+ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/specs/newton/l2-border-gateway-implementation.rst0000664000175000017500000002260214572557755027673 0ustar00jamespagejamespage =================================== Implementation of L2 Border Gateway =================================== Problem Description =================== The current implementation of the L2 Gateway connects bare metal servers connected to hardware switch to OpenStack overlay tenant networks. This spec propose an extension of the current functionality to a scenario where two or more overlay networks running on two or more different OpenStack domains (such as OpenStack instances, cells or regions) needs to be connected in Layer 2. The name - Border Gateway will be used in order to provide distinction between the L2GW functionality, which is to connect overlay network to bare metal servers and the functionality of the Border Gateway, which is to connect two or more overlay networks that resides on different clouds. The diagram below provides high level overview of the system components:: +-------------------+ | | OpenStack A | | | Computer Node | +----------------+ | | | | | +------------+ | VXLAN Tunnel | Border Gateway | <---------+ | | OVS | | <--------------> | | | | +------------+ | +----------------+ | +-------------------+ | | VXLAN | WAN Tunnel | | +-------------------+ | | | OpenStack B | | | | | Computer Node | +----------------+ | | | | | | | +------------+ | VXLAN Tunnel | Border Gateway | <---------+ | | OVS | | <--------------> | | | +------------+ | +----------------+ +-------------------+ Network Segmentation Handling ============================= The connection between the Border Gateway and the internal overlay network is unchanged and done by using the create gateway and create gateway-connection commands. These commands actually creates logical switch in the physical switch (L2GW device) and also create a tunnel from the logical switch to the OVS in each Compute Node. The segmentation of the overlay networks is done using the overlay network segmentation id. Each cloud manages its own segmentation id and so the usage of single end-to-end segmentation id, which is translated to VXLAN tunnel key, is hard to manage or even impossible. To overcome this challenge, the Border Gateway uses different tunnel key internally - to connect to each Compute Nodes, and externally - to connect the two Border Gateways. When configuring end-to-end connection between the overlay networks, the admin needs to make sure that the remote gateway connection uses the same segmentation id on both sides on the WAN link, as the configuration of the WAN tunnel is done on every Border Gateway separately. Following is a diagram that provides overview of the different segmentation id used by the Border Gateway:: OpenStack A +----------------+ +----------------+ | Compute Node 1 | ------------------ | Border Gateway |-----------------+ +----------------+ Seg-ID 123 +----------------+ Seg-ID 456 | | | | | Seg-ID 123 | | +----------------+ | | | Compute Node 2 | -------------------------+ | +----------------+ Seg-ID 123 | | | | OpenStack B | +----------------+ +----------------+ | | Compute Node | ------------------ | Border Gateway |-----------------+ +----------------+ Seg-ID 789 +----------------+ Seg-ID 456 The segmentation id that is used in each cloud between each of the Compute Nodes and the Border Gateway is handled by Neutron. When setting up gateway connection using l2-gateway-connection-create command, a tunnel is created between a logical switch in the Border Gateway and the OpenVSwitch in each Compute Node with the same segmentation id as the tunnel key. In the diagram above you can see segmentation ids 123 and 789 that are used for intra-cloud connection for OpenStack A and OpenStack B respectively. When setting up the connection between the Border Gateways, the admin need to provide segmentation id to be used by each of the gateways to connect the internal overlay network to the inter-cloud tunnel. The inter-cloud connection command, l2-remote-gateway-connection-create, needs to be run on the two Border Gateway while using the same segmentation id - 456 on both sides, in addition to the internal overlay network information. When configuring the inter-cloud tunnel, the segmentation id is optional and if not provided the tunnel between the Border Gateways will use the same segmentation id on this link as the id used for Border Gateway to Compute Node tunnels. OVSDB Support ============= The L2 Gateway project uses OVSDB's HARDWARE VTEP schema to configure the hardware switches. The previous HARDWARE VTEP schema had a tunnel key parameter in the Logical_Switch table, which leads to a situation where all tunnels that connects to this logical switch would use the same tunnel id. The schema needs to be updated so Physical_Locator table will also have tunnel key parameter providing the ability to setup different tunnels the uses different tunnel key while connecting to a single logical switch. The tunnel setting will support hierarchical configuration in a way that if the tunnel key is configured in the Physical_Locator table, it will be used for the tunnel configuration, and if not, the tunnel key that is configured in the Logical_Switch table will be used. Tunnel Protocol Support ======================= In the existing version, only VXLAN is supported as a tunnel protocol. The limitation is not a technical one but the fact that the encapsulation_type field in the Physical_Locator table is an enum with only one value: vxlan_over_ipv4. In future releases, a multi tunnel protocol support can be achieved with the different tunnel keys support for intra and inter cloud tunnels explained above by adding additional values to the encapsulation_type field. With these modifications, not only the inter-cloud and intra-cloud tunnels will be able to use other protocols than VXLAN, but the Border Gateway will be able to use different protocol for different connection (inter or intra cloud connection). Data Model Impact ----------------- To support different segmentation id on each tunnel, a new column will be added to physical_locators Neutron table. Two Additional tables will be added to Neutron DB: 1. l2remotegateways table that will hold remote gateway information 2. l2remotegatewayconnections table that will hold configuration information for remote gateway connection. REST API Impact --------------- API commands will be added for the following: 1. Create/Update/Delete/List/Show Remote Gateway configuration 2. Create/Delete/List/Show Remote Gateway Connection configuration 3. Create/Delete/List/Show Remote MAC configuration. This will enable adding remote host switching information. The above commands can be invoked by administrator or by special purpose process. See l2-border-gateway-api.rst document for more detailed information. Security Impact --------------- None. Notifications Impact -------------------- A cast message from the plugin to L2 gateway agents to create connection to remote gateway for unknown MAC addresses. This will instruct the switch to forward packets with unknown destination MAC addresses and broadcast destination MAC to a connection to remote gateway. A cast message from the plugin to L2 gateway agents to create remote MAC with a remote gateway connection to be used for packet forwarding. Performance Impact ------------------ None IPv6 Impact ----------- None Dependencies ============ * L2 gateway APIs Implementation ============== Assignee(s) ----------- Ofer Ben-Yacov (oferby) Testing ======= Tempest Tests ------------- None Functional Tests ---------------- None API Tests --------- None Documentation Impact ==================== User Documentation ------------------ Functionality and configuration details will be documented Developer Documentation ----------------------- OpenStack Neutron wiki needs to be updated. See here: https://wiki.openstack.org/wiki/Neutron/L2-GW References ========== API change request: https://bugs.launchpad.net/networking-l2gw/+bug/1529863 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/test-requirements.txt0000664000175000017500000000123114572557755022474 0ustar00jamespagejamespagehacking>=6.1.0,<6.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD psycopg2>=2.8.5 # LGPL/ZPL PyMySQL>=0.7.6 # MIT License oslotest>=3.2.0 # Apache-2.0 astroid==2.15.8 # LGPLv2.1 isort==4.3.21 # MIT pylint==2.17.4 # GPLv2 pytest>=5.3.5 # MIT stestr>=1.0.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testresources>=2.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT ddt>=1.0.1 # MIT # This is necessary as pecan dropped this dependency # see https://review.opendev.org/c/openstack/neutron/+/848706 WebTest>=2.0.27 # MIT ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1709891565.0 networking-l2gw-20.0.1.dev5/tox.ini0000664000175000017500000000617114572557755017556 0ustar00jamespagejamespage[tox] envlist = py38,pep8 minversion = 3.18.0 skipsdist = False ignore_basepython_conflict=true [testenv] basepython = python3 setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning usedevelop = True deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt allowlist_externals = * commands = stestr run {posargs} [testenv:pep8] commands = flake8 neutron-db-manage --subproject networking-l2gw check_migration [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -d doc/build/doctrees -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] # N530 direct neutron imports not allowed # W504 Line break occurred after a binary operator # E126 continuation line over-indented for hanging indent # E128 continuation line under-indented for visual indent # H405 multi line docstring summary not separated with an empty line # I202 Additional newline in a group of imports # E731 do not assign a lambda expression, use a def # W504 line break after binary operator ignore = E126,E128,E731,I202,H405,N530,W504 # H106: Don't put vim configuration in source files # H203: Use assertIs(Not)None to check for None # H204: Use assert(Not)Equal to check for equality # H205: Use assert(Greater|Less)(Equal) for comparison # H904: Delay string interpolations at logging calls enable-extensions=H106,H203,H204,H205,H904 show-source = True builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,.tmp import-order-style = pep8 [hacking] import_exceptions = networking_l2gw._i18n [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed # separately, outside of the requirements files, and develop mode disabled # explicitly to avoid unnecessarily installing the checked-out repo too (this # further relies on "tox.skipsdist = True" above). deps = bindep commands = bindep test usedevelop = False [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt [testenv:dev] # run locally (not in the gate) using editable mode # https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs commands = pip install -q -e "git+https://opendev.org/x/networking-l2gw#egg=networking_l2gw" pip install -q -e "git+https://opendev.org/openstack/neutron#egg=neutron" [testenv:py3-dev] commands = {[testenv:dev]commands} {[testenv]commands} [testenv:pep8-dev] deps = {[testenv]deps} commands = {[testenv:dev]commands} {[testenv:pep8]commands}