././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5219789 networking-bgpvpn-21.0.0/0000775000175000017500000000000000000000000015276 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/.coveragerc0000664000175000017500000000015700000000000017422 0ustar00zuulzuul00000000000000[run] branch = True source = networking-bgpvpn omit = networking-bgpvpn/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/.mailmap0000664000175000017500000000013100000000000016712 0ustar00zuulzuul00000000000000# Format is: # # ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/.pre-commit-config.yaml0000664000175000017500000000257200000000000021565 0ustar00zuulzuul00000000000000--- default_language_version: # force all unspecified python hooks to run python3 python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace - id: mixed-line-ending args: ['--fix', 'lf'] exclude: '.*\.(svg)$' - id: check-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements - id: check-yaml - repo: https://github.com/lucas-c/pre-commit-hooks rev: v1.5.4 hooks: - id: remove-tabs exclude: '.*\.(svg)$' - repo: https://opendev.org/openstack/hacking rev: 6.1.0 hooks: - id: hacking additional_dependencies: ['neutron-lib'] exclude: '^(doc|releasenotes|tools)/.*$' - repo: local hooks: - id: flake8 name: flake8 additional_dependencies: - hacking>=6.1.0,<6.2.0 - neutron-lib language: python entry: flake8 files: '^.*\.py$' exclude: '^(doc|releasenotes|tools)/.*$' # todo(slaweq): enable pylint check once all issues in the current code will # be solved # - id: pylint # name: pylint # entry: .tox/pep8/bin/pylint # files: ^networking-bgpvpn/ # language: system # types: [python] # args: ['--rcfile=.pylintrc', '--output-format=colorized'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/.pylintrc0000664000175000017500000000527400000000000017153 0ustar00zuulzuul00000000000000# The format of this file isn't really documented; just use --generate-rcfile [MASTER] # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. # ignore=.git,tests [MESSAGES CONTROL] # NOTE(gus): This is a long list. A number of these are important and # should be re-enabled once the offending code is fixed (or marked # with a local disable) disable= # "F" Fatal errors that prevent further processing import-error, # "I" Informational noise locally-disabled, # "E" Error for important programming issues (likely bugs) no-member, no-method-argument, no-self-argument, # "W" Warnings for stylistic problems or minor programming issues abstract-method, arguments-differ, attribute-defined-outside-init, bad-indentation, broad-except, dangerous-default-value, expression-not-assigned, fixme, global-statement, non-parent-init-called, not-callable, protected-access, redefined-builtin, redefined-outer-name, super-init-not-called, unpacking-non-sequence, unused-argument, # "C" Coding convention violations consider-using-f-string, invalid-name, len-as-condition, missing-docstring, multiple-statements, superfluous-parens, # "R" Refactor recommendations duplicate-code, inconsistent-return-statements, no-else-return, too-few-public-methods, too-many-ancestors, too-many-arguments, too-many-branches, too-many-instance-attributes, too-many-lines, too-many-locals, too-many-public-methods, too-many-return-statements, too-many-statements, useless-object-inheritance [BASIC] # Variable names can be 1 to 31 characters long, with lowercase and underscores variable-rgx=[a-z_][a-z0-9_]{0,30}$ # Argument names can be 2 to 31 characters long, with lowercase and underscores argument-rgx=[a-z_][a-z0-9_]{1,30}$ # Method names should be at least 3 characters long # and be lowecased with underscores method-rgx=([a-z_][a-z0-9_]{2,}|setUp|tearDown)$ # Module names matching neutron-* are ok (files in bin/) module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(neutron-[a-z0-9_-]+))$ # Don't require docstrings on tests. no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$ [FORMAT] # Maximum number of characters on a single line. max-line-length=79 [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. # _ is used by our localization additional-builtins=_ [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules= [TYPECHECK] # List of module names for which member attributes should not be checked ignored-modules= [REPORTS] # Tells whether to display a full report or only the messages reports=no ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/.stestr.conf0000664000175000017500000000011700000000000017546 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./networking_bgpvpn/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/.zuul.yaml0000664000175000017500000001452500000000000017246 0ustar00zuulzuul00000000000000- job: name: networking-bgpvpn-functional-bagpipe parent: neutron-functional required-projects: - openstack/neutron - openstack/networking-bgpvpn - openstack/networking-bagpipe vars: project_name: networking-bgpvpn devstack_localrc: NETWORKING_BGPVPN_DRIVER: BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver:default BAGPIPE_DATAPLANE_DRIVER_IPVPN: ovs BAGPIPE_BGP_PEERS: "-" devstack_plugins: networking-bagpipe: https://opendev.org/openstack/networking-bagpipe networking-bgpvpn: https://opendev.org/openstack/networking-bgpvpn neutron: https://opendev.org/openstack/neutron - job: name: networking-bgpvpn-functional-full parent: neutron-functional required-projects: - openstack/neutron - openstack/networking-bgpvpn - openstack/networking-bagpipe - openstack/horizon vars: project_name: networking-bgpvpn # NOTE(lajoskatona): neutron-functional sets tox_install_siblings to # false. This needs to be true so for example neutron is installed into # tox env from source. tox_install_siblings: true devstack_plugins: networking-bgpvpn: https://opendev.org/openstack/networking-bgpvpn neutron: https://opendev.org/openstack/neutron - job: name: networking-bgpvpn-bagpipe-install parent: devstack-tempest timeout: 7800 required-projects: - openstack/networking-bagpipe - openstack/networking-bgpvpn vars: devstack_services: # Disable OVN services ovn-controller: false ovn-northd: false ovs-vswitchd: false ovsdb-server: false q-ovn-metadata-agent: false # Enable Neutron services that are not used by OVN q-agt: true q-dhcp: true q-l3: true q-meta: true q-metering: true br-ex-tcpdump: true br-int-flows: true devstack_localrc: NETWORKING_BGPVPN_DRIVER: "BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver:default" BAGPIPE_DATAPLANE_DRIVER_IPVPN: "ovs" BAGPIPE_BGP_PEERS: "-" Q_AGENT: openvswitch Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch devstack_plugins: networking-bgpvpn: https://opendev.org/openstack/networking-bgpvpn networking-bagpipe: https://opendev.org/openstack/networking-bagpipe - job: name: networking-bgpvpn-install parent: devstack-tempest timeout: 7800 required-projects: - openstack/networking-bgpvpn vars: devstack_services: # Disable OVN services ovn-controller: false ovn-northd: false ovs-vswitchd: false ovsdb-server: false q-ovn-metadata-agent: false # Enable Neutron services that are not used by OVN q-agt: true q-dhcp: true q-l3: true q-meta: true q-metering: true br-ex-tcpdump: true br-int-flows: true devstack_localrc: Q_AGENT: openvswitch Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch devstack_plugins: networking-bgpvpn: https://opendev.org/openstack/networking-bgpvpn - job: name: networking-bgpvpn-openstack-tox-py310-with-sqlalchemy-main required-projects: - name: github.com/sqlalchemy/sqlalchemy override-checkout: main - name: github.com/sqlalchemy/alembic override-checkout: main - openstack/oslo.db - openstack/neutron - openstack/neutron-lib parent: openstack-tox-py310-with-oslo-master - project: templates: - openstack-python3-jobs-neutron - publish-openstack-docs-pti - release-notes-jobs-python3 - periodic-stable-jobs-neutron - check-requirements - horizon-non-primary-django-jobs check: jobs: - openstack-tox-pep8: required-projects: - openstack/networking-bagpipe - openstack/horizon - openstack-tox-py39: required-projects: &bgpvpn_required_projects - openstack/neutron - openstack/networking-bagpipe - openstack/horizon - openstack-tox-py310: required-projects: *bgpvpn_required_projects - openstack-tox-py311: required-projects: *bgpvpn_required_projects - openstack-tox-py312: required-projects: *bgpvpn_required_projects - neutron-tempest-plugin-bgpvpn-bagpipe: irrelevant-files: - ^(test-|)requirements.txt$ - ^setup.cfg$ - openstack-tox-cover: required-projects: *bgpvpn_required_projects - horizon-tox-python3-django32: required-projects: *bgpvpn_required_projects - networking-bgpvpn-functional-full gate: jobs: - openstack-tox-pep8: required-projects: - openstack/networking-bagpipe - openstack/horizon - openstack-tox-py39: required-projects: *bgpvpn_required_projects - openstack-tox-py310: required-projects: *bgpvpn_required_projects - openstack-tox-py311: required-projects: *bgpvpn_required_projects - neutron-tempest-plugin-bgpvpn-bagpipe: irrelevant-files: - ^(test-|)requirements.txt$ - ^setup.cfg$ - networking-bgpvpn-functional-full experimental: jobs: - networking-bgpvpn-install - networking-bgpvpn-bagpipe-install - networking-bgpvpn-functional-bagpipe - openstack-tox-py310-with-oslo-master: required-projects: *bgpvpn_required_projects - networking-bgpvpn-openstack-tox-py310-with-sqlalchemy-main: required-projects: *bgpvpn_required_projects periodic-weekly: jobs: - openstack-tox-py312: required-projects: - openstack/neutron - openstack-tox-py311: required-projects: *bgpvpn_required_projects - openstack-tox-py310-with-oslo-master: required-projects: *bgpvpn_required_projects - networking-bgpvpn-openstack-tox-py310-with-sqlalchemy-main: required-projects: *bgpvpn_required_projects - neutron-tempest-plugin-bgpvpn-bagpipe - networking-bgpvpn-functional-full ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/AUTHORS0000664000175000017500000001104500000000000016347 0ustar00zuulzuul00000000000000Akihiro Motoki Andreas Jaeger Andrzej Denisiewicz Anh Tran Antoine Eiche Armando Migliaccio Atsushi SAKAI Bernard Cafarelli Bhagyashri Shewale Bin-Lu <369283883@qq.com> Bob Melander Boden R Cao Xuan Hoang Cedric Savignan Chuck Short Claire Delcourt Corey Bryant Cédric Savignan Daniel Mellado Dariusz Smigiel Davanum Srinivas David Blaisonneau Deepthi V V Doug Hellmann Edouard Thuleau Elod Illes Emilien Macchi Flavio Percoco Francois Eleouet Georg Kunz Ghanshyam Mann Henry Henry Gessau Henry Gessau Hervé Beraud Ian Wienand Ihar Hrachyshka James E. Blair Jean-Philippe Braun Jeremy Stanley Ji-Wei Joan Meseguer Jon Schlueter Kailun Qin Ken'ichi Ohmichi Le Hou Li-zhigang Lukas Piwowarski Luke Hinds Luong Anh Tuan Marios Andreou Masayuki Igawa Mathieu Rohon Monty Taylor Morgan Richomme Ngo Quoc Cuong Nguyen Hung Phuong Nguyen Van Trung Nikolas Hermanns Nishant Kumar Nurmatov Mamatisa Oleg Bondarev Omar Sanhaji OpenStack Release Bot Paul Carver Pawel Rusak Peter V. Saveliev Pierre Crégut Rafael Folco Ramanjaneya Rodolfo Alonso Hernandez Romanos Skiadas Sean McGinnis Slawek Kaplonski Szymon Datko Takashi Kajinami Takashi Kajinami Thomas Monguillon Thomas Morin Thomas Morin Tuan Do Anh Vieri <15050873171@163.com> Vishal Thapar Vivekanandan Narasimhan Vu Cong Tuan Wim De Clercq YAMAMOTO Takashi YuehuiLei YuehuiLei bfernando bhargavaregalla chenxing davidblaisonneau-orange elajkat gengchc2 ghanshyam huang.zhiping janonymous lidong likui malei manchandavishal mathieu-rohon melissaml pengyuesheng qinchunhua ravikiran shanyunfan33 suresh kumar wangzihao yatin yatinkarel ythomas1 zhangboye zhanghao zhangyanxian zhaojingjing0067370 Édouard Thuleau Édouard Thuleau Łukasz Rajewski ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/CONTRIBUTING.rst0000664000175000017500000000103300000000000017734 0ustar00zuulzuul00000000000000If 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/bgpvpn ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/ChangeLog0000664000175000017500000007071400000000000017061 0ustar00zuulzuul00000000000000CHANGES ======= 21.0.0 ------ * Doc: remove sphinxcontrib-\*diag from doc dependencies * Add pre-commit configuration * Remove executable from python files which don't really needs it * Fix trailing whitespaces and replace tabs with 4 spaces * reno: Update master for unmaintained/wallaby * Fix compatibility with SQLAlchemy 2.0 * Replace CRLF by LF * Imported Translations from Zanata * Update jobs based on testing runtime for 2024.2 * Update master for stable/2024.1 20.0.0 ------ * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/victoria * Bump hacking * tox: Drop envdir * reno: Update master for unmaintained/yoga * coveragerc: Remove reference to non-existant path * Remove reference to glance-registry * Drop x bit from dashboard files * Dashboard: change ugettext\_lazy to gettext\_lazy * Update python classifier with py3.11 in setup.cfg * py311: add required projects to py311 job and add it to weekly * Update master for stable/2023.2 * Add python3.10 support in testing runtime 19.0.0 ------ * Fix bindep.txt for python 3.11 job(Debian Bookworm) * Fix tox.ini * Imported Translations from Zanata * Make neutron-tempest-plugin-bgpvpn-bagpipe job voting again * Fix issues due to rcent RBAC changes * CI: add oslo\_master and sqlalchemy to periodic weekly * Use neutron-lib policy rules * Drop rc1 and b1 from min versions in requirements.txt * Update master for stable/2023.1 18.0.0 ------ * Imported Translations from Zanata * Fix some pylint indentation warnings * Tox4: add allowlist\_externals where necessary * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed 17.0.0 ------ * Change dsvm-functional to dsvm-functional-gate * Tests: fix requirements for unit tests * Remove TripleO job * Imported Translations from Zanata * Update python testing as per zed cycle teting runtime * Address RemovedInDjango40Warning * Drop lower-constraints.txt and its testing * CI: fix weekly job * Register common cfg options from Neutron * Add weekly jobs * Add Python3 zed unit tests * Update master for stable/yoga 16.0.0 ------ * CI: Use latest Neutron for jobs * remove unicode from code * Run TripleO jobs on CentOS8 instead of CentOS7 * Add Python3 yoga unit tests * Update master for stable/xena 15.0.0 ------ * Use assertCountEqual instead of assertItemsEqual * Changed minversion in tox to 3.18.0 * Remove unecessary READER and WRITER context from bgpvpn\_db * DOC: remove duplicate mitaka entry * Use payloads for PORT events * use payloads for ROUTER\_INTERFACE events * Explicitly set job networking-bgpvpn-install to ML2/OVS * Use quota \`\`DbQuotaDriver\`\` instead of \`\`ConfDriver\`\` * setup.cfg: Replace dashes with underscores * Update master for stable/wallaby * Add Python3 xena unit tests * Update master for stable/wallaby 14.0.0 ------ * Use TOX\_CONSTRAINTS\_FILE * Imported Translations from Zanata * Make lower-constraints job non-voting * Imported Translations from Zanata * Rename jobs from the experimental queue * bump py37 to py38 in tox.ini * Bump hacking min version to 3.0.1 * Add Python3 wallaby unit tests * Update master for stable/victoria 13.0.0 ------ * Cleanup for Refactor-error-messages * Use the 'all' tox env instead of 'all-plugin' * [goal] Migrate testing to ubuntu focal * finish the zuulv3 migration of some bgpvpn jobs * Switch to hacking 3.0.1 * Fix pep8 job * Stop to use the \_\_future\_\_ module * Remove \_MovedItems in pylintrc * Remove usage of six * Switch to newer openstackdocstheme and reno versions * Use unittest.mock instead of third party mock * test-requirements: upgrade min version of networking-bagpipe * Cleanup py27 support * Add Python3 victoria unit tests * Fix dsvm-functional and cleanup tox.ini * mock out ProviderConfiguration.\_\_init\_\_ * Update master for stable/ussuri 12.0.0 ------ * Fix recent gate failures * Add release note on horizon optional dependency * Use extras for horizon dependency * Switch functional/install jobs to Zuulv3 syntax 12.0.0.0b1 ---------- * Remove references for unittest2 * Fix lower constraints * Fix sphinx requirements * Drop Django 1.11 support * translation: drop babel extractor definitions * Imported Translations from Zanata * [ussuri][goal] Drop python 2.7 support and testing * use standard\_attr db from neutron-lib * Update the constraints url * Use Horizon project template for django jobs * PDF documentation build * Update master for stable/train 11.0.0 ------ * Update api-ref location * Add Python 3 Train unit tests * Add local bindep.txt * Change tempest regex used in devstack-gate-bagpipe-rc * Fix bagpipe driver to work with SQLAlchemy 1.3 * fix tox python3 overrides * Remove tempest tests entry point * Move db class definitions before orm relationships to those classes * Rehome tempest tests to neutron-tempest-plugin repo * Update to opendev repository * OpenDev Migration Patch * Dropping the py35 testing * doc: Add policy reference * lower-constraints: align pyscopg version to global reqs * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 10.0.0 ------ * fix tox python3 overrides * stop using common db mixin * add python 3.7 unit test job * use rpc from neutron-lib * tox: make pep8-dev use python3 like pep8 * Remove tripleo newton and ocata jobs * Upgrade pylint to a version that works with python3 * make tempest bgpvpn tests voting again * Convert policy.json into policy-in-code * use neutron-lib for model\_query * Replace tripleo scenario004-multinode with scenario004-standalone * use payloads for ROUTER\_INTERFACE BEFORE\_DELETE events * Change openstack-dev to openstack-discuss * Trival-fix: Missing parameter in declaration * Update min tox version to 2.0 * use context manager from neutron-lib * Increment versioning with pbr instruction * Remove extra publish-openstack-python-branch-tarball job * add local tox targets for pep8 and py3 * opt in for neutron-lib consumption patches * Import legacy jobs * Fix lower-constraints.txt * Drop nose dependencies * Cleanup .zuul.yaml * mark test\_router\_association\_update unit test as unreliable * tempest: reenable tests now that bug 1789878 is fixed * Remove opencontrail configurations * Remove dead code * adjust requirements * remove deprecated drivers with out-of-tree alternatives * add python 3.6 unit test job * tempest: temporarily disable some tests until bug 1789878 is fixed * switch documentation job to new PTI * import zuul job settings from project-config * Remove use\_mox directive * Update reno for stable/rocky 9.0.0 ----- * update requirements for neutron-lib 1.18.0 * Trivial fix typo of description 9.0.0.0b3 --------- * heat plugin: control 'local\_pref' of BGPVPN resource * Add Heat support for Port Associations * heat plugin: resources depend on the API extension being enabled * DB models: add standard attributes * Add release notes link in README * tempest: mark test\_port\_association\_many\_bgpvpn\_routes unstable * [dashboard] Remove old buttons to create/delete associations * switch to stestr * New tempest test added for many bgvpn routes * Add python3 django 1.11 job instead of django 2.0 job * dashboard: Fix test failures caused by django test runner 9.0.0.0b2 --------- * use new neutronclient (more) * unit tests: cleanup setup\_extension call * devstack: support non-legacy neutron * unit test fix: fix api\_extension\_path being overriden * Django 2.0 support * Change sourcing neutron l2 agent script for devstack * ref driver: use decorators for registry callbacks * DB: add missing descriptions for migration scripts * [dashboard] Modify bgpvpn router associations * [dashboard] Modify bgpvpn network associations * dashboard: use new neutronclient * move n8g-odl and n8g-bagpipe as test requirements * heat: use BGPVPN API method from neutronclient * use sub-resource API extension support * doc: update python API client documentation 9.0.0.0b1 --------- * tempest: fix test\_bgpvpn\_port\_association\_bgpvpn\_route * tempest: dynamic RT allocation * use callback registry decorators * Use ALIAS instead of LABEL * remove unused plugin.get\_plugin\_name() * [dashboard] Refactoring some common code * add lower-constraints job * Updated from global requirements * Avoid tox-install.sh * Move neutron/horizon to requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Update the outdated links * doc update: better introduction, more links * Imported Translations from Zanata * Update doc to integrate Keystone V3 * remove use of RESOURCE\_ATTRIBUTE\_MAP * doc: update driver feature support matrix * bagpipe: documentation update * Provide missing release notes * tempest: remove now-useless workarounds for unreliable tests * tempest: use IP address ranges based on environment * Imported Translations from Zanata * add tempest test for Port Association routes of type 'bgpvpn' * Imported Translations from Zanata * Update reno for stable/queens 8.0.0 ----- * switch to use new DB facade * dsvm tempest setup: use ovsfw * Correct django template pattern in babel-django.cfg * ‘local\_pref’ can be updated in 'test\_bgpvpn\_create\_update\_delete()' * enable tempest RT update test * routes-control: DB, adjust lazy loading * check consistency of BGPVPN types in Port routes of type "bgpvpn" * requirements.txt hints for deps managed in tools/tox\_install.sh * tempest test improvements * Zuul: Remove project name * Updated from global requirements * functional test fix: ignore more tables in DB consistency check * Local\_pref attr tempest test for port association * bagpipe: advertise support for VNI extension 8.0.0.0b3 --------- * tempest: enable test\_bgpvpn\_port\_association\_create\_and\_update * Updated from global requirements * doc: formatting fix for OSC doc link * db: minor, add missing DB migration script message * bagpipe v2 driver * db: refresh port association db object after route update * Deprecates old OpenContrail driver * Fix small typo in docs configuration file * routes-control: add 'local\_pref' attribute to BGPVPN resource * Basic tempest tests for port associations * Utility functions for port association tests in tempest * Updated from global requirements * Updated from global requirements * routes-control: add advertise\_extra\_routes to router\_association * Updated from global requirements * bagpipe: remove use of BGPVPNAssociations * [bgpvpn\_dashboard] Fix bug when a network or router name doesn't exist * bagpipe driver: add support for Port Associations * bagpipe driver doc update * [bgpvpn\_dashboard] Minor typo fix * Upgrade hacking specs * Fix minor problem in bgpvpn\_dashboard unit test * Add missing tempest tests for listing and showing objects * Add vni attribute to bgpvpn resource * change how drivers indicate support for an extension * make get\_extended\_resources class methods * bagpipe driver: use OVO-based push/pull RPCs * Imported Translations from Zanata * Updated from global requirements * Adding idempotent IDs to tempest tests * Imported Translations from Zanata 8.0.0.0b2 --------- * Improve message information for translation * Imported Translations from Zanata * Imported Translations from Zanata * Updated from global requirements * Imported Translations from Zanata * devstack: fix linuxbridge configuration * Use SQL BigInteger type to store BGP LOCAL\_PREF * zuul: run tripleo scenario004 like before * tempest: update to follow code deprecation * Remove policy check * bagpipe: fix BGPVPN update/delete for router association * routes-control: fix driver class for non-DB drivers * Remove setting of version/release from releasenotes * Updated from global requirements * Check if bgpvpn enabled in tempest test * Various tempest tests of L3 BGPVPN update * Updated from global requirements * routes-control: port associations (API ext, DB, driver API) * Tempest tests with delete operations * Tempest tests to check L3 BGPVPN RTs update * Updated from global requirements * Tempest utility functions modified * dashboard: impossible to add a bgpvpn with a empty route target * Updated from global requirements * Filter duplicated RTs in compiled list * Test that an empty RT is not accepted * Update doc to use openstack CLI instead neutron CLI 8.0.0.0b1 --------- * dashboard: edit variable containing Regex RT * dashboard: Add unit tests for bgpvpn\_dashboard * Replace the usage of some aliases in tempest * functional tests: ignore all ODL tables * Replace the usage of some aliases in tempest * devstack fixes for linuxbridge * bagpipe driver: enable l2vpn * Update reno for stable/mitaka * Switch DB and driver precommit methods for delete and update * dashboard: introduce usage of the policy file * dashboard: clean code in forms.py * Updated from global requirements * Shrink Tempest scenario manager copy * Updated from global requirements * Two negative tempest tests added * Modified utility functions for negative tests * dashboard: fix bug about route target validation * Updated from global requirements * Fix post gate hook to accommodate for new os-testr * Two new tempest test variants are added * Bug fix in tempest tests * Drop MANIFEST.in - it's not needed by pbr * Updated from global requirements * Updated from global requirements * Update reno for stable/pike * Tempest test base modified and new variants added 7.0.0.0rc1 ---------- * Updated from global requirements * Remove WebTest from test requirements * Add auto-generated config reference * Automatically generate configuration files 7.0.0.0b3 --------- * Updated from global requirements * Replace deprecated test.attr with decorators.attr * Replace deprecated test.attr with decorators.attr * Translation support * Update the documentation link for doc migration * Updated from global requirements * dashboard: refactor views * Remove "=None" in call to \_make\_net\_assoc\_dict * Add driver compatibility matrix to documentation * Updated from global requirements * dashboard: change admin panel * dashboard: fix call of method patterns * Using neutron-lib hacking rules * doc: rendering cleanup * Rearrange existing documentation to fit the new standard layout * Switch from oslosphinx to openstackdocstheme * Turn on warning-is-error in doc build * misc cleanups * bgpvpn-routes-control: policy.json * devstack: declare n-api-meta * policy.json: remove unimplemented attributes * dashboard: change clean method in create and update * dashboard: fix bug about the create BGPVPN form * Speed up tox\_install.sh * use service type constants from neutron\_lib plugins * Updated from global requirements * dashboard: allow bgpvpns with the same name * dashboard: fix RT validation * Add unit tests for bgpvpn\_dashboard * Replace the usage of 'manager' with 'os\_primary' * Updated from global requirements * Updated from global requirements * doc, bagpipe/ovs driver update * update doc on installation and versions * network\_association\_delete function log errors * Updated from global requirements 7.0.0.0b2 --------- * use networking-odl from pypi instead of git master * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * dashboard: fix constant import for RT/RD validation * Updated from global requirements * Updated from global requirements * Updated from global requirements * devstack: source neutron l2 agent script only if q-agt enabled * Remove windows-style line breaks * consume neutron-lib callbacks * use i18n.\_ * Register query hooks at BGPVPNPluginDb object creation * Add net-bgpvpn.conf to config file read by neutron * Add constants for bgpvpn\_tests * Updated from global requirements * Updated from global requirements * Move API definition out of n8g-bgpvpn into neutron-lib 7.0.0.0b1 --------- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * remove bagpipe\_bgpvpn agent extension * Stop using config backed quota engine in unit tests * bagpipe driver: cleanup, remove OVSInterceptBridge * Point API entry to neutron-lib API Reference * devstack job config cleanups * consume ServicePluginBase from neutron-lib * Updated from global requirements * Update bagpipe driver documentation * Indicating the location tests directory in oslo\_debug\_helper * Switch to use stable data\_utils * Updated from global requirements * 'bgpvpn' entry point for neutron.conf:service\_plugins= * Switch to use stable data\_utils * Updated from global requirements * tempest: Switch to local copy of tempset/scenario/manager.py * leave branch unspecified for "current" series * Updated from global requirements * Updated from global requirements * bagpipe: follow move of db/models * Update reno for stable/ocata * Use neutron-lib's context module * Introduce precommit hooks for delete\_bgpvpn\_xxx 6.0.0 ----- * Log a deprecated warning for ODL v1 driver * Revert "Add OpenStack client BGP VPN extension" * Enable multiple RDs of a BGPVPN to be passed to OpenDaylight * doc: improve explanation on the location of config files * Advertise support for python 3 and drop py34 jobs * Typo fix: datas => data * Prepare for using standard python tests * Filtering BGP VPN list with resource associations * devstack job config: add placement-api service * Improve pip installation documentation * Remove doc modindex ref * Use neutron-lib portbindings api-def * Updated from global requirements * Updated from global requirements * Add router association dict formatter method * Removes unnecessary utf-8 encoding * Fix reno release naming and dvsm functional job * LOG marker mismatch in plugin.py * Follow ODL's master * Revert "Temporary workaround to our gate issues" * Adds Tempest scenario test for networking-bgpvpn * devstack: configure tempest at test-config stage * Use ExtensionDescriptor from neutron-lib * Use DB field sizes instead of \_MAX\_LEN constants * Show team and repo badges on README * Use model\_base in neutron\_lib * Switch to using plugins directory in lieu of neutron manager * Replace six.iteritems() with .items() * Use uuidutils instead of uuid.uuid4() * Replace LOG.warn with LOG.warning * Updated from global requirements * Updated from global requirements * Install networking-bagpipe test dependency via tox\_install/zuul-cloner * Use temporary directory for neutron and horizon install * Temporary workaround to our gate issues * OpenContrail : fix exception class usage issues * Remove white space between print () in bgpvpn-sample01.py * Add OpenStack client BGP VPN extension * Install the networking\_bgpvpn\_heat package * Fix tox cover target * Remove last vestiges of oslo-incubator * migration tests as functional tests * Remove custom OVS compilation trigger for Openstack CI * Don't include openstack/common in flake8 exclude list * bagpipe: rely on ROUTER\_INTERFACE registry callbacks * python3: bagpipe driver fix for bridge cookies * devstack/bagpipe: pin OVS to branch-2.5 * Changed the home-page link * Update reno for stable/mitaka * Modify bgpvpn relations with association tables to select * Update reno for stable/newton * Support infrastructure for functional tests * Fix error when the tenant of a bgpvpn resource doesn't exist 5.0.0 ----- * undo some of the incorrect changes for prepping Newton * prepare Newton release * Stop adding ServiceAvailable group option * Pin ODL's dependency to a working commit * bagpipe: compatibility with Neutron routers * opencontrail: not check tenant existence on update * import validate\_regex from neutron\_lib * Enable release notes translation * devstack: fix to load bagpipe l2 agent extension * bagpipe: rely on Port AFTER\_DELETE callbacks * bagpipe: port+orig\_port are in Port AFTER\_UPDATE callbacks * Prevent mixing bgpvpn associations * Fix a typo in documentation * Remove python 3 from setup.cfg classifiers * Import DB model\_base from neutron-lib * Use os-testr instead of testr * TrivialFix: Remove logging import unused * devstack, bagpipe driver: properly set the l2 agent extension * Horizon plugin patch to let user handle BGPVPN resources * Add doc for devstack configuration * Use horizon UT framework * Use more permissive UTs * Enable L2 BGPVPN to be passed to OpenDaylight * Add tempest tests for router association * Add more tempest tests for read permissions * Raise NotImplementedError instead of NotImplemented * Use constrained pip install for all jobs * Remove windows-style line breaks * Import \_ explicitly from .\_i18n * Add error management regarding malformed UUID * Add a tempest test on read permission with bgpvpn\_list * Added the negative cases * Add tempest tests on route-target update * import api validators/converters from neutron\_lib * enable tempest tests for bgpvpn * unit test fix: specify the tenand\_id at Port/Net/Subnet creation * Bad parameter name in disassociate\_network\_from\_bgpvpn * Add a tenant ID check to create a bgpvpn resource * Remove temporary local HasProject * Enable DeprecationWarning in test environments * Add tempest test associate\_disassociate\_network * Delete execute permission of two files * Add Python 3.5 classifier and venv * Updated from global requirements * Updated from global requirements * Remove discover from test-requirements * Add test delete\_bgpvpn\_as\_non\_admin\_fail * Rename DB columns: tenant -> project * Bring models in sync with migrations, add test * Fix the permission of file -rwxr-xr-x * Update API usage with Python and a sample code * Remove useless/broken call in a bagpipe driver test * Remove unused LOG to keep code clean * Improve bagpipe unittest involving OVS bridges * Fix tox unit test issue * Initialize the routers key in make\_bgpvpn\_dict * minor doc layout improvement * Horizon plugin to let the admin handle BGPVPN * Fix RD regex to match RFC 4364, chapter 4.2 * Move from neutron.i18n to oslo.i18n * Update OpenContrail driver documentation * README cleanup * Update bagpipe driver documentation * Make test jobs constrained * Import neutron exceptions from neutron\_lib * bagpipe driver: add a unit test for agent extension * Import constants from neutron\_lib * bagpipe: improve unit test * bagpipe driver: enable a previously disabled unit test * Fix typos in bgpvpn installation manual * bagpipe: really use the extension-specific cookie * bagpipe: update unit tests to follow a Neutron ML2 change * Typo in OpenContrail driver documentation manual * Add info on Nuage Networks driver * Improve installation documentation 4.0.0 ----- * bagpipe: ignore all probe ports * devstack: fix OVS compilation hook * Updated from global requirements * bagpipe: do not ignore probe ports * Add release notes for mitaka * fix release notes build * ODL: Add precommit to create/update\_bgpvpn * Add support for reno release-notes manager * devstack job: enable bagpipe-bgp in bagpipe jobs * Add limitation chapter to bagpipe doc * bagpipe: skip network:\* ports and external networks ports * Add dummy gate\_hook.sh * bagpipe driver: no RPC for updates not changing port status * Add precommit checks to bagpipe driver * devstack: add pre|post\_test\_hook.sh files * Add precommit hooks for create\_bgpvpn\_net/router\_assoc * Add precommit to the driver create/update\_bgpvpn API * Use bagpipe l2 agent extension when bagpipe is activated * Update and improve bagpipe driver documentation * correcting url for nuage website * [Tempest] test\_create\_bgpvpn\_as\_non\_admin\_fail * Uplift to latest Tempest * Adding missings () after a method call * OVS Agent extension for bagpipe driver * Add rcfiles for gate jobs * Fix client test to follow python-neutronclient change * Heat: allow names instead of ids in templates * Heat: improve documentation * Add BGPVPN-ROUTER-ASSOCIATION to heat plugin * Initialize Heat plugin * Devstack : configure tempest file during extra hook * remove neutron-client@liberty dependency * Fixing pylint upgrade issues * Fix rendering issues on block diagrams * bagpipe: agent, update RPC setup code * Follow Neutron master * Add support for router association in ODL driver * bagpipe: only select gw for IPv4 subnets * py26/py33 are no longer supported by Infra's CI * remove python 2.6 trove classifier 3.0.0 ----- * Add a type restriction to bgpvpns when creating a router association * Fix RT/RD validation * Update the spec : remove RTs consolidation part * Add unit tests for neutronclient extension * Add help to Neutron BGVPN CLI commands * bagpipe: always pass network info to the agent * Implementation of router bgpvpn associations * Raise an exception for identical associations * Remove partial implementation of auto\_aggregate * bagpipe: work on notification without port info * Zuul tox needs to use the right neutron branch * Enable bgpvpn in tempest * Init tempest plugin * pylint fixes + pylint downgrade * bagpipe driver: catch exceptions on Neutron notifs * Added documentation for ODL Driver * WiP: Porting bgpvpn odldriver * driver documentation layout fix * bagpipe: port delete action on BEFORE\_DELETE event * Add OpenContrail driver * Remove a spurious tab in setup.cfg * Fix typo in API error message * Add tenant-id to subresources * Remove hardcoded utf8 coding for bgpvpns table * Fix driver control through devstack * Checks consistency of net association vs BGPVPN id * Fix bogus pip install URI in tox\_install.sh * Other adjustments following bagpipe rename * rename bagpipe-l2 in test-requirements * Client : adding the tenant-id if specified for a net association creation * have tox use neutron stable/liberty * Fix oslo dependencies * Do not enforce non-empty route-target lists * bagpipe-l2 now in openstack * Client support for associations as sub-resource * Treat associations as subresources * Populate doc directory * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix devstack plugin.sh * add pylint in tox pep8 task * pylint fixes * Updated from global requirements * Let devstack configure the service provider * Alembic migration update/cleanup * Updated from global requirements * update requirements * bagpipe driver: sync extended OVS agent * Neutronclient: one command for all associations * Tiny formatting and grammar fix * Add introductory documentation for networking-bgpvpn * bagpipe driver: missing return to ignore DHCP ports * Change ignore-errors to ignore\_errors * Set correct default values for some attributes * Updated from global requirements * Adding network association management : bagpipe driver * Fix README : rename networking-bagpipe-l2 links * Fix resource map to enforce policy * neutronclient: unbreak create/update * Fix README : use openstack git instead of github * devstack: add service plugin class earlier * Log a warning if multiple drivers configured * devstack: use helper to add a service plugin * Refer to the new specs * Adapts neutronclient to the new association API * Adding network association management : API & DB layer * bagpipe: follow RPC renaming * Bagpipe driver: use Neutron registry not an ML2 MD * Fixes JSON policy, tenant\_id control and nits * Fix a typo in README.rst * Add route\_distinguishers field to the DB scheme * Fix name of service provider config file * Read networking\_bgpvpn.conf for service providers * Devstack plugin: create neutron policy.d * Complete change 218359 * oslo.config is now oslo\_config * API/DB should not duplicate the policy framework * Updated from global requirements * Add tests for the service driver interface * Updated from global requirements * Updated from global requirements * Add policy.json * Fix one typo on networking-bgpvpn documentation * README improvements * Updated from global requirements * typo correction from BGPVPNDriverBD to BGPVPNDriverDB * Remove BGPVPN plugin dependency on database * Update BaGPipe OVS agent * Neutron constant COMMON\_PREFIXES does not exist anymore * Avoid cloning neutron on test jobs * README/devstack: fix spurious space in IPVPN driver specification * Follow neutron service plugin definition change * Complete the removal of bgpvpn module * Improved README-bagpipe for devstack use * Support route\_distinguishers operation * update README-bagpipe.rst for bagpipe-bgp devstack plugin * Remove bgpvpn module when useless * Move from n.o.commons.uuidutils to osloutils.uuid\_utils * Network\_id should not be a foreign key * First database unit tests * devstack: do not run update-db on a compute node * links to stackforge now link to openstack * Update .gitreview file for project rename * Adding initial unit tests * add bagpipe driver and agent * Adding the initial spec implemented by the bgpvpn framework * add devstack plugin * use oslo\_log.log instead of n.openstack.common.log * BGVPNDriver: some methods need not be abstract * use neutron.openstack.common.log rather than oslo\_log * fix bug on BGPVPN connection update * Update README * Initial proposal with db layer, API extension and client extension * Initial Cookiecutter Commit * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/HACKING.rst0000664000175000017500000000024700000000000017077 0ustar00zuulzuul00000000000000networking-bgpvpn Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/LICENSE0000664000175000017500000002363700000000000016316 0ustar00zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5219789 networking-bgpvpn-21.0.0/PKG-INFO0000664000175000017500000000627300000000000016403 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: networking-bgpvpn Version: 21.0.0 Summary: API and Framework to interconnect bgpvpn to neutron networks Home-page: https://docs.openstack.org/networking-bgpvpn/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/networking-bgpvpn.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on =============================================== BGP-MPLS VPN Extension for OpenStack Networking =============================================== This project provides an API and Framework to interconnect BGP/MPLS VPNs to Openstack Neutron networks, routers and ports. The Border Gateway Protocol and Multi-Protocol Label Switching are widely used Wide Area Networking technologies. The primary purpose of this project is to allow attachment of Neutron networks and/or routers to VPNs built in carrier provided WANs using these standard protocols. An additional purpose of this project is to enable the use of these technologies within the Neutron networking environment. A vendor-neutral API and data model are provided such that multiple SDN controllers may be used as backends, while offering the same tenant facing API. A reference implementation working along with Neutron reference drivers is also provided. * Free software: Apache license * Source: https://opendev.org/openstack/networking-bgpvpn * Bugs: https://bugs.launchpad.net/bgpvpn * Doc: https://docs.openstack.org/networking-bgpvpn/latest/ * Release notes: https://docs.openstack.org/releasenotes/networking-bgpvpn/ =================== Introduction videos =================== The following videos are filmed presentations of talks given during the Barcelona OpenStack Summit (Oct' 2016). Although they do not cover the work done since, they can be a good introduction to the project: * https://www.youtube.com/watch?v=kGW5R8mtmRg * https://www.youtube.com/watch?v=LCDeR7MwTzE Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 Provides-Extra: bagpipe Provides-Extra: horizon Provides-Extra: test ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/README.rst0000664000175000017500000000342400000000000016770 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/networking-bgpvpn.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on =============================================== BGP-MPLS VPN Extension for OpenStack Networking =============================================== This project provides an API and Framework to interconnect BGP/MPLS VPNs to Openstack Neutron networks, routers and ports. The Border Gateway Protocol and Multi-Protocol Label Switching are widely used Wide Area Networking technologies. The primary purpose of this project is to allow attachment of Neutron networks and/or routers to VPNs built in carrier provided WANs using these standard protocols. An additional purpose of this project is to enable the use of these technologies within the Neutron networking environment. A vendor-neutral API and data model are provided such that multiple SDN controllers may be used as backends, while offering the same tenant facing API. A reference implementation working along with Neutron reference drivers is also provided. * Free software: Apache license * Source: https://opendev.org/openstack/networking-bgpvpn * Bugs: https://bugs.launchpad.net/bgpvpn * Doc: https://docs.openstack.org/networking-bgpvpn/latest/ * Release notes: https://docs.openstack.org/releasenotes/networking-bgpvpn/ =================== Introduction videos =================== The following videos are filmed presentations of talks given during the Barcelona OpenStack Summit (Oct' 2016). Although they do not cover the work done since, they can be a good introduction to the project: * https://www.youtube.com/watch?v=kGW5R8mtmRg * https://www.youtube.com/watch?v=LCDeR7MwTzE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/babel-django.cfg0000664000175000017500000000005700000000000020266 0ustar00zuulzuul00000000000000[python: **.py] [django: **/templates/**.html] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/babel-djangojs.cfg0000664000175000017500000000006100000000000020616 0ustar00zuulzuul00000000000000[javascript: **.js] [angular: **/static/**.html] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/babel.cfg0000664000175000017500000000002100000000000017015 0ustar00zuulzuul00000000000000[python: **.py] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/0000775000175000017500000000000000000000000020601 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/__init__.py0000664000175000017500000000000000000000000022700 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/api/0000775000175000017500000000000000000000000021352 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/api/__init__.py0000664000175000017500000000000000000000000023451 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/api/bgpvpn.py0000664000175000017500000001313000000000000023216 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 logging from openstack_dashboard.api import neutron LOG = logging.getLogger(__name__) neutronclient = neutron.neutronclient class Bgpvpn(neutron.NeutronAPIDictWrapper): """Wrapper for neutron bgpvpn.""" class NetworkAssociation(neutron.NeutronAPIDictWrapper): """Wrapper for neutron bgpvpn networks associations.""" class RouterAssociation(neutron.NeutronAPIDictWrapper): """Wrapper for neutron bgpvpn routers associations.""" def bgpvpns_list(request, **kwargs): LOG.debug("bgpvpn_list(): params=%s", kwargs) bgpvpns = neutronclient(request).list_bgpvpns(**kwargs).get('bgpvpns') return [Bgpvpn(v) for v in bgpvpns] def bgpvpn_get(request, bgpvpn_id, **kwargs): LOG.debug("bgpvpn_get(): bgpvpnid=%s, kwargs=%s", bgpvpn_id, kwargs) bgpvpn = neutronclient(request).show_bgpvpn(bgpvpn_id, **kwargs).get('bgpvpn') return Bgpvpn(bgpvpn) def bgpvpn_create(request, **kwargs): LOG.debug("bgpvpn_create(): params=%s", kwargs) body = {'bgpvpn': kwargs} client = neutronclient(request) bgpvpn = client.create_bgpvpn(body=body).get('bgpvpn') return Bgpvpn(bgpvpn) def bgpvpn_update(request, bgpvpn_id, **kwargs): LOG.debug("bgpvpn_update(): bgpvpnid=%s, kwargs=%s", bgpvpn_id, kwargs) body = {'bgpvpn': kwargs} bgpvpn = neutronclient(request).update_bgpvpn(bgpvpn_id, body=body).get('bgpvpn') return Bgpvpn(bgpvpn) def bgpvpn_delete(request, bgpvpn_id): LOG.debug("bgpvpn_delete(): bgpvpnid=%s", bgpvpn_id) neutronclient(request).delete_bgpvpn(bgpvpn_id) def network_association_get(request, bgpvpn_id, network_assoc_id, **kwargs): LOG.debug("network_association_get(): " "bgpvpn_id=%s, network_assoc_id=%s, kwargs=%s", bgpvpn_id, network_assoc_id, kwargs) network_association = neutronclient(request).show_bgpvpn_network_assoc( bgpvpn_id, network_assoc_id).get('network_association') return NetworkAssociation(network_association) def network_association_list(request, bgpvpn_id, **kwargs): LOG.debug("network_association_list(): bgpvpn_id=%s, kwargs=%s", bgpvpn_id, kwargs) network_associations = neutronclient( request).list_bgpvpn_network_assocs( bgpvpn_id, **kwargs).get('network_associations') return [NetworkAssociation(v) for v in network_associations] def network_association_create(request, bgpvpn_id, **kwargs): LOG.debug("network_association_create(): bgpvpnid=%s kwargs=%s", bgpvpn_id, kwargs) body = {'network_association': kwargs} network_association = ( neutronclient(request) .create_bgpvpn_network_assoc(bgpvpn_id, body=body) .get('network_association')) return NetworkAssociation(network_association) def network_association_delete(request, resource_id, bgpvpn_id): LOG.debug("network_association_delete(): resource_id=%s bgpvpnid=%s", resource_id, bgpvpn_id) neutronclient(request).delete_bgpvpn_network_assoc(bgpvpn_id, resource_id) def router_association_get(request, bgpvpn_id, router_assoc_id, **kwargs): LOG.debug("router_association_get(): " "bgpvpn_id=%s, router_assoc_id=%s, kwargs=%s", bgpvpn_id, router_assoc_id, kwargs) router_association = neutronclient(request).show_bgpvpn_router_assoc( bgpvpn_id, router_assoc_id).get('router_association') return RouterAssociation(router_association) def router_association_list(request, bgpvpn_id, **kwargs): LOG.debug("router_association_list(): bgpvpn_id=%s, kwargs=%s", bgpvpn_id, kwargs) router_associations = ( neutronclient(request) .list_bgpvpn_router_assocs(bgpvpn_id, **kwargs) .get('router_associations')) return [RouterAssociation(v) for v in router_associations] def router_association_create(request, bgpvpn_id, **kwargs): LOG.debug("router_association_create(): bgpvpnid=%s params=%s", bgpvpn_id, kwargs) body = {'router_association': kwargs} router_associations = neutronclient(request).create_bgpvpn_router_assoc( bgpvpn_id, body=body).get('router_association') return RouterAssociation(router_associations) def router_association_update(request, bgpvpn_id, router_association_id, **kwargs): LOG.debug("router_association_update(): bgpvpnid=%s " "router_association_id=%s params=%s", bgpvpn_id, router_association_id, kwargs) body = {'router_association': kwargs} router_associations = neutronclient(request).update_bgpvpn_router_assoc( bgpvpn_id, router_association_id, body=body).get('router_association') return RouterAssociation(router_associations) def router_association_delete(request, resource_id, bgpvpn_id): LOG.debug("router_association_delete(): resource_id=%s bgpvpnid=%s", resource_id, bgpvpn_id) neutronclient(request).delete_bgpvpn_router_assoc(bgpvpn_id, resource_id) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/common/0000775000175000017500000000000000000000000022071 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/common/__init__.py0000664000175000017500000000000000000000000024170 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/common/bgpvpn.py0000664000175000017500000000211600000000000023737 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from django.utils.translation import gettext_lazy as _ ROUTE_TARGET_HELP = _("A single BGP Route Target or a " "comma-separated list of BGP Route Target. Example: " "64512:1 or 64512:1,64512:2,64512:3") RT_FORMAT_ATTRIBUTES = ('route_targets', 'import_targets', 'export_targets') def format_rt(route_targets): if route_targets: return re.compile(" *, *").split(route_targets) else: return [] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/0000775000175000017500000000000000000000000022713 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/__init__.py0000664000175000017500000000000000000000000025012 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/0000775000175000017500000000000000000000000024003 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/__init__.py0000664000175000017500000000000000000000000026102 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/0000775000175000017500000000000000000000000025277 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/__init__.py0000664000175000017500000000000000000000000027376 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/forms.py0000664000175000017500000001051300000000000026777 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.core.validators import RegexValidator from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from horizon import forms from openstack_dashboard import api from bgpvpn_dashboard.common import bgpvpn as bgpvpn_common from networking_bgpvpn.neutron.services.common import constants from bgpvpn_dashboard.dashboards.project.bgpvpn import forms \ as project_forms if constants.RTRD_REGEX[0] == '^' and constants.RTRD_REGEX[-1] == '$': RTRD_REGEX = constants.RTRD_REGEX[1:-1] else: msg = _("Bug, inconsistency between neutron-lib and " "networking-bgpvpn for RTRD regex") raise Exception(msg) RTRDS_REGEX = '^%s( *, *%s)*$' % (RTRD_REGEX, RTRD_REGEX) class CommonData(project_forms.CommonData): route_targets = forms.CharField( max_length=255, validators=[RegexValidator(regex=RTRDS_REGEX, message=_("Route targets is not valid"))], label=_("Route targets"), required=False, help_text=bgpvpn_common.ROUTE_TARGET_HELP) import_targets = forms.CharField( max_length=255, validators=[RegexValidator(regex=RTRDS_REGEX, message=_("Import targets is not valid"))], label=_("Import targets"), required=False, help_text=bgpvpn_common.ROUTE_TARGET_HELP + ' To use only on import.') export_targets = forms.CharField( max_length=255, validators=[RegexValidator(regex=RTRDS_REGEX, message=_("Export targets is not valid"))], label=_("Export targets"), required=False, help_text=bgpvpn_common.ROUTE_TARGET_HELP + ' To use only on export.') failure_url = reverse_lazy('horizon:admin:bgpvpn:index') def __init__(self, request, *args, **kwargs): super(CommonData, self).__init__(request, *args, **kwargs) class CreateBgpVpn(CommonData): tenant_id = forms.ChoiceField(label=_("Project")) type = forms.ChoiceField(choices=[("l3", _('l3')), ("l2", _('l2'))], label=_("Type"), help_text=_("The type of VPN " " and the technology behind it.")) fields_order = ['name', 'tenant_id', 'type', 'route_targets', 'import_targets', 'export_targets'] def __init__(self, request, *args, **kwargs): super(CreateBgpVpn, self).__init__(request, *args, **kwargs) tenant_choices = [('', _("Select a project"))] tenants, has_more = api.keystone.tenant_list(request) for tenant in tenants: if tenant.enabled: tenant_choices.append((tenant.id, tenant.name)) self.fields['tenant_id'].choices = tenant_choices self.action = 'create' class EditDataBgpVpn(CommonData): bgpvpn_id = forms.CharField(label=_("ID"), widget=forms.TextInput( attrs={'readonly': 'readonly'})) type = forms.CharField(label=_("Type"), widget=forms.TextInput( attrs={'readonly': 'readonly'})) tenant_id = forms.CharField(widget=forms.HiddenInput()) fields_order = ['name', 'bgpvpn_id', 'tenant_id', 'type', 'route_targets', 'import_targets', 'export_targets'] def __init__(self, request, *args, **kwargs): super(EditDataBgpVpn, self).__init__(request, *args, **kwargs) self.action = 'update' class CreateNetworkAssociation(project_forms.CreateNetworkAssociation): project_id = forms.CharField(widget=forms.HiddenInput()) def _set_params(self, data): params = super(CreateNetworkAssociation, self)._set_params(data) params['tenant_id'] = data['project_id'] return params ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/panel.py0000664000175000017500000000144500000000000026754 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.utils.translation import gettext_lazy as _ import horizon class BGPVPNInterconnections(horizon.Panel): name = _("BGPVPN Interconnections") slug = "bgpvpn" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/tables.py0000664000175000017500000000743500000000000027134 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 logging from django.urls import reverse from django.utils import html from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import exceptions from horizon import tables from openstack_dashboard import policy from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.project.bgpvpn import tables as project_tables LOG = logging.getLogger(__name__) class DeleteBgpvpn(policy.PolicyTargetMixin, tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy(u"Delete BGPVPN", u"Delete BGPVPNs", count) @staticmethod def action_past(count): return ngettext_lazy(u"Deleted BGPVPN", u"Deleted BGPVPNs", count) def delete(self, request, obj_id): try: bgpvpn_api.bgpvpn_delete(request, obj_id) except Exception: msg = _('Failed to delete BGPVPN %s') % obj_id LOG.info(msg) redirect = reverse('horizon:admin:bgpvpn:index') exceptions.handle(request, msg, redirect=redirect) class CreateBgpVpn(tables.LinkAction): name = "create" verbose_name = _("Create BGPVPN") url = "horizon:admin:bgpvpn:create" classes = ("ajax-modal",) icon = "plus" class EditInfoBgpVpn(project_tables.EditInfoBgpVpn): url = "horizon:admin:bgpvpn:edit" class CreateNetworkAssociation(project_tables.CreateNetworkAssociation): url = "horizon:admin:bgpvpn:create-network-association" class CreateRouterAssociation(project_tables.CreateRouterAssociation): url = "horizon:admin:bgpvpn:create-router-association" def get_route_targets(bgpvpn): return ', '.join(rt for rt in bgpvpn.route_targets) def get_import_targets(bgpvpn): return ', '.join(it for it in bgpvpn.import_targets) def get_export_targets(bgpvpn): return ', '.join(et for et in bgpvpn.export_targets) def get_network_url(network): url = reverse('horizon:admin:networks:detail', args=[network.id]) instance = '%s' % (url, html.escape(network.name_or_id)) return instance def get_router_url(router): url = reverse('horizon:admin:routers:detail', args=[router.id]) instance = '%s' % (url, html.escape(router.name_or_id)) return instance class BgpvpnTable(project_tables.BgpvpnTable): route_targets = tables.Column(get_route_targets, verbose_name=_("Route Targets")) import_targets = tables.Column(get_import_targets, verbose_name=_("Import Targets")) export_targets = tables.Column(get_export_targets, verbose_name=_("Export Targets")) class Meta(object): table_actions = (CreateBgpVpn, DeleteBgpvpn) row_actions = (EditInfoBgpVpn, CreateNetworkAssociation, CreateRouterAssociation, DeleteBgpvpn) columns = ("tenant_id", "name", "type", "route_targets", "import_targets", "export_targets", "networks", "routers") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/tabs.py0000664000175000017500000000224700000000000026607 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \ import tabs as network_tabs from bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations \ import tabs as router_tabs from bgpvpn_dashboard.dashboards.project.bgpvpn import tabs as project_tabs class OverviewTab(project_tabs.OverviewTab): template_name = "admin/bgpvpn/_detail_overview.html" class BgpvpnDetailsTabs(project_tabs.BgpvpnDetailsTabs): tabs = (OverviewTab, network_tabs.NetworkAssociationsTab, router_tabs.RouterAssociationsTab) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4579785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/0000775000175000017500000000000000000000000027275 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/0000775000175000017500000000000000000000000030571 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_create.html0000664000175000017500000000054500000000000033065 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body %}
{% include "horizon/common/_form_fields.html" %}

{% trans "Description:" %}

{% trans "Create a new BGP VPN for any project as you need."%}

{% endblock %}././@PaxHeader0000000000000000000000000000021000000000000011446 xustar0000000000000000114 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_detail_overview.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_detail_overview.0000664000175000017500000000165200000000000034125 0ustar00zuulzuul00000000000000{% load i18n %}
{% trans "BGPVPN Name" %}
{{ bgpvpn.name }}
{% trans "BGPVPN ID" %}
{{ bgpvpn.id }}
{% trans "Type" %}
{{ bgpvpn.type }}
{% trans "Route Targets" %}
{% for rt in bgpvpn.route_targets %} {{ rt }} {% endfor %}
{% trans "Import Targets" %}
{% for it in bgpvpn.import_targets %} {{ it }} {% endfor %}
{% trans "Export Targets" %}
{% for et in bgpvpn.export_targets %} {{ et }} {% endfor %}
{% trans "Project ID" %}
{{ bgpvpn.tenant_id }}
{% include "project/bgpvpn/_associated_networks.html" %} {% include "project/bgpvpn/_associated_routers.html" %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/create.html0000664000175000017500000000026200000000000032722 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create BGPVPN" %}{% endblock %} {% block main %} {% include "admin/bgpvpn/_create.html" %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/urls.py0000664000175000017500000000267300000000000026646 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.urls import re_path import bgpvpn_dashboard.dashboards.admin.bgpvpn.views as bgpvpn_views BGPVPN = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', bgpvpn_views.IndexView.as_view(), name='index'), re_path(r'^create/$', bgpvpn_views.CreateView.as_view(), name='create'), re_path(BGPVPN % 'edit', bgpvpn_views.EditDataView.as_view(), name='edit'), re_path(BGPVPN % 'create-network-association', bgpvpn_views.CreateNetworkAssociationView.as_view(), name='create-network-association'), re_path(BGPVPN % 'create-router-association', bgpvpn_views.CreateRouterAssociationView.as_view(), name='create-router-association'), re_path(r'^(?P[^/]+)/detail/$', bgpvpn_views.DetailProjectView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/views.py0000664000175000017500000000647600000000000027023 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon.utils import memoized from openstack_dashboard import api import bgpvpn_dashboard.api.bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.admin.bgpvpn import forms as bgpvpn_forms from bgpvpn_dashboard.dashboards.admin.bgpvpn import tables as bgpvpn_tables from bgpvpn_dashboard.dashboards.admin.bgpvpn import tabs as bgpvpn_tabs from bgpvpn_dashboard.dashboards.admin.bgpvpn import workflows \ as bgpvpn_workflows from bgpvpn_dashboard.dashboards.project.bgpvpn import views as project_views class IndexView(project_views.IndexView): table_class = bgpvpn_tables.BgpvpnTable @memoized.memoized_method def _get_tenant_name(self, id): try: return api.keystone.tenant_get(self.request, id).name except Exception: msg = _("Unable to retrieve information about the " "tenant %s") % id exceptions.handle(self.request, msg) def get_data(self): bgpvpns_list = bgpvpn_api.bgpvpns_list(self.request) for bgpvpn in bgpvpns_list: bgpvpn.networks = [api.neutron.network_get( self.request, id, expand_subnet=False) for id in bgpvpn.networks] bgpvpn.routers = [api.neutron.router_get(self.request, id) for id in bgpvpn.routers] bgpvpn.tenant_name = self._get_tenant_name(bgpvpn.tenant_id) return bgpvpns_list class CreateView(forms.ModalFormView): form_class = bgpvpn_forms.CreateBgpVpn form_id = "create_bgpvpn_form" template_name = 'admin/bgpvpn/create.html' submit_label = _("Create BGPVPN") success_url = reverse_lazy('horizon:admin:bgpvpn:index') page_title = _("Create BGPVPN") submit_url = reverse_lazy("horizon:admin:bgpvpn:create") class EditDataView(project_views.EditDataView): form_class = bgpvpn_forms.EditDataBgpVpn submit_url = 'horizon:admin:bgpvpn:edit' success_url = reverse_lazy('horizon:admin:bgpvpn:index') class CreateNetworkAssociationView(project_views.CreateNetworkAssociationView): form_class = bgpvpn_forms.CreateNetworkAssociation submit_url = 'horizon:admin:bgpvpn:create-network-association' success_url = reverse_lazy('horizon:admin:bgpvpn:index') class CreateRouterAssociationView(project_views.CreateRouterAssociationView): workflow_class = bgpvpn_workflows.RouterAssociation failure_url = reverse_lazy("horizon:admin:bgpvpn:index") class DetailProjectView(project_views.DetailProjectView): tab_group_class = bgpvpn_tabs.BgpvpnDetailsTabs redirect_url = 'horizon:admin:bgpvpn:index' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/admin/bgpvpn/workflows.py0000664000175000017500000000205500000000000027710 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 bgpvpn_dashboard.dashboards.project.bgpvpn import workflows \ as project_workflows class RouterAssociation(project_workflows.RouterAssociation): success_url = "horizon:admin:bgpvpn:index" def _set_params(self, data, association_type, resource): params = super(RouterAssociation, self)._set_params( data, association_type, resource) params['tenant_id'] = data['tenant_id'] return params ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4739785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/0000775000175000017500000000000000000000000024361 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/__init__.py0000664000175000017500000000000000000000000026460 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4779785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/0000775000175000017500000000000000000000000025655 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/__init__.py0000664000175000017500000000000000000000000027754 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/forms.py0000664000175000017500000001367400000000000027370 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import collections import logging from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import messages from openstack_dashboard import api from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.common import bgpvpn as bgpvpn_common LOG = logging.getLogger(__name__) class CommonData(forms.SelfHandlingForm): fields_order = [] name = forms.CharField(max_length=255, label=_("Name"), required=False) failure_url = reverse_lazy('horizon:project:bgpvpn:index') def __init__(self, request, *args, **kwargs): super(CommonData, self).__init__(request, *args, **kwargs) if 'keyOrder' in self.fields: self.fields.keyOrder = self.fields_order else: self.fields = collections.OrderedDict( (k, self.fields[k]) for k in self.fields_order) @staticmethod def _del_attributes(attributes, data): for attribute in attributes: del data[attribute] def handle(self, request, data): params = {} for key in bgpvpn_common.RT_FORMAT_ATTRIBUTES: if key in data: params[key] = bgpvpn_common.format_rt(data.pop(key, None)) params.update(data) error_msg = _('Something went wrong with BGPVPN %s') % data['name'] try: if self.action == 'update': error_msg = _('Failed to update BGPVPN %s') % data['name'] # attribute tenant_id is required in request when admin user is # logged and bgpvpn form from admin menu is used if request.user.is_superuser and data.get('tenant_id'): attributes = ('bgpvpn_id', 'type', 'tenant_id') # attribute tenant_id does not exist in request # when non-admin user is logged else: attributes = ('bgpvpn_id', 'type') self._del_attributes(attributes, params) bgpvpn = bgpvpn_api.bgpvpn_update(request, data['bgpvpn_id'], **params) msg = _('BGPVPN %s was successfully updated.') % data['name'] elif self.action == 'create': error_msg = _('Failed to create BGPVPN %s') % data['name'] bgpvpn = bgpvpn_api.bgpvpn_create(request, **params) msg = _('BGPVPN %s was successfully created.') % data['name'] else: raise Exception( _('Unsupported action type: %s') % self.action) LOG.debug(msg) messages.success(request, msg) return bgpvpn except Exception: exceptions.handle(request, error_msg, redirect=self.failure_url) return False class EditDataBgpVpn(CommonData): bgpvpn_id = forms.CharField(label=_("ID"), widget=forms.TextInput( attrs={'readonly': 'readonly'})) type = forms.CharField(label=_("Type"), widget=forms.TextInput( attrs={'readonly': 'readonly'})) fields_order = ['name', 'bgpvpn_id', 'type'] def __init__(self, request, *args, **kwargs): super(EditDataBgpVpn, self).__init__(request, *args, **kwargs) self.action = 'update' class CreateNetworkAssociation(forms.SelfHandlingForm): bgpvpn_id = forms.CharField(widget=forms.HiddenInput()) network_resource = forms.ChoiceField( label=_("Associate Network"), widget=forms.ThemableSelectWidget( data_attrs=('name', 'id'), transform=lambda x: "%s" % x.name_or_id)) def __init__(self, request, *args, **kwargs): super(CreateNetworkAssociation, self).__init__( request, *args, **kwargs) # when an admin user uses the project panel BGPVPN, there is no # project in data because bgpvpn_get doesn't return it project_id = kwargs.get('initial', {}).get("project_id", None) if request.user.is_superuser and project_id: tenant_id = project_id else: tenant_id = self.request.user.tenant_id try: networks = api.neutron.network_list_for_tenant(request, tenant_id) if networks: choices = [('', _("Choose a network"))] + [(n.id, n) for n in networks] self.fields['network_resource'].choices = choices else: self.fields['network_resource'].choices = [('', _("No network"))] except Exception: exceptions.handle( request, _("Unable to retrieve networks.")) def handle(self, request, data): try: params = self._set_params(data) bgpvpn_api.network_association_create( request, data['bgpvpn_id'], **params) return True except Exception: exceptions.handle(request, (_('Unable to associate network "%s".') % data["network_resource"])) return False def _set_params(self, data): params = dict() params['network_id'] = data['network_resource'] return params ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4779785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/0000775000175000017500000000000000000000000032125 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/__init__.py0000664000175000017500000000000000000000000034224 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/tables.py0000664000175000017500000000665100000000000033761 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 logging from django.urls import reverse from django.urls import reverse_lazy from django.utils import safestring from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import exceptions from horizon import tables from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api LOG = logging.getLogger(__name__) class DeleteNetworkAssociation(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy(u"Delete Network Association", u"Delete Network Associations", count) @staticmethod def action_past(count): return ngettext_lazy(u"Deleted Network Association", u"Deleted Network Associations", count) def delete(self, request, asso_id): try: bgpvpn_api.network_association_delete( request, asso_id, self.table.kwargs['bgpvpn_id']) except Exception: msg = _('Failed to delete Network Association %s') % asso_id LOG.info(msg) redirect = reverse('horizon:project:bgpvpn:detail') exceptions.handle(request, msg, redirect=redirect) class CreateNetworkAssociation(tables.LinkAction): name = "create_network_association" verbose_name = _("Create Network Association") url = "horizon:project:bgpvpn:create-network-association" classes = ("ajax-modal",) icon = "pencil" def get_link_url(self, datum=None): bgpvpn_id = self.table.kwargs['bgpvpn_id'] return reverse(self.url, args=(bgpvpn_id,)) class IDColumn(tables.Column): url = "horizon:project:bgpvpn:network_assos:detail" def get_link_url(self, asso): bgpvpn_id = self.table.kwargs['bgpvpn_id'] return reverse(self.url, args=(bgpvpn_id, asso.id)) class NetworkColumn(tables.Column): def get_raw_data(self, asso): url = reverse('horizon:project:networks:detail', args=[asso.network_id]) instance = '%s' % (url, asso.network_name or asso.network_id) return safestring.mark_safe(instance) class NetworkAssociationsTable(tables.DataTable): id = IDColumn("id", verbose_name=_("Association ID"), link='horizon:project:bgpvpn:network_assos:detail') network = NetworkColumn("network_id", verbose_name=_("Network")) failure_url = reverse_lazy('horizon:project:bgpvpn:detail') class Meta(object): name = "network_associations" verbose_name = _("Network Associations") table_actions = (CreateNetworkAssociation,) row_actions = (DeleteNetworkAssociation,) hidden_title = False def get_object_display(self, asso): return asso.id ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/tabs.py0000664000175000017500000000611500000000000033433 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django.urls import reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import tabs from horizon.utils import memoized from openstack_dashboard import api from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \ import tables as network_tables class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" template_name = "project/bgpvpn/network_associations/_detail_overview.html" def get_context_data(self, request): network_association = self.tab_group.kwargs['network_association'] network = self.get_network(network_association.network_id) network_association.network_name = network.get('name') network_association.network_url = self.get_network_detail_url( network_association.network_id) return {'network_association': network_association} @memoized.memoized_method def get_network(self, network_id): try: network = api.neutron.network_get(self.request, network_id) except Exception: network = {} msg = _('Unable to retrieve network details.') exceptions.handle(self.request, msg) return network @staticmethod def get_network_detail_url(network_id): return reverse('horizon:project:networks:detail', args=(network_id,)) class NetworkAssociationsTab(tabs.TableTab): name = _("Network Associations") slug = "network_associations_tab" table_classes = (network_tables.NetworkAssociationsTable,) template_name = ("horizon/common/_detail_table.html") preload = False def get_network_associations_data(self): try: bgpvpn_id = self.tab_group.kwargs['bgpvpn_id'] network_associations = bgpvpn_api.network_association_list( self.request, bgpvpn_id) for network_asso in network_associations: network = api.neutron.network_get(self.request, network_asso.network_id) network_asso.network_name = network.name except Exception: network_associations = [] msg = _('Network associations list can not be retrieved.') exceptions.handle(self.request, msg) return network_associations class NetworkAssociationDetailTabs(tabs.TabGroup): slug = "network_association_details" tabs = (OverviewTab,) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/urls.py0000664000175000017500000000203300000000000033462 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django.urls import re_path from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \ import views as bgpvpn_network_associations_views NETWORK_ASSO = r'^(?P[^/]+)/network_assos/' \ r'(?P[^/]+)/%s$' urlpatterns = [ re_path(NETWORK_ASSO % 'detail', bgpvpn_network_associations_views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/views.py0000664000175000017500000000452600000000000033643 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django.urls import reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import tabs from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \ import tabs as project_tabs class DetailView(tabs.TabView): tab_group_class = project_tabs.NetworkAssociationDetailTabs template_name = 'horizon/common/_detail.html' page_title = "{{ network_association.id }}" def get_data(self): network_association_id = self.kwargs['network_association_id'] bgpvpn_id = self.kwargs['bgpvpn_id'] try: network_association = bgpvpn_api.network_association_get( self.request, bgpvpn_id, network_association_id) return network_association except Exception: network_association = [] msg = _('Unable to retrieve network association details.') exceptions.handle(self.request, msg, redirect=self.get_redirect_url()) return network_association def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) network_association = self.get_data() context["url"] = reverse( "horizon:project:bgpvpn:network_associations_tab", args=(self.kwargs["bgpvpn_id"], network_association.id)) return context def get_tabs(self, request, *args, **kwargs): network_association = self.get_data() return self.tab_group_class( request, network_association=network_association, **kwargs) @staticmethod def get_redirect_url(): return reverse('horizon:project:bgpvpn:index') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/panel.py0000664000175000017500000000144500000000000027332 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.utils.translation import gettext_lazy as _ import horizon class BGPVPNInterconnections(horizon.Panel): name = _("BGPVPN Interconnections") slug = "bgpvpn" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4779785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/0000775000175000017500000000000000000000000031754 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/__init__.py0000664000175000017500000000000000000000000034053 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/forms.py0000664000175000017500000000433400000000000033460 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 logging from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api LOG = logging.getLogger(__name__) class UpdateRouterAssociation(forms.SelfHandlingForm): bgpvpn_id = forms.CharField(widget=forms.HiddenInput()) router_association_id = forms.CharField( label=_("ID"), widget=forms.TextInput(attrs={'readonly': 'readonly'})) router_id = forms.CharField( label=_("Router"), widget=forms.TextInput(attrs={'readonly': 'readonly'})) advertise_extra_routes = forms.BooleanField( label=_("Advertise Extra Routes"), initial=True, required=False, help_text="Boolean flag controlling whether or not the routes " "specified in the routes attribute of the router will be " "advertised to the BGPVPN (default: true).") def __init__(self, request, *args, **kwargs): super(UpdateRouterAssociation, self).__init__(request, *args, **kwargs) self.action = 'update' def handle(self, request, data): router_association_id = data['router_association_id'] bgpvpn_id = data['bgpvpn_id'] try: bgpvpn_api.router_association_update( request, bgpvpn_id, router_association_id, advertise_extra_routes=data['advertise_extra_routes']) return True except exceptions as e: exceptions.handle( request, "Unable to update bgpvpn router association %s:" % str(e)) return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/tables.py0000664000175000017500000001001500000000000033575 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 logging from django.urls import reverse from django.urls import reverse_lazy from django.utils import safestring from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import exceptions from horizon import tables from openstack_dashboard import api from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api LOG = logging.getLogger(__name__) class DeleteRouterAssociation(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy(u"Delete Router Association", u"Delete Router Associations", count) @staticmethod def action_past(count): return ngettext_lazy(u"Deleted Router Association", u"Deleted Router Associations", count) def delete(self, request, asso_id): try: bgpvpn_api.router_association_delete( request, asso_id, self.table.kwargs['bgpvpn_id']) except Exception: msg = _('Failed to delete Router Association %s') % asso_id LOG.info(msg) redirect = reverse('horizon:project:bgpvpn:detail') exceptions.handle(request, msg, redirect=redirect) class UpdateRouterAssociation(tables.LinkAction): name = "update" verbose_name = _("Update Router Association") url = "horizon:project:bgpvpn:update-router-association" classes = ("ajax-modal",) icon = "pencil" def get_link_url(self, asso): bgpvpn_id = self.table.kwargs['bgpvpn_id'] return reverse(self.url, args=(bgpvpn_id, asso.id)) def allowed(self, request, datum=None): if api.neutron.is_extension_supported(request, 'bgpvpn-routes-control'): return True class CreateRouterAssociation(tables.LinkAction): name = "create_router_association" verbose_name = _("Create Router Association") url = "horizon:project:bgpvpn:create-router-association" classes = ("ajax-modal",) icon = "pencil" def get_link_url(self, datum=None): bgpvpn_id = self.table.kwargs['bgpvpn_id'] return reverse(self.url, args=(bgpvpn_id,)) class IDColumn(tables.Column): url = "horizon:project:bgpvpn:router_assos:detail" def get_link_url(self, asso): bgpvpn_id = self.table.kwargs['bgpvpn_id'] return reverse(self.url, args=(bgpvpn_id, asso.id)) class RouterColumn(tables.Column): def get_raw_data(self, asso): url = reverse('horizon:project:routers:detail', args=[asso.router_id]) instance = '%s' % (url, asso.router_name or asso.router_id) return safestring.mark_safe(instance) class RouterAssociationsTable(tables.DataTable): id = IDColumn("id", verbose_name=_("Association ID"), link='horizon:project:bgpvpn:router_assos:detail') router = RouterColumn("router_id", verbose_name=_("Router")) failure_url = reverse_lazy('horizon:project:bgpvpn:detail') class Meta(object): name = "router_associations" verbose_name = _("Router Associations") table_actions = (CreateRouterAssociation, DeleteRouterAssociation) row_actions = (UpdateRouterAssociation, DeleteRouterAssociation,) hidden_title = False def get_object_display(self, asso): return asso.id ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/tabs.py0000664000175000017500000000602700000000000033264 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django.urls import reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import tabs from horizon.utils import memoized from openstack_dashboard import api from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations \ import tables as router_tables class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" template_name = "project/bgpvpn/router_associations/_detail_overview.html" def get_context_data(self, request): router_association = self.tab_group.kwargs['router_association'] router = self.get_router(router_association.router_id) router_association.router_name = router.get('name') router_association.router_url = self.get_router_detail_url( router_association.router_id) return {'router_association': router_association} @memoized.memoized_method def get_router(self, router_id): try: router = api.neutron.router_get(self.request, router_id) except Exception: router = {} msg = _('Unable to retrieve router details.') exceptions.handle(self.request, msg) return router @staticmethod def get_router_detail_url(router_id): return reverse('horizon:project:routers:detail', args=(router_id,)) class RouterAssociationsTab(tabs.TableTab): name = _("Router Associations") slug = "router_associations_tab" table_classes = (router_tables.RouterAssociationsTable,) template_name = ("horizon/common/_detail_table.html") preload = False def get_router_associations_data(self): try: bgpvpn_id = self.tab_group.kwargs['bgpvpn_id'] router_associations = bgpvpn_api.router_association_list( self.request, bgpvpn_id) for router_asso in router_associations: router = api.neutron.router_get(self.request, router_asso.router_id) router_asso.router_name = router.name except Exception: router_associations = [] msg = _('Router associations list can not be retrieved.') exceptions.handle(self.request, msg) return router_associations class RouterAssociationDetailTabs(tabs.TabGroup): slug = "router_association_details" tabs = (OverviewTab,) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/urls.py0000664000175000017500000000201600000000000033312 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django.urls import re_path import bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations.views \ as bgpvpn_router_associations_views ROUTER_ASSO = r'^(?P[^/]+)/router_assos/' \ r'(?P[^/]+)/%s$' urlpatterns = [ re_path(ROUTER_ASSO % 'detail', bgpvpn_router_associations_views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/views.py0000664000175000017500000000747600000000000033501 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django.urls import reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import tabs from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations \ import forms as bgpvpn_router_associations_forms from bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations \ import tabs as project_tabs class UpdateRouterAssociationsView(forms.ModalFormView): form_class = bgpvpn_router_associations_forms.UpdateRouterAssociation form_id = "update_router_association_form" modal_header = _("Update BGPVPN Router Associations") submit_label = _("Update") submit_url = 'horizon:project:bgpvpn:update-router-association' template_name = 'project/bgpvpn/router_associations/modify.html' page_title = _("Update BGPVPN Router Association") url = 'horizon:project:bgpvpn:detail' def get_success_url(self): return reverse(self.url, args=(self.kwargs['bgpvpn_id'],)) def get_context_data(self, **kwargs): context = super( UpdateRouterAssociationsView, self).get_context_data(**kwargs) args = (self.kwargs['bgpvpn_id'], self.kwargs['router_association_id']) context["bgpvpn_id"] = self.kwargs['bgpvpn_id'] context["router_association_id"] = self.kwargs['router_association_id'] context["submit_url"] = reverse(self.submit_url, args=args) return context def get_initial(self): bgpvpn_id = self.kwargs['bgpvpn_id'] router_association_id = self.kwargs['router_association_id'] try: router_association = bgpvpn_api.router_association_get( self.request, bgpvpn_id, router_association_id) except Exception: exceptions.handle( self.request, _('Unable to retrieve Router Association details.'), redirect=self.success_url) else: data = router_association.to_dict() data['router_association_id'] = data.pop('id') data['bgpvpn_id'] = bgpvpn_id return data class DetailView(tabs.TabView): tab_group_class = project_tabs.RouterAssociationDetailTabs template_name = 'horizon/common/_detail.html' page_title = "{{ router_association.id }}" def get_data(self): router_association_id = self.kwargs['router_association_id'] bgpvpn_id = self.kwargs['bgpvpn_id'] try: router_association = bgpvpn_api.router_association_get( self.request, bgpvpn_id, router_association_id) return router_association except Exception: router_association = [] msg = _('Unable to retrieve router association details.') exceptions.handle(self.request, msg, redirect=self.get_redirect_url()) return router_association def get_tabs(self, request, *args, **kwargs): router_association = self.get_data() return self.tab_group_class( request, router_association=router_association, **kwargs) @staticmethod def get_redirect_url(): return reverse('horizon:project:bgpvpn:index') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/tables.py0000664000175000017500000000615500000000000027510 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.urls import reverse from django.utils import html from django.utils.http import urlencode from django.utils import safestring from django.utils.translation import gettext_lazy as _ from horizon import tables class EditInfoBgpVpn(tables.LinkAction): name = "update_info" verbose_name = _("Edit BGPVPN") url = "horizon:project:bgpvpn:edit" classes = ("ajax-modal",) icon = "pencil" class CreateNetworkAssociation(tables.LinkAction): name = "create_network_association" verbose_name = _("Create Network Association") url = "horizon:project:bgpvpn:create-network-association" classes = ("ajax-modal",) icon = "pencil" class CreateRouterAssociation(tables.LinkAction): name = "create_router_association" verbose_name = _("Create Router Association") url = "horizon:project:bgpvpn:create-router-association" classes = ("ajax-modal",) icon = "pencil" def get_link_url(self, bgpvpn): step = 'create_router_association' base_url = reverse(self.url, args=[bgpvpn.id]) param = urlencode({"step": step}) return "?".join([base_url, param]) def get_network_url(network): url = reverse('horizon:project:networks:detail', args=[network.id]) instance = '%s' % (url, html.escape(network.name_or_id)) return instance def get_router_url(router): url = reverse('horizon:project:routers:detail', args=[router.id]) instance = '%s' % (url, html.escape(router.name_or_id)) return instance class NetworksColumn(tables.Column): def get_raw_data(self, bgpvpn): networks = [get_network_url(network) for network in bgpvpn.networks] return safestring.mark_safe(', '.join(networks)) class RoutersColumn(tables.Column): def get_raw_data(self, bgpvpn): routers = [get_router_url(router) for router in bgpvpn.routers] return safestring.mark_safe(', '.join(routers)) class BgpvpnTable(tables.DataTable): name = tables.Column("name_or_id", verbose_name=_("Name"), link=("horizon:project:bgpvpn:detail")) type = tables.Column("type", verbose_name=_("Type")) networks = NetworksColumn("networks", verbose_name=_("Networks")) routers = RoutersColumn("routers", verbose_name=_("Routers")) class Meta(object): name = "bgpvpns" verbose_name = _("BGPVPN") row_actions = (EditInfoBgpVpn, CreateNetworkAssociation, CreateRouterAssociation) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/tabs.py0000664000175000017500000000400000000000000027152 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 bgpvpn_dashboard.api.bgpvpn as bgpvpn_api from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import tabs from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \ import tabs as network_tabs from bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations \ import tabs as router_tabs class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" template_name = ("project/bgpvpn/_detail_overview.html") preload = False def _get_data(self): bgpvpn = {} bgpvpn_id = None try: bgpvpn_id = self.tab_group.kwargs['bgpvpn_id'] bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id) bgpvpn.set_id_as_name_if_empty(length=0) except Exception: msg = _('Unable to retrieve details for bgpvpn "%s".') % ( bgpvpn_id) exceptions.handle(self.request, msg) return bgpvpn def get_context_data(self, request, **kwargs): context = super(OverviewTab, self).get_context_data(request, **kwargs) bgpvpn = self._get_data() context["bgpvpn"] = bgpvpn return context class BgpvpnDetailsTabs(tabs.DetailTabsGroup): slug = "bgpvpn_tabs" tabs = (OverviewTab, network_tabs.NetworkAssociationsTab, router_tabs.RouterAssociationsTab) sticky = True ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4579785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/0000775000175000017500000000000000000000000027653 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/0000775000175000017500000000000000000000000031147 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000021600000000000011454 xustar0000000000000000120 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_associated_networks.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_associated_net0000664000175000017500000000062500000000000034221 0ustar00zuulzuul00000000000000{% load i18n %}
{% if bgpvpn.networks|length > 1 %} {% trans "Associated Networks" %} {% else %} {% trans "Associated Network" %} {% endif %}
{% for network in bgpvpn.networks %}
{% url 'horizon:project:networks:detail' network as network_url %} {{ network }}
{% endfor %} ././@PaxHeader0000000000000000000000000000021500000000000011453 xustar0000000000000000119 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_associated_routers.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_associated_rou0000664000175000017500000000060200000000000034233 0ustar00zuulzuul00000000000000{% load i18n %}
{% if bgpvpn.routers|length > 1 %} {% trans "Associated Routers" %} {% else %} {% trans "Associated Router" %} {% endif %}
{% for router in bgpvpn.routers %}
{% url 'horizon:project:routers:detail' router as router_url %} {{ router }}
{% endfor %}././@PaxHeader0000000000000000000000000000022500000000000011454 xustar0000000000000000127 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_create_network_association.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_create_network0000664000175000017500000000031100000000000034240 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Associate a BGPVPN to a Network" %}

{% endblock %}././@PaxHeader0000000000000000000000000000021200000000000011450 xustar0000000000000000116 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_detail_overview.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_detail_overvie0000664000175000017500000000062200000000000034232 0ustar00zuulzuul00000000000000{% load i18n %}
{% trans "BGPVPN Name" %}
{{ bgpvpn.name }}
{% trans "BGPVPN ID" %}
{{ bgpvpn.id }}
{% trans "Type" %}
{{ bgpvpn.type }}
{% include "project/bgpvpn/_associated_networks.html" %} {% include "project/bgpvpn/_associated_routers.html" %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_modify.html0000664000175000017500000000034700000000000033467 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "You may update the editable properties of your BGP VPN here." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000022400000000000011453 xustar0000000000000000126 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/create_network_association.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/create_network_0000664000175000017500000000032500000000000034245 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Network Association" %}{% endblock %} {% block main %} {% include "project/bgpvpn/_create_network_association.html" %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/index.html0000664000175000017500000000022600000000000033144 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "BGP VPNs" %}{% endblock %} {% block main %} {{ table.render }} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/modify.html0000664000175000017500000000026400000000000033326 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update BGPVPN" %}{% endblock %} {% block main %} {% include "project/bgpvpn/_modify.html" %} {% endblock %} ././@PaxHeader0000000000000000000000000000022000000000000011447 xustar0000000000000000116 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/network_associations/ 28 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/network_associa0000775000175000017500000000000000000000000034263 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000023700000000000011457 xustar0000000000000000137 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/network_associations/_detail_overview.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/network_associa0000664000175000017500000000123700000000000034270 0ustar00zuulzuul00000000000000{% load i18n sizeformat %}
{% trans "Association ID" %}
{{network_association.id|default:_("None") }}
{% trans "Network Name" %}
{{ network_association.network_name|default:_("None") }}
{% trans "Network ID" %}
{{ network_association.network_id|default:_("None") }}
././@PaxHeader0000000000000000000000000000021700000000000011455 xustar0000000000000000115 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associations/ 28 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associat0000775000175000017500000000000000000000000034276 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000023600000000000011456 xustar0000000000000000136 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associations/_detail_overview.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associat0000664000175000017500000000155300000000000034304 0ustar00zuulzuul00000000000000{% load i18n sizeformat %}
{% trans "Association ID" %}
{{router_association.id|default:_("None") }}
{% trans "Router Name" %}
{{ router_association.router_name|default:_("None") }}
{% trans "Router ID" %}
{{ router_association.router_id|default:_("None") }}
{% trans "Advertise Extra Routes" %}
{{router_association.advertise_extra_routes }}
././@PaxHeader0000000000000000000000000000022500000000000011454 xustar0000000000000000127 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associations/_modify.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associat0000664000175000017500000000037100000000000034301 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "You may update the editable properties of your BGPVPN Router Association here." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000022400000000000011453 xustar0000000000000000126 path=networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associations/modify.html 22 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associat0000664000175000017500000000030700000000000034300 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update BGPVPN Router Association" %}{% endblock %} {% block main %} {% include "project/bgpvpn/_modify.html" %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/urls.py0000664000175000017500000000550500000000000027221 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.conf.urls import include from django.urls import re_path from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations import \ urls as network_associations_urls from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations import \ views as network_associations_views from bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations import \ urls as router_associations_urls from bgpvpn_dashboard.dashboards.project.bgpvpn.router_associations import \ views as router_associations_views from bgpvpn_dashboard.dashboards.project.bgpvpn import views as bgpvpn_views BGPVPN = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', bgpvpn_views.IndexView.as_view(), name='index'), re_path(BGPVPN % 'edit', bgpvpn_views.EditDataView.as_view(), name='edit'), re_path(BGPVPN % 'create-network-association', bgpvpn_views.CreateNetworkAssociationView.as_view(), name='create-network-association'), re_path(BGPVPN % 'create-router-association', bgpvpn_views.CreateRouterAssociationView.as_view(), name='create-router-association'), re_path(r'^(?P[^/]+)/detail/$', bgpvpn_views.DetailProjectView.as_view(), name='detail'), re_path(r'^(?P[^/]+)/network_assos/' r'(?P[^/]+)/' r'detail\?tab=bgpvpns__network__associations_tab$', network_associations_views.DetailView.as_view(), name='network_associations_tab'), re_path(r'^(?P[^/]+)/router_assos/(?P' r'[^/]+)/' r'detail\?tab=bgpvpns__router_associations_tab$', router_associations_views.DetailView.as_view(), name='router_associations_tab'), re_path(r'^(?P[^/]+)/router_assos/(?P' r'[^/]+)/' r'update$', router_associations_views.UpdateRouterAssociationsView.as_view(), name='update-router-association'), re_path(r'^network_assos/', include((network_associations_urls, 'network_assos'))), re_path(r'^router_assos/', include((router_associations_urls, 'router_assos'))), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/views.py0000664000175000017500000001411400000000000027365 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 django.urls import reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import tables from horizon import tabs from horizon.utils import memoized from horizon import workflows from openstack_dashboard import api from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.common import bgpvpn as bgpvpn_common from bgpvpn_dashboard.dashboards.project.bgpvpn import forms as bgpvpn_forms from bgpvpn_dashboard.dashboards.project.bgpvpn import tables as bgpvpn_tables from bgpvpn_dashboard.dashboards.project.bgpvpn import tabs as bgpvpn_tabs from bgpvpn_dashboard.dashboards.project.bgpvpn import workflows \ as bgpvpn_workflows class IndexView(tables.DataTableView): table_class = bgpvpn_tables.BgpvpnTable template_name = 'project/bgpvpn/index.html' page_title = _("BGP VPNs") @memoized.memoized_method def get_data(self): tenant_id = self.request.user.tenant_id bgpvpns_list = bgpvpn_api.bgpvpns_list( self.request, tenant_id=tenant_id) for bgpvpn in bgpvpns_list: bgpvpn.networks = [api.neutron.network_get( self.request, id, expand_subnet=False) for id in bgpvpn.networks] bgpvpn.routers = [api.neutron.router_get(self.request, id) for id in bgpvpn.routers] return bgpvpns_list class EditDataView(forms.ModalFormView): form_class = bgpvpn_forms.EditDataBgpVpn form_id = "edit_data_bgpvpn_form" modal_header = _("Edit BGPVPN") submit_label = _("Update Change") submit_url = 'horizon:project:bgpvpn:edit' success_url = reverse_lazy('horizon:project:bgpvpn:index') template_name = 'project/bgpvpn/modify.html' page_title = _("Edit BGPVPN") @staticmethod def _join_rts(route_targets_list): return ','.join(route_targets_list) def get_context_data(self, **kwargs): context = super(EditDataView, self).get_context_data(**kwargs) args = (self.kwargs['bgpvpn_id'],) context["bgpvpn_id"] = self.kwargs['bgpvpn_id'] context["submit_url"] = reverse(self.submit_url, args=args) return context def get_initial(self): bgpvpn_id = self.kwargs['bgpvpn_id'] try: # Get initial bgpvpn information bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id) except Exception: exceptions.handle( self.request, _('Unable to retrieve BGPVPN details.'), redirect=self.success_url) else: data = bgpvpn.to_dict() if self.request.user.is_superuser: for attribute in bgpvpn_common.RT_FORMAT_ATTRIBUTES: data[attribute] = self._join_rts(bgpvpn[attribute]) data['bgpvpn_id'] = data.pop('id') return data class GetBgpvpnMixin(object): def get_initial(self): bgpvpn_id = self.kwargs['bgpvpn_id'] try: # Get initial bgpvpn information bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id) data = bgpvpn.to_dict() data['bgpvpn_id'] = data.pop('id') return data except Exception: exceptions.handle( self.request, _('Unable to retrieve BGPVPN details.'), redirect=self.success_url) class CreateNetworkAssociationView(GetBgpvpnMixin, forms.ModalFormView): form_class = bgpvpn_forms.CreateNetworkAssociation form_id = "create_network_association_form" modal_header = _("Create Network Association") submit_label = _("Create") submit_url = 'horizon:project:bgpvpn:create-network-association' success_url = reverse_lazy('horizon:project:bgpvpn:index') template_name = 'project/bgpvpn/create_network_association.html' page_title = _("Create Network Association") def get_context_data(self, **kwargs): context = super( CreateNetworkAssociationView, self).get_context_data(**kwargs) args = (self.kwargs['bgpvpn_id'],) context["bgpvpn_id"] = self.kwargs['bgpvpn_id'] context["submit_url"] = reverse(self.submit_url, args=args) return context class CreateRouterAssociationView(GetBgpvpnMixin, workflows.WorkflowView): workflow_class = bgpvpn_workflows.RouterAssociation page_title = _("Create Router associations") failure_url = reverse_lazy("horizon:project:bgpvpn:index") class DetailProjectView(tabs.TabbedTableView): tab_group_class = bgpvpn_tabs.BgpvpnDetailsTabs template_name = 'horizon/common/_detail.html' page_title = "{{ bgpvpn.name }}" redirect_url = 'horizon:project:bgpvpn:index' def get_context_data(self, **kwargs): context = super(DetailProjectView, self).get_context_data(**kwargs) bgpvpn = self.get_data() table = bgpvpn_tables.BgpvpnTable(self.request) context["bgpvpn"] = bgpvpn context["url"] = reverse(self.redirect_url) context["actions"] = table.render_row_actions(bgpvpn) return context @memoized.memoized_method def get_data(self): try: bgpvpn_id = self.kwargs['bgpvpn_id'] return bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id) except Exception: exceptions.handle(self.request, _('Unable to retrieve BGPVPN details.'), redirect=reverse(self.redirect_url)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/dashboards/project/bgpvpn/workflows.py0000664000175000017500000001324700000000000030273 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 logging from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import workflows from openstack_dashboard import api from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api LOG = logging.getLogger(__name__) class AddRouterParametersInfoAction(workflows.Action): advertise_extra_routes = forms.BooleanField( label=_("Advertise Extra Routes"), initial=True, required=False, help_text="Boolean flag controlling whether or not the routes " "specified in the routes attribute of the router will be " "advertised to the BGPVPN (default: true).") class Meta(object): name = _("Optional Parameters") slug = "add_router_parameters" def __init__(self, request, context, *args, **kwargs): super(AddRouterParametersInfoAction, self).__init__( request, context, *args, **kwargs) if 'with_parameters' in context: self.fields['with_parameters'] = forms.BooleanField( initial=context['with_parameters'], required=False, widget=forms.HiddenInput() ) class CreateRouterAssociationInfoAction(workflows.Action): router_resource = forms.ChoiceField( label=_("Associate Router"), widget=forms.ThemableSelectWidget( data_attrs=('name', 'id'), transform=lambda x: "%s" % x.name_or_id)) class Meta(object): name = _("Create Association") help_text = _("Create a new router association.") slug = "create_router_association" def __init__(self, request, context, *args, **kwargs): super(CreateRouterAssociationInfoAction, self).__init__( request, context, *args, **kwargs) # when an admin user uses the project panel BGPVPN, there is no # tenant_id in context because bgpvpn_get doesn't return it if request.user.is_superuser and context.get("project_id"): tenant_id = context.get("project_id") else: tenant_id = self.request.user.tenant_id try: routers = api.neutron.router_list(request, tenant_id=tenant_id) if routers: choices = [('', _("Choose a router"))] + [(r.id, r) for r in routers] self.fields['router_resource'].choices = choices else: self.fields['router_resource'].choices = [('', _("No router"))] except Exception: exceptions.handle(request, _("Unable to retrieve routers")) if api.neutron.is_extension_supported(request, 'bgpvpn-routes-control'): self.fields['with_parameters'] = forms.BooleanField( label=_("Optional parameters"), initial=False, required=False, widget=forms.CheckboxInput(attrs={ 'class': 'switchable', 'data-hide-tab': 'router_association__' 'add_router_parameters', 'data-hide-on-checked': 'false' })) class AddRouterParametersInfo(workflows.Step): action_class = AddRouterParametersInfoAction depends_on = ("bgpvpn_id", "name") contributes = ("advertise_extra_routes",) class CreateRouterAssociationInfo(workflows.Step): action_class = CreateRouterAssociationInfoAction contributes = ("router_resource", "with_parameters") class RouterAssociation(workflows.Workflow): slug = "router_association" name = _("Associate a BGPVPN to a Router") finalize_button_name = _("Create") success_message = _('Router association with "%s" created.') failure_message = _('Unable to create a router association with "%s".') success_url = "horizon:project:bgpvpn:index" default_steps = (CreateRouterAssociationInfo, AddRouterParametersInfo) wizard = True def format_status_message(self, message): name = self.context['name'] or self.context['bgpvpn_id'] return message % name def handle(self, request, context): bgpvpn_id = context['bgpvpn_id'] router_id = context["router_resource"] msg_error = _("Unable to associate router %s") % router_id try: router_association = bgpvpn_api.router_association_create( request, bgpvpn_id, router_id=router_id) except Exception: exceptions.handle(request, msg_error) return False if not context["with_parameters"]: return True asso_id = router_association['router_association']['id'] try: bgpvpn_api.router_association_update( request, bgpvpn_id, asso_id, advertise_extra_routes=context['advertise_extra_routes']) return True except exceptions: bgpvpn_api.router_association_delete(request, asso_id, bgpvpn_id) exceptions.handle(request, msg_error) return False ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/enabled/0000775000175000017500000000000000000000000022173 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/enabled/_1495_project_bgpvpn_panel.py0000664000175000017500000000065400000000000027574 0ustar00zuulzuul00000000000000# The slug of the panel to be added to HORIZON_CONFIG. Required. PANEL = 'BGPVPN Interconnections' # The slug of the dashboard the PANEL associated with. Required. PANEL_DASHBOARD = 'project' # The slug of the panel group the PANEL is associated with. PANEL_GROUP = 'network' # Python panel class of the PANEL to be added. ADD_PANEL = ('bgpvpn_dashboard.' 'dashboards.project.bgpvpn.panel.BGPVPNInterconnections') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/enabled/_2360_admin_bgpvpn_panel.py0000664000175000017500000000065000000000000027202 0ustar00zuulzuul00000000000000# The slug of the panel to be added to HORIZON_CONFIG. Required. PANEL = 'BGPVPN Interconnections' # The slug of the dashboard the PANEL associated with. Required. PANEL_DASHBOARD = 'admin' # The slug of the panel group the PANEL is associated with. PANEL_GROUP = 'network' # Python panel class of the PANEL to be added. ADD_PANEL = ('bgpvpn_dashboard.' 'dashboards.admin.bgpvpn.panel.BGPVPNInterconnections') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/enabled/__init__.py0000664000175000017500000000000000000000000024272 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/etc/0000775000175000017500000000000000000000000021354 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/etc/bgpvpn-horizon.conf0000664000175000017500000000370400000000000025211 0ustar00zuulzuul00000000000000{ "context_is_admin": "role:admin", "admin_only": "rule:context_is_admin", "admin_or_owner": "rule:context_is_admin or tenant_id:%(tenant_id)s", "create_bgpvpn": "rule:admin_only", "get_bgpvpn": "rule:admin_or_owner", "get_bgpvpn:tenant_id": "rule:admin_only", "get_bgpvpn:route_targets": "rule:admin_only", "get_bgpvpn:import_targets": "rule:admin_only", "get_bgpvpn:export_targets": "rule:admin_only", "get_bgpvpn:route_distinguishers": "rule:admin_only", "update_bgpvpn": "rule:admin_or_owner", "update_bgpvpn:tenant_id": "rule:admin_only", "update_bgpvpn:route_targets": "rule:admin_only", "update_bgpvpn:import_targets": "rule:admin_only", "update_bgpvpn:export_targets": "rule:admin_only", "update_bgpvpn:route_distinguishers": "rule:admin_only", "delete_bgpvpn": "rule:admin_only", "create_bgpvpn_network_association": "rule:admin_or_owner", "get_bgpvpn_network_association": "rule:admin_or_owner", "get_bgpvpn_network_association:tenant_id": "rule:admin_only", "get_bgpvpn_network_associations": "rule:admin_or_owner", "update_bgpvpn_network_association": "rule:admin_or_owner", "delete_bgpvpn_network_association": "rule:admin_or_owner", "create_bgpvpn_router_association": "rule:admin_or_owner", "get_bgpvpn_router_association": "rule:admin_or_owner", "get_bgpvpn_router_association:tenant_id": "rule:admin_only", "get_bgpvpn_router_associations": "rule:admin_or_owner", "update_bgpvpn_router_association": "rule:admin_or_owner", "delete_bgpvpn_router_association": "rule:admin_or_owner", "create_bgpvpn_port_association": "rule:admin_or_owner", "get_bgpvpn_port_association": "rule:admin_or_owner", "get_bgpvpn_port_association:tenant_id": "rule:admin_only", "get_bgpvpn_port_associations": "rule:admin_or_owner", "update_bgpvpn_port_association": "rule:admin_or_owner", "delete_bgpvpn_port_association": "rule:admin_or_owner" } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4579785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/locale/0000775000175000017500000000000000000000000022040 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4579785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/locale/en_GB/0000775000175000017500000000000000000000000023012 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000024577 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/locale/en_GB/LC_MESSAGES/django.po0000664000175000017500000002033000000000000026377 0ustar00zuulzuul00000000000000# Andi Chandler , 2018. #zanata # Andi Chandler , 2019. #zanata # Andi Chandler , 2020. #zanata # Andi Chandler , 2022. #zanata msgid "" msgstr "" "Project-Id-Version: networking-bgpvpn VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2022-04-26 17:36+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2022-06-13 07:53+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "" "A single BGP Route Target or a comma-separated list of BGP Route Target. " "Example: 64512:1 or 64512:1,64512:2,64512:3" msgstr "" "A single BGP Route Target or a comma-separated list of BGP Route Target. " "Example: 64512:1 or 64512:1,64512:2,64512:3" msgid "Advertise Extra Routes" msgstr "Advertise Extra Routes" msgid "Associate Network" msgstr "Associate Network" msgid "Associate Router" msgstr "Associate Router" msgid "Associate a BGPVPN to a Network" msgstr "Associate a BGPVPN to a Network" msgid "Associate a BGPVPN to a Router" msgstr "Associate a BGPVPN to a Router" msgid "Associated Network" msgstr "Associated Network" msgid "Associated Networks" msgstr "Associated Networks" msgid "Associated Router" msgstr "Associated Router" msgid "Associated Routers" msgstr "Associated Routers" msgid "Association ID" msgstr "Association ID" msgid "BGP VPNs" msgstr "BGP VPNs" msgid "BGPVPN" msgstr "BGPVPN" #, python-format msgid "BGPVPN %s was successfully created." msgstr "BGPVPN %s was successfully created." #, python-format msgid "BGPVPN %s was successfully updated." msgstr "BGPVPN %s was successfully updated." msgid "BGPVPN ID" msgstr "BGPVPN ID" msgid "BGPVPN Interconnections" msgstr "BGPVPN Interconnections" msgid "BGPVPN Name" msgstr "BGPVPN Name" msgid "" "Bug, inconsistency between neutron-lib and networking-bgpvpn for RTRD regex" msgstr "" "Bug, inconsistency between neutron-lib and networking-bgpvpn for RTRD regex" msgid "Choose a network" msgstr "Choose a network" msgid "Choose a router" msgstr "Choose a router" msgid "Create" msgstr "Create" msgid "Create Association" msgstr "Create Association" msgid "Create BGPVPN" msgstr "Create BGPVPN" msgid "Create Network Association" msgstr "Create Network Association" msgid "Create Router Association" msgstr "Create Router Association" msgid "Create Router associations" msgstr "Create Router associations" msgid "Create a new BGP VPN for any project as you need." msgstr "Create a new BGP VPN for any project as you need." msgid "Create a new router association." msgstr "Create a new router association." msgid "Delete BGPVPN" msgid_plural "Delete BGPVPNs" msgstr[0] "Delete BGPVPN" msgstr[1] "Delete BGPVPNs" msgid "Delete Network Association" msgid_plural "Delete Network Associations" msgstr[0] "Delete Network Association" msgstr[1] "Delete Network Associations" msgid "Delete Router Association" msgid_plural "Delete Router Associations" msgstr[0] "Delete Router Association" msgstr[1] "Delete Router Associations" msgid "Deleted BGPVPN" msgid_plural "Deleted BGPVPNs" msgstr[0] "Deleted BGPVPN" msgstr[1] "Deleted BGPVPNs" msgid "Deleted Network Association" msgid_plural "Deleted Network Associations" msgstr[0] "Deleted Network Association" msgstr[1] "Deleted Network Associations" msgid "Deleted Router Association" msgid_plural "Deleted Router Associations" msgstr[0] "Deleted Router Association" msgstr[1] "Deleted Router Associations" msgid "Description:" msgstr "Description:" msgid "Edit BGPVPN" msgstr "Edit BGPVPN" msgid "Export Targets" msgstr "Export Targets" msgid "Export targets" msgstr "Export targets" msgid "Export targets is not valid" msgstr "Export targets is not valid" #, python-format msgid "Failed to create BGPVPN %s" msgstr "Failed to create BGPVPN %s" #, python-format msgid "Failed to delete BGPVPN %s" msgstr "Failed to delete BGPVPN %s" #, python-format msgid "Failed to delete Network Association %s" msgstr "Failed to delete Network Association %s" #, python-format msgid "Failed to delete Router Association %s" msgstr "Failed to delete Router Association %s" #, python-format msgid "Failed to update BGPVPN %s" msgstr "Failed to update BGPVPN %s" msgid "ID" msgstr "ID" msgid "Import Targets" msgstr "Import Targets" msgid "Import targets" msgstr "Import targets" msgid "Import targets is not valid" msgstr "Import targets is not valid" msgid "Name" msgstr "Name" msgid "Network" msgstr "Network" msgid "Network Associations" msgstr "Network Associations" msgid "Network ID" msgstr "Network ID" msgid "Network Name" msgstr "Network Name" msgid "Network associations list can not be retrieved." msgstr "Network associations list can not be retrieved." msgid "Networks" msgstr "Networks" msgid "No network" msgstr "No network" msgid "No router" msgstr "No router" msgid "None" msgstr "None" msgid "Optional Parameters" msgstr "Optional Parameters" msgid "Optional parameters" msgstr "Optional parameters" msgid "Overview" msgstr "Overview" msgid "Project" msgstr "Project" msgid "Project ID" msgstr "Project ID" msgid "Route Targets" msgstr "Route Targets" msgid "Route targets" msgstr "Route targets" msgid "Route targets is not valid" msgstr "Route targets is not valid" msgid "Router" msgstr "Router" msgid "Router Associations" msgstr "Router Associations" msgid "Router ID" msgstr "Router ID" msgid "Router Name" msgstr "Router Name" #, python-format msgid "Router association with \"%s\" created." msgstr "Router association with \"%s\" created." msgid "Router associations list can not be retrieved." msgstr "Router associations list can not be retrieved." msgid "Routers" msgstr "Routers" msgid "Select a project" msgstr "Select a project" #, python-format msgid "Something went wrong with BGPVPN %s" msgstr "Something went wrong with BGPVPN %s" msgid "The type of VPN and the technology behind it." msgstr "The type of VPN and the technology behind it." msgid "Type" msgstr "Type" #, python-format msgid "Unable to associate network \"%s\"." msgstr "Unable to associate network \"%s\"." #, python-format msgid "Unable to associate router %s" msgstr "Unable to associate router %s" #, python-format msgid "Unable to create a router association with \"%s\"." msgstr "Unable to create a router association with \"%s\"." msgid "Unable to retrieve BGPVPN details." msgstr "Unable to retrieve BGPVPN details." msgid "Unable to retrieve Router Association details." msgstr "Unable to retrieve Router Association details." #, python-format msgid "Unable to retrieve details for bgpvpn \"%s\"." msgstr "Unable to retrieve details for bgpvpn \"%s\"." #, python-format msgid "Unable to retrieve information about the tenant %s" msgstr "Unable to retrieve information about the tenant %s" msgid "Unable to retrieve network association details." msgstr "Unable to retrieve network association details." msgid "Unable to retrieve network details." msgstr "Unable to retrieve network details." msgid "Unable to retrieve networks." msgstr "Unable to retrieve networks." msgid "Unable to retrieve router association details." msgstr "Unable to retrieve router association details." msgid "Unable to retrieve router details." msgstr "Unable to retrieve router details." msgid "Unable to retrieve routers" msgstr "Unable to retrieve routers" #, python-format msgid "Unsupported action type: %s" msgstr "Unsupported action type: %s" msgid "Update" msgstr "Update" msgid "Update BGPVPN" msgstr "Update BGPVPN" msgid "Update BGPVPN Router Association" msgstr "Update BGPVPN Router Association" msgid "Update BGPVPN Router Associations" msgstr "Update BGPVPN Router Associations" msgid "Update Change" msgstr "Update Change" msgid "Update Router Association" msgstr "Update Router Association" msgid "You may update the editable properties of your BGP VPN here." msgstr "You may update the editable properties of your BGP VPN here." msgid "" "You may update the editable properties of your BGPVPN Router Association " "here." msgstr "" "You may update the editable properties of your BGPVPN Router Association " "here." msgid "l2" msgstr "l2" msgid "l3" msgstr "l3" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4579785 networking-bgpvpn-21.0.0/bgpvpn_dashboard/locale/fr/0000775000175000017500000000000000000000000022447 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/locale/fr/LC_MESSAGES/0000775000175000017500000000000000000000000024234 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/locale/fr/LC_MESSAGES/django.po0000664000175000017500000001040700000000000026040 0ustar00zuulzuul00000000000000# Cédric Savignan , 2017. #zanata # Thomas Morin , 2017. #zanata # Cédric Savignan , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: networking-bgpvpn VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2019-12-05 06:33+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-12 01:19+0000\n" "Last-Translator: Cédric Savignan \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "" "A single BGP Route Target or a comma-separated list of BGP Route Target. " "Example: 64512:1 or 64512:1,64512:2,64512:3" msgstr "" "Un simple Route Target BGP ou une liste de Route Targets BGP séparés par " "des virgules. Exemple: 64512:1 ou 64512:1,64512:2,64512:3 ." msgid "Associated Network" msgstr "Réseau Associé" msgid "Associated Networks" msgstr "Réseaux Associés" msgid "Associated Router" msgstr "Router Associé" msgid "Associated Routers" msgstr "Routers Associés" msgid "BGP VPNs" msgstr "BGP VPNs" msgid "BGPVPN" msgstr "BGPVPN" #, python-format msgid "BGPVPN %s was successfully created." msgstr "Le BGPVPN %s a été correctement créé." #, python-format msgid "BGPVPN %s was successfully updated." msgstr "Le BGPVPN %s a été correctement mis à jour." msgid "BGPVPN ID" msgstr "BGPVPN ID" msgid "BGPVPN Interconnections" msgstr "Interconnexions BGPVPN" msgid "BGPVPN Name" msgstr "Nom du BGPVPN" msgid "" "Bug, inconsistency between neutron-lib and networking-bgpvpn for RTRD regex" msgstr "" "Bug, incohérence entre neutron-lib et networking-bgpvpn pour la regex RTRD" msgid "Create BGPVPN" msgstr "Créer un BGPVPN" msgid "Create a new BGP VPN for any project as you need." msgstr "Créez un nouveau BGPVPN pour chaque projet selon vos besoins." msgid "Delete BGPVPN" msgid_plural "Delete BGPVPNs" msgstr[0] "Supprimer le BGPVPN" msgstr[1] "Supprimer les BGPVPNs" msgid "Deleted BGPVPN" msgid_plural "Deleted BGPVPNs" msgstr[0] "BGPVPN supprimé" msgstr[1] "BGPVPN supprimés" msgid "Description:" msgstr "Description:" msgid "Edit BGPVPN" msgstr "Modifier le BGPVPN" msgid "Export Targets" msgstr "Export Targets" msgid "Export targets" msgstr "Export Targets" msgid "Export targets is not valid" msgstr "Export Targets invalide" #, python-format msgid "Failed to create BGPVPN %s" msgstr "Echec dans la création du BGPVPN %s" #, python-format msgid "Failed to delete BGPVPN %s" msgstr "Echec lors de la suppression du BGPVPN %s" #, python-format msgid "Failed to update BGPVPN %s" msgstr "Echec lors de la mise à jour du BGPVPN %s" msgid "ID" msgstr "ID" msgid "Import Targets" msgstr "Import Targets" msgid "Import targets" msgstr "Import Targets" msgid "Import targets is not valid" msgstr "Import Targets invalide" msgid "Name" msgstr "Nom" msgid "Network Associations" msgstr "Associations de Réseaux" msgid "Networks" msgstr "Réseaux" msgid "Project" msgstr "Projet" msgid "Project ID" msgstr "Project ID" msgid "Route Targets" msgstr "Route Targets" msgid "Route targets" msgstr "Route Targets" msgid "Route targets is not valid" msgstr "Route Targets invalide" msgid "Router Associations" msgstr "Associations de Routeurs" msgid "Routers" msgstr "Routeurs" msgid "Select a project" msgstr "Sélectionner un projet" #, python-format msgid "Something went wrong with BGPVPN %s" msgstr "Il y a eu un problème avec le BGPVPN %s" msgid "The type of VPN and the technology behind it." msgstr "Le type de VPN." msgid "Type" msgstr "Type" msgid "Unable to retrieve BGPVPN details." msgstr "Impossible de récupérer les détails du BGPVPN." #, python-format msgid "Unable to retrieve information about the tenant %s" msgstr "Impossible de récupérer les informations à propos du tenant %s" #, python-format msgid "Unsupported action type: %s" msgstr "Type d'action non supporté: %s" msgid "Update BGPVPN" msgstr "Modifier BGPVPN" msgid "Update Change" msgstr "Sauvegarger les changements" msgid "You may update the editable properties of your BGP VPN here." msgstr "" "Vous pouvez mettre à jour ici les propriétés modifiables de votre BGP VPN." msgid "l2" msgstr "l2" msgid "l3" msgstr "l3" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4819784 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/0000775000175000017500000000000000000000000021560 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/__init__.py0000664000175000017500000000000000000000000023657 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/admin/0000775000175000017500000000000000000000000022650 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/admin/__init__.py0000664000175000017500000000000000000000000024747 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/admin/test_forms.py0000664000175000017500000001002600000000000025406 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 openstack_dashboard.test import helpers from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.admin.bgpvpn import forms as bgpvpn_a_form from bgpvpn_dashboard.dashboards.project.bgpvpn import forms as bgpvpn_p_form class Tenant(object): def __init__(self, id, name, enabled): self.id = id self.name = name self.enabled = enabled class TestCreateDataBgpVpn(helpers.APITestCase): @mock.patch.object(bgpvpn_a_form, 'api') def setUp(self, mock_api): super(TestCreateDataBgpVpn, self).setUp() self.mock_request = mock.MagicMock() @mock.patch.object(bgpvpn_p_form, 'bgpvpn_api') @mock.patch.object(bgpvpn_a_form, 'api') def test_invalid_rt(self, mock_api, mock_bgpvpn_api): mock_api.keystone.tenant_list.return_value = [], False mock_bgpvpn_api.list_bgpvpns.return_value = [] data = {"route_targets": "xyz", "import_targets": "0", "export_targets": "64512:1000000000000, xyz"} self.bgpvpn_form = bgpvpn_a_form.CreateBgpVpn(self.mock_request, data) self.assertTrue(self.bgpvpn_form.has_error("route_targets")) self.assertTrue(self.bgpvpn_form.has_error("import_targets")) self.assertTrue(self.bgpvpn_form.has_error("export_targets")) @mock.patch.object(bgpvpn_p_form, 'bgpvpn_api') @mock.patch.object(bgpvpn_a_form, 'api') def test_valid_rt(self, mock_api, mock_bgpvpn_api): mock_api.keystone.tenant_list.return_value = [], False mock_bgpvpn_api.list_bgpvpns.return_value = [] data = {"route_targets": "65421:1", "import_targets": "65421:1, 65421:2", "export_targets": "65421:3"} self.bgpvpn_form = bgpvpn_a_form.CreateBgpVpn(self.mock_request, data) self.assertFalse(self.bgpvpn_form.has_error("route_targets")) self.assertFalse(self.bgpvpn_form.has_error("import_targets")) self.assertFalse(self.bgpvpn_form.has_error("export_targets")) @mock.patch.object(bgpvpn_p_form, 'bgpvpn_api') @mock.patch.object(bgpvpn_a_form, 'api') def test_handle_update(self, mock_api, mock_bgpvpn_api): data = {"bgpvpn_id": "foo-id", "type": "l3", "name": "foo-name", "route_targets": "65421:1", "import_targets": "65421:2", "export_targets": "65421:3"} mock_api.keystone.tenant_list.return_value = [], False self.bgpvpn_form = bgpvpn_a_form.CreateBgpVpn(self.mock_request) self.bgpvpn_form.action = "update" expected_data = bgpvpn_api.Bgpvpn({"bgpvpn_id": "foo-id", "name": "foo-name", "tenant_id": "tenant_id", "route_targets": ["65421:1"], "import_targets": ["65421:2"], "export_targets": ["65421:3"]}) mock_bgpvpn_api.bgpvpn_update.return_value = expected_data result = self.bgpvpn_form.handle(self.mock_request, data) mock_bgpvpn_api.bgpvpn_update.assert_called_once_with( self.mock_request, "foo-id", name="foo-name", route_targets=["65421:1"], import_targets=["65421:2"], export_targets=["65421:3"]) self.assertEqual(result, expected_data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/admin/test_tables.py0000664000175000017500000000222600000000000025535 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django import test from unittest import mock from bgpvpn_dashboard.dashboards.admin.bgpvpn import tables as bgpvpn_tables class TestDeleteBgpvpns(test.TestCase): @mock.patch.object(bgpvpn_tables, 'bgpvpn_api') def test_delete(self, mock_bgpvpn_api): mock_request = mock.Mock() delete_bgpvpn = bgpvpn_tables.DeleteBgpvpn() delete_bgpvpn.delete(mock_request, "id") mock_bgpvpn_api.bgpvpn_delete.assert_called_once_with(mock_request, "id") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/admin/test_views.py0000664000175000017500000000621100000000000025416 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 collections import namedtuple from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.admin.bgpvpn import tables as bgpvpn_tables from bgpvpn_dashboard.dashboards.admin.bgpvpn import views as bgpvpn_views from openstack_dashboard.test import helpers VIEWS = "bgpvpn_dashboard.dashboards.admin.bgpvpn.views" class TestIndexView(helpers.APITestCase): def setUp(self): super(TestIndexView, self).setUp() mock_request = mock.Mock(horizon={'async_messages': []}) self.bgpvpn_view = bgpvpn_views.IndexView(request=mock_request) self.assertEqual(bgpvpn_tables.BgpvpnTable, self.bgpvpn_view.table_class) def _get_mock_bgpvpn(self, prefix): bgpvpn_info = {} if prefix: bgpvpn_info = { "name": "%s_name" % prefix, "route_targets": [], "import_targets": [], "export_targets": [], "networks": [], "routers": [], "tenant_id": "tenant_id", "type": "l3" } return bgpvpn_api.Bgpvpn(bgpvpn_info) @mock.patch.object(bgpvpn_views.api, 'keystone', autospec=True) def test_get_tenant_name(self, mock_api): Tenant = namedtuple("Tenant", ["id", "name"]) tenant = Tenant("tenant_id", "tenant_name") mock_api.tenant_get.return_value = tenant result = self.bgpvpn_view._get_tenant_name("tenant_id") mock_api.tenant_get.assert_called_once_with( self.bgpvpn_view.request, "tenant_id") self.assertEqual(result, "tenant_name") @mock.patch('%s.IndexView._get_tenant_name' % VIEWS, return_value={"tenant_id": "tenant_name"}) @mock.patch.object(bgpvpn_views, 'api', autospec=True) @mock.patch.object(bgpvpn_views, 'bgpvpn_api', autospec=True) def test_get_data(self, mock_bgpvpn_api, mock_api, mock_get_tenant_name): bgpvpn_foo = self._get_mock_bgpvpn("foo") bgpvpn_bar = self._get_mock_bgpvpn("bar") mock_neutron_client = mock_api.neutron.neutronclient(mock.Mock()) mock_bgpvpn_api.bgpvpns_list.return_value = [bgpvpn_foo, bgpvpn_bar] mock_neutron_client.list_networks.return_value = [] mock_neutron_client.list_routers.return_value = [] expected_bgpvpns = [bgpvpn_foo, bgpvpn_bar] result = self.bgpvpn_view.get_data() calls = [mock.call("tenant_id"), mock.call("tenant_id")] mock_get_tenant_name.assert_has_calls(calls) self.assertEqual(result, expected_bgpvpns) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/api_tests/0000775000175000017500000000000000000000000023553 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/api_tests/__init__.py0000664000175000017500000000000000000000000025652 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/api_tests/test_bgpvpn.py0000664000175000017500000002062400000000000026464 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 openstack_dashboard.test import helpers as test from bgpvpn_dashboard import api from bgpvpn_dashboard.test import helpers as bgpvpn_test from neutronclient.v2_0.client import Client as neutronclient class BgpvpnApiTests(bgpvpn_test.APITestCase): def setUp(self): bgpvpn_test.APITestCase.setUp(self) # Since the early days of networking-bgpvpn, the package was providing # a neutronclient.extensions entry point so that neutronclient would # dynamically add BGPVPN API methods to a Client instance. This is # only kept for backward compatibility, but not required anymore for # users of >=Ocata python-neutronclient. However, the dynamic addition # of these methods makes is a pain to mock. The patch below disables # the addition of the dynamic BGPVPN methods. mock.patch('neutronclient.v2_0.client.Client._register_extensions' ).start() @test.create_mocks({neutronclient: ('list_bgpvpns',)}) def test_bgpvpn_list(self): exp_bgpvpns = self.bgpvpns.list() api_bgpvpns = {'bgpvpns': self.api_bgpvpns.list()} self.mock_list_bgpvpns.return_value = api_bgpvpns ret_vals = api.bgpvpn.bgpvpns_list(self.request) for (ret_val, exp_bgpvpn) in zip(ret_vals, exp_bgpvpns): self.assertIsInstance(ret_val, api.bgpvpn.Bgpvpn) self.assertEqual(exp_bgpvpn.id, ret_val.id) self.assertEqual(exp_bgpvpn.name, ret_val.name) @test.create_mocks({neutronclient: ('create_bgpvpn',)}) def test_bgpvpn_create(self): bgpvpn = self.bgpvpns.first() data = {'name': bgpvpn.name, 'route_targets': bgpvpn.route_targets, 'tenant_id': bgpvpn.tenant_id} ret_dict = {'bgpvpn': data} self.mock_create_bgpvpn.return_value = ret_dict ret_val = api.bgpvpn.bgpvpn_create(self.request, **data) self.mock_create_bgpvpn.assert_called_once_with(body=ret_dict) self.assertIsInstance(ret_val, api.bgpvpn.Bgpvpn) self.assertEqual(bgpvpn.name, ret_val.name) @test.create_mocks({neutronclient: ('show_bgpvpn',)}) def test_bgpvpn_get(self): bgpvpn = self.bgpvpns.first() ret_dict = {'bgpvpn': self.api_bgpvpns.first()} self.mock_show_bgpvpn.return_value = ret_dict ret_val = api.bgpvpn.bgpvpn_get(self.request, bgpvpn.id) self.assertIsInstance(ret_val, api.bgpvpn.Bgpvpn) self.assertEqual(bgpvpn.name, ret_val.name) @test.create_mocks({neutronclient: ('update_bgpvpn',)}) def test_bgpvpn_update(self): bgpvpn = self.bgpvpns.first() bgpvpn_dict = self.api_bgpvpns.first() bgpvpn.name = 'new name' bgpvpn.route_targets = ['65001:2'] bgpvpn_dict['name'] = 'new name' bgpvpn_dict['route_targets'] = ['65001:2'] data = {'name': bgpvpn.name, 'route_targets': bgpvpn.route_targets} ret_dict = {'bgpvpn': bgpvpn_dict} self.mock_update_bgpvpn.return_value = ret_dict ret_val = api.bgpvpn.bgpvpn_update(self.request, bgpvpn.id, **data) self.assertIsInstance(ret_val, api.bgpvpn.Bgpvpn) self.assertEqual('new name', ret_val.name) self.assertEqual(['65001:2'], ret_val.route_targets) @test.create_mocks({neutronclient: ('delete_bgpvpn',)}) def test_bgpvpn_delete(self): bgpvpn = self.bgpvpns.first() api.bgpvpn.bgpvpn_delete(self.request, bgpvpn.id) self.mock_delete_bgpvpn.assert_called_once_with(bgpvpn.id) @test.create_mocks({ neutronclient: ('show_bgpvpn_network_assoc',)}) def test_network_association_get(self): bgpvpn = self.bgpvpns.first() na = self.network_associations.first() ret_dict = { 'network_association': self.api_network_associations.first()} self.mock_show_bgpvpn_network_assoc.return_value = ret_dict ret_val = api.bgpvpn.network_association_get( self.request, bgpvpn.id, na.id) self.assertIsInstance(ret_val, api.bgpvpn.NetworkAssociation) @test.create_mocks({ neutronclient: ('list_bgpvpn_network_assocs',)}) def test_network_association_list(self): exp_nas = self.network_associations.list() api_na = {'network_associations': self.api_network_associations.list()} self.mock_list_bgpvpn_network_assocs.return_value = api_na ret_vals = api.bgpvpn.network_association_list(self.request, 'dummy') for (ret_val, exp_na) in zip(ret_vals, exp_nas): self.assertIsInstance(ret_val, api.bgpvpn.NetworkAssociation) self.assertEqual(exp_na.id, ret_val.id) self.assertEqual(exp_na.network_id, ret_val.network_id) @test.create_mocks({ neutronclient: ('create_bgpvpn_network_assoc',)}) def test_network_association_create(self): bgpvpn = self.bgpvpns.first() network = self.networks.first() data = {'network_id': network.id} ret_dict = {'network_association': data} self.mock_create_bgpvpn_network_assoc.return_value = ret_dict ret_val = api.bgpvpn.network_association_create( self.request, bgpvpn.id, **data) self.assertIsInstance(ret_val, api.bgpvpn.NetworkAssociation) self.assertEqual(network.id, ret_val.network_id) @test.create_mocks({ neutronclient: ('delete_bgpvpn_network_assoc',)}) def test_network_association_delete(self): bgpvpn = self.bgpvpns.first() na = self.network_associations.first() api.bgpvpn.network_association_delete(self.request, na.id, bgpvpn.id) self.mock_delete_bgpvpn_network_assoc.assert_called_once_with( bgpvpn.id, na.id) @test.create_mocks({ neutronclient: ('show_bgpvpn_router_assoc',)}) def test_router_association_get(self): bgpvpn = self.bgpvpns.first() na = self.router_associations.first() ret_dict = { 'router_association': self.api_router_associations.first()} self.mock_show_bgpvpn_router_assoc.return_value = ret_dict ret_val = api.bgpvpn.router_association_get( self.request, bgpvpn.id, na.id) self.assertIsInstance(ret_val, api.bgpvpn.RouterAssociation) @test.create_mocks({ neutronclient: ('list_bgpvpn_router_assocs',)}) def test_router_association_list(self): exp_nas = self.router_associations.list() api_na = {'router_associations': self.api_router_associations.list()} self.mock_list_bgpvpn_router_assocs.return_value = api_na ret_vals = api.bgpvpn.router_association_list(self.request, 'dummy') for (ret_val, exp_na) in zip(ret_vals, exp_nas): self.assertIsInstance(ret_val, api.bgpvpn.RouterAssociation) self.assertEqual(exp_na.id, ret_val.id) self.assertEqual(exp_na.router_id, ret_val.router_id) @test.create_mocks({ neutronclient: ('create_bgpvpn_router_assoc',)}) def test_router_association_create(self): bgpvpn = self.bgpvpns.first() router = self.routers.first() data = {'router_id': router.id} ret_dict = {'router_association': data} self.mock_create_bgpvpn_router_assoc.return_value = ret_dict ret_val = api.bgpvpn.router_association_create( self.request, bgpvpn.id, **data) self.assertIsInstance(ret_val, api.bgpvpn.RouterAssociation) self.assertEqual(router.id, ret_val.router_id) @test.create_mocks({ neutronclient: ('delete_bgpvpn_router_assoc',)}) def test_router_association_delete(self): bgpvpn = self.bgpvpns.first() na = self.router_associations.first() api.bgpvpn.router_association_delete(self.request, na.id, bgpvpn.id) self.mock_delete_bgpvpn_router_assoc.assert_called_once_with( bgpvpn.id, na.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/helpers.py0000664000175000017500000000201600000000000023573 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # 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 openstack_dashboard.test import helpers from bgpvpn_dashboard.test.test_data import utils class TestCase(helpers.TestCase): def _setup_test_data(self): super(TestCase, self)._setup_test_data() utils.load_test_data(self) class APITestCase(helpers.APITestCase): def _setup_test_data(self): super(APITestCase, self)._setup_test_data() utils.load_test_data(self) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/project/0000775000175000017500000000000000000000000023226 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/project/__init__.py0000664000175000017500000000000000000000000025325 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/project/test_forms.py0000664000175000017500000000352000000000000025765 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 openstack_dashboard.test import helpers from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.project.bgpvpn import forms as bgpvpn_form class TestEditDataBgpVpn(helpers.APITestCase): def setUp(self): super(TestEditDataBgpVpn, self).setUp() self.mock_request = mock.MagicMock() self.bgpvpn_form = bgpvpn_form.EditDataBgpVpn(self.mock_request) self.bgpvpn_form.action = "update" @mock.patch.object(bgpvpn_form, 'bgpvpn_api') def test_handle(self, mock_bgpvpn_api): self.bgpvpn_form.request.user.is_superuser = False test_data = {"bgpvpn_id": "foo-id", "name": "foo-name", "type": "l3"} expected_data = bgpvpn_api.Bgpvpn({"id": "foo-id", "name": "foo-name", "type": "l3"}) mock_bgpvpn_api.bgpvpn_update.return_value = expected_data result = self.bgpvpn_form.handle(self.mock_request, test_data) self.assertEqual(expected_data, result) mock_bgpvpn_api.bgpvpn_update.assert_called_once_with( self.mock_request, "foo-id", name="foo-name") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/project/test_tables.py0000664000175000017500000000622400000000000026115 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # 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 django import test from django.utils.translation import gettext_lazy as _ from bgpvpn_dashboard.dashboards.project.bgpvpn import tables as bgpvpn_tables class TestFunctionGet(test.TestCase): @mock.patch.object(bgpvpn_tables, 'reverse') def test_get_network_url(self, mock_reverse): mock_reverse.return_value = 'foo_reverse_url' mock_network = mock.Mock(id='foo-id', name_or_id="foo") result = bgpvpn_tables.get_network_url(mock_network) mock_reverse.assert_called_once_with( 'horizon:project:networks:detail', args=['foo-id']) self.assertEqual('foo', result) @mock.patch.object(bgpvpn_tables, 'reverse') def test_get_router_url(self, mock_reverse): mock_reverse.return_value = 'foo_reverse_url' mock_network = mock.Mock(id='foo-id', name_or_id="foo") result = bgpvpn_tables.get_router_url(mock_network) mock_reverse.assert_called_once_with( 'horizon:project:routers:detail', args=['foo-id']) self.assertEqual('foo', result) class TestNetworksColumn(test.TestCase): def setUp(self): super(TestNetworksColumn, self).setUp() self.nets_column = bgpvpn_tables.NetworksColumn( "networks", verbose_name=_("Networks")) def test_get_raw_data(self): result_expected = "foo1, " \ "foo2" mock_net1 = mock.Mock(id="id1", name_or_id="foo1") mock_net2 = mock.Mock(id="id2", name_or_id="foo2") networks = [mock_net1, mock_net2] mock_bgpvpn = mock.Mock(networks=networks) result = self.nets_column.get_raw_data(mock_bgpvpn) self.assertEqual(result_expected, result) class TestRoutersColumn(test.TestCase): def setUp(self): super(TestRoutersColumn, self).setUp() self.routers_column = bgpvpn_tables.RoutersColumn( "routers", verbose_name=_("Routers")) def test_get_raw_data(self): result_expected = "foo1, " \ "foo2" mock_router1 = mock.Mock(id="id1", name_or_id="foo1") mock_router2 = mock.Mock(id="id2", name_or_id="foo2") routers = [mock_router1, mock_router2] mock_bgpvpn = mock.Mock(routers=routers) result = self.routers_column.get_raw_data(mock_bgpvpn) self.assertEqual(result_expected, result) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/project/test_views.py0000664000175000017500000001371200000000000026000 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Orange. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 django.urls import reverse from django.urls import reverse_lazy from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api from bgpvpn_dashboard.dashboards.project.bgpvpn import forms as bgpvpn_form from bgpvpn_dashboard.dashboards.project.bgpvpn import tables as bgpvpn_tables from bgpvpn_dashboard.dashboards.project.bgpvpn import views as bgpvpn_views from openstack_dashboard.test import helpers class TestIndexView(helpers.APITestCase): def setUp(self): super(TestIndexView, self).setUp() mock_request = mock.Mock(horizon={'async_messages': []}) self.bgpvpn_view = bgpvpn_views.IndexView(request=mock_request) self.bgpvpn_view._prev = False self.bgpvpn_view._more = False self.assertEqual(bgpvpn_tables.BgpvpnTable, self.bgpvpn_view.table_class) self.assertEqual('project/bgpvpn/index.html', self.bgpvpn_view.template_name) def _get_mock_bgpvpn(self, prefix): bgpvpn_info = {} if prefix: bgpvpn_info = { "name": "%s_name" % prefix, "route_targets": [], "import_targets": [], "export_targets": [], "networks": [], "routers": [], "tenant_id": "tenant_id", "type": "l3" } return bgpvpn_api.Bgpvpn(bgpvpn_info) @mock.patch.object(bgpvpn_views, 'bgpvpn_api', autospec=True) def test_get_data(self, mock_bgpvpn_api): """Test that get_data works.""" bgpvpn_foo = self._get_mock_bgpvpn("foo") bgpvpn_bar = self._get_mock_bgpvpn("bar") mock_bgpvpn_api.bgpvpns_list.return_value = [bgpvpn_bar, bgpvpn_foo] result = self.bgpvpn_view.get_data() expected_bgpvpns = [bgpvpn_bar, bgpvpn_foo] self.assertEqual(expected_bgpvpns, result) class TestEditDataView(helpers.APITestCase): def setUp(self): super(TestEditDataView, self).setUp() mock_request = mock.Mock(horizon={'async_messages': []}) self.bgpvpn_view = bgpvpn_views.EditDataView(request=mock_request) fake_response = {'status_code': 200} self.mock_request = mock.Mock(return_value=fake_response, META=[]) self.bgpvpn_view.request = self.mock_request self.bgpvpn_view.kwargs = {'bgpvpn_id': 'foo-id'} self.assertEqual(bgpvpn_form.EditDataBgpVpn, self.bgpvpn_view.form_class) self.assertEqual('horizon:project:bgpvpn:edit', self.bgpvpn_view.submit_url) self.assertEqual(reverse_lazy('horizon:project:bgpvpn:index'), self.bgpvpn_view.success_url) self.assertEqual('project/bgpvpn/modify.html', self.bgpvpn_view.template_name) @mock.patch.object(bgpvpn_views, 'bgpvpn_api', autospec=True) def test_get_initial_user(self, mock_bgpvpn_api): self.bgpvpn_view.request.user.is_superuser = False bgpvpn_data = {"name": "foo-name", "id": "foo-id", "type": "l3"} expected_data = {"name": "foo-name", "bgpvpn_id": "foo-id", "type": "l3"} mock_bgpvpn_api.bgpvpn_get.return_value = bgpvpn_api.Bgpvpn( bgpvpn_data) result = self.bgpvpn_view.get_initial() mock_bgpvpn_api.bgpvpn_get.assert_called_once_with( self.bgpvpn_view.request, "foo-id") for key, val in expected_data.items(): self.assertEqual(val, result[key]) @mock.patch.object(bgpvpn_views, 'bgpvpn_api', autospec=True) def test_get_initial_admin(self, mock_bgpvpn_api): self.bgpvpn_view.request.user.is_superuser = True bgpvpn_data = {"name": "foo-name", "id": "foo-id", "type": "l3", "route_targets": ["65432:1", "65432:2"], "import_targets": [], "export_targets": []} expected_data = {"name": "foo-name", "bgpvpn_id": "foo-id", "type": "l3", "route_targets": "65432:1,65432:2", "import_targets": "", "export_targets": ""} mock_bgpvpn_api.bgpvpn_get.return_value = bgpvpn_api.Bgpvpn( bgpvpn_data) result = self.bgpvpn_view.get_initial() mock_bgpvpn_api.bgpvpn_get.assert_called_once_with( self.bgpvpn_view.request, "foo-id") for key, val in expected_data.items(): self.assertEqual(val, result[key]) def test_get_context_data(self): mock_form = mock.Mock() args = ("foo-id",) expected_context = { 'bgpvpn_id': 'foo-id', 'submit_url': reverse("horizon:project:bgpvpn:edit", args=args)} context = self.bgpvpn_view.get_context_data(form=mock_form) self.assertIn('view', context) self.assertIsInstance(context['view'], bgpvpn_views.EditDataView) for key, val in expected_context.items(): self.assertIn(key, context) self.assertEqual(val, context[key]) def test_join_rts(self): route_targets_list = ["65400:1", "65401:1"] expected_result = "65400:1,65401:1" result = self.bgpvpn_view._join_rts(route_targets_list) self.assertEqual(expected_result, result) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/settings.py0000664000175000017500000000255100000000000023775 0ustar00zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Default to Horizons test settings to avoid any missing keys from horizon.test.settings import * # noqa from openstack_dashboard.test.settings import * # noqa import bgpvpn_dashboard.enabled import openstack_dashboard.enabled from openstack_dashboard.utils import settings # pop these keys to avoid log warnings about deprecation # update_dashboards will populate them anyway HORIZON_CONFIG.pop('dashboards', None) # noqa: F405 HORIZON_CONFIG.pop('default_dashboard', None) # noqa: F405 settings.update_dashboards( [ bgpvpn_dashboard.enabled, openstack_dashboard.enabled, ], HORIZON_CONFIG, # noqa: F405 INSTALLED_APPS # noqa: F405 ) # Ensure any duplicate apps are removed after the update_dashboards call INSTALLED_APPS = list(set(INSTALLED_APPS)) # noqa: F405 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/test_data/0000775000175000017500000000000000000000000023530 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/test_data/__init__.py0000664000175000017500000000000000000000000025627 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/test_data/bgpvpn_data.py0000664000175000017500000000400300000000000026364 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from bgpvpn_dashboard.api import bgpvpn from openstack_dashboard.test.test_data import utils def data(TEST): TEST.bgpvpns = utils.TestDataContainer() TEST.api_bgpvpns = utils.TestDataContainer() TEST.network_associations = utils.TestDataContainer() TEST.api_network_associations = utils.TestDataContainer() TEST.router_associations = utils.TestDataContainer() TEST.api_router_associations = utils.TestDataContainer() bgpvpn_dict = {'id': 'b595e758-1877-4aec-92a2-6834d76f1025', 'tenant_id': '1', 'name': 'bgpvpn1', 'route_targets': '64500:1' } TEST.api_bgpvpns.add(bgpvpn_dict) b = bgpvpn.Bgpvpn(copy.deepcopy(bgpvpn_dict)) TEST.bgpvpns.add(b) network_association_dict = { 'id': '99ef096d-21fb-43a7-9e2a-b3c464abef3a', 'network_id': '063cf7f3-ded1-4297-bc4c-31eae876cc91', 'tenant_id': '1'} TEST.api_network_associations.add(network_association_dict) na = bgpvpn.NetworkAssociation(copy.deepcopy(network_association_dict)) TEST.network_associations.add(na) router_association_dict = { 'id': '9736c228-745d-4e78-83a5-d971d9fd8f2c', 'router_id': '279989f7-54bb-41d9-ba42-0d61f12fda61', 'tenant_id': '1'} TEST.api_router_associations.add(router_association_dict) ra = bgpvpn.RouterAssociation(copy.deepcopy(router_association_dict)) TEST.router_associations.add(ra) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/test_data/utils.py0000664000175000017500000000223300000000000025242 0ustar00zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from openstack_dashboard.test.test_data import utils def load_test_data(load_onto=None): from openstack_dashboard.test.test_data import exceptions from openstack_dashboard.test.test_data import neutron_data from bgpvpn_dashboard.test.test_data import bgpvpn_data # The order of these loaders matters, some depend on others. loaders = ( exceptions.data, neutron_data.data, bgpvpn_data.data, ) if load_onto: for data_func in loaders: data_func(load_onto) return load_onto else: return utils.TestData(*loaders) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bgpvpn_dashboard/test/urls.py0000664000175000017500000000125400000000000023121 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from django.conf import urls import openstack_dashboard.urls urlpatterns = [ urls.url(r'', urls.include(openstack_dashboard.urls)) ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/bindep.txt0000664000175000017500000000074100000000000017302 0ustar00zuulzuul00000000000000# This is a cross-platform list tracking distribution packages needed for install and tests; # see https://docs.openstack.org/infra/bindep/ for additional information. libpq-dev [platform:dpkg] mysql-client [platform:dpkg !platform:debian] mysql-server [platform:dpkg !platform:debian] mariadb-server [platform:rpm platform:redhat platform:debian] postgresql [test] postgresql-client [platform:dpkg test] postgresql-devel [platform:rpm test] postgresql-server [platform:rpm test] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/devstack/0000775000175000017500000000000000000000000017102 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/devstack/devstack-gate-bagpipe-rc0000664000175000017500000000337600000000000023567 0ustar00zuulzuul00000000000000# This file is hooked from https://github.com/openstack-infra/project-config/blob/master/jenkins/jobs/networking-bgpvpn.yaml export OVERRIDE_ENABLED_SERVICES=n-api,n-crt,n-cpu,n-cond,n-api-meta,n-sch,placement-api,g-api,neutron-api,neutron-agent,neutron-dhcp,neutron-l3,neutron-metadata-agent,neutron-bagpipe-bgp,key,mysql,rabbit if [[ $DEVSTACK_GATE_TEMPEST -eq 1 ]] ; then export DEVSTACK_GATE_TEMPEST_REGEX="^neutron_tempest_plugin\.bgpvpn" export OVERRIDE_ENABLED_SERVICES=${OVERRIDE_ENABLED_SERVICES},tempest fi export DEVSTACK_LOCAL_CONFIG+=$'\n'"NETWORKING_BGPVPN_DRIVER=BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver:default" export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin networking-bagpipe https://opendev.org/openstack/networking-bagpipe" export DEVSTACK_LOCAL_CONFIG+=$'\n'"BAGPIPE_DATAPLANE_DRIVER_IPVPN=ovs" # until we do multinode, there is no BGP peer to connect to export DEVSTACK_LOCAL_CONFIG+=$'\n'"BAGPIPE_BGP_PEERS=-" # https://bugs.launchpad.net/devstack/+bug/1567052 # so we need VERBOSE=False until bagpipe-bgp uses rootwrap and is not run with sudo (same for bagpipe-fakerr) export DEVSTACK_LOCAL_CONFIG+=$'\n'"VERBOSE=False" # at least some DB setup things (e.g. for functional tests) require # helpers from neutron devstack plugin export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin neutron https://opendev.org/openstack/neutron" # Using the openvswitch firewall driver for security groups is currently # required to allow BGPVPN/router coexistence in single node or DVR setup export DEVSTACK_LOCAL_CONFIG+=$'\n'"[[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]]" export DEVSTACK_LOCAL_CONFIG+=$'\n'"[securitygroup]" export DEVSTACK_LOCAL_CONFIG+=$'\n'"firewall_driver = openvswitch" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/devstack/devstack-gate-rc0000664000175000017500000000034300000000000022151 0ustar00zuulzuul00000000000000# This file is hooked from https://github.com/openstack-infra/project-config/blob/master/jenkins/jobs/networking-bgpvpn.yaml export OVERRIDE_ENABLED_SERVICES=neutron-api,neutron-agent,neutron-dhcp,neutron-l3,key,mysql,rabbit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/devstack/plugin.sh0000775000175000017500000000770300000000000020746 0ustar00zuulzuul00000000000000#!/bin/bash # Save trace setting _XTRACE_NETWORKING_BGPVPN=$(set +o | grep xtrace) set -o xtrace if [[ "$1" == "source" ]]; then # no-op : elif [[ "$1" == "stack" && "$2" == "install" ]]; then echo_summary "Installing networking-bgpvpn" setup_develop $NETWORKING_BGPVPN_DIR elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then if is_service_enabled neutron-api || is_service_enabled q-svc; then echo_summary "Configuring networking-bgpvpn" neutron_service_plugin_class_add bgpvpn mkdir -v -p $(dirname $NETWORKING_BGPVPN_CONF) && cp -v $NETWORKING_BGPVPN_DIR/etc/neutron/networking_bgpvpn.conf $NETWORKING_BGPVPN_CONF inicomment $NETWORKING_BGPVPN_CONF service_providers service_provider iniadd $NETWORKING_BGPVPN_CONF service_providers service_provider $NETWORKING_BGPVPN_DRIVER neutron_server_config_add $NETWORKING_BGPVPN_CONF fi if (is_service_enabled neutron-agent || is_service_enabled q-agt) && (is_service_enabled b-bgp || is_service_enabled neutron-bagpipe-bgp); then echo_summary "Configuring agent for bagpipe bgpvpn" source $NEUTRON_DIR/devstack/lib/l2_agent plugin_agent_add_l2_agent_extension bagpipe_bgpvpn configure_l2_agent if is_neutron_legacy_enabled; then if [[ "$Q_AGENT" == "openvswitch" ]]; then # l2pop and arp_responder are required for bagpipe driver iniset /$Q_PLUGIN_CONF_FILE agent l2_population True iniset /$Q_PLUGIN_CONF_FILE agent arp_responder True elif [[ "$Q_AGENT" == "linuxbridge" ]]; then # l2pop is required for EVPN/VXLAN bagpipe driver iniset /$Q_PLUGIN_CONF_FILE vxlan l2_population True else die $LINENO "unsupported agent for networking-bagpipe: $Q_AGENT" fi else if [[ "$NEUTRON_AGENT" == "openvswitch" ]]; then # l2pop and arp_responder are required for bagpipe driver iniset $NEUTRON_CORE_PLUGIN_CONF agent l2_population True iniset $NEUTRON_CORE_PLUGIN_CONF agent arp_responder True elif [[ "$NEUTRON_AGENT" == "linuxbridge" ]]; then # l2pop is required for EVPN/VXLAN bagpipe driver iniset $NEUTRON_CORE_PLUGIN_CONF vxlan l2_population True else die $LINENO "unsupported agent for networking-bagpipe: $NEUTRON_AGENT" fi fi fi if is_service_enabled h-eng; then echo_summary "Enabling bgpvpn in $HEAT_CONF" iniset $HEAT_CONF DEFAULT plugin_dirs $NETWORKING_BGPVPN_DIR/networking_bgpvpn_heat fi if is_service_enabled horizon; then cp $BGPVPN_DASHBOARD_ENABLE $HORIZON_DIR/openstack_dashboard/local/enabled/ # Add policy file for BGPVPN_DASHBOARD _set_policy_file $DEST/horizon/openstack_dashboard/local/local_settings.py \ networking-bgpvpn $NETWORKING_BGPVPN_DIR/bgpvpn_dashboard/etc/bgpvpn-horizon.conf fi fi function _ensure_policy_file { local file=$1 # Look for POLICY_FILES dict. start=$(grep -nE '^\s*POLICY_FILES\s*=\s*' $file | cut -d : -f 1) if [ ! -n "$start" ]; then # If POLICY_FILES is not found, define it. cat <> $file POLICY_FILES = { 'identity': 'keystone_policy.json', 'compute': 'nova_policy.json', 'volume': 'cinder_policy.json', 'image': 'glance_policy.json', 'orchestration': 'heat_policy.json', 'network': 'neutron_policy.json', } EOF fi } function _set_policy_file { local file=$1 local policy_name=$2 local policy_file=$3 _ensure_policy_file $file echo "POLICY_FILES['$policy_name'] = '$policy_file'" >> $file } if [[ "$1" == "unstack" ]]; then #no-op : fi if [[ "$1" == "clean" ]]; then # Remove bgpvpn-dashboard enabled files and pyc rm -f $HORIZON_DIR/openstack_dashboard/local/enabled/*_bgpvpn_panel* fi # Restore XTRACE ${_XTRACE_NETWORKING_BGPVPN} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/devstack/settings0000664000175000017500000000053000000000000020663 0ustar00zuulzuul00000000000000NETWORKING_BGPVPN_DIR="$DEST/networking-bgpvpn" NETWORKING_BGPVPN_CONF="$NEUTRON_CONF_DIR/networking_bgpvpn.conf" BGPVPN_DASHBOARD_ENABLE="$NETWORKING_BGPVPN_DIR/bgpvpn_dashboard/enabled/*" NETWORKING_BGPVPN_DRIVER=${NETWORKING_BGPVPN_DRIVER:-BGPVPN:Dummy:networking_bgpvpn.neutron.services.service_drivers.driver_api.BGPVPNDriverRC:default} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/doc/0000775000175000017500000000000000000000000016043 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/requirements.txt0000664000175000017500000000055700000000000021336 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.policy>=3.12.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/doc/source/0000775000175000017500000000000000000000000017343 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4859786 networking-bgpvpn-21.0.0/doc/source/_static/0000775000175000017500000000000000000000000020771 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/_static/.placeholder0000664000175000017500000000000000000000000023242 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/conf.py0000664000175000017500000000717500000000000020654 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os 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', #'sphinx.ext.intersphinx', 'openstackdocstheme', 'oslo_config.sphinxext', 'oslo_config.sphinxconfiggen', 'oslo_policy.sphinxext', 'oslo_policy.sphinxpolicygen', ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/networking-bgpvpn' openstackdocs_pdf_link = True openstackdocs_bug_project = 'bgpvpn' openstackdocs_bug_tag = '' # 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. copyright = '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' # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} # -- 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'] html_theme = 'openstackdocs' # -- Options for LaTeX output ------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-networking-bgpvpn.tex', 'Networking BGPVPN Documentation', 'OpenStack Foundation', 'manual'), ] latex_elements = { 'extraclassoptions': 'openany,oneside', } # -- Options for oslo_config.sphinxconfiggen --------------------------------- _config_generator_config_files = [ 'networking-bgpvpn.conf', ] def _get_config_generator_config_definition(conf_file): config_file_path = '../../etc/oslo-config-generator/%s' % conf_file # oslo_config.sphinxconfiggen appends '.conf.sample' to the filename, # strip file extentension (.conf or .ini). output_file_path = '_static/config-samples/%s' % conf_file.rsplit('.', 1)[0] return (config_file_path, output_file_path) config_generator_config_file = [ _get_config_generator_config_definition(conf_file) for conf_file in _config_generator_config_files ] # -- Options for oslo_policy.sphinxpolicygen --------------------------------- policy_generator_config_file = '../../etc/oslo-policy-generator/policy.conf' sample_policy_basename = '_static/networking-bgpvpn' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/configuration/0000775000175000017500000000000000000000000022212 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/configuration/index.rst0000664000175000017500000000134600000000000024057 0ustar00zuulzuul00000000000000=================== Configuration Guide =================== Configuration ------------- This section provides a list of all possible options for each configuration file. networking-bgpvpn uses the following configuration file. .. toctree:: :maxdepth: 1 networking-bgpvpn The following is a sample configuration file for networking-bgpvpn. It is generated from code and reflect the current state of code in the networking-bgpvpn repository. .. toctree:: :maxdepth: 1 samples/networking-bgpvpn Policy ------ networking-bgpvpn, like most OpenStack projects, uses a policy language to restrict permissions on REST API actions. .. toctree:: :maxdepth: 1 Policy Reference Sample Policy File ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/configuration/networking-bgpvpn.rst0000664000175000017500000000132100000000000026422 0ustar00zuulzuul00000000000000====================== networking-bgpvpn.conf ====================== To use networking-bgpvpn, you need to configure one of valid service providers for ``BGPVPN`` service in ``service_provider`` of ``[service_providers]`` group of the neutron server. Note that you can specify multiple providers for BGPVPN but only one of them can be default. * Dummy provider: ``service_provider = BGPVPN:Dummy:networking_bgpvpn.neutron.services.service_drivers.driver_api.BGPVPNDriver:default`` * BaGPipe provider: ``service_provider = BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe.BaGPipeBGPVPNDriver:default`` .. show-options:: :config-file: etc/oslo-config-generator/networking-bgpvpn.conf ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/configuration/policy-sample.rst0000664000175000017500000000111400000000000025517 0ustar00zuulzuul00000000000000==================================== Sample networking-bgpvpn Policy File ==================================== The following is a sample networking-bgpvpn policy file for adaptation and use. The sample policy can also be viewed in :download:`file form `. .. important:: The sample policy file is auto-generated from networking-bgpvpn when this documentation is built. You must ensure your version of networking-bgpvpn matches the version of this documentation. .. literalinclude:: /_static/networking-bgpvpn.policy.yaml.sample ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/configuration/policy.rst0000664000175000017500000000047200000000000024246 0ustar00zuulzuul00000000000000========================== networking-bgpvpn policies ========================== The following is an overview of all available policies in networking-bgpvpn. For a sample configuration file, refer to :doc:`/configuration/policy-sample`. .. show-policy:: :config-file: etc/oslo-policy-generator/policy.conf ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/configuration/samples/0000775000175000017500000000000000000000000023656 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/configuration/samples/networking-bgpvpn.rst0000664000175000017500000000045600000000000030076 0ustar00zuulzuul00000000000000============================= Sample networking-bgpvpn.conf ============================= This sample configuration can also be viewed in `the raw format <../../_static/config-samples/networking-bgpvpn.conf.sample>`_. .. literalinclude:: ../../_static/config-samples/networking-bgpvpn.conf.sample ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/contributor/0000775000175000017500000000000000000000000021715 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/contributor/contributing.rst0000664000175000017500000000011600000000000025154 0ustar00zuulzuul00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/contributor/future/0000775000175000017500000000000000000000000023227 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/contributor/future/attributes.rst0000664000175000017500000000411000000000000026143 0ustar00zuulzuul00000000000000================= Future attributes ================= Specifications for the following attributes have been defined but not implemented yet: .. csv-table:: Future attributes :header: Attribute Name,Type,Access,Default Value,Validation/Constraint,Description technique, string, RW admin only, None, for instance "ipvpn" or "evpn", (optional) selection of the technique used to implement the VPN auto_aggregate,bool,RW admin only,False,{ True | False },enable prefix aggregation or not (type l3 only) but no support in any driver admin_state_up,bool,RW admin only,True,{ True | False },interconnection with this BGPVPN is enabled by the admin 'auto_aggregate' attribute ~~~~~~~~~~~~~~~~~~~~~~~~~~ The 'auto_aggregate' flag controls whether or not routes should be automatically aggregated before being advertised outside Neutron. A backend may or may not support this behavior, and its driver should report an API error in the latter case. 'technique' attribute ~~~~~~~~~~~~~~~~~~~~~ The 'technique' attribute is optional and can be used by the admin to select one of multiple techniques when more than one is supported by the driver. When no technique is specified, the driver will use a default value. An API call will be available to let the API user know about the types supported by the driver for a said vpn type. Currently defined techniques are: * for l3: * 'ipvpn': this corresponds to RFC4364 * 'evpn-prefix': this corresponds to draft-ietf-bess-evpn-prefix-advertisement * for l2: * 'evpn': this corresponds to RFC7432 API call to list the available techniques, with example answers: * GET /bgpvpn/techniques: .. code-block:: json { "techniques": { "l3": [ "ipvpn" ], "l2": [ "evpn" ] } } * GET /bgpvpn/techniques/l3: .. code-block:: json { "l3": [ "ipvpn"] } * GET /bgpvpn/techniques/l2: .. code-block:: json { "l2": [ "evpn"] } 'admin_state_up' attribute ~~~~~~~~~~~~~~~~~~~~~~~~~~ This is an admin-only attribute allowing the admin to shutdown connectivity to and from a BGP VPN and expose this state to the tenant. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/contributor/future/index.rst0000664000175000017500000000012300000000000025064 0ustar00zuulzuul00000000000000To be implemented ================= .. toctree:: :maxdepth: 2 attributes ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/contributor/index.rst0000664000175000017500000000017600000000000023562 0ustar00zuulzuul00000000000000================= Contributor Guide ================= .. toctree:: :maxdepth: 2 contributing specs future/index ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/contributor/specs.rst0000664000175000017500000000077300000000000023573 0ustar00zuulzuul00000000000000.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode Specification notes =================== Database design --------------- The implementation will rely on three tables: * one for BGPVPN objects * one to define the n-n relation ship between BGPVPNs and Networks * one to define the n-n relation ship between BGPVPNs and Routers The information stored in these tables will reflect what is exposed on the API. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/index.rst0000664000175000017500000000103300000000000021201 0ustar00zuulzuul00000000000000.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode ============================================= Neutron BGP VPN Interconnection Documentation ============================================= .. include:: introduction.rst .. only:: html Contents: .. toctree:: :maxdepth: 2 user/index install/index configuration/index contributor/index .. only:: html .. rubric:: Indices and Tables * :ref:`genindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/install/0000775000175000017500000000000000000000000021011 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/install/index.rst0000664000175000017500000000634400000000000022661 0ustar00zuulzuul00000000000000========================= Install and Configuration ========================= Installation ============ The details related to how a package should be installed may depend on your environment. If possible, you should rely on packages provided by your Linux and/or Openstack distribution. If you use ``pip``, follow these steps to install networking-bgpvpn: * identify the version of the networking-bgpvpn package that matches your Openstack version: * Liberty: most recent of 3.0.x * Mitaka: most recent of 4.0.x * Newton: most recent of 5.0.x * Ocata: most recent of 6.0.x * Pike: most recent of 7.0.x * (see ``_) * indicate pip to (a) install precisely this version and (b) take into account Openstack upper constraints on package versions for dependencies (example for ocata): .. code-block:: console $ pip install -c https://releases.openstack.org/constraints/upper/ocata Configuration ============= The service plugin is enabled in Neutron, by adding ``bgpvpn`` to the list of enabled service plugins in ``neutron.conf`` (typically in ``/etc/neutron/`` but the location used may depend on your setup or packaging). For instance: .. code-block:: ini service_plugins = router,bgpvpn The BGPVPN driver to use is then specified in the ``networking_bgpvpn.conf`` file (located by default under ``/etc/neutron/``, but in any case in one of the directories specified with ``--config-dir`` at neutron startup, which may differ from ``/etc/neutron`` in your setup): .. code-block:: ini [service_providers] service_provider = BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver:default #service_provider= BGPVPN:Dummy:networking_bgpvpn.neutron.services.service_drivers.driver_api.BGPVPNDriver:default A given driver may require additional packages to work; the driver section provides detailed installation information for each specific driver. Policy ====== API Policy for the BGPVPN service plugin can be controlled via the standard policy framework. When pip is used to install the package, a default policy file is installed at ``/etc/neutron/policy.d/bgpvpn.conf``. Database setup ============== The DB tables for networking-bgpvpn are created and upgraded with: .. code-block:: console neutron-db-manage --config-file /etc/neutron/neutron.conf --subproject networking-bgpvpn upgrade Devstack ======== You can easily test the bgpvpn service plugin with devstack, by adding the following line to your local.conf: .. code-block:: none enable_plugin networking-bgpvpn https://git.openstack.org/openstack/networking-bgpvpn.git Or the following if you want a specific branch or version (example for Mitaka): .. code-block:: none enable_plugin networking-bgpvpn https://git.openstack.org/openstack/networking-bgpvpn.git stable/mitaka By default, the service driver will use a dummy driver, that only responds to API calls, and stores data in the database. If you want to test a fully functional driver with devstack, you can configure the bagpipe driver with its devstack plugin (see :doc:`/user/drivers/bagpipe/index`). Detailed information on how to use other drivers is provided in the documentation for each of these drivers. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/introduction.rst0000664000175000017500000000226000000000000022616 0ustar00zuulzuul00000000000000BGP-based IP VPNs networks are widely used in the industry especially for enterprises. This project aims at supporting inter-connection between L3VPNs and Neutron resources, i.e. Networks, Routers and Ports. A typical use-case is the following: a tenant already having a BGP IP VPN (a set of external sites) setup outside the datacenter, and they want to be able to trigger the establishment of connectivity between VMs and these VPN sites. Another similar need is when E-VPN is used to provide an Ethernet interconnect between multiple sites, and inherits the base protocol architecture from BGP/MPLS IP VPNs. This service plugin exposes an API to interconnect OpenStack Neutron ports, typically VMs, via the Networks and Routers they are connected to, with a IP VPN as defined by [RFC4364]_ (BGP/MPLS IP Virtual Private Networks) or with an E-VPN [RFC7432]_. .. rubric:: Introduction videos: The following videos are filmed presentations of talks given during the Barcelona OpenStack Summit (Oct' 2016). Although they do not cover the work done since, they can be a good introduction to the project: * https://www.youtube.com/watch?v=kGW5R8mtmRg * https://www.youtube.com/watch?v=LCDeR7MwTzE ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/samples/0000775000175000017500000000000000000000000021007 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/samples/__init__.py0000664000175000017500000000000000000000000023106 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/samples/bgpvpn-sample01.py0000664000175000017500000001252700000000000024304 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import sys from keystoneauth1.identity import v3 from keystoneauth1 import session from neutronclient.v2_0 import client # Parameter for subnet neutron object SUBNET_IP = "192.168.24.0/24" # Parameters for bgpvpn neutron object BGPVPN_RT = "64512:2" # Function to obtain stack parameters from system vars def get_keystone_creds(): d = {} try: d['username'] = os.environ['OS_USERNAME'] d['password'] = os.environ['OS_PASSWORD'] d['auth_url'] = os.environ['OS_AUTH_URL'] d['project_name'] = os.environ['OS_PROJECT_NAME'] d['project_domain_id'] = os.environ['OS_PROJECT_DOMAIN_ID'] d['user_domain_id'] = os.environ['OS_USER_DOMAIN_ID'] except KeyError: print("ERROR: Stack environment variables type " "OS_* are not properly set") sys.exit(1) return d # Main function def main(): # Call function that imports (dev)stack vars creds = get_keystone_creds() # Authentication auth = v3.Password(**creds) sess = session.Session(auth=auth) # Neutron object # It dynamically loads the BGPVPN API neutron = client.Client(session=sess) try: # Network object creation. This dummy network will be used to bind the # attached subnet to the BGPVPN object. # Creation of the Network net_obj = neutron.create_network({'network': {'name': "dummyNet"}}) # Verify creation print('Network created\t[network-id:%s]...' % net_obj['network']['id']) # Creation of the subnet, is attached to the created network subnet_obj = neutron.create_subnet( {'subnet': {'name': "dummySubnet", 'cidr': SUBNET_IP, 'network_id': net_obj['network']['id'], 'ip_version': 4}}) # Verify print("Subnet created\t[subnet-id:%s]..." % subnet_obj['subnet']['id']) # Creation of a BGPVPN object. This object is created with the # required parameter 'routes_targets'. # This object can be created with others parameters or be updated with # them by calling the update function on the object. print("\nBGPVPN object handling.") # Creation of the BGPVPN object bgpvpn_obj = neutron.create_bgpvpn( {'bgpvpn': {'route_targets': [BGPVPN_RT]}}) print("BGPVPN object created\t[bgpvpn-id:%s]..." % bgpvpn_obj['bgpvpn']['id']) # Update the BGPVPN object bgpvpn_obj = neutron.update_bgpvpn( bgpvpn_obj['bgpvpn']['id'], {'bgpvpn': {'name': "dummyBGPVPN"}}) # List all BGPVPN objects list_bgpvpn_obj = neutron.list_bgpvpns() print("List of all BGPVPN object\t[%s]" % list_bgpvpn_obj) # List of all BGPVPN objects filtered on the type parameter set to l3 # value list_bgpvpn_obj = neutron.list_bgpvpns(type='l3') print("List of all BGPVPN object with type=l3\t[%s]" % list_bgpvpn_obj) # Creation of a BGPVPN Network association. print("\nBGPVPN Network Association object handling.") # Creation of a Network Association bound on the created BGPVPN object bgpvpn_net_assoc_obj = neutron.create_bgpvpn_network_assoc( bgpvpn_obj['bgpvpn']['id'], {'network_association': {'network_id': net_obj['network']['id']}}) print("BGPVPN Network Association created\t" "[network_association:%s]..." % bgpvpn_net_assoc_obj['network_association']['id']) # List all NETWORK ASSOCIATION object filtered on the network created # above list_bgpvpn_net_assoc_obj = neutron.list_bgpvpn_network_assocs( bgpvpn_obj['bgpvpn']['id'], network_id=net_obj['network']['id']) print("List of NETWORK ASSOCIATION objects using network_id" "[%s]\t[%s]" % (net_obj['network']['id'], list_bgpvpn_net_assoc_obj)) # Deletion of all objects created in this example print("\nDeletion of all created objects") # First declared associations related of the created BGPVPN object in # this example neutron.delete_bgpvpn_network_assoc( bgpvpn_net_assoc_obj['network_association']['id'], bgpvpn_obj['bgpvpn']['id']) # Then the BGPVPN object neutron.delete_bgpvpn(bgpvpn_obj['bgpvpn']['id']) # Subnet neutron.delete_subnet(subnet_obj['subnet']['id']) # And finally the Network neutron.delete_network(net_obj['network']['id']) except Exception as e: print("[ERROR][%s]" % str(e)) sys.exit(1) print("[Done]") if __name__ == '__main__': main() __all__ = ['main'] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/user/0000775000175000017500000000000000000000000020321 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/api.rst0000664000175000017500000000460100000000000021625 0ustar00zuulzuul00000000000000.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode === API === This API is documented in the `Neutron API Reference `_. ADMIN ===== Configuration ============= On VXLAN VNI ------------ .. note:: This feature is under development in the Queens release VXLAN is one option among others that could be used for BGP E-VPNs. When VXLAN is used on a hardware platform the use of a locally-assigned id may not be always possible which introduces the need to configure a globally-assigned VXLAN VNI. The optional ``vni`` attribute is an admin-only parameter and allows the admin to enforce the use of a chosen globally-assigned VXLAN VNI for the said BGPVPN. The default when no VNI is specified and the VXLAN encapsulation is used, is to let the backend choose the VNI in advertised routes, and use the VNI in received routes for transmitted traffic. The backend will conform to E-VPN overlay specs. If the ``vni`` attribute is set for a BGPVPN, the following is enforced: * the routes announced by the backend will advertise the specified VNI (this relates to traffic sent from this BGP VPN to a Network or Router) * for the routes received by the backend for this BGPVPN, and that carry a different VNI that the VNI specified for the BGPVPN the behavior may depend on the backend, with the recommended behavior being to liberally accept such routes. If a backend does not support the approach recommended above of liberally accepting routes with a different VNI, the check can be implemented as follows: * when a route is imported, for each BGPVPN associated to the Network or Router and having a VNI defined: * the set of Route Targets of the route is intersected with the import_rts of the BGPVPN * if this intersection is non-empty the ``vni`` of the BGPVPN is retained * the route is used to establish connectivity to the destination in the forwarding plane only if the advertised VNI is equal to all retained VNIs in the previous step The above check is applied similarly for a Router associated to multiple BGP VPN. The backend is expected to provide troubleshooting information for the cases when a route ends up not being used because the VNI check failed. Valid range for the ``vni`` attribute is [1, 2\ :sup:`24`\ -1]. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/components-sdn.blockdiag0000664000175000017500000000255700000000000025142 0ustar00zuulzuul00000000000000blockdiag components-sdn { span_width = 64; node_height = 100; shadow_style=none; default_shape = roundedbox; group bgpvpn { label="BGPVPN service plugin"; color=red; api[label="BGPVPN API"]; db[shape=flowchart.database,label="DB"]; driver; } group backend_g { label="Backend"; color=orange; comment[label="can be e.g.\nan 'SDN' solution...",shape=note,color=orange,style=none]; backend[label="...",shape=box,stacked,color=none]; vswitches[stacked,label="vswitches\nand/or routers"]; bgpspeakers[stacked,label="MP-BGP Speakers"]; } group routers { color=lightgrey; shape=line; style=dashed; bgppeers[label="BGP Peers",stacked,color=green]; mplsrouters[label="MPLS routers"]; bgppeers -- mplsrouters[style=dotted,folded]; } admin_or_tenant [shape=actor,label="admin, tenant"]; admin_or_tenant -> api[color=blue]; api -> driver ; api -> db[folded]; driver -> db[folded]; driver -> backend; backend <-> bgpspeakers; bgpspeakers <-> bgppeers[color=green,label="MP-BGP",textcolor=green]; backend -> vswitches[folded]; vswitches <-> mplsrouters[label="MPLS\nor ..",folded]; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4899786 networking-bgpvpn-21.0.0/doc/source/user/drivers/0000775000175000017500000000000000000000000021777 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/doc/source/user/drivers/bagpipe/0000775000175000017500000000000000000000000023406 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/drivers/bagpipe/index.rst0000664000175000017500000001206000000000000025246 0ustar00zuulzuul00000000000000.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode ================================ OVS/linuxbridge driver (bagpipe) ================================ Introduction ------------ The **BaGPipe** driver for the BGPVPN service plugin is designed to work jointly with the openvswitch and linuxbridge ML2 mechanism drivers. It relies on the use of the bagpipe-bgp BGP VPN implementation on compute nodes and the MPLS implementation in OpenVSwitch and or linuxbridge. Architecture overview --------------------- The bagpipe driver for the BGPVPN service plugin interacts with the Neutron agent on each compute node, which is extended to support new RPCs to trigger the local configuration on compute nodes of BGP VPN instances and of their MPLS dataplane. Example with the OpenVSwitch mechanism driver and agent: .. image:: ../../figures/overview_blockdiag.png Limitations ----------- On DHCP ports, Router interface ports, external network ports, etc. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ No connectivity will be setup with BGP VPNs for DHCP ports or Router interface ports, or other network specific ports. This improves the load on network nodes by avoiding them to import/export a significant amount of routes, without limiting BGP VPN deployment scenarios because no useful traffic would be exchanged between a router or DHCP interface of a network associated to a BGP VPN. Similarly, the driver will not bind a port on an external network. This behavior will be revisited once a use case is well identified. bagpipe_v2 driver ----------------- For Queens release, the mechanism used by this driver for RPCs was changed. The v1 driver ``networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe.BaGPipeBGPVPNDriver`` is backwards compatible with pre-Queens neutron agents and can be used during a rolling upgrade, e.g. from Pike to Queens. The v2 driver ``networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver`` does not produce the old RPCs anymore and can be used: * on a greenfield deployment * after an upgrade * during a non-rolling upgrade (some BGPVPN operations would be disrupted during the time where pre-Queens agent still run) Future developments may happen only on the v2 driver and the v1 driver will be ultimately abandoned. How to use ? ------------ The steps to take to use this driver are generally: * install the networking-bagpipe package on both control nodes and compute nodes * on control node, configure neutron to use bagpipe driver * on compute nodes, configure the neutron agent to use bagpipe_bgpvpn extension and configure bagpipe-bgp Of course, the typical way is to have all this taken care of by an automated Openstack installer. In devstack ~~~~~~~~~~~ * follow the instruction in README.rst * ``local.conf``: * add the following to enable the BaGPipe driver for the BGPVPN service plugin: .. code-block:: ini NETWORKING_BGPVPN_DRIVER="BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver:default" * enable networking-bagpipe_, which contains code for agent extensions: .. code-block:: ini enable_plugin networking-bagpipe https://opendev.org/openstack/networking-bagpipe.git # enable_plugin networking-bagpipe https://opendev.org/openstack/networking-bagpipe.git stable/pike # enable_plugin networking-bagpipe https://opendev.org/openstack/networking-bagpipe.git stable/queens * on a control node, if you want to run the Fake Route-Reflector there (relevant only for a multinode setup): .. code-block:: none enable_service b-fakerr * on compute nodes: * the compute node Neutron agent is the Neutron openvswitch or linuxbridge agent, with the ``bagpipe_bgpvpn`` agent extension: * install networking-bagpipe_ (the code to interact with ``bagpipe-bgp`` comes from there): .. code-block:: ini enable_plugin networking-bagpipe https://opendev.org/openstack/networking-bagpipe.git # enable_plugin networking-bagpipe https://opendev.org/openstack/networking-bagpipe.git stable/queens # enable_plugin networking-bagpipe https://opendev.org/openstack/networking-bagpipe.git stable/pike * the ``bagpipe_bgpvpn`` agent extension is automatically added to the agent configuration by the devstack plugin * bagpipe-bgp will be installed automatically (part of networking-bagpipe since Pike, or as a submodule before) * you need to enable and configure bagpipe-bgp, typically with a peering to a BGP Route-Reflector or BGP router(s): .. code-block:: ini enable_service b-bgp BAGPIPE_DATAPLANE_DRIVER_IPVPN=ovs BAGPIPE_DATAPLANE_DRIVER_EVPN=ovs # IP of your route-reflector or BGP router, or fakeRR # BAGPIPE_BGP_PEERS defaults to $SERVICE_HOST, which will point to the controller in a # multi-node devstack setup #BAGPIPE_BGP_PEERS=1.2.3.4,2.3.4.5 .. _networking-bagpipe: https://docs.openstack.org/networking-bagpipe/latest/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/drivers/bagpipe/overview.blockdiag0000664000175000017500000000313300000000000027115 0ustar00zuulzuul00000000000000blockdiag components-bagpipe { span_width = 64; node_height = 100; shadow_style=none; default_shape = roundedbox; group bgpvpn { label="BGPVPN service plugin"; color=red; api[label="BGPVPN API"]; db[shape=flowchart.database,label="Neutron DB"]; driver[label="bagpipe driver"]; api -> driver ; api -> db[folded]; driver -> db[folded]; } group backend_g { label="bagpipe backend"; color=orange; comment[label="on each compute\nnode ... : ",shape=note,color=orange,style=none]; backend[label="OpenVSwitch Agent\n+ BGPVPN extension",color=grey,textcolor=darkorange]; vswitch[label="OVS br-int/br-tun",color=lightgrey]; mplsvswitch[label="OVS br-mpls",color="darkorange"]; bgpspeaker[label="bagpipe-bgp",color="darkorange"]; backend -> bgpspeaker; backend -> vswitch[folded]; vswitch <-> mplsvswitch[label="packets"]; bgpspeaker -> mplsvswitch[folded]; } group routers { color=lightgrey; shape=line; style=dashed; bgppeers[label="BGP Peers",stacked,color=green]; mplsrouters[label="MPLS routers"]; bgppeers -- mplsrouters[style=dotted,folded]; } admin_or_tenant [shape=actor,label="admin, tenant"]; admin_or_tenant -> api[color=blue]; driver <-> backend [label="RPCs"]; bgpspeaker <-> bgppeers[color=green,label="MP-BGP",textcolor=green]; mplsvswitch <-> mplsrouters[label="MPLS\nor ..",folded]; } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/drivers/index.rst0000664000175000017500000001222200000000000023637 0ustar00zuulzuul00000000000000======= Drivers ======= The BGPVPN service plugin supports the following drivers: .. toctree:: :maxdepth: 1 bagpipe/index opencontrail/index opendaylight/index nuage/index The API is consistent across drivers, but not all drivers support all parts of the API. Refer to the Driver Compatibility Matrix to determine what is supported with each driver. Driver Compatibility Matrix --------------------------- +----------------------------------------------+-------------------------------------------------+ | API | Driver | +---------------------+------------------------+-----------+--------------+--------------+-------+ | Object | Attribute | Neutron | OpenContrail | OpenDaylight | Nuage | | | | (bagpipe) | [#]_ | [#]_ | | +=====================+========================+===========+==============+==============+=======+ | bgpvpn | base object | ✔ | ✔ | ✔ | ✔ | +---------------------+-------+----------------+-----------+--------------+--------------+-------+ | | | L3 | ✔ | ✔ | ✔ | ✔ | | | type +----------------+-----------+--------------+--------------+-------+ | | | L2 | ✔ | ✔ | ✔ | | | +-------+----------------+-----------+--------------+--------------+-------+ | | route_targets | ✔ | ✔ | ✔ | ✔ | +---------------------+------------------------+-----------+--------------+--------------+-------+ | | import_targets | ✔ | ✔ | ✔ | ✔ | | +------------------------+-----------+--------------+--------------+-------+ | | export_targets | ✔ | ✔ | ✔ | ✔ | | +------------------------+-----------+--------------+--------------+-------+ | | route_distinguishers | | | ✔ | ✔ | | +------------------------+-----------+--------------+--------------+-------+ | | vni | ✔ | | ✔ | | | +------------------------+-----------+--------------+--------------+-------+ | | local_pref | ✔ | | | | +---------------------+------------------------+-----------+--------------+--------------+-------+ | network_association | base object | ✔ | ✔ | ✔ | | +---------------------+------------------------+-----------+--------------+--------------+-------+ | router_association | base object | ✔ | ✔ | ✔ | ✔ | +---------------------+------------------------+-----------+--------------+--------------+-------+ | | advertise_extra_routes | | | [#]_ | | +---------------------+------------------------+-----------+--------------+--------------+-------+ | port_association | base object | ✔ | | | | +---------------------+------------------------+-----------+--------------+--------------+-------+ | | advertise_fixed_ips | ✔ | | | | +---------------------+------------------------+-----------+--------------+--------------+-------+ | | routes:prefix | ✔ | | | | +---------------------+------------------------+-----------+--------------+--------------+-------+ | | routes:bgpvpn | ✔ | | | | +---------------------+------------------------+-----------+--------------+--------------+-------+ | | routes:local_pref | ✔ | | | | +---------------------+------------------------+-----------+--------------+--------------+-------+ .. [#] This applies to the `current BGPVPN Contrail driver `_ sometimes called *v2 driver*, which is different from the now obsolete *v1 driver* that was under ``networking_bgpvpn``. .. [#] This applies to the `current BGPVPN ODL v2 driver `_ sometimes called *v2 driver*, which is different from the now obsolete *v1 driver* that was under ``networking_bgpvpn``. .. [#] The behavior corresponding to ``advertise_extra_routes: true``, is supported as the default with ODL, without support in the API for turning it off. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/doc/source/user/drivers/nuage/0000775000175000017500000000000000000000000023076 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/drivers/nuage/index.rst0000664000175000017500000000143200000000000024737 0ustar00zuulzuul00000000000000.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode ===================== Nuage Networks driver ===================== The Nuage Network driver works jointly with `Nuage Networks VSP `__. A pre-requisite for the nuage BGPVPN driver is that the Nuage-specific installation and configuration steps have been applied; in particular the installation of the ``nuage_neutron`` package. Please refer to Nuage Networks documentation. The driver will be enabled, by specifying in ``/etc/neutron/networking_bgpvpn.conf``: .. code-block:: ini [service_providers] service_provider = BGPVPN:Nuage:nuage_neutron.bgpvpn.services.service_drivers.driver.NuageBGPVPNDriver:default ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/doc/source/user/drivers/opencontrail/0000775000175000017500000000000000000000000024474 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/drivers/opencontrail/index.rst0000664000175000017500000000704700000000000026345 0ustar00zuulzuul00000000000000=================== OpenContrail driver =================== Introduction ------------ The **OpenContrail** driver for the BGPVPN service plugin is designed to work jointly with the `OpenContrail SDN controller`_ (`GitHub`_). The BGP VPN driver can be found in the `monolithic Neutron plugin tree`__ [#]_. .. Note:: The BGPVPN Contrail driver that was under ``networking_bgpvpn`` (``networking_bgpvpn.neutron.services.service_drivers.opencontrail.opencontrail.OpenContrailBGPVPNDriver``) has been deprecated in Queens release, and has been completly removed in Stein release. The documentation below refers to the production ready `driver`_ under ``Juniper/contrail-neutron-plugin``. Be careful, **no** migration path is planned. Limitations ----------- Route Distinguishers ~~~~~~~~~~~~~~~~~~~~ The OpenContrail driver for the BGPVPN service plugin does not permit specifying `route distinguisher`_. Resource Association ~~~~~~~~~~~~~~~~~~~~ The OpenContrail driver for the BGPVPN service plugin does not yet support `association with ports`_. But it supports `network associations`_ and `router associations`_. VPN Type ~~~~~~~~ The OpenContrail driver for the BGPVPN service plugin can create L2 & L3 VPN types for network associations and L3 VPN type for router association. How to use ? ------------ On an Openstack Installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [TBC (package installation + config)] In devstack ~~~~~~~~~~~ A `devstack plugin`_ can be used to setup an OpenContrail dev/test platform. * Clone devstack: .. code-block:: console git clone git@github.com:openstack-dev/devstack * Here a proposed devstack ``local.conf`` file which permits to deploy OpenStack keystone, glance, nova, neutron/networking-bgpvpn and compile/install all OpenContrail services and dependencies: .. code-block:: bash [[local|localrc]] LOG=True LOGDAYS=1 PASSWORD="secret" DATABASE_PASSWORD=$PASSWORD RABBIT_PASSWORD=$PASSWORD SERVICE_TOKEN=$PASSWORD SERVICE_PASSWORD=$PASSWORD ADMIN_PASSWORD=$PASSWORD # disable some nova services disable_service n-obj n-novnc n-cauth # disable cinder disable_service cinder c-api c-vol c-sch # disable heat disable_service h-eng h-api h-api-cfn h-api-cw # diable horizon disable_service horizon # disable swift disable_service swift s-proxy s-object s-container s-account # disable some contrail services #disable_service ui-webs ui-jobs named dns query-engine DEST=/opt/stack/openstack CONTRAIL_DEST=/opt/stack/contrail enable_plugin contrail https://github.com/zioc/contrail-devstack-plugin.git enable_plugin networking-bgpvpn https://opendev.org/openstack/networking-bgpvpn.git NETWORKING_BGPVPN_DRIVER="BGPVPN:OpenContrail:neutron_plugin_contrail.plugins.opencontrail.networking_bgpvpn.contrail.ContrailBGPVPNDriver:default" .. [#] That driver requires OpenContrail release upper or equal to 4.0 .. _OpenContrail SDN controller: http://www.opencontrail.org/ .. _GitHub: https://github.com/Juniper/contrail-controller .. _driver: https://github.com/Juniper/contrail-neutron-plugin/tree/master/neutron_plugin_contrail/plugins/opencontrail/networking_bgpvpn __ driver_ .. _route distinguisher: https://docs.openstack.org/api-ref/network/v2/#on-route-distinguishers-rds .. _router associations: https://docs.openstack.org/api-ref/network/v2/#router-associations .. _network associations: https://docs.openstack.org/api-ref/network/v2/#network-associations .. _association with ports: https://docs.openstack.org/api-ref/network/v2/#port-associations .. _devstack plugin: https://github.com/zioc/contrail-devstack-plugin ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/doc/source/user/drivers/opendaylight/0000775000175000017500000000000000000000000024466 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/drivers/opendaylight/index.rst0000664000175000017500000000262200000000000026331 0ustar00zuulzuul00000000000000=================== OpenDaylight driver =================== The **OpenDaylight** driver for the BGPVPN service plugin is designed to work jointly with the `OpenDaylight SDN controller `__. OpenDaylight driver requires `networking-odl plugin`_ which comes with its own devstack scripts. Details on how to configure devstack for OpenDaylight plugin can be found at `networking-odl/devstack`_. .. note:: The legacy BGPVPN *v1 driver* for ODL that was hosted in ``networking-bgpvpn`` tree (``networking_bgpvpn.neutron.services.service_drivers.opendaylight.odl.OpenDaylightBgpvpnDriver``) has been deprecated in Rocky OpenStack release, and removed in Stein OpenStack release. The documentation below refers to the newer *v2 driver* in the ``networking-odl`` project. * add the following to local.conf to enable networking-odl plugin: .. code-block:: none enable_plugin networking-odl http://opendev.org/openstack/networking-odl * add the following to local.conf to enable ODL Driver for BGPVPN service Plugin: .. code-block:: ini NETWORKING_BGPVPN_DRIVER="BGPVPN:OpenDaylight:networking_odl.bgpvpn.odl_v2.OpenDaylightBgpvpnDriver:default" * Run stack.sh: .. code-block:: console ./stack.sh .. _networking-odl plugin : https://launchpad.net/networking-odl .. _networking-odl/devstack : https://github.com/openstack/networking-odl/tree/master/devstack ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/doc/source/user/figures/0000775000175000017500000000000000000000000021765 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/figures/components_sdn_blockdiag.png0000664000175000017500000003362400000000000027533 0ustar00zuulzuul00000000000000PNG  IHDR3J7[IDATx͏#}'$*ZeYB0m {*/={iXA_} C[ƶ,5]R{fgddX$7O|>@d0|"㛿牔sNN0&h0&h0&h0&h0&h0&h0&h0&h0&h0&h0&hu7Qi `y FJ1mt“ˏ/v|B4 ?9`P&MxUJ9F'=yyfN6G:{ R4  H0(OW|Ust(?/^ܽk\}o\ctSGJ)$(9#W?_Kݳ6mF?Rqz[ðzm%DJr~uu݉p؉prv>k|?f=?쏦ם|zˮ?mں4/_0j8GU `7DUWu'r݋_qw˧If̼?xzdy4˛OOG;x5ۙھ|i;S6Ym^Nn^9Rat9ݣboiD"">i^ OE|7Obx4q:%}_|?>ӕLoO~vٸ~2>EY>sڏ&Ei<<#b`0(Eq:p؉~7^O}f<|ѣ8:jCjjuWg-uh9;K=M {sڹhN=o_sU&*ꨪ^?NYܽ(NNv#"Gwqt:IU'Ǹ8ޏ=7mr4m{rY[ԖEhf߼}֢׬ym_ewzy4_)֭wqҗ>?_ƛo*O}I40?w>woo/~8[q~'F I#\ҟMy?6{?|nzYzY/z9/BH:^?wq0i/c_>Q>4c`0(_Nxq;?~#>//8~?ӧq|A 0S|+""&ok4YTu=K_m|+_??sa?N$ 6UFx/_x?߼ǿ1<މt^ۨ*#DE(۸[_Ǹ_U^zE۟~+%uO, g˖ m^`7`_UNR69Rzݭ)":㟽ⵟ?ٴYE7ubj&+c/V(jByyA6Hϟ;/ފ[79gs5۔/)CMi;KIsDϟ߉:n f˜zLV5˴gmOKW^|fe@ۙ1WuY]=/u=w]v[.S)z#T_Snp B0]&jm'+U_ !`!0~5`6:ï 6BDDV B0`[eï҅_+l]Es`@`[¯Em B0T-sWCH"¯ (l\kq `M_˄`M"7~5`@ `n]WCl;[wL6%jm%@~5`6mM B0`6Ȧ_ !M`b[¯  mWClؚmkNF~5`&ۈJ B0`Su݀6jžKF~5!؅q(l/ovVP4E3rLΛfnx'l>9JPUn}ы1RJqЖ#/FvK)Ev]}ETݐyU^WMuf&8}mt{U_%dYaϬgr'9/([t;7ۯ֟܌*QuFX"ZGp.L5٤0h_6 C*{80k65r]t \ g9l܎M/ fvWnd5EVyjNUÔljr œYMBo{7~;C RJZhU$r͚̜`Uzuh[֚fk*"|BON7M!`ټPpDV|o/9GX NM#B0nO?zwm]Xv?0`Yɼ0Y\Uz)oN y2C&3ynl4pi|YbM `lM 6 B%.J L_z<:hqU8GH5aTs^0vۜܖ> 0BV{ͪRCKW5hb5C \60c$cQ$> V4Y\֟m_py@AEyz=~;~DιBbw "`N@@dy( Vy|Ɣ"?Ob FzGaDqYꁵ2 ( ( (Zw .usP +ݏ#G?E4`M@`M@`M@`M@`Ioʕ_w _zm.phm ,5mmR:s^cKW4\wV2{Y L,<"">]w[N|M ,R3MZ zͼm]j^J۰jBAO;=hq?" ?^.L7x+κHow5: `( Z6e?LLf/ZtN5 LV ݼms9o1;X|g,cXs2u/弰h[u{jYYWQ5+Pk7dEײWr/*_!La}vs~?{~zl$ )"u'^ ߉߽|c[['B0Γ?\mZ:BEPgayy6f۝\z6y^e֬.yo7)/ɢl. ropv?fݟ\vg?;ylPy_ަX'`-VA: Ƽ[ ( 0x=@yTP4`~wGQO; EP4EP4EP4EAnN)AJ)pCn^9hBE";$NÐj0aX9^\q%:s9W):%ܸ&/ `綆vpMqą}ߎ? Iݖ"6YaYɼc7ogqm }|gTh}W X L }@;U[̠pB9J"C5&H_f×6}i^Q;Ś,bk>۶c!XDng=?B d_eCޯQ~o<{.o_7 SG6}`9m`݀#r?yWUmUՑsD]oDWbgcv=n1]LBc(] Lڣ_`-t?Z d)H)t/=w`a:Sjns&C*B- >W Z~2APN0At:uzuD m2Mq0먪/AʯNg0z~z=Aܺu(YMT~ݺu=9+aT/m s9%`>`&vqr@H>j7>E3sl6? &|M/T |M`&h0xk7>E뮻p/L[˃N:<f*dеc`i/!!pam8~A7q")[TlYC/2qk J (  `Øj 6Hp-(!M@`M@L@q+.[e<]XI"w v9_8z@yTvYӕZYu'_3km,tlYGk:lZTٵeM/vC +fUrͫH U{MX?C h*^&de'ɟά0`TU}7d4]%6o⢡q]Yj_w `u*ӡѢiV`lʲeTj5I#T\Ul0`.lR lz(yW^6Ey`+_PuaM6YI5}:ۘ\~k-jì[)y0%_1SA1؉xN/ފɷݲ/| ¶*5N9񢋪 m]\+xsm"SG3Q7ƭ:o7>׾?>Q"UÈcm, rպByE.@.$Myϲh{.2y+y0Mȥ ̀kݓ7Ӫ&+2/݄/Pٚ!UlE.EPlskYj-?lyۚ5W׬֙|; EWmgM`u*!@P1fH9RUGjN:UUG]QEDb9K9"գʯ^?ݣӨvFC";DVlaItEΣOv#"{G}ߋ\W+?H)׷C9 T{'Jٻb;,R$R5e/(Ey_x)"u7`71|~7OE|7ObxNAg<}s9j);=vφwFΣytn=HH` l=x^eÕ_:`EHuU{:ZGgYw'¯zEڒ9+""X5< ݣ?=wG` [ F磫t4W݉<쌪NG_^5͝z'QGWQ OQB ~MU;^a4Mz?.)*͛s;hίpTU GW j02L`9E*"WW_|V ѐT 40r/R> ?~AAPqH0^;3}2|/h@Vp`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`bIENDB`././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/figures/overview_blockdiag.png0000664000175000017500000003711100000000000026343 0ustar00zuulzuul00000000000000PNG  IHDR3J>IDATxO$ٝ'3w?YYJMUKFEð}ShˮE tR5{آaAPK`2akVFjJU7"G*#NqV'dRBrr~eeًI/ʲ''ag"_/'IU|/NZ>myonKAWF]sWwEq2~FQDQ1 F믯FUe/rI/YvV>ig|:ײM2MQqVFD*NƏÈ$k S_8>pv [1,{qrkU Voo|_/,>{>Vmh^^\&uv?kygnym'פdW|  b08~(ru(ATǃ(;QnE9DU:UEDrq3v}zs&?i_9>8 W1FE18bPDƜ9 g˲ǃ8<ٳ8<܉89`1Yi@~'owR:w~V/o||i;S-o.'vZ>'ע9W)EI'u$vwx~9,qX: (:'8yv3Ý(Fa'^TUnzeu3cy~<ݟ~cB}gYL{ij?1s{8?b(>QH2TEz 7U5wVCDDEySnUr'b>R ǏoA#+t>0<GQF&y Uޟb8܎gnƣG_O>y->__ߌz8d,Z]ߘ:_Ť/|nZ{֟I6ԁsퟷs9zmyNZϲ:]*(*aŭ[O嗿툈1FxRqکb\UDuҋrnї/^QlE5DU\aUMqnyt|O?y{O[6e?5ϓYB4Jo}GeSs#50[U0zr|OaaH܉Q)g|(VވǏ_>{=>-_O?~;>݉pΐ~?kO{n3im77MjoV_fmg~M޴}m53Zyom/o?OA]R~0n8W^yw|GWxū~n}O;N!_lEyx#5kqϯwb8~3츞?!7ZWsߍajyIYYy}HOz/6YX)eao}b+~ۯ~[G4x!g/b>_m?ō?ulw1iv(a Ae/`'?~;O^Gna?qr2k$d*WwJ7eQ}߹LX(+?88x;;+ΕXqfga}^G[qr7c8q+qf(QKHuۯTY`f+wz/H2qq2>vbgs%670䦾d}QOǻxF<}}~@p%;w>uh < n8xt'hT!{r;=nj\ 6cyV?݉ǻqf?'O<)+i<6|;i٢m,{pTQQu<:T 0Q;1p+vnp8 T]Rq0Mu4>'#_~Z!X5D9V8yLJ[Q*.hVE_lΏ!ǽJdJy:h+ˈLqrRqtpXp? T]pv2"/?>?Q*vtݬ ,"2Fw{<)"{QG_"a??@.ˢ"QGs"e=*J)䬮^C,N=_ .Hl,scqV Vg?auYQVEE8?W}ytN:_4j`9wW~n~pe)=><áXgGYDy߀@E)8F=b|`.xy/-.MJbc9|>7U_/77NQ}jc|_߹$8g".sV)% |Pn0?o*39cv @;ȡK&\䧪r1B3¯*R'.3C ;(TN{:XG4CYaKDžn_T[[ D:К"^㳂0`c 2R*/SиkV)6P#S CE`ZfUJjB0` 2rWMlYe:jM" k0&u l Y5Y5Y5X*RJ׺͔RTU֞ Ț,9 G`1Ț,#Q 4\e&60#CW M%2C0Wg Є_&e llͪ1X׬I`t7& k0f+ּY%0ty1WtkίKݍW!@M+MT9MZtyenwR6iI}ݴJ6pa›im"kWIaT{y׫?oX eNUVg ` `m8-Zd򫜔~Z, 0Xz[hpw~>liC.NԞ3*|}kg:QK; \:w^vwg׵W-_Ū&Cźl;׹ڟ6 r, :"o~꾴5/#n_ם|z?~R?g*vy&/~/~UC~GDT7n/mwJ鹋EOjs׳.'mcVX &}ӂ14ɬ`uV;k}Ҳ̓l@zYҧގO?}!{/Xk_E."֥U{RD?;쵟>ZYfEuhe:c/w}@wf1:rN3+?|}^k-Cztk/rUUغ6p.}g.)*EYb8܉Oog~k[uQz3/۴ yf4+y6Vu[vc|E2T7n >VGq (N"*G"/pqo~TUc.0󶳌 e뷔VҟΝ׿vs#\*#ǯ8/ӴI3Ӫ& ˚6kʵqV񛟧9LqzU"Njʭ9IsMϪTo>vf.Cxs}漋˴7Z2wN2Vk"\&?i/z,.ZY˟?xwUQ .Jm*^u?`#N9yՈTCz*G>${>jVsM| .`B͹C#ka~|j.\L |&=zmN Gi:o[vUJ`:FU`ݓRw9kp`:AAO¯9]@^I6!|kj4CrvXW9]\Wu@4oH4ie5ݧf5EV9iza||9]YaΤyba>ͳ݋S>r;rL8_F+J W 5Y5Y5Y5Y5Y5Y5Y5Y믺V j @`dM@`dM@`d~؅t@Fa,l?w~dM@ :#ͱoW=Nm?w~y@gn $ k0SUyȗ iW{ț NZ䫿#mPB5`S{Vl~v]%T./^UU 9~6  jsb.@. K#{A"j~N嫿E K'٤.hmW-|nӽ}!+gw &I)RJl/[SUչ?o`¯7įEՏ qm~uȾ!50"Ao` `t~u{F!F:i_a$ K[iR[np8pfRT!t[w\&U`lTvk`_?zr%XI) `0t|\ILל@u-AUU)%HGߵ?CB0wNÐ( Ɇh;wȤ0tӃN+y3Xn?0`Iɴa绀å!񳗗@?Xu.ϻRs3`f~~ϝy.oxkM\t `Is5i޷& `=5晛kPmZ3!<դu]T]kawmwv7b?l 19Y3܍ o79(HKTR*4lRTe/?㥷?0vOGѿ0A$b9`u  k0& k0& k0_uyAY?SB{?qN@M@`d`d燷#"/\6{?"s`n2\pfoM&U\5?7^fYUseR?歺Շi=3 `Aͤgv[^WMϴ}i\V  2J8]ev_Ӳf$W沕Jͪ[iˮsn*`>*2^?,,'jyC%'6fW#UUV RTUpOގ>{=~7Wz;>7׿J|xp7=IOnUU꾳")I)EzelmAܾ,^{x[ocWO͛c088I)UU-?"SD*{Qw8t_+T_%}|'wN[Kؼ0fߏ꽕vJ5IET?<zWe{"q݃~}7}O^_>VlQo>bp8X*=x𠬟ݨ~3)2[τ߽xoo/b)C 2袮ۯەVvѴ/GMrUC\ߌ7Wݏe+_~>)[+3^םޛ~݈%Zt@7z=b\$œu &UhMj;-o?3lwY ގq6*k,|Vo;yIۘyֆ `R:_k?ZvY/]'k][>y l}mw7k"Zl +)Ӑ~L)z]?o5mEڛd55Cf-n}߯_~5{~MKc3jiW?^Yۤv'3O?I}?WݏP*6*?qڜ~bӏ6Tk]VKӂ{fz=2TފGyٶxr^>myߴ 7lCЬ`hVyӤ m{~_Koh} "ڞ勬Zڬ?@֦[ˬjW]`u>z^6MiO Yz]q^6k>3i3kyg}ïhWfH3\r/b\el\`UUuy00EÖvϯ&kʨI\{;em\yδɢ퓟Ύ;x+>|'>܍gvӧ[*+}:)^=۷k}ox |W)^}ya Q'RVtRD*{Qw8x7ѯ> _%}|'wN[;+DJIe{8A n?>o>nKo~_z| RqvVT9j (F·֭g/}0nz;;u4+`tC~E dͣ}n=_"=ïxt'AQUgXUWdA1 ƍx啇qΧ_ĭ[c{ aze ̜`t92?zJ!w}<(vw_vDD+|[1, `;d0vv֭'_ĝ;5n"vw`pEq;QCDDlmK/};qt48~Tա𣪮~9\qܾExt}\RJe.`_ y0vv/_eW~%`@ADQ`ۣ8};;c08,MN/FtÈxEQ`p{qrҋJ9`@7掆c080s`2""b~9aloW_@7UUQ@l~E* @v REJ)j&/+ YV*O(`# ?lT"Q%8zGdAW O[ `N6 ־`:d`&nqq@H>6_>Y36ޞsl.? &|dMd/T |dMd`& k0 {6_>Y믺pҽU]O_9[)v T O;.]3? ȞniU._? 耔RyZJ@']T%`@m|:zQB9.믺to=>]WArLDtg_37V~THqW.ݽYj~૪Uw%k|1@{m{W0A'Yݦ{BIRJ+_w;`a0~u;3m^¯~ܝa~uGUUt~uU gUY{{{sfU{lwnS1qTQqQo򚺴t˝ 61$J) : 0& k H)=7_شy֬QTWc3iWUU/"*ҽUTﭺpI!0.\hO27jVՏ] H:XҘR9|X.`$jf%W{]$~zU2nyLL\ Ț Ț Ț WrW*"ytM+"Rjt?ErPFQE`20 gYV~ (zeI~9:_Ujt>r"F]; Q >b8z7#"QETbíT¯ލyq(0`89=Yd~[GQEAo='QlEy܏8efXW~Io=(v:`=!dH<6s[Ob>؉anE*/y@ZwME?Qo=/}W֓Bdt>AN`k|qT*(q ^"A?݉r8금j`ٞAVQ/ `w>_D(FC"{e xqȀ `ӝN~_DEoI nvDDvbQlE5DU+r7KE5#qhT)}}ip8eN FK >Ql`'ʣ{GU_UU%rd|p1E ^CwD18VW xA{{{ ϝ T ;G)Yy*>X !:t8!C!B-}M@O,AXGQp(oʯC7'g(%)d(l6TUc !t!8J}R2I'%SL}R҃xpx㺮ۺUUUU-Ɩpd^}>)dꓒOJVp}rR Op8UUB7y'eSL}R2IJOWR yB޽d2z<.|5/$>)dꓒOJVh}rƊ RJyB!|W0; !OJ duJ}>)dꓒOJVb}rފ |"_B4`0 꺮:bhꓒOJ>)Yə*:XJ)9pdr3L۶mRJUJ˽=_$I'%SL}RRU=b0o]=>>g٠jD}d9ԛ>)d'gKn|||Oa۶"?t}zA}RSճԳ,Yw}gѷ]j&CӢ>)Y*|4Ntu]Rx88IN>qgS]_vW^|o~>94OJ>)`u]70yI1'%;&ܿΦ/e5hZiӺ mmYmsmSONd(^uq1'PTs5Km_w޷siyپuO?4OJ>)Ms,b̰&%RsJnK;78OJ>)Ko`'EQTsP7m>ymc׶:nosiPL}R=^BO?eOSSO(/x㟲C}!0zT .q?# 39SN>O]m>'I'%9`%OOh.V_}nc h7ھMߺ%>ꉡrR6쩔KЯe[{Or07Ñduu™k[r;Dm}lm٦ϋg,-,(A66jmoKM[v _VmlZu6~~7=>}}ڶ绩O >R_/Zw.L{L|n붭l`ON" Ͱdms˷MYlcy݅]s7{v>CoW5?ܿi}deMng>=<ꓢ}A.!Aʺߋ,cqwپAz-ZiN_;l9X F&}/^X7ʮCg/]%ijnWNǦ#`cuK Voo vNߧmm?/'+W9ߋo[}]GK .YWՠj. pvgu6DOg(z!˺G}=mںmcIoO [m<5\}vKsz-ܠK?W{mv^ºjYzZq~gӺ˿y<7maw遴nuZLڹڮݿеmrߥҺmku}J_;W۵=y6LĽQrIM_7meԟ@mjCӦǓg]m<{u/zmaΦc}lZZ9nkv^C+ɖߛ޶u/o'?ڥ?ٰ(J<)ضm= ^zX+L>M??}g?~~;w6Ƹ).O WGק^0+>1Dm;i;Qԧ0lſ)>g'E'gz,'׫7>mku7eu/ߗ~\g?ǒ^J^JmCAw~7)>X˦uv{ P8v3/x $,g/HZ+i WwE>ۮBmwCA >6-[Hߵ,&/E%"X `c"OJ>)dꓒO"X `c춯>)dꓒOJ>)`oIQ'%SL}R2I',q c-J}R2I'%SL}Rū*| !8R I'%SL}R2IiC7`?icQ=OMJOM}Ro ?9t8듳S|t{{_~?ſ/'op}}lt]t]W7_ݎWUUUU5kf6ow/q0է,T%SdS}L}]ycnp8L&7!0fٰm:TBJ'7o~sFE4p8z{yyn2 `Xuc\)>xէTESdS}L}ϒBUj8cWUU4t~м|8h?<<۶Ł3RJ|Ѝx)6Įh4|͛\^^L&iiUUR*t^}T%SdS}L}ϒ3WjB4>-x|syyv:Baz:t]'Hq?/oox˦>gԧ,T%S'`)ZtL&7B WWW_?>>0T>tC% ^_^^}Wf0K>gԧ,,>9SK+J!4t2,]իSnkX>pVU9o&x<u]O:9@[էTSdS}L}ϒVlik1?DmUU`0xL&׳l00|R4?n+XL6v>Qt~Ճ1W>8T*>ԧ,T%SdS}L}YXx){u]u]ݶmۺmzW)(Łs11aW붮iUU|rnq=`[QL}R2I'%;L,}cLYu]W;`%4R;]FõtHyV'%SL}R2IɎ>9OKsp.^ Z4xy'%SL}R2IɎ>93,-(k)_V>)dꓒOJvə8`i1}'16TtJygԧ,T%SdUUvq.sy\޷sy\޷sy\޷sy\޷sy\޷sypC7$X ` ,%"X KL)C7YKd,ETcw6@I'%SL}R2 ,E@YKd,E@YbJ)="X ` ,COJ>)dꓒO(` ,%"X ` ,%.;W &AcRR$I'%SƤݶMպfy}X ,f-G9oMզw]ڸkp`ݦ(mrBǬDvum[^wB p g,,.Zwr>+9n} 8U͡;1wK1RLNG'%SL}Rך{]B}kjG}צyvϏ?<9pR2I'%S|!s/䶝Α`pL}R2I'%+>KZm:e%'+R8)dꓒOJ>yIOgJ}By| x5E0i,p0&8n͡N~9mMVm囖oz'L g,إ?)zTm{Al4B!Ə/ <)WSpX%`c !t!nw DO '` Nbm;1UUBm i@I},.ؙROJ>)<<<\u=iUUO |Qoj|>7 '` N=B޿iuUUUpǪf zR'RuWUl8Û`pR8Ti,'Fa !pssmۦzi;PpDKpv}8fv_yi` h:f`Zf>K[ QL}R2ɱivжmRz@'`EC(,͇}+S %X `p1m>ꓒOJ>S!X ` ,%"X ` ,% RQ,I'%SpR@YKd,. G}R2I'p*Kd,E@YKd,E@RRJ#>)d8Nj"X ` c8gCOJ>)N` ,%"X ` ,%"X*\J{D'%SL}9t5C%SKH?MWG!u]WB1sm_7l~EM)U)ym &%SL}R2 /B]ӮиEP88Q!3ot{^}}b<<>>NՅb1kx` ̛//Kﭻ!v;/Rm ^]w{~EU]׳B1BN$X5gGvcCv۰nu_mǶ6w׶m=w;smO!wwwou]1Ɛ.c Rک¦@"_WÂuAm׶u{<ٴuϫo=smg/d2y?͆m6]-ϻ3`K!Eu/?nvn;߶zn/k`/{|_hO!\\\f1. {K?JzcaxxC\o\Cٖ!0!BKC` x6.m225jO9VYK)ZkϜv~_:-ڴ:m~c^M=v[.714 UUB`p)GrOJ>)Czv*ܦ/}l])@L)ŔR]COW?߿mɻyٶӯ??߿t: o޼է~|_F뺮bm1B۶`0xv,87/~;?_]]}=C[=߾xp8^X zJtUU͚xBPz6C]N`xqqn<_0dދmVe$,̭3[0үG"Xwh4ZKIW/=bSа,^\>}|{[(׻g].m[o[Y7x}myOj(Ucuկ>ַO>_~5L7 g!sA/ֆ˷<,om)VÖmwm!ۺ}u^}YS{V[{=WϨCn1irO%Y eK96&o///}uu_O&ߎFMu=] |MUUu1v>_YzcP}m { =YFвcRJa@iA¥d:RL}R2 ‡piڶu}4⧮c ýv鑳iu=^mϾ%Ysj./ezA-uq]Mc7-_^gYs>a{zJ!8օs.-_=-념*}iϜHNcm#=u{n}~w鱥w7 ("4Mrz=u[!gX aϑ` c 4 1HZ[Lz2me|k9x ꓒOJ>8~˨*=RpeƝg#L ;_yʶ,pnoo??կ~W/䷿}9͚뚮`j4l<\]]}~_`0xcv~*X͇cWu; d2|8!p0͆m)*R2m1mQM<û&p8 u]1nKE*b]UUm4yt}yya*?<<۶EB)f4B,kF]]htyy͛7|;L}4Ӫ^K{/ 8yyct: !쯧Auq9XS0WSUU`8.//ߎy٢Ҷm 80dr3; !`puux:SJuJc/%nqEp˷o޼j2 ۪) <@a2,~o9;ǢGr|{u7mg67mgv:oZwj-Z@e5`ZX / k{{zmgvX}rz&6VÏ]d[h>}Զ=nSmמl:O!X*\YB]ß}اӢz(lz.զlϱξ>%OJ>8~ByKgb[mz٥ M[7?H [wiöOom6KqU)T6wSoOyK.?| ` ,%"X .$;C1nUr>)d 'LJ  K'nj/mbVm{}wdgi}6uڴuɮ[޷u}w+nw]ξK{^^Mes:w}`p1gb|Ie }9wxئ1[O%y'<7 c(ܙ[l:Zm!En,YXwסdض߾(whۮ,۵=>}b][m NK'nyurR}_}VΰM =>)  ɻOܦ-/K)T.Ki]߷|m(/gb,œÝ7ms}_-T }b~f,P;Ox ꓒO =, w|ط|e%X:r_Nɶ|/!WmPp<R UnRRJ1Ç)n{ OټJ'G-v UU5f0ܦ>vRJ#>)@ ;V !Om6]Ku׶)?8~By|(8;Bl6躮 ` 1NGl0T-'Xlm;h۶I)UKL]bO͢!p'@O(` 1߾!GO@YKd,E@YK[,6F}q  ` ,% c>8~q"X ` ,%"X ` ,¥GK}q  ` ,% c>8~q"X ` ,%4n@.7#&XZn_(B1B:l}b&PUURRJqsv,0J>yg_]ZK&ԡy?<>*&tGϾ9I?"4MжmRJA%Jc;s,G)r1H?"YUU!ƘRJ!X+EΑ` 8F; Z-/[ܿ/[n[nmٶ7m}-뻻7u]c.q L9rS8])١s>DjW•Pgۋ/lߵnwuW/d2y?͆q.=>o_m]UUbbx?~6ֺuM>p' 0/D{>b=낤ovsMxw{{ic)>sϥ/ϯ~95Ms?J)g[mbROCqMGA轲O.`ڧϺ{ܦ^Li]}֏0`>|"=e)bxS@KK?=`%Y4r.Y7Ү=K٥]uY=ͼR1B[ Exyki>Թ (` 8J9s,ܗsN{]ooZ\^x777ߪjy.h2oJcSڶ8VU5 !CP0w'牢޽{7l6躮~I!GJz=<<\MӋl6nxnz,'zz޿ht7c]׳y%78;_v8!BWUU !OuG@}qq~2Fb%s-qJ?߆AR[UU-iKP7C91)}]UUU~ټze$X'wwwW_?o/կ~?o~o?<_NJ CocWUUWUU7 FO>~1몰RJaסp߿^UUpx;omJP:= 4%Rmf:'Żq! Çl6\ %(*o<\ MC>B/ھdsvrssh4L&myqk=cR]C˷Køm߻+p{9o/..]\\FwIc}t{5c^k,.GLYv0<˷tBt:nu]Y*S !y4 7o|=Li+)Nl6VGPpRUU>0T/sdPi.@|{uuv<ǪZ=8Um3pKY|Wt0T:N&E4g]^ߢRzd27aiH99JD‡0v0LC+M&pECq~E8_ʳږS}0< p0 {9-z-y?9os9;,GiDtUUt{ m֋)-Q"("X81{]׋iUU9,%XBK]]պ@o),.-ץ+uuL`` 8ZIc1ƔRWUU\Z9T.CaQۿ7VJTmie7 #X*ˢSs;9:$ ;` 8]B & ` ,-]Z>xM?<%"X ` ,%"X `p)%R&PJ"X `p1m>#X ` ,%"X ` ,% RQ, kr  ` ,% c>xM?<%"X ` ,%"X `p)%R&PJ"X `p1m>#X ` ,%"X ` ,% RQ, kr  ` ,% c>xM?<%"X ` ,%"X `p)%R&PJ4nb߻b o^z;//?bGj]AEJzp}̀Gcp}'P p8=^ݿ#q/^#{nk6m^M\]^]ۿ~9@!muw.W6o{B]-uXo[wysc ,JY6ᔳ]iAʺ(Z^mOm'<{ya( v9l 6Mkϰ %Gm neٺG놸v[wSuC6 []?}.{Vmk]C]=g(XZhY}?*tۆeJKg~ڶ-x.%#&8@! 8$Ή%S&PJ"X `pWO^O(!J X82ˡ@ 8$'"Gf9L1v"X8B)j5`:d{d(Y"` R8) `pGO^O(%YKd,ETuROcXK\vMz>Pk('}D;fJcg8,b9X/bguڴ1vlg޷}Z]קo}^۞+ND_v_XX|y ٷNM]^]_u],+<@Jne*/ 8u-jv}}ֽFz1==m1*;tWҡwy}!OixFpV{)-['V]B]-^/YruC6 \]v{}i~C}ɻsˏeMˮٴS̽pKpž3PZf=B]1p ̘NPJzP)m;躮Y B2ӣ MӋgUUÕ+voc;UUՅ|boC΄`p?NK}yds8 @FC֙Kj,>m,YP-{xifu]UU Δ` 0_~o 6ۂ{6?ϳlcl'u4ML-z-pKzwQ`0뺫Ƿn0<4M3zkIpfKkvclM!Fczzqqx<u]Oą `8+%#RCɥ9ht4l8M&WWW_O&ywK{yxI9rv4M&__^^p8ia>גIyqPQ4Wxd,E@Rbݡ}'@Od,E@Rbݡ}'@Od,E@Rbݡ}'@Od,E@Rbݡ}'@Od,E@Rbݡ}'@Odi^G[sayYJZ]w}ڱ|ߺrpfV&-8}o|N c >Ӧ@)j[à`Jo%N'@O(u3cia䬻q[йum\X yn}|J̬(Z׃:m?P=ĦP'.qg@Et !^p'J4RRJ{*(O89~qN@ `ct!42 .:WZv)p-R?u]O뺞p $X:r)j[K[n喗|'W777~W7_ןt8 C4Ӻgaq)(\LƧny.>)j/`vOx%)b]UUi_x1v.KBK%BRn*{,.dn>)"\J!&|7B$ #X,Bo޴.O)+Ѝ,¹Z%SL}R2 #X ` ,%"X ` ,% RQ,I'%Sy?<>d,E@Rbݡ}'%SL}q"X ` ,%"X ` ,¥GK}R2I'@O(%YKd,ETcw6@I'%Sy?<%"X ` ,%"X `p)%RL}R2 C @YKd,. G}R2I'@O(` ,%"X ` ,%"X*\J{D'%SL}qPE@YK1vnQL}R2 #X ` ,%"XΓs9~4n/k\M!F>˶mo,n!r(;mSuۥ _nyo;ߏsk.>P.T{B $V[7k!`H_u>M;g}۹1{}^\',SL}qP]{C‹}՞6B]kզ!S w3Smu&\VOi]]מupeĭn~%ӷ nn7ЮҺ7m[{wYovm,?}9RbSz<~y]뺹v]}yתo|9^d ',%"X ` , GQb ׺GD1Ҷ@i9|ZX~j8շ.6[scmRJO~b gmmgݶ=n[{ѷeˡj`]_vNs'I'@O(,[Φf}ڳBk(ilgusASo%N/gl541lVS}N[йum\8fX:S#{ncM}m1{j}KRL}qtͩ7Tkrz?M!njIKu]cL!.AYKm@)n_%m1ƶY ¥. [7-B}R s#p5N/f٨AJ2W ʣ.vtww`p;m*|%`%!Hjj|7 ` SSu]UUլ뇺ͻ+{,pRJaסp߿^UUpx;omJPp8`G&,ŰcoBqܶmu]4y^Kl6t]C%d,p6h6 潕apK[Qmm6)j:O8~q"X ` ,%"X ` ,¥GK}q  ` ,% c>8~q"X ` ,%"X ` ,¥GK}q  ` ,% c>8~q"X ` ,%"X ` `O.w `p)%R91v'@O(%<%`8v%g `Qs!|gx6pp]';c$X*\sI'/󫫫_wMc !Cm ڿBqyܶ2P3yʗcS!1c0E,^K@qKx<~>ImUUmϷt,z,Ӯ몪fpUUݕV$X*KE}lR { px3 nSJ͇5PJÎBq۶MuZK/k@q4]5PIMPt:fyojy@K1 RϧmA۶MJZP@= ` oRJlNKd,E@YKd,n0P kr  ` ,% c>xM?<%"X ` ,%"X `p)%R&PJ"X `p1m>#X ` ,%"X ` ,% RQ, kr  ` ,% c>xM?<%"X ` ,%"X `p)%R&PJ"X `p1m>#X ` ,%"X ` ,% RQ, kr  ` ,% c>xM?<%"X ` ,%"X `p)%R&PJ#cs=}T[cY?T &X8BBEдyAT8'L}e5P2 (O( "T^Ss|Jkrpuw8N,Ժ+-O޽l5,*/ W5 lk l#&X*nL}@ٞ+`1O%'LJNSyԅBh>]}mݕ,|Ǧu>v۷o?u6@a?x<~4mA|7v;J?i^K, W8WdGJziRJ1|9'%&;N)*S.'9{(nжf)` A%׸t:zVUU>\).pfK^cbwWUUB 8%`/777ߝIO !pfK^WUմiiyj:ﮤ1y7oݽy||hvu]RC7W$X*kL'Oí^x?<>^'t4m6/%3$X2Flu]4NpK^fYӶm4NpK1d+Rp=*=xm?<%`/+/A %"X ` ,% RQ, kr  ` ,% c>xM?<%"X ` ,%"X `p)%R&PJ"X `p1m>#X ` ,%"X ` ,% RQ, kr  ` ,% c>xM?<%"X ` ,%"X `p)%R&PJ"X821mAT<'L}cK bng1lVUme1cLKHPhmuR0p~Kq:AV/:TrÞ?޸iY]mUU|%3%X(̗_~¥Ma ^MOq;~wwl6dr4C4Ӫf^Kz.'@޾}p`0Ljx< ML뺞-Z.@ڶ[xsH.꺞^\\x7}]ӥ!q!Ί` Brma46M3wדp8q,-"R:F^x}h4Fwחx;oyϵdo^c Gl4x<~qqo///;^K1YAo:3$61ΚF'xh4 wUU}c(\k RJp~0 ۦi8 _IT8@S2 a6pjZUt~5YqpbByKFm6]Up)ävSi& ΐ` بmۦm:R$-pKFm6)zqH| X6J)UJ1#Xq58,E@YKd,E@RRJ#>xM?<>d,E@Rbݡ}''G@YKd,E@YKd,.=XC @YKd,. G}ByKd,E@YKd,E@RRJ#>xM?<>d,E@Rbݡ}''G@YKd,E@YKd,.=XC @YKd,. G}ByKd,E@YKd,Ek1llRQ,)(@ByKBjv6pުꖃ!gm4=t biӅh%g}Y=~uȜ喇-?޸i]׳c.MTcg1R/ϷK{[[~W=>>~pussW_}ٟٟ߿t:`4ʹ|X\1 88[o߾xp8^X !cWUլii!lv4h0BPz6C]N`pqqn<_yviȝ (` RJu,9t[8 +cma8ބ›7o~4ʹmA]dn27M<̃ńއ}"=K ObccNT8|P2 BK%m)XUU `iwR7?D%R0oRw~aEpKc}uJ'Xx]B#dZ8vR2  'G@YKd,E@YKd,.=X '@O(%YKd,ETcw6@  'G@YKd,E@YKd,.=X '@O(%YKd,ETcw6@  'G@YKd,E@YKd,.=X '@O(%YKd,ETcw6@  'G@YKd,E@YKd,.=X '@O(%YKd,ETcw6@  'G@YKd,E@YKd,.=X '@O(%YKd,ETcw6@  'G@YK'NWQ4n/C4҉([>N8z!P.Ƙ?]!L#KNJ[f._]Y~׉x򺮧u]bK%xR}J)U¥Me[n%._ͧ_}go׿߿t:`4ʹ|X\1 xu7{Jr ]S$o۩^[[2FR: ?|]u*0_a*ȥmF5Lɪ'ߐ@pCWO$BPzֶh41Vt8 &ףmۇyRt0K xKWvK1TUU4ͤmx|}4ʹA]x|=۶mf2z#X*\Gl1)lyd8OOp8]KmޏFp,L98~qx-㬮I۶!puul6of2 ڶzcTw@&/b[ !Lcw!0`qͪu]O뺞u=] T-K y媪.~7}W!>O}b ` |I)~*yTCx@%^E|ޤfe1%d,E@Rb蟔Ld'@O(` ,%"X ` ,%"X*\J{DOJR2 'LJ,%"X*\?w`韔L ,E@YKdiB1!ҧq{>&g;#Xj5@YMv^Z@Zk` 8msϲ}ucmkuw8\nM\w961ywbqĔbۺ%:d['sngrclTL  ` ,%"X ` ,͹pJ1~//O)Tg}_v,߷n}sp)%:F1ۋwn;BW|7;|{y?<> r[g5Zo(h.?vp&RI]UU]Ϸt$X5ثbǏ?jVǪfr%Jp /ZJ);Ǐ?jֶ]۶.y(|8'ŰgR!qu]}4yAK{Bl6J%$X͘Nl6W+U 4Dt]7躮I)UKJpry?<> %C8KoK)9 "X*\?w` '@O(` ,%"X ` ,%"X*\J{DO<y?<>d,E@Rby?8~ByKd,E@YKd,E@RRJ#q  ` ,% cD #X ` ,%"X ` ,%5'¥GK޲c09~qPALi@)^%F1ƮY ¥> {L&u]O꺞VU5 aE|bJi/]۶u.WUBq>璪%8%t4}.[:s6,<*>~﫪fpUUJ" s8ۥ¾C>~㪪fm޵m{; RJ͇i?<>#=BGW뚾/Pm TKk@q8}7PIMPt:fyR< T] buݠ&T-U+'GPE!pK"X(|s9YKd,E@YK[: FBy|("X ` ,s6?8%PYKd,E@YKd,ETbO(%YKd,ETc6&'#X ` ,%"X ` ,% RQ,Sr  ` ,% cDByKd,E@YKd,E@RRJ#pJ?<>d,E@RbO(` ,%"X ` ,%"X*\J{DON'LJ,%"X*\?w`Sr ,E@YKd,E@YKK)y( )9P`1BBտ)J` Z-EشiSxKsGOJ"ҹ!89xEJ)5n#PNɉ )@Z7q>ZwɻS9x JspfpJ%xev͵ &X*uJ@َ0'gC SyԇBh~_] rxlJ޸W_}/>|j} Gu4w (ůX0pAOx9RJձI))Zgʣb ^8TBcLSdbo7FBǬPZAR%7G"t:zVUU>]).5L ,yxx"}UU}!'6$,r&?!.b ,qUUӦiyhfRUt^Z 1y7p/=>>^t]}_K;䋐KGWV p< PJ t:f}$X2͆l}4N fziP ,.蒭Ki^TTԜByKAV%WxKAI,"X ` ,%"X*\J{DON'LJ,%"X*\?w`Sr ,E@YKd,E@YKK)y( )9PE@YK1nlpJ?<%"X ` ,%"X `p)%?8%PJ"X `p1mMON'G@YKd,E@YKd,.=X'C @YK/L?wB,ω#%?N+;R㹛ijv6c1lg$X(p8p6:`_ x{Kyw<d۫CE,<(~M.Q%|?.m v 廤?W_?gYcM4iUUUK*&@>|p8J0 u]UUun4I4ӺgKUK%7FP1ޜp81...GM۶u]Oą `xSK/@J!$]4m񇫫Ƕm~K!qn.'$X*Ss?Nϱsw~<Oڶmf2k$<;@(` تih?_^^y4}bj:7H lc5Ms??px3 Pj M,[`0 wMuy&RMOxfY=UUMWW'Glu]}5d,E@RbO(` XK@YKd,E@YKK)y( )9PE@YK1nlpJ?<%"X ` ,%"X `p)%?8%PJ"X `p1mMON'G@YKd,E@YKd,.=X'C @YKd,.؟ )9"X ` ,%"X ` ,¥GKBy|("X ` ,s6?8%PYKd,E@YKd,.g 5nۥK5(@ByKBjv6UU/1s6`/%޴pmB1OBH x޿],WޯC뺞u=1K@K1S*opi[-W=>>h2\{?7?~t:m`04M3z6b%%?<%ެ>h46gB)WU5kfBn64M3 ht׶iiUU3C@u0ؙ#CY۶!pyyycp0L...GM۶niȝ (` RJuW-+ckfҶm!{OMLu=mۦi&`i1y%Xalyd8.`mha8.I _cubꏳl}UUU4d0ܵm{W$8 !&Xx>al.01ޅxoB1| $1.؛ (`pd'^R!.͗bUU1̃~*BKo.iޤ>Ro1%p&d'@O۷._dru{{__Oi; ii]׳c.qr ,jp<BH1ƾY4Ӷm'n64M3 ht׶iiUU3CiKIx&yu=ka4\^^^t: htӶüj[&8` 8)6[1i&mގi] 꺞x|ݶm4yO^RbqĔ)SȖM_O?EԶh40$ޜ8~ByK}qVmBl6}_UU7M3 wmu=1B ` n1-UUՅ1ƻB~BaUUӺu]O꺞. `%^ż]UUMÛB >͇u1|_OkBqiBu]?TUoRUUiE<` dۆ KFs7RJ#R2y?<>d,E@Rv\Jd'%?8~ByKd,E@YKd,E@RRJ#R2y?<>d,E@Rb蟔Ld'@O(` ,%"X ` ,%"X*\J{DOJR2 'LJ,%"X*\?w`韔L ,E@YKd,E@YKK)y(IOJqPE@YK1nlR2y?<%"X ` ,%"X `p)%?)IOvmo>vsu)u^]}g}? :Pa98tcvmkϾmZZMٵm.ߵmuu^}/'/*@ٜ.=XuŦ!6-|Ye*ݿO`u]w9+꟰L "iuwYoWlvݟۦu,c_Z^o[9C0PC+\r}ëM_W}ZUBٶjלWlI=p6jm3hCuϺnhz/ڴMCӶ Y5oҺd}6s;B'xT,.GL?gg[t貜}y)'%?8~By| "X ` ,%"X^C` 8ՐHh4nۥs5ԆO}mL f9Z }5TZ2|KYEl 1xT&}qXMfMCx?) ʣb 8hcIYKd,E@Y\xQb }ǿ bϱmJT/5ϔB.cBrq1ˁr(hPgmΦ}yR8E}uAq)jnB}uNv: h5׶`  X?wU%=gO@:p8~R2 'Ggyj`nenk;E%2y7p6s&mZq};iu^3_"XH@KˤP60@K&(.$X[W1U[1Ry?<%,C &nW_B\^=m'%^rBs$`L%^cT}S!c.@T n()=Bcy!F]׵)&T/ӰX?8~ByT,P瘣k2\u=zRiNR ` .8r0ܵm[B}UU)s.,ft4}.[:sE,Uǟ}_UU5{j6/WR,𢥔¾C>~㪪fm޵m{; RJ͇{rU8^G]5}߯^N,=<<\<>>g٠f* `pKI蟼4t8jjy'@O(%oFu뚔RTd,f,* ,ḟ}/t'X*\?w` '@O(` ,%"X ` ,%"X*\J{DO<y?<>d,E@Rby?8~ByKd,E@YKd,E@RRJ#q  ` ,% cD #X ` ,%"X ` @J` C0o`p)%?&Oҙ` yUǟ}_UU5{j6/WRILRJaߡp?~qUUmڶ w)Cr'9E*=BGW뚾AxBy|(^p6 o_J@KNl6W+UJ%X(@u뚔RTP4Rb.1LOYT(O(` oRJl^Kd,E@YKd,ETCiON'LJ,%"X*\?w`Sr ,E@YKd,E@YKK)y( )9PE@YK1nlpJ?<%"X ` ,%"X `p)%?8%PJ"X `p1mMON'G@YKd,E@K:w(` ,¥GKBy|("X ` ,s6?8%PYKd,9w8<>RRX@U7n@_c}B1BHm[^oBUU]JJ)Ϲ/` 7!Pz>4n. p3f;d}9Ͽ[lC1[>oBl㸪~>gSB1F@GN^Z(~Mqmz mf%*ck8Q_|YSuZζǭnnoom~wmgu^C{mS{s밭ס q4Wu뺋1RPSnCum]M-wu[}hir{v ;ƾW߿{||u]yB8!=%xe3 Y-;{[,5>¾{uw<}?tYۿcѐ8NԱm{N5q}}e]ӺgUUͪ}m {j!,¥7I]__ٶC۶`8`Ik ߟ NKSq~_˷?|gv}}x|3o`0}?76%|?ht4Cqqu"-g]wϫ~CNuo޽{n2\LӶ뺺i*НmP2RaE8^cRZ\nJpO^d2fWTLs!(Tbg}ʯn9T%zxxNlv]}_C` ^,~uݠiUU)ԅO_wK!ѹÓ6c}ڶ-Yo׽?WX٬fyZi)\0^g=ϟNluݰ&g~9TX?-򿫿{m7h:$YmmٷmX lV{l5Z}md}]JJ,npN'M_L&t:WwMglϺ6l6puPV'OɔJ` 8͏~x9> GU.$sD;ҹm!!m:th"@ 'XrssNj벇mg5V:Ϯ!pFǘ{}ۻiM>}졕^_C`O x:OF*UU5 _|///?FylQtk$XVB]׳`0ogw!0 &WWW?>>iR珏m 91W[ysyyݻwߍ`0뺫*`+paC`i2nBix<]J}׫뫯=RUUåht;oGM۶u]OKr4ϷIuRJ)Etr6< 뺞uu=OݫVKS[Kx_UU_!Z({~6¢ v/d `+pLF}17ֶ1<4.p\B%IK }4q<~ж\DŽ= 粫LvH1 H)ϵg.z%T|yxeTE@R.q ?8%PYKd,E@YK@Kd,.=X'C @YKd,.؟ )9"X ` ,%"X ` ,¥GKBy|("X ` ,s6?8%PYKd,E@YKd,ETbO(%YKd,ETc6&'#X ` ,%"X ` ,% RQ,Sr  ` 1n@!4n(Q㹛@!T, $%?NY;w;`!Ƙbi9ʣb Pù̃~5`,ߵ_W}-_}uy4Ko` 0~wK‚]Ao{.lx|4ͤi|fRv ÇF׆Q`0뺯Fw~0Lu=[Z.1%u]71vUUޮpxc u]O/..>\\\\Fm꺞. A^RBH9 ûifmޏWWWߏmu8C\NHm{4d>גI RV1Y4x<07ྪ UJi.= `p4}]ן m,[fz.uUUMί71^7DT8d' ]5}Wp)än^J<;PUuMuuJ).¥)HZ ,[u]פR#` *Tͫba%`yTj% JPI } YK[\yJpJ?<>d,E@RbfEp ,E@YKd,E@YKK)y( )9PE@YK1nlpJ?<%"X ` ,%"X `p)%?8%PJ"X `p1mMON'G@YKd,E@YKd,.=X'C @YKd,.؟ )9"X ` ,%"X ` ,¥GKBy|("X ` ,s6?8%PYKd,E@YKd,ETbO(%YKd,ETc6&'nױ>yөwJ?<͹ױO˛umcӯRkmMM5Q"LY,~lZuA֡g-wumm۫{]{U3-/%Xf5Xz> JJվͺne4ضjU ]m VVE )\nϺp_ڧM˯>b e%˺̭tCîc_WŤr ؗYjϦmRqMUUO c6oSEԶ6{}kzKѬǞ(wlkI7ݷG9Үm"KɁrO(%YKd,ET )9*pT?Ǿc%8g` x5үRԬ;٧֭ͫ/ArZ)YW- fynj絺BTw=mZn!˺o{LJ8 *U낇Maº۹yb7=e>Cö,gZ_.8Ԧ Cuw߇ܿi<]CQ9t"PP8 v;9>@UCDzkyE;:i[) #|}C պNmrY =6u\H4ku۪VۿϐmY&+y 6 m;y>WL* r&낊]C:$kj۶l{즰l6܏S{cn'LJXPg=@RbTzJON'G@s,G<9{^/xIo%\Wh%,% bʥCY2cn̯v8E@Rb4rO(` ,%}!KoAs,[J_jTZ7=~uwmmxYUV+wU3-/j@m>] kj8bgߡr*}vnS,?OJkkSgSi;έk!xi̱TDO`;gnCp\?<>Qv$֫2-imTCJZXnӜHΡ:d2}ڠ x "X*\Jf) !:$'Gɬr 89%F/{{muZKW[7})` ۷۟_]]h4n!S!moWer?v:m8uޏTB! NDY<%PZW!~{ %8 Rd'9 UBnc쪪)\p NBIBid2zZiUUO ` Ĕ^K_m]*|%UKKX777?F§ 4xK1污T'h?|8cUUyj%xf%R ㏫m{׶`0K)}J7<$=BGW뚾륪N@ .l}3Jp"%^t:fyR< x~%^]5)jZ 8/֢B88/|)s6RPm,%"X*\?w`m,E@YKd,E@YKK)y( R@YKd,.؟ "X ` ,%"X ` ,¥GKx|) ` ,% cDxKd,E@YKd,E@RRJ#mE@YK1nlm%"Xxc} N?L͹pا_jYm//OJծm7-_w{c7=SPH0er}WimYnZ۶uYCo>uYWʹ| ^ꭆBևAW7YmsЀf5v{~Wñ]]}=0/֦`e5X?o@  iՊ}ڴ!V۰ x̱T$KiXaIJnX?4:}^uUL*!^Cv֭ZųiTnsSUS˜MT|߶ pgΞ`ayncs׭>WJڴuU;Tkm{ֽ/ڻ9-#C/L7!VUu ,JEg^}:,?>rC~:]9uU9x[| ǔKI&u=)Bn T,"_|=Ԟjr;N~m i&㪪}B,z (` +Hv:u Pgq_L|~Mm{pUcL): A”?k;MZ-< 6{W{rmǭivw߄8߿:up tXZҿfmPb]rum[m"Z~Һ`k9dz-?%0?f뺦yYXx^~5X QevUՔdξ/N-}_\\\M41Ƥr $XxF)!l/)4G:d٦}\5uLw;dBH!4§SQ vV,ͳPMurx{}Sn!q떯i߿{||u]7N)U 9]mQT\Br[Ű0mºenCT{\NilON;,w[noo~>.lCJo` r(\i6]Qm׺5$|zZYUU<`:zjvm!`3F) xǪz^__ٶC۶`8cKñOMR ,+s}}x|3o`0}?J9S*:ߵ_2ga[/C>p"{P*tss7޽{7L.iu]4MB螺o?.m v 4Lnoow߽~~?t:q0L.W=\b_,+pp9LFlp|_>h46g§ Y4BFwi`0Fm'ML \=x_et4ڮ뚾䀹"Q: js(4gm>w[\^^^FmUKd&xf(D WC[2Ҥ+rem_s\fY;PsRtTW!s ׼.SUU]4moCݻwjfuݠx<m6M3Y +SPMAIϿvEWYwr[o xM_w}ʥyR&Pz:tbyd8./ϻwpf&QPemY]uKz>++n1Y]ן+8͆}WUUML]۶wu]Ob98@Uu*Aκ!v [}@ij)L! ^0TUUBBa<AJW4M뺞u=z<.ZgLpۆz=>]מuVm>t+gkRJAKc£I! Ûpa4}?? Û`p77] uy>++Bq>\Z1%K0"` yER_n1W|^&X(@14M ht?om]׏!p86Y *`Z9> }BJMC꺞,U, jyh\P`p18bJOR? Ku]KW  O(` ]5}K_>iZ o p2%,BR }ŮyC !@CR.%2GPR0 X%"X*%8'C @YKep58,.؟ )9"X ` ,%"X ` ,¥GKBy|(`c6@s7J"P PUmm_6cl^K1S*`8~8w biӇ%?<%ޤ~:#'C,<z򺮧u]bRP4oҷ~]Ҷ`W`^G'}g7ͿOi; ii]׳c.E,f}Gѵp<BH1ƾY4BFwi`0Fm'ML%޼1@h*uu]ڶ}!c5N`rqqq=nڶ}W-uKCL@K0Rümu_-SUU]4moCݻwjfuݠx<m6M3K D6, WȖM"XpxڶF"X(` 1Y]ן+8͆}WUUML]۶wu]Ob9h9%?vZ cKUUu!iuݠAJW4M뺞u=z<.Z!8 bޮ1>5MS}_ba,-U'P)-=H%R4oRriE" c#T'AQ1v)z׺O(` T#,%"X ` ,% RQ, '@O(%YKd,ETc6&'@O? &͹z򺮧u]bҗ#7\%^M&۟|w7Ǐ?N`0x iu]:Rї#Ms)K)b}UUi!0fM4`0FwmNVU53,.d,R_h_gm>_͇6XM`0\\\\Fmu&qHP/D)Ƙꚦm{;ov]7z:u۶ML_Ҟ' X5X1}7UUvŨmha8.LB q<` ע1꺞m{BHM[Rf9~%^B_͆O>E<` b(UUb˗>K'd,,ۆiBd,k:@Ob6}SJ܍Q@YKd,E@YKd,E@YKر 9.x$WIENDB`././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/heat.rst0000664000175000017500000001075600000000000022005 0ustar00zuulzuul00000000000000==== Heat ==== Installation and Configuration ============================== Devstack will automatically configure heat to support BGPVPN. Other deployments need to add the directory for the python networking_bgpvpn_heat module to ``plugin_dirs`` in the heat config: ``/etc/heat/heat.conf``. This directory can be found out with: .. code-block:: console dirname $(python -c "import networking_bgpvpn_heat as n;print(n.__file__)") Examples ======== Heat Orchestration Template (HOT) example 1 ------------------------------------------- This template has to be run with admin rights and will create a BGPVPN for the current tenant, along with a Network associated with it: .. literalinclude:: ../../../networking_bgpvpn_heat/examples/bgpvpn_test-00.yaml :language: yaml In devstack, this HOT file can be used with cloud admin privileges in the demo project; such privileges can be obtained with the command: .. code-block:: console source openrc admin demo This example can then be run: .. code-block:: console $ heat stack-create networks -f bgpvpn_test-00.yaml +--------------------------------------+------------+--------------------+---------------------+--------------+ | id | stack_name | stack_status | creation_time | updated_time | +--------------------------------------+------------+--------------------+---------------------+--------------+ | 5a6c2bf1-c5da-4f8f-9838-4c3e59d13d41 | networks | CREATE_IN_PROGRESS | 2016-03-02T08:32:52 | None | +--------------------------------------+------------+--------------------+---------------------+--------------+ $ heat stack-list +--------------------------------------+------------+-----------------+---------------------+--------------+ | id | stack_name | stack_status | creation_time | updated_time | +--------------------------------------+------------+-----------------+---------------------+--------------+ | 5a6c2bf1-c5da-4f8f-9838-4c3e59d13d41 | networks | CREATE_COMPLETE | 2016-03-02T08:32:52 | None | +--------------------------------------+------------+-----------------+---------------------+--------------+ Heat Orchestration Template (HOT) example 2 ------------------------------------------- This is a set of two templates: * one that has to be run with admin rights and will create a BGPVPN for the 'demo' tenant: .. literalinclude:: ../../../networking_bgpvpn_heat/examples/bgpvpn_test-04-admin.yaml :language: yaml .. code-block:: console $ source openrc admin admin $ heat stack-create bgpvpn -f bgpvpn_test-04-admin.yaml * one to run as a plain 'demo' tenant user, that will: * create a Network and bind it to the 'default_vpn' BGPVPN * create a second Network connected to a Router, and bind the Router to the 'default_vpn' .. literalinclude:: ../../../networking_bgpvpn_heat/examples/bgpvpn_test-04-tenant.yaml :language: yaml .. code-block:: console $ source openrc demo demo $ heat stack-create networks_bgpvpn -f bgpvpn_test-04-tenant.yaml +--------------------------------------+-----------------+--------------------+---------------------+--------------+ | id | stack_name | stack_status | creation_time | updated_time | +--------------------------------------+-----------------+--------------------+---------------------+--------------+ | a3cf1c1b-ac6c-425c-a4b5-d8ca894539f2 | networks_bgpvpn | CREATE_IN_PROGRESS | 2016-03-02T09:16:39 | None | +--------------------------------------+-----------------+--------------------+---------------------+--------------+ $ openstack bgpvpn list +--------------------------------------+-------------+------+-------------------------------------------+------------------------------------------------+ | id | name | type | networks | routers | +--------------------------------------+-------------+------+-------------------------------------------+------------------------------------------------+ | 473e5218-f4a2-46bd-8086-36d6849ecf8e | default VPN | l3 | [u'5b1af75b-0608-4e03-aac1-2608728be45d'] | [u'cb9c7304-e844-447d-88e9-4a0a2dc14d21'] | +--------------------------------------+-------------+------+-------------------------------------------+------------------------------------------------+ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/horizon.rst0000664000175000017500000000436600000000000022554 0ustar00zuulzuul00000000000000======== Horizon ======== General information =================== Networking-bgpvpn contains the bgpvpn_dashboard plugin for Horizon. It adds a BGPVPN Interconnections panel in the admin section. Admin users can handle BGPVPNs resources through this panel. The operations possible for admin users are: * listing BGPVPN * creating a BGPVPN * editing a BGPVPN * associating or disassociating a BGPVPN to network(s) * associating or disassociating a BGPVPN to router(s) * deleting a BGPVPN For non admin users the plugin adds a BGPVPN Interconnections panel in the Project section under the Network subsection. The operations possible for non admin users are: * listing BGPVPN (display only name, type, networks and routers associations) * editing a BGPVPN (only the name) * associating or disassociating a BGPVPN to network(s) * associating or disassociating a BGPVPN to router(s) Installation and Configuration ============================== Devstack will automatically configure Horizon to enable the Horizon plugin. For others deployments we assume that Horizon and networking-bgpvpn are already installed. Their installation folders are respectively and . Copy configuration file: .. code-block:: shell cp /bgpvpn_dashboard/enabled/_[0-9]*.py /openstack_dashboard/local/enabled/ Configure the policy file for BGPVPN dashboard in OpenStack Dashboard ``local_settings.py``. ```` is a directory which contains configurations for BGPVPN dashboard and the location varies across distributions or deployments. ```` can be found with: ``dirname $(python -c 'import bgpvpn_dashboard as _; print _.__file__')`` .. code-block:: python POLICY_FILES[' networking-bgpvpn'] = '/bgpvpn_dashboard/etc/bgpvpn-horizon.conf' .. note:: If you do not configure ``POLICY_FILES`` in your ``local_settings.py``, you also need to define the default ``POLICY_FILES`` in ``local_settings.py``. If you use the example ``local_settings.py`` file from horizon, what you need is to uncomment ``POLICY_FILES`` (which contains the default values). Restart the web server hosting Horizon. The BGPVPN Interconnections panels will now be in your Horizon dashboard. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/index.rst0000664000175000017500000000023000000000000022155 0ustar00zuulzuul00000000000000================== User Documentation ================== .. toctree:: :maxdepth: 2 overview drivers/index usage horizon heat api ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/overview.rst0000664000175000017500000001056700000000000022732 0ustar00zuulzuul00000000000000.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode ========================================== BGP VPN Interconnection Service Overview ========================================== .. include:: ../introduction.rst Alternatives and related techniques ----------------------------------- Other techniques are available to build VPNs, but the goal of this proposal is to make it possible to create the interconnect we need when the technology of BGP-based VPNs is already used outside an OpenStack cloud. Reminder on BGP VPNs and Route Targets -------------------------------------- BGP-based VPNs allow a network operator to offer a VPN service to a VPN customer, delivering isolating connectivity between multiple sites of this customer. Unlike IPSec or SSL-based VPNs, for instance, these VPNs are not typically built over the Internet, are most often not encrypted, and their creation is not at the hand of the end-user. Here is a reminder on how the connectivity is defined between sites of a VPN (VRFs). In BGP-based VPNs, a set of identifiers called Route Targets are associated with a VPN, and in the typical case identify a VPN ; they can also be used to build other VPN topologies such as hub'n'spoke. Each VRF (Virtual Routing and Forwarding) in a PE (Provider Edge router) imports/exports routes from/to Route Targets. If a VRF imports from a Route Target, BGP IP VPN routes will be imported in this VRF. If a VRF exports to a Route Target, the routes in the VRF will be associated to this Route Target and announced by BGP. Mapping between PEs/CEs and Neutron constructs ---------------------------------------------- As outlined in the overview, how PEs, CEs (Customer Edge router), VRFs map to Neutron constructs will depend on the backend driver used for this service plugin. For instance, with the current bagpipe driver, the PE and VRF functions are implemented on compute nodes and the VMs are acting as CEs. This PE function will BGP-peer with edge IP/MPLS routers, BGP Route Reflectors or other PEs. Bagpipe BGP which implements this function could also be instantiated in network nodes, at the l3agent level, with a BGP speaker on each l3agent; router namespaces could then be considered as CEs. Other backends might want to consider the router as a CE and drive an external PE to peer with the service provider PE, based on information received with this API. It's up to the backend to manage the connection between the CE and the cloud provider PE. Another typical option is where the driver delegates the work to an SDN controller which drives a BGP implementation advertising/consuming the relevant BGP routes and remotely drives the vswitches to setup the datapath accordingly. API and Workflows ----------------- BGP VPN are deployed, and managed by the operator, in particular to manage Route Target identifiers that control the isolation between the different VPNs. Because of this BGP VPN parameters cannot be chosen by tenants, but only by the admin. In addition, network operators may prefer to not expose actual Route Target values to the users. The operation that is let at the hand of a tenant is the association of a BGPVPN resource that it owns with his Neutron Networks or Routers. So there are two workflows, one for the admin, one for a tenant. * Admin/Operator Workflow: Creation of a BGPVPN * the cloud/network admin creates a BGPVPN for a tenant based on contract and OSS information about the VPN for this tenant * at this stage, the list of associated Networks and Routers can be empty * Tenant Workflow: Association of a BGPVPN to Networks and/or Routers, on-demand * the tenant lists the BGPVPNs that it can use * the tenant associates a BGPVPN with one or more Networks or Routers. Sequence diagram summarizing these two workflows: .. image:: figures/workflows_seqdiag.png Component architecture overview ------------------------------- This diagram gives an overview of the architecture: .. image:: figures/components_sdn_blockdiag.png This second diagram depicts how the *bagpipe* reference driver implements its backend: .. image:: figures/overview_blockdiag.png References ---------- .. [RFC4364] BGP/MPLS IP Virtual Private Networks (IP VPNs) http://tools.ietf.org/html/rfc4364 .. [RFC7432] BGP MPLS-Based Ethernet VPN (Ethernet VPNs, a.k.a E-VPN) http://tools.ietf.org/html/rfc7432 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/usage.rst0000664000175000017500000001734200000000000022166 0ustar00zuulzuul00000000000000======== Usage ======== Use from OpenStack CLI ------------------------ Example commands to use by the admin to create a BGPVPN resource: .. code-block:: console openstack bgpvpn create --route-target 64512:1 --project b954279e1e064dc9b8264474cb3e6bd2 openstack bgpvpn list openstack bgpvpn set --name myBGPVPN Example commands to use by the tenant owning the BGPVPN to associate a Network to it: .. code-block:: console openstack bgpvpn network association create myBGPVPN # returns openstack bgpvpn network association list myBGPVPN openstack bgpvpn network association show myBGPVPN openstack bgpvpn network association delete myBGPVPN There are more details in the `OpenStack Client (OSC) documentation for BGPVPN `_. Use from Horizon ---------------- See :doc:`horizon`. Use from Heat ------------- See :doc:`heat`. Use from Python --------------- The python ``neutroclient`` library includes support for the BGPVPN API extensions since Ocata release. .. note:: For older releases, the dynamic extension of ``neutronclient`` provided in ``networking-bgpvpn`` is available. In that case, the methods to list, get, create, delete and update network associations and router associations are different from what is documented here: * different name: ``list_network_associations`` instead of `list_bgpvpn_network_assocs``, and same change for all the methods * order of parameters: BGPVPN UUID as first parameter, association UUID as second parameter These old methods are deprecated. Methods ~~~~~~~ BGPVPN Resources ^^^^^^^^^^^^^^^^ .. csv-table:: API methods for BGPVPN resources :header: Method Name,Description,Input parameter(s),Output "list_bgpvpns()", "Get the list of defined BGPVPN resources for the current tenant. An optional list of BGPVPN parameters can be used as filter.", "1. Use \**kwargs as filter, e.g. list_bgpvpn(param1=val1, param2=val2,...) (Optional)", "Dictionary of BGPVPN attributes" "create_bgpvpn()", "Create a BGPVPN resource for the current tenant. Extra information about the BGPVPN resource can be provided as input.", "1. Dictionary of BGPVPN attributes (Optional)", "Dictionary of BGPVPN attributes" "show_bgpvpn()", "Get all information for a given BGPVPN.", "1. UUID of the said BGPVPN", "Dictionary of BGPVPN attributes related to the BGPVPN provided as input" "update_bgpvpn()", "Update the BGPVPN resource with the parameters provided as input.", "1. UUID of the said BGPVPN 2. Dictionary of BGPVPN attributes to be updated", "Dictionary of BGPVPN attributes" "delete_bgpvpn()", "Delete a given BGPVPN resource of which the UUID is provided as input.", "1. UUID of the said BGPVPN", "Boolean" Network Association Resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. csv-table:: API methods for Network association resources :header: Method Name,Description,Input parameter(s),Output "list_bgpvpn_network_assocs()", "Get the list of defined NETWORK ASSOCIATION resources for a given BGPVPN. An optional list of NETWORK ASSOCIATION parameters can be used as filter.", "1. UUID of the BGPVPN 2. Use \**kwargs as filter, e.g. list_bgpvpn_network_assocs( BGPVPN UUID, param1=val1, param2=val2,...) (Optional)", "List of dictionaries of NETWORK ASSOCIATION attributes, one of each related to a given BGPVPN" "create_bgpvpn_network_assoc()", "Create a NETWORK ASSOCIATION resource for a given BGPVPN. Network UUID must be defined, provided in a NETWORK ASSOCIATION resource as input parameter.", "1. UUID of the said BGPVPN 2. Dictionary of NETWORK ASSOCIATION parameters", "Dictionary of NETWORK ASSOCIATION attributes" "show_bgpvpn_network_assoc()", "Get all parameters for a given NETWORK ASSOCIATION.", "1. UUID of the BGPVPN resource 2. UUID of the NETWORK ASSOCIATION resource", "Dictionary of NETWORK ASSOCIATION parameters" "update_bgpvpn_network_assoc()", "Update the parameters of the NETWORK ASSOCIATION resource provided as input.", "1. UUID of the BGPVPN resource 2. UUID of the NETWORK ASSOCIATION resource, 3. Dictionary of NETWORK ASSOCIATION parameters", "Dictionary of NETWORK ASSOCIATION parameters" "delete_bgpvpn_network_assoc()", "Delete a given NETWORK ASSOCIATION resource of which the UUID is provided as input.", "1. UUID of the BGPVPN resource 2. UUID of the NETWORK ASSOCIATION resource", "Boolean" Router Association Resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. csv-table:: API methods for Router association resources :header: Method Name,Description,Input parameter(s),Output "list_bgpvpn_router_assocs()", "Get the list of defined ROUTER ASSOCIATION resources for a given BGPVPN. An optional list of ROUTER ASSOCIATION parameters can be used as filter.", "1. UUID of the BGPVPN 2. Use \**kwargs as filter, e.g. list_bgpvpn_router_assocs( BGPVPN UUID, param1=val1, param2=val2,...) (Optional)", "List of dictionaries of ROUTER ASSOCIATION attributes, one of each related to a given BGPVPN" "create_bgpvpn_router_assoc()", "Create a ROUTER ASSOCIATION resource for a given BGPVPN. Router UUID must be defined, provided in a ROUTER ASSOCIATION resource as input parameter.", "1. UUID of the said BGPVPN 2. Dictionary of ROUTER ASSOCIATION parameters", "Dictionary of ROUTER ASSOCIATION attributes" "show_bgpvpn_router_assoc()", "Get all parameters for a given ROUTER ASSOCIATION.", "1. UUID of the BGPVPN resource 2. UUID of the ROUTER ASSOCIATION resource", "Dictionary of ROUTER ASSOCIATION parameters" "update_bgpvpn_router_assoc()", "Update the parameters of the ROUTER ASSOCIATION resource provided as input.", "1. UUID of the BGPVPN resource 2. UUID of the ROUTER ASSOCIATION resource, 3. Dictionary of ROUTER ASSOCIATION parameters", "Dictionary of ROUTER ASSOCIATION parameters" "delete_bgpvpn_router_assoc()", "Delete a given ROUTER ASSOCIATION resource of which the UUID is provided as input.", "1. UUID of the BGPVPN resource 2. UUID of the ROUTER ASSOCIATION resource", "Boolean" Port Association Resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. csv-table:: API methods for Port association resources :header: Method Name,Description,Input parameter(s),Output "list_bgpvpn_port_assocs()", "Get the list of defined PORT ASSOCIATION resources for a given BGPVPN. An optional list of PORT ASSOCIATION parameters can be used as filter.", "1. UUID of the BGPVPN 2. Use \**kwargs as filter, e.g. list_bgpvpn_port_assocs( BGPVPN UUID, param1=val1, param2=val2,...) (Optional)", "List of dictionaries of PORT ASSOCIATION attributes, one of each related to a given BGPVPN" "create_bgpvpn_port_assoc()", "Create a PORT ASSOCIATION resource for a given BGPVPN. Port UUID must be defined, provided in a PORT ASSOCIATION resource as input parameter.", "1. UUID of the said BGPVPN 2. Dictionary of PORT ASSOCIATION parameters", "Dictionary of PORT ASSOCIATION attributes" "show_bgpvpn_port_assoc()", "Get all parameters for a given PORT ASSOCIATION.", "1. UUID of the BGPVPN resource 2. UUID of the PORT ASSOCIATION resource", "Dictionary of PORT ASSOCIATION parameters" "update_bgpvpn_port_assoc()", "Update the parameters of the PORT ASSOCIATION resource provided as input.", "1. UUID of the BGPVPN resource 2. UUID of the PORT ASSOCIATION resource, 3. Dictionary of PORT ASSOCIATION parameters", "Dictionary of PORT ASSOCIATION parameters" "delete_bgpvpn_port_assoc()", "Delete a given PORT ASSOCIATION resource of which the UUID is provided as input.", "1. UUID of the BGPVPN resource 2. UUID of the PORT ASSOCIATION resource", "Boolean" Examples ~~~~~~~~ BGPVPN + Network Association Resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../samples/bgpvpn-sample01.py ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/doc/source/user/workflows.seqdiag0000664000175000017500000000423500000000000023721 0ustar00zuulzuul00000000000000diagram { span_width = 40; os-admin [label="Openstack Admin",color="lightgray"]; tenant [label="Openstack Tenant X",color="lightblue"]; api [label="Neutron BGPVPN API",color="red"]; driver [label="BGPVPN Driver for\nbackend Foo",color="red"]; backend [label="BGPVPN Backend Foo\n",color="orange"]; bgppeers [label="BGP Peers",color="green"]; backend --> bgppeers[label="BGP peerings",color=green]; backend <-- bgppeers[rightnote="BGP sessions live in parallel\nto BGPVPN service plugin",color=green]; os-admin -> api [label="POST: create a BGP VPN\nresource corresponding to a\nBGP VPN",color=blue]; api -> driver [leftnote="persist resource"]; driver --> backend [label="(driver-backend exchanges\nvarying bw. backends)"]; driver <-- backend; api <-- driver; os-admin <-- api [label="BGPVPN Y",color=blue]; os-admin -> api [label="UPDATE: set tenant X as\nowner of BGPVPN Y",color=blue]; api -> driver; driver --> backend [label="(?)"]; driver <-- backend; api <-- driver; os-admin <-- api; tenant -> api [label="GET:Learns that it\nowns BGPVPN Y",color=blue]; api -> driver; driver --> backend [label="(?)"]; driver <-- backend; api <-- driver; tenant <-- api; tenant -> api [label="UPDATE:Associate BGPVPN Y to\nnetwork Z",color=blue]; api -> driver; driver -> backend [rightnote="now ready to interconnect\nNetwork Z and BGPVPN Y"]; driver <-- backend; backend -> bgppeers[label="MP-BGP VPNv4 routes\ntoward Network Z exported\nto BGP VPN Y",color=green]; backend <-- bgppeers[label="MP-BGP VPNv4 routes\nfrom BGP VPN Y prefixes", leftnote="forwarding plane setup\n(e.g. MPLS/GRE)",color=green]; api <-- driver; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/etc/0000775000175000017500000000000000000000000016051 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/etc/README.txt0000664000175000017500000000102300000000000017543 0ustar00zuulzuul00000000000000To generate the sample networking-bgpvpn configuration files and the sample policy file, run the following commands respectively from the top level of the networking-bgpvpn directory: tox -e genconfig tox -e genpolicy If a 'tox' environment is unavailable, then you can run the following commands respectively instead to generate the configuration files: oslo-config-generator --config-file etc/oslo-config-generator/networking-bgpvpn.conf oslopolicy-sample-generator --config-file=etc/oslo-policy-generator/policy.conf ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/etc/neutron/0000775000175000017500000000000000000000000017543 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/etc/neutron/networking_bgpvpn.conf0000664000175000017500000000046000000000000024155 0ustar00zuulzuul00000000000000[service_providers] # both cannot be default, please choose: service_provider=BGPVPN:Dummy:networking_bgpvpn.neutron.services.service_drivers.driver_api.BGPVPNDriver:default #service_provider=BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe.BaGPipeBGPVPNDriver:default ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/etc/oslo-config-generator/0000775000175000017500000000000000000000000022254 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/etc/oslo-config-generator/networking-bgpvpn.conf0000664000175000017500000000020200000000000026576 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/neutron/networking-bgpvpn.conf.sample wrap_width = 79 namespace = networking-bgpvpn.service_provider ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/etc/oslo-policy-generator/0000775000175000017500000000000000000000000022306 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/etc/oslo-policy-generator/policy.conf0000664000175000017500000000011500000000000024451 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/policy.yaml.sample namespace = networking-bgpvpn ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4939785 networking-bgpvpn-21.0.0/networking_bgpvpn/0000775000175000017500000000000000000000000021041 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/__init__.py0000664000175000017500000000121000000000000023144 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo( 'networking_bgpvpn').version_string() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/_i18n.py0000664000175000017500000000256600000000000022342 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import oslo_i18n DOMAIN = "networking_bgpvpn" _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" # requires oslo.i18n >=2.1.0 _C = _translators.contextual_form # The plural translation function using the name "_P" # requires oslo.i18n >=2.1.0 _P = _translators.plural_form # Translators for log levels. # # The abbreviated names are meant to reflect the usual use of a short # name like '_'. The "L" is for "log" and the other letter comes from # the level. _LI = _translators.log_info _LW = _translators.log_warning _LE = _translators.log_error _LC = _translators.log_critical def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4619784 networking-bgpvpn-21.0.0/networking_bgpvpn/locale/0000775000175000017500000000000000000000000022300 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4619784 networking-bgpvpn-21.0.0/networking_bgpvpn/locale/en_GB/0000775000175000017500000000000000000000000023252 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000025037 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/locale/en_GB/LC_MESSAGES/networking_bgpvpn.po0000664000175000017500000001305600000000000031147 0ustar00zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: networking-bgpvpn VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2019-12-05 06:33+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-09 11:27+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #, python-format msgid "%(method)s failed." msgstr "%(method)s failed." msgid "BGP VPN type selection between L3VPN (l3) and EVPN (l2), default:l3" msgstr "BGP VPN type selection between L3VPN (l3) and EVPN (l2), default:l3" #, python-format msgid "BGPVPN %(driver)s driver does not support %(type)s type" msgstr "BGPVPN %(driver)s driver does not support %(type)s type" #, python-format msgid "" "BGPVPN %(driver)s driver does not support multiple router association with a " "bgpvpn" msgstr "" "BGPVPN %(driver)s driver does not support multiple router association with a " "BGPVPN" #, python-format msgid "BGPVPN %(driver)s driver does not support router associations" msgstr "BGPVPN %(driver)s driver does not support router associations" #, python-format msgid "" "BGPVPN %(driver)s driver does not support to fetch BGPVPNs associated to " "network id %(net_id)" msgstr "" "BGPVPN %(driver)s driver does not support to fetch BGPVPNs associated to " "network id %(net_id)" #, python-format msgid "" "BGPVPN %(driver)s driver does not support to manually set route distinguisher" msgstr "" "BGPVPN %(driver)s driver does not support to manually set route distinguisher" #, python-format msgid "BGPVPN %(id)s could not be found" msgstr "BGPVPN %(id)s could not be found" #, python-format msgid "" "BGPVPN network association %(id)s could not be found for BGPVPN %(bgpvpn_id)s" msgstr "" "BGPVPN network association %(id)s could not be found for BGPVPN %(bgpvpn_id)s" #, python-format msgid "" "BGPVPN port association %(id)s could not be found for BGPVPN %(bgpvpn_id)s" msgstr "" "BGPVPN port association %(id)s could not be found for BGPVPN %(bgpvpn_id)s" #, python-format msgid "" "BGPVPN router association %(id)s could not be found for BGPVPN %(bgpvpn_id)s" msgstr "" "BGPVPN router association %(id)s could not be found for BGPVPN %(bgpvpn_id)s" msgid "ID or name of the BGPVPN." msgstr "ID or name of the BGPVPN." msgid "ID or name of the network." msgstr "ID or name of the network." msgid "ID or name of the router." msgstr "ID or name of the router." msgid "" "It is not allowed to add an interface to a router if both the router and the " "network are bound to an L3 BGPVPN." msgstr "" "It is not allowed to add an interface to a router if both the router and the " "network are bound to an L3 BGPVPN." msgid "" "List of RDs that will be used to advertize VPN routes.Usage: -- --route-" "distinguishers list=true : : ..." msgstr "" "List of RDs that will be used to advertise VPN routes.Usage: -- --route-" "distinguishers list=true : : ..." msgid "" "List of additional Route Targets to export to. Usage: -- --export-targets " "list=true : : ..." msgstr "" "List of additional Route Targets to export to. Usage: -- --export-targets " "list=true : : ..." msgid "" "List of additional Route Targets to import from. Usage: -- --import-targets " "list=true : : ..." msgstr "" "List of additional Route Targets to import from. Usage: -- --import-targets " "list=true : : ..." msgid "Name of the BGP VPN" msgstr "Name of the BGP VPN" #, python-format msgid "" "Network %(network)s already associated with %(bgpvpn)s. BGPVPN %(driver)s " "driver does not support same network associated to multiple bgpvpns" msgstr "" "Network %(network)s already associated with %(bgpvpn)s. BGPVPN %(driver)s " "driver does not support same network associated to multiple BGPVPNs" msgid "" "Route Targets list to import/export for this BGP VPN. Usage: -- --route-" "targets list=true : : ..." msgstr "" "Route Targets list to import/export for this BGP VPN. Usage: -- --route-" "targets list=true : : ..." #, python-format msgid "bgpvpn specified in route does not belong to the tenant (%(bgpvpn_id)s)" msgstr "" "BGPVPN specified in route does not belong to the tenant (%(bgpvpn_id)s)" #, python-format msgid "bgpvpn specified in route does not exist (%(bgpvpn_id)s)" msgstr "BGPVPN specified in route does not exist (%(bgpvpn_id)s)" #, python-format msgid "" "bgpvpn specified in route is of type %(route_bgpvpn_type)s, differing from " "type of associated BGPVPN %(bgpvpn_type)s)" msgstr "" "BGPVPN specified in route is of type %(route_bgpvpn_type)s, differing from " "type of associated BGPVPN %(bgpvpn_type)s)" msgid "driver does not support associating an externalnetwork to a BGPVPN" msgstr "driver does not support associating an external network to a BGPVPN" #, python-format msgid "network %(net_id)s is already associated to BGPVPN %(bgpvpn_id)s" msgstr "network %(net_id)s is already associated to BGPVPN %(bgpvpn_id)s" #, python-format msgid "port %(port_id)s is already associated to BGPVPN %(bgpvpn_id)s" msgstr "port %(port_id)s is already associated to BGPVPN %(bgpvpn_id)s" #, python-format msgid "router %(router_id)s is already associated to BGPVPN %(bgpvpn_id)s" msgstr "router %(router_id)s is already associated to BGPVPN %(bgpvpn_id)s" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/0000775000175000017500000000000000000000000022533 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/__init__.py0000664000175000017500000000000000000000000024632 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/0000775000175000017500000000000000000000000023120 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/__init__.py0000664000175000017500000000000000000000000025217 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/bgpvpn_db.py0000664000175000017500000006141300000000000025440 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_db import exception as db_exc from oslo_log import log from oslo_utils import uuidutils import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.orm import exc from neutron_lib.api.definitions import bgpvpn as bgpvpn_def from neutron_lib.api.definitions import bgpvpn_routes_control as bgpvpn_rc_def from neutron_lib.api.definitions import bgpvpn_vni as bgpvpn_vni_def from neutron_lib.db import api as db_api from neutron_lib.db import constants as db_const from neutron_lib.db import model_base from neutron_lib.db import model_query from neutron_lib.db import standard_attr from neutron_lib.db import utils as db_utils from neutron_lib.plugins import directory from networking_bgpvpn.neutron.extensions import bgpvpn as bgpvpn_ext from networking_bgpvpn.neutron.extensions\ import bgpvpn_routes_control as bgpvpn_rc_ext from networking_bgpvpn.neutron.services.common import utils LOG = log.getLogger(__name__) class HasProjectNotNullable(model_base.HasProject): project_id = sa.Column(sa.String(db_const.PROJECT_ID_FIELD_SIZE), index=True, nullable=False) class BGPVPNNetAssociation(standard_attr.HasStandardAttributes, model_base.BASEV2, model_base.HasId, HasProjectNotNullable): """Represents the association between a bgpvpn and a network.""" __tablename__ = 'bgpvpn_network_associations' bgpvpn_id = sa.Column(sa.String(36), sa.ForeignKey('bgpvpns.id', ondelete='CASCADE'), nullable=False) network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id', ondelete='CASCADE'), nullable=False) sa.UniqueConstraint(bgpvpn_id, network_id) network = orm.relationship("Network", backref=orm.backref('bgpvpn_associations', cascade='all'), lazy='joined',) # standard attributes support: api_collections = [] api_sub_resources = [bgpvpn_def.NETWORK_ASSOCIATIONS] collection_resource_map = {bgpvpn_def.NETWORK_ASSOCIATIONS: bgpvpn_def.NETWORK_ASSOCIATION} class BGPVPNRouterAssociation(standard_attr.HasStandardAttributes, model_base.BASEV2, model_base.HasId, HasProjectNotNullable): """Represents the association between a bgpvpn and a router.""" __tablename__ = 'bgpvpn_router_associations' bgpvpn_id = sa.Column(sa.String(36), sa.ForeignKey('bgpvpns.id', ondelete='CASCADE'), nullable=False) router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id', ondelete='CASCADE'), nullable=False) sa.UniqueConstraint(bgpvpn_id, router_id) advertise_extra_routes = sa.Column(sa.Boolean(), nullable=False, server_default=sa.true()) router = orm.relationship("Router", backref=orm.backref('bgpvpn_associations', cascade='all'), lazy='joined',) # standard attributes support: api_collections = [] api_sub_resources = [bgpvpn_def.ROUTER_ASSOCIATIONS] collection_resource_map = {bgpvpn_def.ROUTER_ASSOCIATIONS: bgpvpn_def.ROUTER_ASSOCIATION} class BGPVPNPortAssociation(standard_attr.HasStandardAttributes, model_base.BASEV2, model_base.HasId, HasProjectNotNullable): """Represents the association between a bgpvpn and a port.""" __tablename__ = 'bgpvpn_port_associations' bgpvpn_id = sa.Column(sa.String(36), sa.ForeignKey('bgpvpns.id', ondelete='CASCADE'), nullable=False) port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id', ondelete='CASCADE'), nullable=False) sa.UniqueConstraint(bgpvpn_id, port_id) advertise_fixed_ips = sa.Column(sa.Boolean(), nullable=False, server_default=sa.true()) port = orm.relationship("Port", backref=orm.backref('bgpvpn_associations', cascade='all'), lazy='joined',) # standard attributes support: api_collections = [] api_sub_resources = [bgpvpn_rc_def.PORT_ASSOCIATIONS] collection_resource_map = {bgpvpn_rc_def.PORT_ASSOCIATIONS: bgpvpn_rc_def.PORT_ASSOCIATION} class BGPVPN(standard_attr.HasStandardAttributes, model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a BGPVPN Object.""" name = sa.Column(sa.String(255)) type = sa.Column(sa.Enum("l2", "l3", name="vpn_types"), nullable=False) route_targets = sa.Column(sa.String(255), nullable=False) import_targets = sa.Column(sa.String(255), nullable=True) export_targets = sa.Column(sa.String(255), nullable=True) route_distinguishers = sa.Column(sa.String(255), nullable=True) vni = sa.Column(sa.Integer, nullable=True) local_pref = sa.Column(sa.BigInteger, nullable=True) network_associations = orm.relationship("BGPVPNNetAssociation", backref="bgpvpn", lazy='select', cascade='all, delete-orphan') router_associations = orm.relationship("BGPVPNRouterAssociation", backref="bgpvpn", lazy='select', cascade='all, delete-orphan') port_associations = orm.relationship("BGPVPNPortAssociation", backref="bgpvpn", lazy='select', cascade='all, delete-orphan') # standard attributes support: api_collections = [bgpvpn_def.COLLECTION_NAME] collection_resource_map = {bgpvpn_def.COLLECTION_NAME: bgpvpn_def.RESOURCE_NAME} class BGPVPNPortAssociationRoute(model_base.BASEV2, model_base.HasId): """Represents an item of the 'routes' attribute of a port association.""" __tablename__ = 'bgpvpn_port_association_routes' port_association_id = sa.Column( sa.String(length=36), sa.ForeignKey('bgpvpn_port_associations.id', ondelete='CASCADE'), nullable=False) type = sa.Column(sa.Enum(*bgpvpn_rc_def.ROUTE_TYPES, name="bgpvpn_port_association_route_type"), nullable=False) local_pref = sa.Column(sa.BigInteger(), nullable=True) # prefix is NULL unless type is 'prefix' prefix = sa.Column(sa.String(49), nullable=True) # bgpvpn_id is NULL unless type is 'bgpvpn' bgpvpn_id = sa.Column(sa.String(length=36), sa.ForeignKey('bgpvpns.id', ondelete='CASCADE'), nullable=True) port_association = orm.relationship( "BGPVPNPortAssociation", backref=orm.backref('routes', cascade='all'), lazy='joined') bgpvpn = orm.relationship( "BGPVPN", backref=orm.backref("port_association_routes", uselist=False, lazy='select', cascade='all, delete-orphan'), lazy='joined') def _list_bgpvpns_result_filter_hook(query, filters): values = filters and filters.get('networks', []) if values: query = query.join(BGPVPNNetAssociation) query = query.filter(BGPVPNNetAssociation.network_id.in_(values)) values = filters and filters.get('routers', []) if values: query = query.join(BGPVPNRouterAssociation) query = query.filter(BGPVPNRouterAssociation.router_id.in_(values)) values = filters and filters.get('ports', []) if values: query = query.join(BGPVPNRouterAssociation) query = query.filter(BGPVPNPortAssociation.port_id.in_(values)) return query @db_api.CONTEXT_WRITER def _add_port_assoc_route_db_from_dict(context, route, port_association_id): if route['type'] == 'prefix': kwargs = {'prefix': route['prefix']} elif route['type'] == 'bgpvpn': kwargs = {'bgpvpn_id': route['bgpvpn_id']} else: # not reached pass context.session.add(BGPVPNPortAssociationRoute( port_association_id=port_association_id, type=route['type'], local_pref=route.get('local_pref', None), **kwargs )) def port_assoc_route_dict_from_db(route_db): route = { 'type': route_db.type, 'local_pref': route_db.local_pref } if route_db.type == 'prefix': route.update({'prefix': route_db.prefix}) elif route_db.type == 'bgpvpn': route.update({'bgpvpn_id': route_db.bgpvpn_id}) return route class BGPVPNPluginDb(): """BGPVPN service plugin database class using SQLAlchemy models.""" def __new__(cls, *args, **kwargs): model_query.register_hook( BGPVPN, "bgpvpn_filter_by_resource_association", query_hook=None, filter_hook=None, result_filters=_list_bgpvpns_result_filter_hook) return super(BGPVPNPluginDb, cls).__new__(cls, *args, **kwargs) @db_api.CONTEXT_READER def _get_bgpvpns_for_tenant(self, session, tenant_id, fields): try: qry = session.query(BGPVPN) bgpvpns = qry.filter_by(tenant_id=tenant_id) except exc.NoResultFound: return return [self._make_bgpvpn_dict(bgpvpn, fields=fields) for bgpvpn in bgpvpns] @db_api.CONTEXT_READER def _make_bgpvpn_dict(self, bgpvpn_db, fields=None): net_list = [net_assocs.network_id for net_assocs in bgpvpn_db.network_associations] router_list = [router_assocs.router_id for router_assocs in bgpvpn_db.router_associations] port_list = [port_assocs.port_id for port_assocs in bgpvpn_db.port_associations] res = { 'id': bgpvpn_db['id'], 'tenant_id': bgpvpn_db['tenant_id'], 'networks': net_list, 'routers': router_list, 'ports': port_list, 'name': bgpvpn_db['name'], 'type': bgpvpn_db['type'], 'route_targets': utils.rtrd_str2list(bgpvpn_db['route_targets']), 'route_distinguishers': utils.rtrd_str2list(bgpvpn_db['route_distinguishers']), 'import_targets': utils.rtrd_str2list(bgpvpn_db['import_targets']), 'export_targets': utils.rtrd_str2list(bgpvpn_db['export_targets']) } plugin = directory.get_plugin(bgpvpn_def.ALIAS) if utils.is_extension_supported(plugin, bgpvpn_vni_def.ALIAS): res[bgpvpn_vni_def.VNI] = bgpvpn_db.get(bgpvpn_vni_def.VNI) if utils.is_extension_supported(plugin, bgpvpn_rc_def.ALIAS): res[bgpvpn_rc_def.LOCAL_PREF_KEY] = bgpvpn_db.get( bgpvpn_rc_def.LOCAL_PREF_KEY) return db_utils.resource_fields(res, fields) @db_api.CONTEXT_WRITER def create_bgpvpn(self, context, bgpvpn): rt = utils.rtrd_list2str(bgpvpn['route_targets']) i_rt = utils.rtrd_list2str(bgpvpn['import_targets']) e_rt = utils.rtrd_list2str(bgpvpn['export_targets']) rd = utils.rtrd_list2str(bgpvpn.get('route_distinguishers', '')) bgpvpn_db = BGPVPN( id=uuidutils.generate_uuid(), tenant_id=bgpvpn['tenant_id'], name=bgpvpn['name'], type=bgpvpn['type'], route_targets=rt, import_targets=i_rt, export_targets=e_rt, route_distinguishers=rd, vni=bgpvpn.get(bgpvpn_vni_def.VNI), local_pref=bgpvpn.get(bgpvpn_rc_def.LOCAL_PREF_KEY), ) context.session.add(bgpvpn_db) return self._make_bgpvpn_dict(bgpvpn_db) @db_api.CONTEXT_READER def get_bgpvpns(self, context, filters=None, fields=None): return model_query.get_collection( context, BGPVPN, self._make_bgpvpn_dict, filters=filters, fields=fields) @db_api.CONTEXT_READER def _get_bgpvpn(self, context, id): try: return model_query.get_by_id(context, BGPVPN, id) except exc.NoResultFound as no_res: raise bgpvpn_ext.BGPVPNNotFound(id=id) from no_res @db_api.CONTEXT_READER def get_bgpvpn(self, context, id, fields=None): bgpvpn_db = self._get_bgpvpn(context, id) return self._make_bgpvpn_dict(bgpvpn_db, fields) @db_api.CONTEXT_WRITER def update_bgpvpn(self, context, id, bgpvpn): bgpvpn_db = self._get_bgpvpn(context, id) if bgpvpn: # Format Route Target lists to string if 'route_targets' in bgpvpn: rt = utils.rtrd_list2str(bgpvpn['route_targets']) bgpvpn['route_targets'] = rt if 'import_targets' in bgpvpn: i_rt = utils.rtrd_list2str(bgpvpn['import_targets']) bgpvpn['import_targets'] = i_rt if 'export_targets' in bgpvpn: e_rt = utils.rtrd_list2str(bgpvpn['export_targets']) bgpvpn['export_targets'] = e_rt if 'route_distinguishers' in bgpvpn: rd = utils.rtrd_list2str(bgpvpn['route_distinguishers']) bgpvpn['route_distinguishers'] = rd bgpvpn_db.update(bgpvpn) return self._make_bgpvpn_dict(bgpvpn_db) @db_api.CONTEXT_WRITER def delete_bgpvpn(self, context, id): bgpvpn_db = self._get_bgpvpn(context, id) bgpvpn = self._make_bgpvpn_dict(bgpvpn_db) context.session.delete(bgpvpn_db) return bgpvpn @db_api.CONTEXT_READER def _make_net_assoc_dict(self, net_assoc_db, fields=None): res = {'id': net_assoc_db['id'], 'tenant_id': net_assoc_db['tenant_id'], 'bgpvpn_id': net_assoc_db['bgpvpn_id'], 'network_id': net_assoc_db['network_id']} return db_utils.resource_fields(res, fields) @db_api.CONTEXT_READER def _get_net_assoc(self, context, assoc_id, bgpvpn_id): try: query = model_query.query_with_hooks(context, BGPVPNNetAssociation) return query.filter(BGPVPNNetAssociation.id == assoc_id, BGPVPNNetAssociation.bgpvpn_id == bgpvpn_id ).one() except exc.NoResultFound as no_res: raise bgpvpn_ext.BGPVPNNetAssocNotFound( id=assoc_id, bgpvpn_id=bgpvpn_id) from no_res def create_net_assoc(self, context, bgpvpn_id, net_assoc): try: with db_api.CONTEXT_WRITER.using(context): net_assoc_db = BGPVPNNetAssociation( tenant_id=net_assoc['tenant_id'], bgpvpn_id=bgpvpn_id, network_id=net_assoc['network_id']) context.session.add(net_assoc_db) return self._make_net_assoc_dict(net_assoc_db) except db_exc.DBDuplicateEntry as db_dup_exc: LOG.warning("network %(net_id)s is already associated to " "BGPVPN %(bgpvpn_id)s", {'net_id': net_assoc['network_id'], 'bgpvpn_id': bgpvpn_id}) raise bgpvpn_ext.BGPVPNNetAssocAlreadyExists( bgpvpn_id=bgpvpn_id, net_id=net_assoc['network_id']) from db_dup_exc @db_api.CONTEXT_READER def get_net_assoc(self, context, assoc_id, bgpvpn_id, fields=None): net_assoc_db = self._get_net_assoc(context, assoc_id, bgpvpn_id) return self._make_net_assoc_dict(net_assoc_db, fields) @db_api.CONTEXT_READER def get_net_assocs(self, context, bgpvpn_id, filters=None, fields=None): if not filters: filters = {} filters['bgpvpn_id'] = [bgpvpn_id] return model_query.get_collection( context, BGPVPNNetAssociation, self._make_net_assoc_dict, filters, fields) @db_api.CONTEXT_WRITER def delete_net_assoc(self, context, assoc_id, bgpvpn_id): LOG.info("deleting network association %(id)s for " "BGPVPN %(bgpvpn)s", {'id': assoc_id, 'bgpvpn': bgpvpn_id}) net_assoc_db = self._get_net_assoc(context, assoc_id, bgpvpn_id) net_assoc = self._make_net_assoc_dict(net_assoc_db) context.session.delete(net_assoc_db) return net_assoc def _make_router_assoc_dict(self, router_assoc_db, fields=None): res = {'id': router_assoc_db['id'], 'tenant_id': router_assoc_db['tenant_id'], 'bgpvpn_id': router_assoc_db['bgpvpn_id'], 'router_id': router_assoc_db['router_id'], 'advertise_extra_routes': router_assoc_db[ 'advertise_extra_routes'] } return db_utils.resource_fields(res, fields) @db_api.CONTEXT_READER def _get_router_assoc(self, context, assoc_id, bgpvpn_id): try: query = model_query.query_with_hooks( context, BGPVPNRouterAssociation) return query.filter(BGPVPNRouterAssociation.id == assoc_id, BGPVPNRouterAssociation.bgpvpn_id == bgpvpn_id ).one() except exc.NoResultFound as no_res_exc: raise bgpvpn_ext.BGPVPNRouterAssocNotFound( id=assoc_id, bgpvpn_id=bgpvpn_id) from no_res_exc @db_api.CONTEXT_WRITER def create_router_assoc(self, context, bgpvpn_id, router_association): router_id = router_association['router_id'] try: router_assoc_db = BGPVPNRouterAssociation( tenant_id=router_association['tenant_id'], bgpvpn_id=bgpvpn_id, router_id=router_id) context.session.add(router_assoc_db) context.session.flush() return self._make_router_assoc_dict(router_assoc_db) except db_exc.DBDuplicateEntry as db_dup_exc: LOG.warning("router %(router_id)s is already associated to " "BGPVPN %(bgpvpn_id)s", {'router_id': router_id, 'bgpvpn_id': bgpvpn_id}) raise bgpvpn_ext.BGPVPNRouterAssocAlreadyExists( bgpvpn_id=bgpvpn_id, router_id=router_association['router_id']) from db_dup_exc @db_api.CONTEXT_READER def get_router_assoc(self, context, assoc_id, bgpvpn_id, fields=None): router_assoc_db = self._get_router_assoc(context, assoc_id, bgpvpn_id) return self._make_router_assoc_dict(router_assoc_db, fields) @db_api.CONTEXT_READER def get_router_assocs(self, context, bgpvpn_id, filters=None, fields=None): if not filters: filters = {} filters['bgpvpn_id'] = [bgpvpn_id] return model_query.get_collection( context, BGPVPNRouterAssociation, self._make_router_assoc_dict, filters, fields) @db_api.CONTEXT_WRITER def update_router_assoc(self, context, assoc_id, bgpvpn_id, router_assoc): router_assoc_db = self._get_router_assoc(context, assoc_id, bgpvpn_id) router_assoc_db.update(router_assoc) return self._make_router_assoc_dict(router_assoc_db) @db_api.CONTEXT_WRITER def delete_router_assoc(self, context, assoc_id, bgpvpn_id): LOG.info("deleting router association %(id)s for " "BGPVPN %(bgpvpn)s", {'id': assoc_id, 'bgpvpn': bgpvpn_id}) router_assoc_db = self._get_router_assoc(context, assoc_id, bgpvpn_id) router_assoc = self._make_router_assoc_dict(router_assoc_db) context.session.delete(router_assoc_db) return router_assoc @db_api.CONTEXT_READER def _make_port_assoc_dict(self, port_assoc_db, fields=None): routes = [port_assoc_route_dict_from_db(r) for r in port_assoc_db['routes']] res = {'id': port_assoc_db['id'], 'tenant_id': port_assoc_db['tenant_id'], 'bgpvpn_id': port_assoc_db['bgpvpn_id'], 'port_id': port_assoc_db['port_id'], 'advertise_fixed_ips': port_assoc_db['advertise_fixed_ips'], 'routes': routes} return db_utils.resource_fields(res, fields) @db_api.CONTEXT_READER def _get_port_assoc(self, context, assoc_id, bgpvpn_id): try: query = model_query.query_with_hooks( context, BGPVPNPortAssociation) return query.filter(BGPVPNPortAssociation.id == assoc_id, BGPVPNPortAssociation.bgpvpn_id == bgpvpn_id ).one() except exc.NoResultFound as no_res_exc: raise bgpvpn_rc_ext.BGPVPNPortAssocNotFound( id=assoc_id, bgpvpn_id=bgpvpn_id) from no_res_exc def create_port_assoc(self, context, bgpvpn_id, port_association): port_id = port_association['port_id'] advertise_fixed_ips = port_association['advertise_fixed_ips'] try: with db_api.CONTEXT_WRITER.using(context): port_assoc_db = BGPVPNPortAssociation( tenant_id=port_association['tenant_id'], bgpvpn_id=bgpvpn_id, port_id=port_id, advertise_fixed_ips=advertise_fixed_ips) context.session.add(port_assoc_db) except db_exc.DBDuplicateEntry as db_dup_exc: LOG.warning(("port %(port_id)s is already associated to " "BGPVPN %(bgpvpn_id)s"), {'port_id': port_id, 'bgpvpn_id': bgpvpn_id}) raise bgpvpn_rc_ext.BGPVPNPortAssocAlreadyExists( bgpvpn_id=bgpvpn_id, port_id=port_association['port_id']) from db_dup_exc for route in port_association['routes']: _add_port_assoc_route_db_from_dict( context, route, port_assoc_db.id) return self._make_port_assoc_dict(port_assoc_db) @db_api.CONTEXT_READER def get_port_assoc(self, context, assoc_id, bgpvpn_id, fields=None): port_assoc_db = self._get_port_assoc(context, assoc_id, bgpvpn_id) return self._make_port_assoc_dict(port_assoc_db, fields) @db_api.CONTEXT_READER def get_port_assocs(self, context, bgpvpn_id, filters=None, fields=None): if not filters: filters = {} filters['bgpvpn_id'] = [bgpvpn_id] return model_query.get_collection( context, BGPVPNPortAssociation, self._make_port_assoc_dict, filters, fields) def update_port_assoc(self, context, assoc_id, bgpvpn_id, port_assoc): with db_api.CONTEXT_WRITER.using(context): port_assoc_db = self._get_port_assoc(context, assoc_id, bgpvpn_id) for route_db in port_assoc_db.routes: context.session.delete(route_db) for route in port_assoc.pop('routes', []): _add_port_assoc_route_db_from_dict(context, route, assoc_id) port_assoc_db.update(port_assoc) context.session.refresh(port_assoc_db) return self._make_port_assoc_dict(port_assoc_db) @db_api.CONTEXT_WRITER def delete_port_assoc(self, context, assoc_id, bgpvpn_id): LOG.info(("deleting port association %(id)s for " "BGPVPN %(bgpvpn)s"), {'id': assoc_id, 'bgpvpn': bgpvpn_id}) port_assoc_db = self._get_port_assoc(context, assoc_id, bgpvpn_id) port_assoc = self._make_port_assoc_dict(port_assoc_db) context.session.delete(port_assoc_db) return port_assoc ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/head.py0000664000175000017500000000134500000000000024376 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from neutron.db.migration.models import head # pylint: disable=unused-import import networking_bgpvpn.neutron.db.bgpvpn_db # noqa def get_metadata(): return head.model_base.BASEV2.metadata ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/0000775000175000017500000000000000000000000025111 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/__init__.py0000664000175000017500000000000000000000000027210 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/0000775000175000017500000000000000000000000030741 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/README0000664000175000017500000000004600000000000031621 0ustar00zuulzuul00000000000000Generic single-database configuration.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867168.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/__init__.py0000664000175000017500000000000000000000000033040 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/env.py0000664000175000017500000000466100000000000032112 0ustar00zuulzuul00000000000000# Copyright 2014 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from logging import config as logging_config from alembic import context from neutron_lib.db import model_base from oslo_config import cfg from oslo_db.sqlalchemy import session import sqlalchemy as sa from sqlalchemy import event MYSQL_ENGINE = None BGPVPN_VERSION_TABLE = 'alembic_version_bgpvpn' config = context.config neutron_config = config.neutron_config logging_config.fileConfig(config.config_file_name) target_metadata = model_base.BASEV2.metadata def set_mysql_engine(): try: mysql_engine = neutron_config.command.mysql_engine except cfg.NoSuchOptError: mysql_engine = None global MYSQL_ENGINE MYSQL_ENGINE = (mysql_engine or model_base.BASEV2.__table_args__['mysql_engine']) def run_migrations_offline(): set_mysql_engine() kwargs = {} if neutron_config.database.connection: kwargs['url'] = neutron_config.database.connection else: kwargs['dialect_name'] = neutron_config.database.engine kwargs['version_table'] = BGPVPN_VERSION_TABLE context.configure(**kwargs) with context.begin_transaction(): context.run_migrations() @event.listens_for(sa.Table, 'after_parent_attach') def set_storage_engine(target, parent): if MYSQL_ENGINE: target.kwargs['mysql_engine'] = MYSQL_ENGINE def run_migrations_online(): set_mysql_engine() engine = session.create_engine(neutron_config.database.connection) connection = engine.connect() context.configure( connection=connection, target_metadata=target_metadata, version_table=BGPVPN_VERSION_TABLE ) try: with context.begin_transaction(): context.run_migrations() finally: connection.close() engine.dispose() if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/script.py.mako0000664000175000017500000000203700000000000033547 0ustar00zuulzuul00000000000000# Copyright ${create_date.year} # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """${message} Revision ID: ${up_revision} Revises: ${down_revision} Create Date: ${create_date} """ # revision identifiers, used by Alembic. revision = ${repr(up_revision)} down_revision = ${repr(down_revision)} % if branch_labels: branch_labels = ${repr(branch_labels)} % endif from alembic import op import sqlalchemy as sa ${imports if imports else ""} def upgrade(): ${upgrades if upgrades else "pass"} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/0000775000175000017500000000000000000000000032611 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000021100000000000011447 xustar0000000000000000115 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/CONTRACT0000664000175000017500000000001500000000000033745 0ustar00zuulzuul000000000000009d7f1ae5fa56 ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/EXPAND_H0000664000175000017500000000001500000000000033756 0ustar00zuulzuul000000000000007a9482036ecd ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4619784 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/0000775000175000017500000000000000000000000034263 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000022300000000000011452 xustar0000000000000000119 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/contract/ 28 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/0000775000175000017500000000000000000000000034263 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024400000000000011455 xustar0000000000000000142 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/contract/180baa4183e0_initial.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/0000664000175000017500000000165100000000000034270 0ustar00zuulzuul00000000000000# Copyright 2015 Orange # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """contract initial Revision ID: 180baa4183e0 Revises: start_networking_bgpvpn Create Date: 2015-10-01 17:35:11.000000 """ from neutron.db.migration import cli # revision identifiers, used by Alembic. revision = '180baa4183e0' down_revision = 'start_networking_bgpvpn' branch_labels = (cli.CONTRACT_BRANCH,) def upgrade(): pass ././@PaxHeader0000000000000000000000000000022100000000000011450 xustar0000000000000000117 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/expand/ 28 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/0000775000175000017500000000000000000000000034263 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024200000000000011453 xustar0000000000000000140 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/expand/17d9fd4fddee_initial.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/0000664000175000017500000000435600000000000034275 0ustar00zuulzuul00000000000000# Copyright 2015 Orange # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """expand initial Revision ID: 17d9fd4fddee Revises: start_networking_bgpvpn Create Date: 2015-10-01 17:35:11.000000 """ from alembic import op import sqlalchemy as sa from neutron.db.migration import cli # revision identifiers, used by Alembic. revision = '17d9fd4fddee' down_revision = 'start_networking_bgpvpn' branch_labels = (cli.EXPAND_BRANCH,) vpn_types = sa.Enum("l2", "l3", name="vpn_types") def upgrade(): op.create_table( 'bgpvpns', sa.Column('name', sa.String(255), nullable=True), sa.Column('id', sa.String(length=36), nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=True), sa.Column('type', vpn_types, nullable=False), sa.Column('route_targets', sa.String(255), nullable=False), sa.Column('import_targets', sa.String(255), nullable=True), sa.Column('export_targets', sa.String(255), nullable=True), sa.Column('route_distinguishers', sa.String(255), nullable=True), sa.PrimaryKeyConstraint('id'), ) op.create_table( 'bgpvpn_network_associations', sa.Column('id', sa.String(length=36), nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=False), sa.Column('bgpvpn_id', sa.String(36), nullable=False), sa.Column('network_id', sa.String(36), nullable=False), sa.ForeignKeyConstraint(['network_id'], ['networks.id'], ondelete='CASCADE'), sa.ForeignKeyConstraint(['bgpvpn_id'], ['bgpvpns.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('bgpvpn_id', 'network_id') ) ././@PaxHeader0000000000000000000000000000026700000000000011462 xustar0000000000000000161 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/expand/3600132c6147_add_router_association_table.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/0000664000175000017500000000301500000000000034264 0ustar00zuulzuul00000000000000# Copyright 2015 Alcatel-Lucent # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 router association table Revision ID: 3600132c6147 Revises: 17d9fd4fddee Create Date: 2015-11-16 15:48:31.343859 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '3600132c6147' down_revision = '17d9fd4fddee' def upgrade(): op.create_table( 'bgpvpn_router_associations', sa.Column('id', sa.String(length=36), nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=False), sa.Column('bgpvpn_id', sa.String(36), nullable=False), sa.Column('router_id', sa.String(36), nullable=False), sa.ForeignKeyConstraint(['router_id'], ['routers.id'], ondelete='CASCADE'), sa.ForeignKeyConstraint(['bgpvpn_id'], ['bgpvpns.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('bgpvpn_id', 'router_id') ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4659784 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/0000775000175000017500000000000000000000000034123 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000022200000000000011451 xustar0000000000000000118 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/contract/ 28 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/c0000775000175000017500000000000000000000000034266 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000026400000000000011457 xustar0000000000000000158 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/contract/23ce05e0a19f_rename_tenant_to_project.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/c0000664000175000017500000000622100000000000034271 0ustar00zuulzuul00000000000000# Copyright 2016 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from alembic import op import sqlalchemy as sa from sqlalchemy.engine import reflection """rename tenant to project Revision ID: 010308b06b49 Create Date: 2016-06-29 19:42:17.862721 """ # revision identifiers, used by Alembic. revision = '23ce05e0a19f' down_revision = '180baa4183e0' depends_on = ('0ab4049986b8',) _INSPECTOR = None 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 = [ 'bgpvpn_router_associations', 'bgpvpns', 'bgpvpn_network_associations', ] 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() } ././@PaxHeader0000000000000000000000000000022000000000000011447 xustar0000000000000000116 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/expand/ 28 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/e0000775000175000017500000000000000000000000034270 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000026200000000000011455 xustar0000000000000000156 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/expand/0ab4049986b8_add_indexes_to_tenant_id.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/e0000664000175000017500000000203600000000000034273 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """add indexes to tenant_id Revision ID: 0ab4049986b8 Create Date: 2016-07-22 14:19:04.888614 """ from alembic import op # revision identifiers, used by Alembic. revision = '0ab4049986b8' down_revision = '3600132c6147' def upgrade(): for table in [ 'bgpvpns', 'bgpvpn_network_associations', 'bgpvpn_router_associations', ]: op.create_index(op.f('ix_%s_tenant_id' % table), table, ['tenant_id'], unique=False) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4659784 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/0000775000175000017500000000000000000000000034111 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000022000000000000011447 xustar0000000000000000116 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/ 28 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/e0000775000175000017500000000000000000000000034256 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000026100000000000011454 xustar0000000000000000155 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/39411aacf9b8_add_vni_to_bgpvpn_table.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/e0000664000175000017500000000171600000000000034265 0ustar00zuulzuul00000000000000# # Copyright 2017 Ericsson India Global Services Pvt Ltd. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from alembic import op import sqlalchemy as sa """add vni to bgpvpn table Revision ID: 39411aacf9b8 Revises: 9a6664f3b8d4 Create Date: 2017-09-19 17:37:11.359338 """ # revision identifiers, used by Alembic. revision = '39411aacf9b8' down_revision = '9a6664f3b8d4' def upgrade(): op.add_column('bgpvpns', sa.Column('vni', sa.Integer)) ././@PaxHeader0000000000000000000000000000030100000000000011447 xustar0000000000000000171 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/4610803bdf0d_router_assoc_add_advertise_extra_routes.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/e0000664000175000017500000000217200000000000034262 0ustar00zuulzuul00000000000000# 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. # from alembic import op import sqlalchemy as sa """Add 'extra-routes' to router association table Revision ID: 4610803bdf0d Revises: 9a6664f3b8d4 Create Date: 2017-06-26 17:39:11.086696 """ # revision identifiers, used by Alembic. revision = '4610803bdf0d' down_revision = '39411aacf9b8' def upgrade(): op.add_column('bgpvpn_router_associations', sa.Column('advertise_extra_routes', sa.Boolean(), nullable=False, server_default=sa.true())) ././@PaxHeader0000000000000000000000000000025300000000000011455 xustar0000000000000000149 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/666c706fea3b_bgpvpn_local_pref.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/e0000664000175000017500000000206700000000000034265 0ustar00zuulzuul00000000000000# Copyright 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. # from alembic import op import sqlalchemy as sa """ Add local_pref to bgpvpns table Revision ID: 666c706fea3b Revises: 39411aacf9b8 Create Date: 2018-01-18 15:40:05.723129 """ # revision identifiers, used by Alembic. revision = '666c706fea3b' down_revision = '4610803bdf0d' def upgrade(): op.add_column('bgpvpns', sa.Column('local_pref', sa.BigInteger, nullable=True)) ././@PaxHeader0000000000000000000000000000026400000000000011457 xustar0000000000000000158 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/9a6664f3b8d4_add_port_association_table.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/e0000664000175000017500000000601200000000000034257 0ustar00zuulzuul00000000000000# 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. # from alembic import op import sqlalchemy as sa """Add tables for port associations Revision ID: 9a6664f3b8d4 Revises: 0ab4049986b8 Create Date: 2017-06-26 17:34:14.411603 """ # revision identifiers, used by Alembic. revision = '9a6664f3b8d4' down_revision = '0ab4049986b8' def upgrade(): op.create_table( 'bgpvpn_port_associations', sa.Column('id', sa.String(length=36), nullable=False), sa.PrimaryKeyConstraint('id'), sa.Column('project_id', sa.String(length=255), index=True, nullable=False), sa.Column('bgpvpn_id', sa.String(36), nullable=False), sa.ForeignKeyConstraint(['bgpvpn_id'], ['bgpvpns.id'], ondelete='CASCADE'), sa.Column('port_id', sa.String(36), nullable=False), sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'), sa.Column('advertise_fixed_ips', sa.Boolean(), server_default=sa.sql.true(), nullable=False), sa.UniqueConstraint('bgpvpn_id', 'port_id') ) op.create_table( 'bgpvpn_port_association_routes', sa.Column('id', sa.String(length=36), nullable=False), sa.PrimaryKeyConstraint('id'), sa.Column('port_association_id', sa.String(length=36), nullable=False), sa.ForeignKeyConstraint(['port_association_id'], ['bgpvpn_port_associations.id'], ondelete='CASCADE'), sa.Column('type', sa.Enum("prefix", "bgpvpn", name="bgpvpn_port_association_route_type"), nullable=False), sa.Column('local_pref', sa.BigInteger(), autoincrement=False, nullable=True), # an IPv6 prefix can be up to 49 chars (IPv4-mapped IPv6 string # representation: up to 45 chars, plus 4 chars for "/128" which is the # highest/longest possible mask) sa.Column('prefix', sa.String(49), nullable=True), sa.Column('bgpvpn_id', sa.String(length=36), nullable=True), sa.ForeignKeyConstraint(['bgpvpn_id'], ['bgpvpns.id'], ondelete='CASCADE') # NOTE(tmorin): it would be nice to add some CheckConstraint so that # prefix and bgpvpn_id are enforced as NULL unless relevant for the # type, and non-NULL when relevant for the type ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4659784 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/0000775000175000017500000000000000000000000033740 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000022100000000000011450 xustar0000000000000000117 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/contract/ 28 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/co0000775000175000017500000000000000000000000034262 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000026200000000000011455 xustar0000000000000000156 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/contract/9d7f1ae5fa56_add_standard_attributes.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/co0000664000175000017500000000614600000000000034273 0ustar00zuulzuul00000000000000# Copyright 2018 OpenStack Fundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from alembic import op import sqlalchemy as sa """Add standard FK and constraints, and defs for existing objects Revision ID: 9d7f1ae5fa56 Revises: 23ce05e0a19f Create Date: 2018-04-19 12:44:54.352253 """ # revision identifiers, used by Alembic. revision = '9d7f1ae5fa56' down_revision = '23ce05e0a19f' depends_on = ('7a9482036ecd',) # adapted from b12a3ef66e62_add_standardattr_to_qos_policies.py 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), sa.Column('description', sa.String(length=255), nullable=True)) def upgrade(): for table in ('bgpvpns', 'bgpvpn_network_associations', 'bgpvpn_router_associations', 'bgpvpn_port_associations'): upgrade_table(table) def upgrade_table(table): table_model = sa.Table( table, sa.MetaData(), sa.Column('id', sa.String(length=36), nullable=False), sa.Column('standard_attr_id', sa.BigInteger(), nullable=True)) generate_records_for_existing(table, table_model) # add the constraint now that everything is populated on that table op.alter_column(table, 'standard_attr_id', nullable=False, existing_type=sa.BigInteger(), existing_nullable=True, existing_server_default=False) op.create_unique_constraint( constraint_name='uniq_%s0standard_attr_id' % table, table_name=table, columns=['standard_attr_id']) op.create_foreign_key( constraint_name=None, source_table=table, referent_table='standardattributes', local_cols=['standard_attr_id'], remote_cols=['id'], ondelete='CASCADE') def generate_records_for_existing(table, table_model): session = sa.orm.Session(bind=op.get_bind()) values = [] with session.begin(): for row in session.query(table_model): # NOTE(kevinbenton): without this disabled, pylint complains # about a missing 'dml' argument. # pylint: disable=no-value-for-parameter res = session.execute( standardattrs.insert().values(resource_type=table) ) session.execute( table_model.update().values( standard_attr_id=res.inserted_primary_key[0]).where( table_model.c.id == row[0]) ) # this commit is necessary to allow further operations session.commit() return values ././@PaxHeader0000000000000000000000000000021700000000000011455 xustar0000000000000000115 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/expand/ 28 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/ex0000775000175000017500000000000000000000000034275 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000026000000000000011453 xustar0000000000000000154 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/expand/7a9482036ecd_add_standard_attributes.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/ex0000664000175000017500000000221700000000000034301 0ustar00zuulzuul00000000000000# Copyright 2018 OpenStack Fundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from alembic import op import sqlalchemy as sa """Add standard attributes Revision ID: 7a9482036ecd Revises: 666c706fea3b Create Date: 2018-04-04 10:12:40.399032 """ # revision identifiers, used by Alembic. revision = '7a9482036ecd' down_revision = '666c706fea3b' def upgrade(): for table in ('bgpvpns', 'bgpvpn_network_associations', 'bgpvpn_router_associations', 'bgpvpn_port_associations'): op.add_column(table, sa.Column('standard_attr_id', sa.BigInteger(), nullable=True)) ././@PaxHeader0000000000000000000000000000022600000000000011455 xustar0000000000000000128 path=networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/start_networking_bgpvpn.py 22 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/start_ne0000664000175000017500000000154500000000000034360 0ustar00zuulzuul00000000000000# Copyright 2014 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """start networking_bgpvpn chain Revision ID: start_networking_bgpvpn Revises: None Create Date: 2015-10-01 18:04:17.265514 """ # revision identifiers, used by Alembic. revision = 'start_networking_bgpvpn' down_revision = None def upgrade(): pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/extensions/0000775000175000017500000000000000000000000024732 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/extensions/__init__.py0000664000175000017500000000000000000000000027031 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/extensions/bgpvpn.py0000664000175000017500000001615500000000000026610 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc from neutron.api import extensions from neutron.api.v2 import base from neutron.api.v2 import resource_helper from neutron.common import config as common_config from neutron_lib.api.definitions import bgpvpn as bgpvpn_api_def from neutron_lib.api import extensions as api_extensions from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory from neutron_lib.services import base as libbase from oslo_log import log from networking_bgpvpn._i18n import _ from networking_bgpvpn.neutron import extensions as bgpvpn_extensions LOG = log.getLogger(__name__) common_config.register_common_config_options() extensions.append_api_extensions_path(bgpvpn_extensions.__path__) class BGPVPNNotFound(n_exc.NotFound): message = _("BGPVPN %(id)s could not be found") class BGPVPNNetAssocNotFound(n_exc.NotFound): message = _("BGPVPN network association %(id)s could not be found " "for BGPVPN %(bgpvpn_id)s") class BGPVPNRouterAssocNotFound(n_exc.NotFound): message = _("BGPVPN router association %(id)s could not be found " "for BGPVPN %(bgpvpn_id)s") class BGPVPNTypeNotSupported(n_exc.BadRequest): message = _("BGPVPN %(driver)s driver does not support %(type)s type") class BGPVPNRDNotSupported(n_exc.BadRequest): message = _("BGPVPN %(driver)s driver does not support to manually set " "route distinguisher") class BGPVPNFindFromNetNotSupported(n_exc.BadRequest): message = _("BGPVPN %(driver)s driver does not support to fetch BGPVPNs " "associated to network id %(net_id)") class BGPVPNNetAssocAlreadyExists(n_exc.BadRequest): message = _("network %(net_id)s is already associated to " "BGPVPN %(bgpvpn_id)s") class BGPVPNRouterAssociationNotSupported(n_exc.BadRequest): message = _("BGPVPN %(driver)s driver does not support router " "associations") class BGPVPNRouterAssocAlreadyExists(n_exc.BadRequest): message = _("router %(router_id)s is already associated to " "BGPVPN %(bgpvpn_id)s") class BGPVPNMultipleRouterAssocNotSupported(n_exc.BadRequest): message = _("BGPVPN %(driver)s driver does not support multiple " "router association with a bgpvpn") class BGPVPNNetworkAssocExistsAnotherBgpvpn(n_exc.BadRequest): message = _("Network %(network)s already associated with %(bgpvpn)s. " "BGPVPN %(driver)s driver does not support same network" " associated to multiple bgpvpns") class BGPVPNDriverError(n_exc.NeutronException): message = _("%(method)s failed.") class Bgpvpn(api_extensions.APIExtensionDescriptor): api_definition = bgpvpn_api_def @classmethod def get_resources(cls): plural_mappings = resource_helper.build_plural_mappings( {}, bgpvpn_api_def.RESOURCE_ATTRIBUTE_MAP) resources = resource_helper.build_resource_info( plural_mappings, bgpvpn_api_def.RESOURCE_ATTRIBUTE_MAP, bgpvpn_api_def.ALIAS, register_quota=True, translate_name=True) plugin = directory.get_plugin(bgpvpn_api_def.ALIAS) sub_res_attrs = bgpvpn_api_def.SUB_RESOURCE_ATTRIBUTE_MAP for collection_name in sub_res_attrs: # Special handling needed for sub-resources with 'y' ending # (e.g. proxies -> proxy) resource_name = collection_name[:-1] parent = bgpvpn_api_def.SUB_RESOURCE_ATTRIBUTE_MAP[ collection_name].get('parent') params = bgpvpn_api_def.SUB_RESOURCE_ATTRIBUTE_MAP[ collection_name].get('parameters') controller = base.create_resource(collection_name, resource_name, plugin, params, allow_bulk=True, parent=parent, allow_pagination=True, allow_sorting=True) resource = extensions.ResourceExtension( collection_name, controller, parent, path_prefix=bgpvpn_api_def.ALIAS, attr_map=params) resources.append(resource) return resources @classmethod def get_plugin_interface(cls): return BGPVPNPluginBase class BGPVPNPluginBase(libbase.ServicePluginBase, metaclass=abc.ABCMeta): path_prefix = "/" + bgpvpn_api_def.ALIAS supported_extension_aliases = [bgpvpn_api_def.ALIAS] def get_plugin_type(self): return bgpvpn_api_def.ALIAS def get_plugin_description(self): return 'BGP VPN Interconnection service plugin' @abc.abstractmethod def create_bgpvpn(self, context, bgpvpn): pass @abc.abstractmethod def get_bgpvpns(self, context, filters=None, fields=None): pass @abc.abstractmethod def get_bgpvpn(self, context, id, fields=None): pass @abc.abstractmethod def update_bgpvpn(self, context, id, bgpvpn): pass @abc.abstractmethod def delete_bgpvpn(self, context, id): pass @abc.abstractmethod def create_bgpvpn_network_association(self, context, bgpvpn_id, network_association): pass @abc.abstractmethod def get_bgpvpn_network_association(self, context, assoc_id, bgpvpn_id, fields=None): pass @abc.abstractmethod def get_bgpvpn_network_associations(self, context, bgpvpn_id, filters=None, fields=None): pass @abc.abstractmethod def update_bgpvpn_network_association(self, context, assoc_id, bgpvpn_id, network_association): pass @abc.abstractmethod def delete_bgpvpn_network_association(self, context, assoc_id, bgpvpn_id): pass @abc.abstractmethod def create_bgpvpn_router_association(self, context, bgpvpn_id, router_association): pass @abc.abstractmethod def get_bgpvpn_router_association(self, context, assoc_id, bgpvpn_id, fields=None): pass @abc.abstractmethod def get_bgpvpn_router_associations(self, context, bgpvpn_id, filters=None, fields=None): pass @abc.abstractmethod def delete_bgpvpn_router_association(self, context, assoc_id, bgpvpn_id): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/extensions/bgpvpn_routes_control.py0000664000175000017500000001061400000000000031743 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc from neutron.api import extensions from neutron.api.v2 import base from neutron_lib.api.definitions import bgpvpn as bgpvpn_api from neutron_lib.api.definitions import bgpvpn_routes_control as api_def from neutron_lib.api import extensions as api_extensions from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory from neutron_lib.services import base as libbase from oslo_log import log from networking_bgpvpn._i18n import _ LOG = log.getLogger(__name__) class BGPVPNPortAssocNotFound(n_exc.NotFound): message = _("BGPVPN port association %(id)s could not be found " "for BGPVPN %(bgpvpn_id)s") class BGPVPNPortAssocAlreadyExists(n_exc.BadRequest): message = _("port %(port_id)s is already associated to " "BGPVPN %(bgpvpn_id)s") class BGPVPNPortAssocRouteNoSuchBGPVPN(n_exc.BadRequest): message = _("bgpvpn specified in route does not exist (%(bgpvpn_id)s)") class BGPVPNPortAssocRouteWrongBGPVPNTenant(n_exc.BadRequest): message = _("bgpvpn specified in route does not belong to the tenant " "(%(bgpvpn_id)s)") class BGPVPNPortAssocRouteBGPVPNTypeDiffer(n_exc.BadRequest): message = _("bgpvpn specified in route is of type %(route_bgpvpn_type)s, " "differing from type of associated BGPVPN %(bgpvpn_type)s)") class Bgpvpn_routes_control(api_extensions.APIExtensionDescriptor): api_definition = api_def @classmethod def get_resources(cls): """Returns Ext Resources.""" # the plugin we link this extension with is the 'bgpvpn' plugin: plugin = directory.get_plugin(bgpvpn_api.ALIAS) # The port association is the only new (sub-)resource # introduced by the bgpvpn-routes-control extension collection_name = api_def.PORT_ASSOCIATIONS resource_name = collection_name[:-1] parent = api_def.SUB_RESOURCE_ATTRIBUTE_MAP[ collection_name].get('parent') params = api_def.SUB_RESOURCE_ATTRIBUTE_MAP[ collection_name].get('parameters') controller = base.create_resource(collection_name, resource_name, plugin, params, allow_bulk=True, parent=parent, allow_pagination=True, allow_sorting=True) port_association_resource = extensions.ResourceExtension( collection_name, controller, parent, path_prefix='bgpvpn', attr_map=params) return [port_association_resource] @classmethod def get_plugin_interface(cls): return BGPVPNRoutesControlPluginBase class BGPVPNRoutesControlPluginBase(libbase.ServicePluginBase, metaclass=abc.ABCMeta): @abc.abstractmethod def update_bgpvpn_router_association(self, context, assoc_id, bgpvpn_id, router_association): pass @abc.abstractmethod def create_bgpvpn_port_association(self, context, bgpvpn_id, port_association): pass @abc.abstractmethod def get_bgpvpn_port_association(self, context, assoc_id, bgpvpn_id, fields=None): pass @abc.abstractmethod def get_bgpvpn_port_associations(self, context, bgpvpn_id, filters=None, fields=None): pass @abc.abstractmethod def update_bgpvpn_port_association(self, context, assoc_id, bgpvpn_id, port_association): pass @abc.abstractmethod def delete_bgpvpn_port_association(self, context, assoc_id, bgpvpn_id): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/extensions/bgpvpn_vni.py0000664000175000017500000000206600000000000027460 0ustar00zuulzuul00000000000000# # Copyright 2017 Ericsson India Global Services Pvt Ltd. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from neutron_lib.api.definitions import bgpvpn_vni from neutron_lib.api import extensions class Bgpvpn_vni(extensions.APIExtensionDescriptor): """Extension class supporting vni. This class is used by neutron's extension framework to make metadata about the vni attribute in bgpvpn available to external applications. With admin rights one will be able to create and read the values. """ api_definition = bgpvpn_vni ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/opts.py0000664000175000017500000000220400000000000024070 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.conf.services import provider_configuration from oslo_config import cfg def list_service_provider(): return [ ('service_providers', provider_configuration.serviceprovider_opts), ] _dummy_bgpvpn_provider = ':'.join([ 'BGPVPN', 'Dummy', 'networking_bgpvpn.neutron.services.service_drivers.driver_api.' 'BGPVPNDriver', 'default' ]) # Set reasonable example for BGPVPN as a default value def set_service_provider_default(): cfg.set_defaults(provider_configuration.serviceprovider_opts, service_provider=[_dummy_bgpvpn_provider]) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/0000775000175000017500000000000000000000000024356 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/__init__.py0000664000175000017500000000000000000000000026455 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/common/0000775000175000017500000000000000000000000025646 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/common/__init__.py0000664000175000017500000000000000000000000027745 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/common/constants.py0000664000175000017500000000173000000000000030235 0ustar00zuulzuul00000000000000# 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.definitions import bgpvpn BGPVPN_RES = bgpvpn.BGPVPN_RES BGPVPN_L3 = bgpvpn.BGPVPN_L3 BGPVPN_L2 = bgpvpn.BGPVPN_L2 BGPVPN_TYPES = bgpvpn.BGPVPN_TYPES UINT32_REGEX = bgpvpn.UINT32_REGEX UINT16_REGEX = bgpvpn.UINT16_REGEX UINT8_REGEX = bgpvpn.UINT8_REGEX IP4_REGEX = bgpvpn.IP4_REGEX RTRD_REGEX = bgpvpn.RTRD_REGEX ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/common/utils.py0000664000175000017500000001035300000000000027362 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Cloudwatt. # 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.api.definitions import bgpvpn as bgpvpn_def from neutron_lib.api.definitions import bgpvpn_routes_control as bgpvpn_rc_def from neutron_lib.api.definitions import bgpvpn_vni as bgpvpn_vni_def from neutron_lib.plugins import directory def rtrd_list2str(list): """Format Route Target list to string""" if not list: return '' if isinstance(list, str): return list return ','.join(list) def rtrd_str2list(str): """Format Route Target string to list""" if not str: return [] if isinstance(str, list): return str return str.split(',') def filter_resource(resource, filters=None): if not filters: filters = {} for key, value in filters.items(): if key in resource.keys(): if not isinstance(value, list): value = [value] if isinstance(resource[key], list): resource_value = resource[key] else: resource_value = [resource[key]] if not set(value).issubset(set(resource_value)): return False return True def filter_fields(resource, fields): if fields: return dict(((key, item) for key, item in resource.items() if key in fields)) return resource def is_extension_supported(plugin, ext_alias): return ext_alias in plugin.supported_extension_aliases def make_bgpvpn_dict(bgpvpn, fields=None): res = { 'id': bgpvpn['id'], 'tenant_id': bgpvpn['tenant_id'], 'name': bgpvpn['name'], 'type': bgpvpn['type'], 'route_targets': rtrd_str2list(bgpvpn['route_targets']), 'import_targets': rtrd_str2list(bgpvpn['import_targets']), 'export_targets': rtrd_str2list(bgpvpn['export_targets']), 'route_distinguishers': rtrd_str2list(bgpvpn['route_distinguishers']), 'networks': bgpvpn.get('networks', []), 'routers': bgpvpn.get('routers', []), 'ports': bgpvpn.get('ports', []), } plugin = directory.get_plugin(bgpvpn_def.ALIAS) if is_extension_supported(plugin, bgpvpn_vni_def.ALIAS): res[bgpvpn_vni_def.VNI] = bgpvpn.get(bgpvpn_vni_def.VNI) if is_extension_supported(plugin, bgpvpn_rc_def.ALIAS): res[bgpvpn_rc_def.LOCAL_PREF_KEY] = bgpvpn.get( bgpvpn_rc_def.LOCAL_PREF_KEY) return filter_fields(res, fields) def make_net_assoc_dict(id, tenant_id, bgpvpn_id, network_id, fields=None): res = {'id': id, 'tenant_id': tenant_id, 'bgpvpn_id': bgpvpn_id, 'network_id': network_id} return filter_fields(res, fields) def make_router_assoc_dict(id, tenant_id, bgpvpn_id, router_id, fields=None): res = {'id': id, 'tenant_id': tenant_id, 'bgpvpn_id': bgpvpn_id, 'router_id': router_id} return filter_fields(res, fields) def make_port_assoc_dict(id, tenant_id, bgpvpn_id, port_id, fields=None): # NOTE(tmorin): fields need to be added here, this isn't used yet res = {'id': id, 'tenant_id': tenant_id, 'bgpvpn_id': bgpvpn_id, 'port_id': port_id} return filter_fields(res, fields) def get_bgpvpn_differences(current_dict, old_dict): """Compare 2 BGP VPN - added keys - removed keys - changed values for keys in both dictionaries """ set_current = set(current_dict.keys()) set_old = set(old_dict.keys()) intersect = set_current.intersection(set_old) added = set_current - intersect removed = set_old - intersect changed = set( key for key in intersect if old_dict[key] != current_dict[key] ) return (added, removed, changed) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/plugin.py0000664000175000017500000003512400000000000026233 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from neutron.db import servicetype_db as st_db from neutron.services import provider_configuration as pconf from neutron.services import service_base from neutron_lib.api.definitions import bgpvpn as bgpvpn_def from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as const from neutron_lib import exceptions as n_exc from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from oslo_log import log from networking_bgpvpn._i18n import _ from networking_bgpvpn.neutron.extensions import bgpvpn from networking_bgpvpn.neutron.extensions \ import bgpvpn_routes_control as bgpvpn_rc from networking_bgpvpn.neutron.services.common import constants LOG = log.getLogger(__name__) # ("BGPVPN" is the string to match as the first part of the # service_provider configuration: "BGPVPN:Dummy:networking_bgpvpn ...") SERVICE_PROVIDER_TYPE = "BGPVPN" @registry.has_registry_receivers class BGPVPNPlugin(bgpvpn.BGPVPNPluginBase, bgpvpn_rc.BGPVPNRoutesControlPluginBase): def __init__(self): super().__init__() # Need to look into /etc/neutron/networking_bgpvpn.conf for # service_provider definitions: service_type_manager = st_db.ServiceTypeManager.get_instance() service_type_manager.add_provider_configuration( SERVICE_PROVIDER_TYPE, pconf.ProviderConfiguration('networking_bgpvpn')) # Load the default driver drivers, default_provider = service_base.load_drivers( SERVICE_PROVIDER_TYPE, self) LOG.info("BGP VPN Service Plugin using Service Driver: %s", default_provider) self.driver = drivers[default_provider] if len(drivers) > 1: LOG.warning("Multiple drivers configured for BGPVPN, although" "running multiple drivers in parallel is not yet" "supported") @property def supported_extension_aliases(self): exts = copy.copy(super().supported_extension_aliases) exts += self.driver.more_supported_extension_aliases return exts @registry.receives(resources.ROUTER_INTERFACE, [events.BEFORE_CREATE]) def _notify_adding_interface_to_router(self, resource, event, trigger, payload): context = payload.context network_id = payload.metadata.get('network_id') router_id = payload.resource_id try: routers_bgpvpns = self.driver.get_bgpvpns( context, filters={ 'routers': [router_id], }, ) except bgpvpn.BGPVPNRouterAssociationNotSupported: return nets_bgpvpns = self.driver.get_bgpvpns( context, filters={ 'networks': [network_id], 'type': [constants.BGPVPN_L3], }, ) if routers_bgpvpns and nets_bgpvpns: msg = _('It is not allowed to add an interface to a router if ' 'both the router and the network are bound to an ' 'L3 BGPVPN.') raise n_exc.BadRequest(resource='bgpvpn', msg=msg) def _validate_network(self, context, net_id): plugin = directory.get_plugin() network = plugin.get_network(context, net_id) self._validate_network_has_router_assoc(context, network, plugin) return network def _validate_network_has_router_assoc(self, context, network, plugin): filter = {'network_id': [network['id']], 'device_owner': [const.DEVICE_OWNER_ROUTER_INTF]} router_port = plugin.get_ports(context, filters=filter) if router_port: router_id = router_port[0]['device_id'] filter = {'tenant_id': [network['tenant_id']]} bgpvpns = self.driver.get_bgpvpns(context, filters=filter) bgpvpns = [str(bgpvpn['id']) for bgpvpn in bgpvpns if router_id in bgpvpn['routers']] if bgpvpns: msg = ('Network %(net_id)s is linked to a router which is ' 'already associated to bgpvpn(s) %(bgpvpns)s' % {'net_id': network['id'], 'bgpvpns': bgpvpns} ) raise n_exc.BadRequest(resource='bgpvpn', msg=msg) def _validate_router(self, context, router_id): l3_plugin = directory.get_plugin(plugin_constants.L3) router = l3_plugin.get_router(context, router_id) plugin = directory.get_plugin() self._validate_router_has_net_assocs(context, router, plugin) return router def _validate_port(self, context, port_id): plugin = directory.get_plugin() port = plugin.get_port(context, port_id) return port def _validate_router_has_net_assocs(self, context, router, plugin): filter = {'device_id': [router['id']], 'device_owner': [const.DEVICE_OWNER_ROUTER_INTF]} router_ports = plugin.get_ports(context, filters=filter) if router_ports: filter = {'tenant_id': [router['tenant_id']]} bgpvpns = self.driver.get_bgpvpns(context, filters=filter) for port in router_ports: bgpvpns = [str(bgpvpn['id']) for bgpvpn in bgpvpns if port['network_id'] in bgpvpn['networks']] if bgpvpns: msg = ('router %(rtr_id)s has an attached network ' '%(net_id)s which is already associated to ' 'bgpvpn(s) %(bgpvpns)s' % {'rtr_id': router['id'], 'net_id': port['network_id'], 'bgpvpns': bgpvpns}) raise n_exc.BadRequest(resource='bgpvpn', msg=msg) def get_plugin_type(self): return bgpvpn_def.ALIAS def get_plugin_description(self): return "Neutron BGPVPN Service Plugin" def create_bgpvpn(self, context, bgpvpn): bgpvpn = bgpvpn['bgpvpn'] return self.driver.create_bgpvpn(context, bgpvpn) def get_bgpvpns(self, context, filters=None, fields=None): return self.driver.get_bgpvpns(context, filters, fields) def get_bgpvpn(self, context, id, fields=None): return self.driver.get_bgpvpn(context, id, fields) def update_bgpvpn(self, context, id, bgpvpn): bgpvpn = bgpvpn['bgpvpn'] return self.driver.update_bgpvpn(context, id, bgpvpn) def delete_bgpvpn(self, context, id): self.driver.delete_bgpvpn(context, id) def create_bgpvpn_network_association(self, context, bgpvpn_id, network_association): net_assoc = network_association['network_association'] # check net exists net = self._validate_network(context, net_assoc['network_id']) # check every resource belong to the same tenant bgpvpn = self.get_bgpvpn(context, bgpvpn_id) if net['tenant_id'] != bgpvpn['tenant_id']: msg = 'network doesn\'t belong to the bgpvpn owner' raise n_exc.NotAuthorized(resource='bgpvpn', msg=msg) if net_assoc['tenant_id'] != bgpvpn['tenant_id']: msg = 'network association and bgpvpn should belong to\ the same tenant' raise n_exc.NotAuthorized(resource='bgpvpn', msg=msg) return self.driver.create_net_assoc(context, bgpvpn_id, net_assoc) def get_bgpvpn_network_association(self, context, assoc_id, bgpvpn_id, fields=None): return self.driver.get_net_assoc(context, assoc_id, bgpvpn_id, fields) def get_bgpvpn_network_associations(self, context, bgpvpn_id, filters=None, fields=None): return self.driver.get_net_assocs(context, bgpvpn_id, filters, fields) def update_bgpvpn_network_association(self, context, assoc_id, bgpvpn_id, network_association): # TODO(matrohon) : raise an unsuppported error pass def delete_bgpvpn_network_association(self, context, assoc_id, bgpvpn_id): self.driver.delete_net_assoc(context, assoc_id, bgpvpn_id) def create_bgpvpn_router_association(self, context, bgpvpn_id, router_association): router_assoc = router_association['router_association'] router = self._validate_router(context, router_assoc['router_id']) bgpvpn = self.get_bgpvpn(context, bgpvpn_id) if not bgpvpn['type'] == constants.BGPVPN_L3: msg = ("Router associations require the bgpvpn to be of type %s" % constants.BGPVPN_L3) raise n_exc.BadRequest(resource='bgpvpn', msg=msg) if not router['tenant_id'] == bgpvpn['tenant_id']: msg = "router doesn't belong to the bgpvpn owner" raise n_exc.NotAuthorized(resource='bgpvpn', msg=msg) if not (router_assoc['tenant_id'] == bgpvpn['tenant_id']): msg = "router association and bgpvpn should " \ "belong to the same tenant" raise n_exc.NotAuthorized(resource='bgpvpn', msg=msg) return self.driver.create_router_assoc(context, bgpvpn_id, router_assoc) def get_bgpvpn_router_association(self, context, assoc_id, bgpvpn_id, fields=None): return self.driver.get_router_assoc(context, assoc_id, bgpvpn_id, fields) def get_bgpvpn_router_associations(self, context, bgpvpn_id, filters=None, fields=None): return self.driver.get_router_assocs(context, bgpvpn_id, filters, fields) def update_bgpvpn_router_association(self, context, assoc_id, bgpvpn_id, router_association): router_association = router_association['router_association'] return self.driver.update_router_assoc(context, assoc_id, bgpvpn_id, router_association) def delete_bgpvpn_router_association(self, context, assoc_id, bgpvpn_id): self.driver.delete_router_assoc(context, assoc_id, bgpvpn_id) def _validate_port_association_routes_bgpvpn(self, context, port_association, bgpvpn_id, assoc_id=None): for route in [r for r in port_association.get('routes', []) if r['type'] == bgpvpn_rc.api_def.BGPVPN_TYPE]: try: route_bgpvpn = self.get_bgpvpn(context, route['bgpvpn_id']) except bgpvpn.BGPVPNNotFound as not_found_exc: raise bgpvpn_rc.BGPVPNPortAssocRouteNoSuchBGPVPN( bgpvpn_id=route['bgpvpn_id']) from not_found_exc assoc_bgpvpn = self.get_bgpvpn(context, bgpvpn_id) if route_bgpvpn['type'] != assoc_bgpvpn['type']: raise bgpvpn_rc.BGPVPNPortAssocRouteBGPVPNTypeDiffer( route_bgpvpn_type=route_bgpvpn['type'], bgpvpn_type=assoc_bgpvpn['type'] ) assoc_tenant_id = port_association.get('project_id') if assoc_tenant_id is None: # update, rather than create, we need to retrieve the tenant assoc = self.get_bgpvpn_port_association(context, assoc_id, bgpvpn_id) assoc_tenant_id = assoc.get('project_id') if route_bgpvpn['project_id'] != assoc_tenant_id: raise bgpvpn_rc.BGPVPNPortAssocRouteWrongBGPVPNTenant( bgpvpn_id=route['bgpvpn_id']) def create_bgpvpn_port_association(self, context, bgpvpn_id, port_association): port_association = port_association['port_association'] port = self._validate_port(context, port_association['port_id']) bgpvpn = self.get_bgpvpn(context, bgpvpn_id) if not port['tenant_id'] == bgpvpn['project_id']: msg = "port doesn't belong to the bgpvpn owner" raise n_exc.NotAuthorized(resource='bgpvpn', msg=msg) if not (port_association['project_id'] == bgpvpn['project_id']): msg = "port association and bgpvpn should " \ "belong to the same tenant" raise n_exc.NotAuthorized(resource='bgpvpn', msg=msg) self._validate_port_association_routes_bgpvpn(context, port_association, bgpvpn_id) return self.driver.create_port_assoc(context, bgpvpn_id, port_association) def get_bgpvpn_port_association(self, context, assoc_id, bgpvpn_id, fields=None): return self.driver.get_port_assoc(context, assoc_id, bgpvpn_id, fields) def get_bgpvpn_port_associations(self, context, bgpvpn_id, filters=None, fields=None): return self.driver.get_port_assocs(context, bgpvpn_id, filters, fields) def update_bgpvpn_port_association(self, context, assoc_id, bgpvpn_id, port_association): port_association = port_association['port_association'] self._validate_port_association_routes_bgpvpn(context, port_association, bgpvpn_id, assoc_id) return self.driver.update_port_assoc(context, assoc_id, bgpvpn_id, port_association) def delete_bgpvpn_port_association(self, context, assoc_id, bgpvpn_id): self.driver.delete_port_assoc(context, assoc_id, bgpvpn_id) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/service_drivers/0000775000175000017500000000000000000000000027554 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/service_drivers/__init__.py0000664000175000017500000000000000000000000031653 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/service_drivers/bagpipe/0000775000175000017500000000000000000000000031163 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/service_drivers/bagpipe/__init__.py0000664000175000017500000000000000000000000033262 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/service_drivers/bagpipe/bagpipe.py0000664000175000017500000005067600000000000033162 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # 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 sqlalchemy import orm from sqlalchemy import sql from neutron.db.models import l3 from neutron.db import models_v2 from neutron_lib.api.definitions import portbindings from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as const from neutron_lib.db import api as db_api from oslo_log import helpers as log_helpers from oslo_log import log as logging from networking_bagpipe.agent.bgpvpn import rpc_client from networking_bgpvpn.neutron.db import bgpvpn_db from networking_bgpvpn.neutron.services.common import utils from networking_bgpvpn.neutron.services.service_drivers.bagpipe \ import bagpipe_v2 as v2 LOG = logging.getLogger(__name__) @log_helpers.log_method_call @db_api.CONTEXT_READER def get_network_info_for_port(context, port_id, network_id): """Get MAC, IP and Gateway IP addresses informations for a specific port""" try: net_info = (context.session. query(models_v2.Port.mac_address, models_v2.IPAllocation.ip_address, models_v2.Subnet.cidr, models_v2.Subnet.gateway_ip). join(models_v2.IPAllocation, models_v2.IPAllocation.port_id == models_v2.Port.id). join(models_v2.Subnet, models_v2.IPAllocation.subnet_id == models_v2.Subnet.id). filter(models_v2.Subnet.ip_version == 4). filter(models_v2.Port.id == port_id).one()) (mac_address, ip_address, cidr, gateway_ip) = net_info except orm.exc.NoResultFound: return gateway_mac = ( context.session. query(models_v2.Port.mac_address). filter( models_v2.Port.network_id == network_id, (models_v2.Port.device_owner == const.DEVICE_OWNER_ROUTER_INTF) ). one_or_none() ) return {'mac_address': mac_address, 'ip_address': ip_address + cidr[cidr.index('/'):], 'gateway_ip': gateway_ip, 'gateway_mac': gateway_mac[0] if gateway_mac else None} @db_api.CONTEXT_READER def get_gateway_mac(context, network_id): gateway_mac = ( context.session. query(models_v2.Port.mac_address). filter( models_v2.Port.network_id == network_id, (models_v2.Port.device_owner == const.DEVICE_OWNER_ROUTER_INTF) ). one_or_none() ) return gateway_mac[0] if gateway_mac else None @db_api.CONTEXT_READER def get_network_ports(context, network_id): # NOTE(tmorin): currents callers don't look at detailed results # but only test if at least one result exist => can be optimized # by returning a count, rather than all port information return (context.session.query(models_v2.Port). filter(models_v2.Port.network_id == network_id, models_v2.Port.admin_state_up == sql.true()).all()) @db_api.CONTEXT_READER def get_router_ports(context, router_id): return ( context.session.query(models_v2.Port). filter( models_v2.Port.device_id == router_id, models_v2.Port.device_owner == const.DEVICE_OWNER_ROUTER_INTF ).all() ) @db_api.CONTEXT_READER def get_router_bgpvpn_assocs(context, router_id): return ( context.session.query(bgpvpn_db.BGPVPNRouterAssociation). filter( bgpvpn_db.BGPVPNRouterAssociation.router_id == router_id ).all() ) @db_api.CONTEXT_READER def get_network_bgpvpn_assocs(context, net_id): return ( context.session.query(bgpvpn_db.BGPVPNNetAssociation). filter( bgpvpn_db.BGPVPNNetAssociation.network_id == net_id ).all() ) @db_api.CONTEXT_READER def get_bgpvpns_of_router_assocs_by_network(context, net_id): return ( context.session.query(bgpvpn_db.BGPVPN). join(bgpvpn_db.BGPVPN.router_associations). join(bgpvpn_db.BGPVPNRouterAssociation.router). join(l3.Router.attached_ports). join(l3.RouterPort.port). filter( models_v2.Port.network_id == net_id ).all() ) @db_api.CONTEXT_READER def get_networks_for_router(context, router_id): ports = get_router_ports(context, router_id) if ports: return {port['network_id'] for port in ports} else: return [] def _log_callback_processing_exception(resource, event, trigger, kwargs, e): LOG.exception("Error during notification processing " "%(resource)s %(event)s, %(trigger)s, " "%(kwargs)s: %(exc)s", {'trigger': trigger, 'resource': resource, 'event': event, 'kwargs': kwargs, 'exc': e}) @registry.has_registry_receivers class BaGPipeBGPVPNDriver(v2.BaGPipeBGPVPNDriver): """BGPVPN Service Driver class for BaGPipe""" def __init__(self, service_plugin): super().__init__(service_plugin) self.agent_rpc = rpc_client.BGPVPNAgentNotifyApi() def _format_bgpvpn(self, context, bgpvpn, network_id): """JSON-format BGPVPN BGPVPN, network identifiers, and route targets. """ formatted_bgpvpn = {'id': bgpvpn['id'], 'network_id': network_id, 'gateway_mac': get_gateway_mac(context, network_id)} formatted_bgpvpn.update( self._format_bgpvpn_network_route_targets([bgpvpn])) return formatted_bgpvpn def _format_bgpvpn_network_route_targets(self, bgpvpns): """Format BGPVPN network informations (VPN type and route targets) [{ 'type': 'l3', 'route_targets': ['12345:1', '12345:2'], 'import_targets': ['12345:3'], 'export_targets': ['12345:4'] }, { 'type': 'l3', 'route_targets': ['12346:1'] }, { 'type': 'l2', 'route_targets': ['12347:1'] } ] to { 'l3vpn' : { 'import_rt': ['12345:1', '12345:2', '12345:3', '12346:1'], 'export_rt': ['12345:1', '12345:2', '12345:4', '12346:1'] }, 'l2vpn' : { 'import_rt': ['12347:1'], 'export_rt': ['12347:1'] } } """ bgpvpn_rts = {} for bgpvpn in bgpvpns: # Add necessary keys to BGP VPN route targets dictionary if bgpvpn['type'] + 'vpn' not in bgpvpn_rts: bgpvpn_rts.update( {bgpvpn['type'] + 'vpn': {'import_rt': [], 'export_rt': []}} ) if 'route_targets' in bgpvpn: bgpvpn_rts[bgpvpn['type'] + 'vpn']['import_rt'] += ( bgpvpn['route_targets'] ) bgpvpn_rts[bgpvpn['type'] + 'vpn']['export_rt'] += ( bgpvpn['route_targets'] ) if 'import_targets' in bgpvpn: bgpvpn_rts[bgpvpn['type'] + 'vpn']['import_rt'] += ( bgpvpn['import_targets'] ) if 'export_targets' in bgpvpn: bgpvpn_rts[bgpvpn['type'] + 'vpn']['export_rt'] += ( bgpvpn['export_targets'] ) for attribute in ('import_rt', 'export_rt'): if bgpvpn_rts[bgpvpn['type'] + 'vpn'][attribute]: bgpvpn_rts[bgpvpn['type'] + 'vpn'][attribute] = list( set(bgpvpn_rts[bgpvpn['type'] + 'vpn'][attribute])) return bgpvpn_rts def _bgpvpns_for_network(self, context, network_id): return ( self.bgpvpn_db.get_bgpvpns( context, filters={ 'networks': [network_id], }, ) or self.retrieve_bgpvpns_of_router_assocs_by_network(context, network_id) ) def _networks_for_bgpvpn(self, context, bgpvpn): networks = [] networks.extend(bgpvpn['networks']) for router_id in bgpvpn['routers']: networks.extend(get_networks_for_router(context, router_id)) return list(set(networks)) def _retrieve_bgpvpn_network_info_for_port(self, context, port): """Retrieve BGP VPN network informations for a specific port { 'network_id': , 'mac_address': '00:00:de:ad:be:ef', 'ip_address': '10.0.0.2', 'gateway_ip': '10.0.0.1', 'gateway_mac': 'aa:bb:cc:dd:ee:ff', # if a router interface exists 'l3vpn' : { 'import_rt': ['12345:1', '12345:2', '12345:3'], 'export_rt': ['12345:1', '12345:2', '12345:4'] } } """ port_id = port['id'] network_id = port['network_id'] bgpvpn_network_info = {} bgpvpns = self._bgpvpns_for_network(context, network_id) # NOTE(tmorin): We currently need to send 'network_id', 'mac_address', # 'ip_address', 'gateway_ip' to the agent, even in the absence of # a BGPVPN bound to the port. If we don't this information will # lack on an update_bgpvpn RPC. When the agent will have the ability # to retrieve this info by itself, we'll change this method # to return {} if there is no bound bgpvpn. bgpvpn_rts = self._format_bgpvpn_network_route_targets(bgpvpns) LOG.debug("Port connected on BGPVPN network %s with route targets " "%s", (network_id, bgpvpn_rts)) bgpvpn_network_info.update(bgpvpn_rts) LOG.debug("Getting port %s network details", port_id) network_info = get_network_info_for_port(context, port_id, network_id) if not network_info: LOG.warning("No network information for net %s", network_id) return bgpvpn_network_info.update(network_info) return bgpvpn_network_info @db_api.CONTEXT_READER def retrieve_bgpvpns_of_router_assocs_by_network(self, context, network_id): return [self.bgpvpn_db._make_bgpvpn_dict(bgpvpn) for bgpvpn in get_bgpvpns_of_router_assocs_by_network(context, network_id)] def delete_bgpvpn_postcommit(self, context, bgpvpn): for net_id in self._networks_for_bgpvpn(context, bgpvpn): if get_network_ports(context, net_id): # Format BGPVPN before sending notification self.agent_rpc.delete_bgpvpn( context, self._format_bgpvpn(context, bgpvpn, net_id)) def update_bgpvpn_postcommit(self, context, old_bgpvpn, new_bgpvpn): super().update_bgpvpn_postcommit( context, old_bgpvpn, new_bgpvpn) (added_keys, removed_keys, changed_keys) = ( utils.get_bgpvpn_differences(new_bgpvpn, old_bgpvpn)) ATTRIBUTES_TO_IGNORE = set('name') moving_keys = added_keys | removed_keys | changed_keys if len(moving_keys ^ ATTRIBUTES_TO_IGNORE): for net_id in self._networks_for_bgpvpn(context, new_bgpvpn): if (get_network_ports(context, net_id)): self._update_bgpvpn_for_network(context, net_id, new_bgpvpn) def _update_bgpvpn_for_net_with_id(self, context, network_id, bgpvpn_id): if get_network_ports(context, network_id): bgpvpn = self.get_bgpvpn(context, bgpvpn_id) self._update_bgpvpn_for_network(context, network_id, bgpvpn) def _update_bgpvpn_for_network(self, context, net_id, bgpvpn): formated_bgpvpn = self._format_bgpvpn(context, bgpvpn, net_id) self.agent_rpc.update_bgpvpn(context, formated_bgpvpn) def create_net_assoc_postcommit(self, context, net_assoc): super().create_net_assoc_postcommit(context, net_assoc) self._update_bgpvpn_for_net_with_id(context, net_assoc['network_id'], net_assoc['bgpvpn_id']) def delete_net_assoc_postcommit(self, context, net_assoc): if get_network_ports(context, net_assoc['network_id']): bgpvpn = self.get_bgpvpn(context, net_assoc['bgpvpn_id']) formated_bgpvpn = self._format_bgpvpn(context, bgpvpn, net_assoc['network_id']) self.agent_rpc.delete_bgpvpn(context, formated_bgpvpn) def _ignore_port(self, context, port): if (port['device_owner'].startswith( const.DEVICE_OWNER_NETWORK_PREFIX)): LOG.info("Port %s owner is network:*, we'll do nothing", port['id']) return True if v2.network_is_external(context, port['network_id']): LOG.info("Port %s is on an external network, we'll do nothing", port['id']) return True return False @log_helpers.log_method_call def notify_port_updated(self, context, port, original_port): if self._ignore_port(context, port): return agent_host = port[portbindings.HOST_ID] port_bgpvpn_info = {'id': port['id'], 'network_id': port['network_id']} if (port['status'] == const.PORT_STATUS_ACTIVE and original_port['status'] != const.PORT_STATUS_ACTIVE): LOG.debug("notify_port_updated, port became ACTIVE") bgpvpn_network_info = ( self._retrieve_bgpvpn_network_info_for_port(context, port) ) if bgpvpn_network_info: port_bgpvpn_info.update(bgpvpn_network_info) self.agent_rpc.attach_port_on_bgpvpn(context, port_bgpvpn_info, agent_host) else: # currently not reached, because we need # _retrieve_bgpvpn_network_info_for_port to always # return network information, even in the absence # of any BGPVPN port bound. pass elif (port['status'] == const.PORT_STATUS_DOWN and original_port['status'] != const.PORT_STATUS_DOWN): LOG.debug("notify_port_updated, port became DOWN") self.agent_rpc.detach_port_from_bgpvpn(context, port_bgpvpn_info, agent_host) else: LOG.debug("new port status is %s, origin status was %s," " => no action", port['status'], original_port['status']) @log_helpers.log_method_call def notify_port_deleted(self, context, port): port_bgpvpn_info = {'id': port['id'], 'network_id': port['network_id']} if self._ignore_port(context, port): return self.agent_rpc.detach_port_from_bgpvpn(context, port_bgpvpn_info, port[portbindings.HOST_ID]) def create_router_assoc_postcommit(self, context, router_assoc): super().create_router_assoc_postcommit( context, router_assoc) for net_id in get_networks_for_router(context, router_assoc['router_id']): self._update_bgpvpn_for_net_with_id(context, net_id, router_assoc['bgpvpn_id']) def delete_router_assoc_postcommit(self, context, router_assoc): for net_id in get_networks_for_router(context, router_assoc['router_id']): net_assoc = {'network_id': net_id, 'bgpvpn_id': router_assoc['bgpvpn_id']} self.delete_net_assoc_postcommit(context, net_assoc) @log_helpers.log_method_call def notify_router_interface_created(self, context, router_id, net_id): super().notify_router_interface_created( context, router_id, net_id) net_assocs = get_network_bgpvpn_assocs(context, net_id) router_assocs = get_router_bgpvpn_assocs(context, router_id) # if this router_interface is on a network bound to a BGPVPN, # or if this router is bound to a BGPVPN, # then we need to send and update for this network, including # the gateway_mac if net_assocs or router_assocs: for bgpvpn in self._bgpvpns_for_network(context, net_id): self._update_bgpvpn_for_network(context, net_id, bgpvpn) for router_assoc in router_assocs: self._update_bgpvpn_for_net_with_id(context, net_id, router_assoc['bgpvpn_id']) @log_helpers.log_method_call def notify_router_interface_deleted(self, context, router_id, net_id): super().notify_router_interface_deleted( context, router_id, net_id) net_assocs = get_network_bgpvpn_assocs(context, net_id) router_assocs = get_router_bgpvpn_assocs(context, router_id) if net_assocs or router_assocs: for bgpvpn in self._bgpvpns_for_network(context, net_id): self._update_bgpvpn_for_network(context, net_id, bgpvpn) for router_assoc in router_assocs: net_assoc = {'network_id': net_id, 'bgpvpn_id': router_assoc['bgpvpn_id']} self.delete_net_assoc_postcommit(context, net_assoc) @registry.receives(resources.PORT, [events.AFTER_UPDATE]) @log_helpers.log_method_call def registry_port_updated(self, resource, event, trigger, payload): try: context = payload.context port = payload.latest_state original_port = payload.states[0] self.notify_port_updated(context, port, original_port) except Exception as e: _log_callback_processing_exception(resource, event, trigger, payload.metadata, e) @registry.receives(resources.PORT, [events.AFTER_DELETE]) @log_helpers.log_method_call def registry_port_deleted(self, resource, event, trigger, payload): try: context = payload.context port = payload.latest_state self.notify_port_deleted(context, port) except Exception as e: _log_callback_processing_exception(resource, event, trigger, payload.metadata, e) # contrary to mother class, no need to subscribe to router interface # before-delete, because after delete, we still can generate RPCs @registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_DELETE]) @log_helpers.log_method_call def registry_router_interface_deleted(self, resource, event, trigger, payload=None): try: context = payload.context # for router_interface after_delete, in stable/newton, the # callback does not include the router_id directly, but we find # it in the port device_id router_id = payload.metadata.get('port')['device_id'] net_id = payload.metadata.get('port')['network_id'] self.notify_router_interface_deleted(context, router_id, net_id) except Exception as e: _log_callback_processing_exception(resource, event, trigger, payload.metadata, e) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/service_drivers/bagpipe/bagpipe_v2.py0000664000175000017500000002413300000000000033556 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # 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 sqlalchemy import orm from neutron.api.rpc.callbacks import events as rpc_events from neutron.api.rpc.handlers import resources_rpc from neutron.db.models import external_net from neutron_lib.api.definitions import bgpvpn_routes_control as bgpvpn_rc_def from neutron_lib.api.definitions import bgpvpn_vni as bgpvpn_vni_def 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 import exceptions as n_exc from neutron_lib.plugins import directory from networking_bagpipe.objects import bgpvpn as bgpvpn_objects from oslo_log import helpers as log_helpers from oslo_log import log as logging from networking_bgpvpn.neutron.extensions import bgpvpn as bgpvpn_ext from networking_bgpvpn.neutron.services.common import utils from networking_bgpvpn.neutron.services.service_drivers import driver_api LOG = logging.getLogger(__name__) BAGPIPE_DRIVER_NAME = "bagpipe" class BGPVPNExternalNetAssociation(n_exc.NeutronException): message = _("driver does not support associating an external" "network to a BGPVPN") @db_api.CONTEXT_READER def network_is_external(context, net_id): try: context.session.query(external_net.ExternalNetwork).filter_by( network_id=net_id).one() return True except orm.exc.NoResultFound: return False def _log_callback_processing_exception(resource, event, trigger, metadata, e): LOG.exception("Error during notification processing " "%(resource)s %(event)s, %(trigger)s, " "%(metadata)s: %(exc)s", {'trigger': trigger, 'resource': resource, 'event': event, 'metadata': metadata, 'exc': e}) @registry.has_registry_receivers class BaGPipeBGPVPNDriver(driver_api.BGPVPNDriverRC): """BGPVPN Service Driver class for BaGPipe""" more_supported_extension_aliases = [bgpvpn_rc_def.ALIAS, bgpvpn_vni_def.ALIAS] def __init__(self, service_plugin): super().__init__(service_plugin) self._push_rpc = resources_rpc.ResourcesPushRpcApi() def _push_association(self, context, association, event_type): self._push_associations(context, [association], event_type) def _push_associations(self, context, associations, event_type): if not associations: return for assoc in associations: LOG.debug("pushing %s %s (%s)", event_type, assoc, assoc.bgpvpn) self._push_rpc.push(context, associations, event_type) def _common_precommit_checks(self, bgpvpn): # No support yet for specifying route distinguishers if bgpvpn.get('route_distinguishers', None): raise bgpvpn_ext.BGPVPNRDNotSupported(driver=BAGPIPE_DRIVER_NAME) def create_bgpvpn_precommit(self, context, bgpvpn): self._common_precommit_checks(bgpvpn) def delete_bgpvpn_precommit(self, context, bgpvpn): self._push_bgpvpn_associations(context, bgpvpn['id'], rpc_events.DELETED) def update_bgpvpn_precommit(self, context, old_bgpvpn, new_bgpvpn): self._common_precommit_checks(new_bgpvpn) def update_bgpvpn_postcommit(self, context, old_bgpvpn, new_bgpvpn): (added_keys, removed_keys, changed_keys) = ( utils.get_bgpvpn_differences(new_bgpvpn, old_bgpvpn)) ATTRIBUTES_TO_IGNORE = set(['name']) moving_keys = added_keys | removed_keys | changed_keys if len(moving_keys ^ ATTRIBUTES_TO_IGNORE): self._push_bgpvpn_associations(context, new_bgpvpn['id'], rpc_events.UPDATED) def _push_bgpvpn_associations(self, context, bgpvpn_id, event_type): self._push_associations( context, (bgpvpn_objects.BGPVPNNetAssociation.get_objects( context, bgpvpn_id=bgpvpn_id) + bgpvpn_objects.BGPVPNRouterAssociation.get_objects( context, bgpvpn_id=bgpvpn_id) ), event_type) def create_net_assoc_precommit(self, context, net_assoc): if network_is_external(context, net_assoc['network_id']): raise BGPVPNExternalNetAssociation() def create_net_assoc_postcommit(self, context, net_assoc): self._push_association( context, bgpvpn_objects.BGPVPNNetAssociation.get_object( context, id=net_assoc['id']), rpc_events.CREATED) def delete_net_assoc_precommit(self, context, net_assoc): self._push_association( context, bgpvpn_objects.BGPVPNNetAssociation.get_object( context, id=net_assoc['id']), rpc_events.DELETED) def create_port_assoc_postcommit(self, context, port_assoc): self._push_association( context, bgpvpn_objects.BGPVPNPortAssociation.get_object( context, id=port_assoc['id']), rpc_events.CREATED) def update_port_assoc_postcommit(self, context, old_port_assoc, port_assoc): self._push_association( context, bgpvpn_objects.BGPVPNPortAssociation.get_object( context, id=port_assoc['id']), rpc_events.UPDATED) def delete_port_assoc_precommit(self, context, port_assoc): self._push_association( context, bgpvpn_objects.BGPVPNPortAssociation.get_object( context, id=port_assoc['id']), rpc_events.DELETED) def create_router_assoc_postcommit(self, context, router_assoc): self._push_association( context, bgpvpn_objects.BGPVPNRouterAssociation.get_object( context, id=router_assoc['id']), rpc_events.CREATED) def delete_router_assoc_precommit(self, context, router_assoc): self._push_association( context, bgpvpn_objects.BGPVPNRouterAssociation.get_object( context, id=router_assoc['id']), rpc_events.DELETED) @log_helpers.log_method_call def notify_router_interface_created(self, context, router_id, net_id): # update associations for the networks on which the router was plugged self._push_associations( context, (bgpvpn_objects.BGPVPNNetAssociation.get_objects( context, network_id=net_id) + bgpvpn_objects.BGPVPNRouterAssociation.get_objects( context, network_id=net_id)), rpc_events.UPDATED) @log_helpers.log_method_call def notify_router_interface_deleted(self, context, router_id, net_id): # update associations for the networks on which the router was plugged associations = ( bgpvpn_objects.BGPVPNNetAssociation.get_objects( context, network_id=net_id) + bgpvpn_objects.BGPVPNRouterAssociation.get_objects( context, router_id=router_id) ) # NOTE(tmorin): the gateway_mac information in these notifications # will not be None, as it should, because unfortunately the OVObjects # are created before the DB is updated after interface removal. # So we reprocess them to empty this field... for assoc in associations: for subnet in assoc.all_subnets(net_id): subnet['gateway_mac'] = None self._push_associations(context, associations, rpc_events.UPDATED) @registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_CREATE]) @log_helpers.log_method_call def registry_router_interface_created(self, resource, event, trigger, payload=None): try: context = payload.context router_id = payload.resource_id net_id = payload.metadata.get('port')['network_id'] self.notify_router_interface_created(context, router_id, net_id) except Exception as e: _log_callback_processing_exception(resource, event, trigger, payload.metadata, e) # need to subscribe to router interface *before*_delete # because after delete, we can't build the OVO objects from the DB anymore @registry.receives(resources.ROUTER_INTERFACE, [events.BEFORE_DELETE]) @log_helpers.log_method_call def registry_router_interface_deleted(self, resource, event, trigger, payload=None): try: context = payload.context # for router_interface after_delete, in stable/newton, the # callback does not include the router_id directly, but we find # it in the port device_id router_id = payload.resource_id subnet_id = payload.metadata['subnet_id'] # find the network for this subnet network_id = directory.get_plugin().get_subnet( context, subnet_id)['network_id'] self.notify_router_interface_deleted( context, router_id, network_id) except Exception as e: _log_callback_processing_exception( resource, event, trigger, {'subnet_id': subnet_id, 'router_id': router_id}, e) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutron/services/service_drivers/driver_api.py0000664000175000017500000003755600000000000032272 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc import copy from neutron_lib.db import api as db_api from networking_bgpvpn.neutron.db import bgpvpn_db from networking_bgpvpn.neutron.extensions \ import bgpvpn_routes_control as bgpvpn_rc class BGPVPNDriverBase(metaclass=abc.ABCMeta): """BGPVPNDriver interface for driver That driver interface does not persist BGPVPN data in any database. The driver needs to do it by itself. """ more_supported_extension_aliases = [] def __init__(self, service_plugin): self.service_plugin = service_plugin @property def service_type(self): pass @abc.abstractmethod def create_bgpvpn(self, context, bgpvpn): pass @abc.abstractmethod def get_bgpvpns(self, context, filters=None, fields=None): pass @abc.abstractmethod def get_bgpvpn(self, context, id, fields=None): pass @abc.abstractmethod def update_bgpvpn(self, context, id, bgpvpn): pass @abc.abstractmethod def delete_bgpvpn(self, context, id): pass @abc.abstractmethod def create_net_assoc(self, context, bgpvpn_id, network_association): pass @abc.abstractmethod def get_net_assoc(self, context, assoc_id, bgpvpn_id, fields=None): pass @abc.abstractmethod def get_net_assocs(self, context, bgpvpn_id, filters=None, fields=None): pass @abc.abstractmethod def delete_net_assoc(self, context, assoc_id, bgpvpn_id): pass @abc.abstractmethod def create_router_assoc(self, context, bgpvpn_id, router_association): pass @abc.abstractmethod def get_router_assoc(self, context, assoc_id, bgpvpn_id, fields=None): pass @abc.abstractmethod def get_router_assocs(self, context, bgpvpn_id, filters=None, fields=None): pass @abc.abstractmethod def delete_router_assoc(self, context, assoc_id, bgpvpn_id): pass class BGPVPNDriverDBMixin(BGPVPNDriverBase, metaclass=abc.ABCMeta): """BGPVPNDriverDB Mixin to provision the database on behalf of the driver That driver interface persists BGPVPN data in its database and forwards the result to postcommit methods """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.bgpvpn_db = bgpvpn_db.BGPVPNPluginDb() def create_bgpvpn(self, context, bgpvpn): with db_api.CONTEXT_WRITER.using(context): bgpvpn = self.bgpvpn_db.create_bgpvpn( context, bgpvpn) self.create_bgpvpn_precommit(context, bgpvpn) self.create_bgpvpn_postcommit(context, bgpvpn) return bgpvpn def get_bgpvpns(self, context, filters=None, fields=None): return self.bgpvpn_db.get_bgpvpns(context, filters, fields) def get_bgpvpn(self, context, id, fields=None): return self.bgpvpn_db.get_bgpvpn(context, id, fields) def update_bgpvpn(self, context, id, bgpvpn): old_bgpvpn = self.get_bgpvpn(context, id) with db_api.CONTEXT_WRITER.using(context): new_bgpvpn = copy.deepcopy(old_bgpvpn) new_bgpvpn.update(bgpvpn) self.update_bgpvpn_precommit(context, old_bgpvpn, new_bgpvpn) bgpvpn = self.bgpvpn_db.update_bgpvpn(context, id, bgpvpn) self.update_bgpvpn_postcommit(context, old_bgpvpn, bgpvpn) return bgpvpn def delete_bgpvpn(self, context, id): with db_api.CONTEXT_WRITER.using(context): bgpvpn = self.bgpvpn_db.get_bgpvpn(context, id) self.delete_bgpvpn_precommit(context, bgpvpn) self.bgpvpn_db.delete_bgpvpn(context, id) self.delete_bgpvpn_postcommit(context, bgpvpn) def create_net_assoc(self, context, bgpvpn_id, network_association): with db_api.CONTEXT_WRITER.using(context): assoc = self.bgpvpn_db.create_net_assoc(context, bgpvpn_id, network_association) self.create_net_assoc_precommit(context, assoc) self.create_net_assoc_postcommit(context, assoc) return assoc def get_net_assoc(self, context, assoc_id, bgpvpn_id, fields=None): return self.bgpvpn_db.get_net_assoc(context, assoc_id, bgpvpn_id, fields) def get_net_assocs(self, context, bgpvpn_id, filters=None, fields=None): return self.bgpvpn_db.get_net_assocs(context, bgpvpn_id, filters, fields) def delete_net_assoc(self, context, assoc_id, bgpvpn_id): with db_api.CONTEXT_WRITER.using(context): net_assoc = self.bgpvpn_db.get_net_assoc(context, assoc_id, bgpvpn_id) self.delete_net_assoc_precommit(context, net_assoc) self.bgpvpn_db.delete_net_assoc(context, assoc_id, bgpvpn_id) self.delete_net_assoc_postcommit(context, net_assoc) def create_router_assoc(self, context, bgpvpn_id, router_association): with db_api.CONTEXT_WRITER.using(context): assoc = self.bgpvpn_db.create_router_assoc(context, bgpvpn_id, router_association) self.create_router_assoc_precommit(context, assoc) self.create_router_assoc_postcommit(context, assoc) return assoc def get_router_assoc(self, context, assoc_id, bgpvpn_id, fields=None): return self.bgpvpn_db.get_router_assoc(context, assoc_id, bgpvpn_id, fields) def get_router_assocs(self, context, bgpvpn_id, filters=None, fields=None): return self.bgpvpn_db.get_router_assocs(context, bgpvpn_id, filters, fields) def delete_router_assoc(self, context, assoc_id, bgpvpn_id): with db_api.CONTEXT_WRITER.using(context): router_assoc = self.bgpvpn_db.get_router_assoc(context, assoc_id, bgpvpn_id) self.delete_router_assoc_precommit(context, router_assoc) self.bgpvpn_db.delete_router_assoc(context, assoc_id, bgpvpn_id) self.delete_router_assoc_postcommit(context, router_assoc) @abc.abstractmethod def create_bgpvpn_postcommit(self, context, bgpvpn): pass @abc.abstractmethod def create_bgpvpn_precommit(self, context, bgpvpn): pass @abc.abstractmethod def update_bgpvpn_postcommit(self, context, old_bgpvpn, new_bgpvpn): pass @abc.abstractmethod def update_bgpvpn_precommit(self, context, old_bgpvpn, new_bgpvpn): pass @abc.abstractmethod def delete_bgpvpn_postcommit(self, context, bgpvpn): pass @abc.abstractmethod def create_net_assoc_precommit(self, context, net_assoc): pass @abc.abstractmethod def create_net_assoc_postcommit(self, context, net_assoc): pass @abc.abstractmethod def delete_net_assoc_precommit(self, context, net_assoc): pass @abc.abstractmethod def delete_net_assoc_postcommit(self, context, net_assoc): pass @abc.abstractmethod def create_router_assoc_precommit(self, context, router_assoc): pass @abc.abstractmethod def create_router_assoc_postcommit(self, context, router_assoc): pass @abc.abstractmethod def delete_router_assoc_precommit(self, context, router_assoc): pass @abc.abstractmethod def delete_router_assoc_postcommit(self, context, router_assoc): pass class BGPVPNDriver(BGPVPNDriverDBMixin): """BGPVPNDriver interface for driver with database. Each bgpvpn driver that needs a database persistency should inherit from this driver. It can overload needed methods from the following pre/postcommit methods. Any exception raised during a precommit method will result in not having related records in the databases. """ def create_bgpvpn_precommit(self, context, bgpvpn): pass def create_bgpvpn_postcommit(self, context, bgpvpn): pass def update_bgpvpn_precommit(self, context, old_bgpvpn, new_bgpvpn): pass def update_bgpvpn_postcommit(self, context, old_bgpvpn, new_bgpvpn): pass def delete_bgpvpn_precommit(self, context, bgpvpn): pass def delete_bgpvpn_postcommit(self, context, bgpvpn): pass def create_net_assoc_precommit(self, context, net_assoc): pass def create_net_assoc_postcommit(self, context, net_assoc): pass def delete_net_assoc_precommit(self, context, net_assoc): pass def delete_net_assoc_postcommit(self, context, net_assoc): pass def create_router_assoc_precommit(self, context, router_assoc): pass def create_router_assoc_postcommit(self, context, router_assoc): pass def delete_router_assoc_precommit(self, context, router_assoc): pass def delete_router_assoc_postcommit(self, context, router_assoc): pass class BGPVPNDriverRCBase(BGPVPNDriverBase, metaclass=abc.ABCMeta): """Base class for drivers implementing the bgpvpn-routes-control API ext""" more_supported_extension_aliases = [ bgpvpn_rc.Bgpvpn_routes_control.get_alias()] @abc.abstractmethod def update_router_assoc(self, context, assoc_id, router_association): pass @abc.abstractmethod def create_port_assoc(self, bgpvpn_id, port_association): pass @abc.abstractmethod def get_port_assoc(self, context, assoc_id, bgpvpn_id, fields=None): pass @abc.abstractmethod def get_port_assocs(self, context, bgpvpn_id, filters=None, fields=None): pass @abc.abstractmethod def update_port_assoc(self, context, assoc_id, port_association): pass @abc.abstractmethod def delete_port_assoc(self, context, assoc_id, bgpvpn_id): pass class BGPVPNDriverRCDBMixin(BGPVPNDriverRCBase, BGPVPNDriverDBMixin, metaclass=abc.ABCMeta): """BGPVPNDriverDBMixin with DB operations for bgpvpn-route-control ext.""" def __init__(self, *args, **xargs): BGPVPNDriverDBMixin.__init__(self, *args, **xargs) def update_router_assoc(self, context, assoc_id, bgpvpn_id, router_assoc): old_router_assoc = self.get_router_assoc(context, assoc_id, bgpvpn_id) with db_api.CONTEXT_WRITER.using(context): router_assoc = self.bgpvpn_db.update_router_assoc(context, assoc_id, bgpvpn_id, router_assoc) self.update_router_assoc_precommit(context, old_router_assoc, router_assoc) self.update_router_assoc_postcommit(context, old_router_assoc, router_assoc) return router_assoc @abc.abstractmethod def update_router_assoc_precommit(self, context, old_router_assoc, router_assoc): pass @abc.abstractmethod def update_router_assoc_postcommit(self, context, old_router_assoc, router_assoc): pass def create_port_assoc(self, context, bgpvpn_id, port_association): with db_api.CONTEXT_WRITER.using(context): port_assoc = self.bgpvpn_db.create_port_assoc(context, bgpvpn_id, port_association) self.create_port_assoc_precommit(context, port_assoc) self.create_port_assoc_postcommit(context, port_assoc) return port_assoc @abc.abstractmethod def create_port_assoc_precommit(self, context, port_assoc): pass @abc.abstractmethod def create_port_assoc_postcommit(self, context, port_assoc): pass def get_port_assoc(self, context, assoc_id, bgpvpn_id, fields=None): return self.bgpvpn_db.get_port_assoc(context, assoc_id, bgpvpn_id, fields) def get_port_assocs(self, context, bgpvpn_id, filters=None, fields=None): return self.bgpvpn_db.get_port_assocs(context, bgpvpn_id, filters, fields) def update_port_assoc(self, context, assoc_id, bgpvpn_id, port_assoc): old_port_assoc = self.get_port_assoc(context, assoc_id, bgpvpn_id) with db_api.CONTEXT_WRITER.using(context): port_assoc = self.bgpvpn_db.update_port_assoc(context, assoc_id, bgpvpn_id, port_assoc) self.update_port_assoc_precommit(context, old_port_assoc, port_assoc) self.update_port_assoc_postcommit(context, old_port_assoc, port_assoc) return port_assoc @abc.abstractmethod def update_port_assoc_precommit(self, context, old_port_assoc, port_assoc): pass @abc.abstractmethod def update_port_assoc_postcommit(self, context, old_port_assoc, port_assoc): pass def delete_port_assoc(self, context, assoc_id, bgpvpn_id): with db_api.CONTEXT_WRITER.using(context): port_assoc = self.bgpvpn_db.get_port_assoc(context, assoc_id, bgpvpn_id) self.delete_port_assoc_precommit(context, port_assoc) self.bgpvpn_db.delete_port_assoc(context, assoc_id, bgpvpn_id) self.delete_port_assoc_postcommit(context, port_assoc) @abc.abstractmethod def delete_port_assoc_precommit(self, context, port_assoc): pass @abc.abstractmethod def delete_port_assoc_postcommit(self, context, port_assoc): pass class BGPVPNDriverRC(BGPVPNDriverRCDBMixin, BGPVPNDriver): """Base class for a DB driver supporting bgpvpn-routes-control API ext.""" def update_router_assoc_precommit(self, context, old_router_assoc, router_assoc): pass def update_router_assoc_postcommit(self, context, old_router_assoc, router_assoc): pass def create_port_assoc_precommit(self, context, port_assoc): pass def create_port_assoc_postcommit(self, context, port_assoc): pass def update_port_assoc_precommit(self, context, old_port_assoc, port_assoc): pass def update_port_assoc_postcommit(self, context, old_port_assoc, port_assoc): pass def delete_port_assoc_precommit(self, context, port_assoc): pass def delete_port_assoc_postcommit(self, context, port_assoc): pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5019786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/0000775000175000017500000000000000000000000023732 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/__init__.py0000664000175000017500000000000000000000000026031 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/neutron/0000775000175000017500000000000000000000000025424 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/neutron/__init__.py0000664000175000017500000000000000000000000027523 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/neutron/v2_0/0000775000175000017500000000000000000000000026172 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/neutron/v2_0/__init__.py0000664000175000017500000000000000000000000030271 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/0000775000175000017500000000000000000000000027466 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/__init__.py0000664000175000017500000000000000000000000031565 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/bgpvpn.py0000664000175000017500000002205300000000000031336 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # 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 neutronclient.common import extension from neutronclient.neutron import v2_0 as neutronv20 from networking_bgpvpn._i18n import _ # To understand how neutronclient extensions work # read neutronclient/v2.0/client.py (extend_* methods and _register_extension) class BGPVPN(extension.NeutronClientExtension): resource = 'bgpvpn' resource_plural = '%ss' % resource object_path = '/bgpvpn/%s' % resource_plural resource_path = '/bgpvpn/%s/%%s' % resource_plural versions = ['2.0'] class BGPVPNCreateUpdateCommon(BGPVPN): def add_known_arguments(self, parser): """Adds to parser arguments common to create and update commands.""" parser.add_argument( '--name', help=_('Name of the BGP VPN')) parser.add_argument( '--route-targets', help=_('Route Targets list to import/export for this BGP ' 'VPN. Usage: -- --route-targets ' 'list=true : : ...')) parser.add_argument( '--import-targets', help=_('List of additional Route Targets to import from.' ' Usage: -- --import-targets list=true ' ': : ...')) parser.add_argument( '--export-targets', help=_('List of additional Route Targets to export to. Usage: -- ' '--export-targets list=true : : ...')) parser.add_argument( '--route-distinguishers', help=_('List of RDs that will be used to advertize VPN routes.' 'Usage: -- --route-distinguishers list=true ' ': : ...')) def args2body(self, parsed_args): body = { self.resource: {}, } neutronv20.update_dict(parsed_args, body[self.resource], ['name', 'tenant_id', 'type', 'route_targets', 'import_targets', 'export_targets', 'route_distinguishers']) return body class BGPVPNCreate(BGPVPNCreateUpdateCommon, extension.ClientExtensionCreate): """Create a BGPVPN.""" shell_command = 'bgpvpn-create' def add_known_arguments(self, parser): BGPVPNCreateUpdateCommon.add_known_arguments(self, parser) # type is read-only, hence specific to create parser.add_argument( '--type', default='l3', choices=['l2', 'l3'], help=_('BGP VPN type selection between L3VPN (l3) and ' 'EVPN (l2), default:l3')) class BGPVPNUpdate(BGPVPNCreateUpdateCommon, extension.ClientExtensionUpdate): """Update a given BGPVPN.""" shell_command = 'bgpvpn-update' class BGPVPNDelete(extension.ClientExtensionDelete, BGPVPN): """Delete a given BGPVPN.""" shell_command = 'bgpvpn-delete' class BGPVPNList(extension.ClientExtensionList, BGPVPN): """List BGPVPNs that belong to a given tenant.""" shell_command = 'bgpvpn-list' list_columns = [ 'id', 'name', 'type', 'route_targets', 'import_targets', 'export_targets', 'tenant_id', 'networks', 'routers'] pagination_support = True sorting_support = True class BGPVPNShow(extension.ClientExtensionShow, BGPVPN): """Show a given BGPVPN.""" shell_command = 'bgpvpn-show' # BGPVPN associations def _get_bgpvpn_id(client, name_or_id): return neutronv20.find_resourceid_by_name_or_id( client, BGPVPN.resource, name_or_id) class BGPVPNAssociation(): def add_known_arguments(self, parser): parser.add_argument('bgpvpn', metavar='BGPVPN', help=_('ID or name of the BGPVPN.')) def set_extra_attrs(self, parsed_args): self.parent_id = _get_bgpvpn_id(self.get_client(), parsed_args.bgpvpn) # BGPVPN Network associations class BGPVPNNetAssoc(BGPVPNAssociation, extension.NeutronClientExtension): resource = 'network_association' resource_plural = '%ss' % resource # (parent_resource set to True so that the # first %s in *_path will be replaced with parent_id) parent_resource = True object_path = '%s/%s' % (BGPVPN.resource_path, resource_plural) resource_path = '%s/%s/%%%%s' % (BGPVPN.resource_path, resource_plural) versions = ['2.0'] allow_names = False # network associations have no name class BGPVPNNetAssocCreate(BGPVPNNetAssoc, extension.ClientExtensionCreate): """Create a BGPVPN-Network association.""" shell_command = "bgpvpn-net-assoc-create" def add_known_arguments(self, parser): BGPVPNNetAssoc.add_known_arguments(self, parser) parser.add_argument( '--network', required=True, help=_('ID or name of the network.')) def args2body(self, parsed_args): body = { self.resource: {}, } net = neutronv20.find_resourceid_by_name_or_id(self.get_client(), 'network', parsed_args.network) body[self.resource]['network_id'] = net neutronv20.update_dict(parsed_args, body[self.resource], ['tenant_id']) return body class BGPVPNNetAssocUpdate(extension.ClientExtensionUpdate, BGPVPNNetAssoc): """Update a given BGPVPN-Network association.""" shell_command = "bgpvpn-net-assoc-update" class BGPVPNNetAssocDelete(extension.ClientExtensionDelete, BGPVPNNetAssoc): """Delete a given BGPVPN-Network association.""" shell_command = "bgpvpn-net-assoc-delete" class BGPVPNNetAssocList(extension.ClientExtensionList, BGPVPNNetAssoc): """List BGPVPN-Network associations for a given BGPVPN.""" shell_command = "bgpvpn-net-assoc-list" list_columns = ['id', 'network_id'] pagination_support = True sorting_support = True class BGPVPNNetAssocShow(extension.ClientExtensionShow, BGPVPNNetAssoc): """Show a given BGPVPN-Network association.""" shell_command = "bgpvpn-net-assoc-show" # BGPVPN Router associations class BGPVPNRouterAssoc(BGPVPNAssociation, extension.NeutronClientExtension): resource = 'router_association' resource_plural = '%ss' % resource parent_resource = True object_path = '%s/%s' % (BGPVPN.resource_path, resource_plural) resource_path = '%s/%s/%%%%s' % (BGPVPN.resource_path, resource_plural) versions = ['2.0'] allow_names = False class BGPVPNRouterAssocCreate(BGPVPNRouterAssoc, extension.ClientExtensionCreate): """Create a BGPVPN-Router association.""" shell_command = "bgpvpn-router-assoc-create" def add_known_arguments(self, parser): BGPVPNRouterAssoc.add_known_arguments(self, parser) parser.add_argument( '--router', required=True, help=_('ID or name of the router.')) def args2body(self, parsed_args): body = { self.resource: {}, } router = neutronv20.find_resourceid_by_name_or_id(self.get_client(), 'router', parsed_args.router) body[self.resource]['router_id'] = router neutronv20.update_dict(parsed_args, body[self.resource], ['tenant_id']) return body class BGPVPNRouterAssocUpdate(extension.ClientExtensionUpdate, BGPVPNRouterAssoc): """Update a given BGPVPN-Router association.""" shell_command = "bgpvpn-router-assoc-update" class BGPVPNRouterAssocDelete(extension.ClientExtensionDelete, BGPVPNRouterAssoc): """Delete a given BGPVPN-Router association.""" shell_command = "bgpvpn-router-assoc-delete" class BGPVPNRouterAssocList(extension.ClientExtensionList, BGPVPNRouterAssoc): """List BGPVPN-Router associations for a given BGPVPN.""" shell_command = "bgpvpn-router-assoc-list" list_columns = ['id', 'router_id'] pagination_support = True sorting_support = True class BGPVPNRouterAssocShow(extension.ClientExtensionShow, BGPVPNRouterAssoc): """Show a given BGPVPN-Router association.""" shell_command = "bgpvpn-router-assoc-show" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/policies/0000775000175000017500000000000000000000000022650 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/policies/__init__.py0000664000175000017500000000176700000000000024774 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools from networking_bgpvpn.policies import bgpvpn from networking_bgpvpn.policies import network_association from networking_bgpvpn.policies import port_association from networking_bgpvpn.policies import router_association def list_rules(): return itertools.chain( bgpvpn.list_rules(), network_association.list_rules(), router_association.list_rules(), port_association.list_rules(), ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/policies/bgpvpn.py0000664000175000017500000001374500000000000024530 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import policy as base from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( 'create_bgpvpn', base.RULE_ADMIN_ONLY, 'Create a BGP VPN', [ { 'method': 'POST', 'path': '/bgpvpn/bgpvpns', }, ] ), policy.DocumentedRuleDefault( 'update_bgpvpn', base.RULE_ADMIN_OR_OWNER, 'Update a BGP VPN', [ { 'method': 'PUT', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), # TODO(amotoki): tenant_id is not updatable, so perhaps this can be dropped policy.DocumentedRuleDefault( 'update_bgpvpn:tenant_id', base.RULE_ADMIN_ONLY, 'Update ``tenant_id`` attribute of a BGP VPN', [ { 'method': 'PUT', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'update_bgpvpn:route_targets', base.RULE_ADMIN_ONLY, 'Update ``route_targets`` attribute of a BGP VPN', [ { 'method': 'PUT', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'update_bgpvpn:import_targets', base.RULE_ADMIN_ONLY, 'Update ``import_targets`` attribute of a BGP VPN', [ { 'method': 'PUT', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'update_bgpvpn:export_targets', base.RULE_ADMIN_ONLY, 'Update ``export_targets`` attribute of a BGP VPN', [ { 'method': 'PUT', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'update_bgpvpn:route_distinguishers', base.RULE_ADMIN_ONLY, 'Update ``route_distinguishers`` attribute of a BGP VPN', [ { 'method': 'PUT', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), # TODO(amotoki): vni is not updatable, so perhaps this can be dropped policy.DocumentedRuleDefault( 'update_bgpvpn:vni', base.RULE_ADMIN_ONLY, 'Update ``vni`` attribute of a BGP VPN', [ { 'method': 'PUT', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'delete_bgpvpn', base.RULE_ADMIN_ONLY, 'Delete a BGP VPN', [ { 'method': 'DELETE', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn', base.RULE_ADMIN_OR_OWNER, 'Get BGP VPNs', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns', }, { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn:tenant_id', base.RULE_ADMIN_ONLY, 'Get ``tenant_id`` attributes of BGP VPNs', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns', }, { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn:route_targets', base.RULE_ADMIN_ONLY, 'Get ``route_targets`` attributes of BGP VPNs', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns', }, { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn:import_targets', base.RULE_ADMIN_ONLY, 'Get ``import_targets`` attributes of BGP VPNs', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns', }, { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn:export_targets', base.RULE_ADMIN_ONLY, 'Get ``export_targets`` attributes of BGP VPNs', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns', }, { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn:route_distinguishers', base.RULE_ADMIN_ONLY, 'Get ``route_distinguishers`` attributes of BGP VPNs', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns', }, { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn:vni', base.RULE_ADMIN_ONLY, 'Get ``vni`` attributes of BGP VPNs', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns', }, { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{id}', }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/policies/network_association.py0000664000175000017500000000545500000000000027320 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import policy as base from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( 'create_bgpvpn_network_association', base.RULE_ADMIN_OR_OWNER, 'Create a network association', [ { 'method': 'POST', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/network_associations', }, ] ), # TODO(amotoki): PUT operation is not defined in the API ref. Drop it? policy.DocumentedRuleDefault( 'update_bgpvpn_network_association', base.RULE_ADMIN_OR_OWNER, 'Update a network association', [ { 'method': 'PUT', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'network_associations/{network_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'delete_bgpvpn_network_association', base.RULE_ADMIN_OR_OWNER, 'Delete a network association', [ { 'method': 'DELETE', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'network_associations/{network_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn_network_association', base.RULE_ADMIN_OR_OWNER, 'Get network associations', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/network_associations', }, { 'method': 'GET', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'network_associations/{network_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn_network_association:tenant_id', base.RULE_ADMIN_ONLY, 'Get ``tenant_id`` attributes of network associations', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/network_associations', }, { 'method': 'GET', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'network_associations/{network_association_id}'), }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/policies/port_association.py0000664000175000017500000000524300000000000026606 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import policy as base from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( 'create_bgpvpn_port_association', base.RULE_ADMIN_OR_OWNER, 'Create a port association', [ { 'method': 'POST', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/port_associations', }, ] ), policy.DocumentedRuleDefault( 'update_bgpvpn_port_association', base.RULE_ADMIN_OR_OWNER, 'Update a port association', [ { 'method': 'PUT', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'port_associations/{port_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'delete_bgpvpn_port_association', base.RULE_ADMIN_OR_OWNER, 'Delete a port association', [ { 'method': 'DELETE', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'port_associations/{port_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn_port_association', base.RULE_ADMIN_OR_OWNER, 'Get port associations', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/port_associations', }, { 'method': 'GET', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'port_associations/{port_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn_port_association:tenant_id', base.RULE_ADMIN_ONLY, 'Get ``tenant_id`` attributes of port associations', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/port_associations', }, { 'method': 'GET', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'port_associations/{port_association_id}'), }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/policies/router_association.py0000664000175000017500000000531500000000000027142 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import policy as base from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( 'create_bgpvpn_router_association', base.RULE_ADMIN_OR_OWNER, 'Create a router association', [ { 'method': 'POST', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/router_associations', }, ] ), policy.DocumentedRuleDefault( 'update_bgpvpn_router_association', base.RULE_ADMIN_OR_OWNER, 'Update a router association', [ { 'method': 'PUT', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'router_associations/{router_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'delete_bgpvpn_router_association', base.RULE_ADMIN_OR_OWNER, 'Delete a router association', [ { 'method': 'DELETE', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'router_associations/{router_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn_router_association', base.RULE_ADMIN_OR_OWNER, 'Get router associations', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/router_associations', }, { 'method': 'GET', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'router_associations/{router_association_id}'), }, ] ), policy.DocumentedRuleDefault( 'get_bgpvpn_router_association:tenant_id', base.RULE_ADMIN_ONLY, 'Get ``tenant_id`` attributes of router associations', [ { 'method': 'GET', 'path': '/bgpvpn/bgpvpns/{bgpvpn_id}/router_associations', }, { 'method': 'GET', 'path': ('/bgpvpn/bgpvpns/{bgpvpn_id}/' 'router_associations/{router_association_id}'), }, ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/0000775000175000017500000000000000000000000022203 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/__init__.py0000664000175000017500000000000000000000000024302 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/functional/0000775000175000017500000000000000000000000024345 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/functional/__init__.py0000664000175000017500000000000000000000000026444 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/functional/db/0000775000175000017500000000000000000000000024732 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/functional/db/__init__.py0000664000175000017500000000000000000000000027031 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/functional/db/test_migrations.py0000664000175000017500000000503700000000000030524 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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_bgpvpn.neutron.db import head # Tables from other repos that we depend on but do not manage. IGNORED_TABLES_MATCH = ( 'ml2_route_target_allocations', '_bagpipe_', ) # EXTERNAL_TABLES should contain all names of tables that are not related to # current repo. EXTERNAL_TABLES = set(external.TABLES) VERSION_TABLE = 'alembic_version_bgpvpn' class _TestModelsMigrationsBGPVPN(test_migrations._TestModelsMigrations): def db_sync(self, engine): cfg.CONF.set_override( 'connection', engine.url.render_as_string(hide_password=False), 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 or any([match in name for match in IGNORED_TABLES_MATCH]) )): return False if type_ == 'index' and reflected and name.startswith("idx_autoinc_"): return False return True class TestModelsMigrationsMysql(testlib_api.MySQLTestCaseMixin, _TestModelsMigrationsBGPVPN, testlib_api.SqlTestCaseLight): pass class TestModelsMigrationsPostgresql(testlib_api.PostgreSQLTestCaseMixin, _TestModelsMigrationsBGPVPN, testlib_api.SqlTestCaseLight): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/functional/requirements.txt0000664000175000017500000000040200000000000027625 0ustar00zuulzuul00000000000000# Additional requirements for functional tests # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/functional/test_placeholder.py0000664000175000017500000000016200000000000030237 0ustar00zuulzuul00000000000000from neutron.tests import base class PlaceholderTest(base.BaseTestCase): def test_noop(self): pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/0000775000175000017500000000000000000000000023162 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/__init__.py0000664000175000017500000000000000000000000025261 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/client/0000775000175000017500000000000000000000000024440 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/client/__init__.py0000664000175000017500000000000000000000000026537 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/client/test_client.py0000664000175000017500000000501400000000000027327 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # 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.unit.extensions import base as test_extensions_base from neutronclient.v2_0 import client BGPVPN_ID = "uuid-bgpvpn-foo" NET_ASSOC_ID = "uuid-netassoc-bar" ASSOCS_PATH = "/bgpvpn/bgpvpns/%s/network_associations" % BGPVPN_ID ASSOC_PATH = "/bgpvpn/bgpvpns/%s/network_associations/%%s" % BGPVPN_ID class BgpvpnClientTestCase(test_extensions_base.ExtensionTestCase): def setUp(self): super(BgpvpnClientTestCase, self).setUp() self.client = client.Client() self.client.list_ext = mock.Mock() self.client.create_ext = mock.Mock() self.client.show_ext = mock.Mock() self.client.update_ext = mock.Mock() self.client.delete_ext = mock.Mock() def test_api_url_list(self): self.client.list_network_associations(BGPVPN_ID) self.client.list_ext.assert_called_once_with(mock.ANY, ASSOCS_PATH, mock.ANY) def test_api_url_create(self): self.client.create_network_association(BGPVPN_ID, {}) self.client.create_ext.assert_called_once_with(ASSOCS_PATH, mock.ANY) def test_api_url_show(self): self.client.show_network_association(NET_ASSOC_ID, BGPVPN_ID) self.client.show_ext.assert_called_once_with(ASSOC_PATH, NET_ASSOC_ID) def test_api_url_update(self): self.client.update_network_association(NET_ASSOC_ID, BGPVPN_ID, {}) self.client.update_ext.assert_called_once_with(ASSOC_PATH, NET_ASSOC_ID, mock.ANY) def test_api_url_delete(self): self.client.delete_network_association(NET_ASSOC_ID, BGPVPN_ID) self.client.delete_ext.assert_called_once_with(ASSOC_PATH, NET_ASSOC_ID) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5059786 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/db/0000775000175000017500000000000000000000000023547 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/db/__init__.py0000664000175000017500000000000000000000000025646 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/db/test_db.py0000664000175000017500000005231100000000000025547 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # 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.api.definitions import bgpvpn_routes_control as bgpvpn_rc_def from neutron_lib.api.definitions import bgpvpn_vni as bgpvpn_vni_def from neutron_lib import context from networking_bgpvpn.neutron.db.bgpvpn_db import BGPVPNPluginDb from networking_bgpvpn.neutron.extensions.bgpvpn \ import BGPVPNNetAssocAlreadyExists from networking_bgpvpn.neutron.extensions.bgpvpn import BGPVPNNetAssocNotFound from networking_bgpvpn.neutron.extensions.bgpvpn import BGPVPNNotFound from networking_bgpvpn.neutron.services.common import constants from networking_bgpvpn.neutron.services.common import utils from networking_bgpvpn.tests.unit.services import test_plugin def _id_list(list): return [bgpvpn['id'] for bgpvpn in list] class BgpvpnDBTestCase(test_plugin.BgpvpnTestCaseMixin): def setUp(self, service_provider=None): super(BgpvpnDBTestCase, self).setUp(service_provider) self.ctx = context.get_admin_context() self.plugin_db = BGPVPNPluginDb() def test_bgpvpn_create_update_delete(self): with self.network() as net: # create bgpvpn = self.plugin_db.create_bgpvpn( self.ctx, {"tenant_id": self._tenant_id, "type": "l3", "name": "", "route_targets": ["64512:1"], "import_targets": ["64512:11", "64512:12"], "export_targets": ["64512:13", "64512:14"], "route_distinguishers": ["64512:15", "64512:16"], "vni": "1000", "local_pref": "777" } ) net_assoc = {'network_id': net['network']['id'], 'tenant_id': self._tenant_id} # associate network assoc1 = self.plugin_db.create_net_assoc(self.ctx, bgpvpn['id'], net_assoc) # retrieve bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, bgpvpn['id']) # check self.assertEqual("l3", bgpvpn['type']) # we could check tenant_id self.assertEqual(["64512:1"], bgpvpn['route_targets']) self.assertEqual(["64512:11", "64512:12"], bgpvpn['import_targets']) self.assertEqual(["64512:13", "64512:14"], bgpvpn['export_targets']) self.assertEqual(["64512:15", "64512:16"], bgpvpn['route_distinguishers']) if utils.is_extension_supported(self.bgpvpn_plugin, bgpvpn_vni_def.ALIAS): self.assertEqual(1000, bgpvpn['vni']) else: # # Test should ensure vni attribute is not present as # bpvpn_vni extension is not loaded. # self.assertNotIn('vni', bgpvpn) if utils.is_extension_supported(self.bgpvpn_plugin, bgpvpn_rc_def.ALIAS): self.assertEqual(777, bgpvpn['local_pref']) else: # # Test should ensure local_pref attribute is not present as # bpvpn-routes-control extension is not loaded. # self.assertNotIn('local_pref', bgpvpn) self.assertEqual([net['network']['id']], bgpvpn['networks']) assoc1 = self.plugin_db.get_net_assoc(self.ctx, assoc1['id'], bgpvpn['id']) self.assertEqual(net['network']['id'], assoc1['network_id']) self.assertEqual(bgpvpn['id'], assoc1['bgpvpn_id']) with self.network(name='net2') as net2: net_assoc2 = {'network_id': net2['network']['id'], 'tenant_id': self._tenant_id} # associate network assoc2 = self.plugin_db.create_net_assoc(self.ctx, bgpvpn['id'], net_assoc2) # retrieve assoc2 = self.plugin_db.get_net_assoc(self.ctx, assoc2['id'], bgpvpn['id']) assoc_list = self.plugin_db.get_net_assocs(self.ctx, bgpvpn['id']) self.assertIn(assoc2, assoc_list) self.assertIn(assoc1, assoc_list) self._test_router_assocs(bgpvpn['id'], 2) # update self.plugin_db.update_bgpvpn( self.ctx, bgpvpn['id'], {"type": "l2", "name": "foo", "tenant_id": "a-b-c-d", "route_targets": [], "import_targets": ["64512:22"], "route_distinguishers": [], "local_pref": "100" }) # retrieve bgpvpn2 = self.plugin_db.get_bgpvpn(self.ctx, bgpvpn['id']) # check self.assertEqual("l2", bgpvpn2['type']) self.assertEqual("a-b-c-d", bgpvpn2['tenant_id']) self.assertEqual("foo", bgpvpn2['name']) self.assertEqual([], bgpvpn2['route_targets']) self.assertEqual(["64512:22"], bgpvpn2['import_targets']) self.assertEqual(["64512:13", "64512:14"], bgpvpn2['export_targets']) self.assertEqual([], bgpvpn2['route_distinguishers']) self.assertEqual(100, bgpvpn2['local_pref']) # find bgpvpn by network_id bgpvpn3 = self.plugin_db.get_bgpvpns( self.ctx, filters={ 'networks': [net['network']['id']], }, ) self.assertEqual(1, len(bgpvpn3)) self.assertEqual(bgpvpn2['id'], bgpvpn3[0]['id']) # asset that GETting the assoc, but for another BGPVPN, does fails self.assertRaises(BGPVPNNetAssocNotFound, self.plugin_db.get_net_assoc, self.ctx, assoc2['id'], "bogus_bgpvpn_id") # assert that deleting a net remove the assoc self._delete('networks', net2['network']['id']) assoc_list = self.plugin_db.get_net_assocs(self.ctx, bgpvpn['id']) self.assertNotIn(assoc2, assoc_list) self.assertRaises(BGPVPNNetAssocNotFound, self.plugin_db.get_net_assoc, self.ctx, assoc2['id'], bgpvpn['id']) # delete self.plugin_db.delete_bgpvpn(self.ctx, bgpvpn['id']) # check that delete was effective self.assertRaises(BGPVPNNotFound, self.plugin_db.get_bgpvpn, self.ctx, bgpvpn['id']) # check that the assoc has been deleted after deleting the bgpvpn self.assertRaises(BGPVPNNetAssocNotFound, self.plugin_db.get_net_assoc, self.ctx, assoc1['id'], bgpvpn['id']) def _test_router_assocs(self, bgpvpn_id, max_assocs, assoc_count=0, previous_assocs=None): with self.router(tenant_id=self._tenant_id) as router: router_assoc = {'router_id': router['router']['id'], 'tenant_id': self._tenant_id} assoc = self.plugin_db.create_router_assoc(self.ctx, bgpvpn_id, router_assoc) assoc_count += 1 assoc = self.plugin_db.get_router_assoc(self.ctx, assoc['id'], bgpvpn_id) assoc_list = self.plugin_db.get_router_assocs(self.ctx, bgpvpn_id) bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, bgpvpn_id) self.assertIn(router['router']['id'], bgpvpn['routers']) if previous_assocs is None: previous_assocs = [] previous_assocs.append(assoc) for assoc in previous_assocs: self.assertIn(assoc, assoc_list) if assoc_count == max_assocs: return else: self._test_router_assocs(bgpvpn_id, max_assocs, assoc_count=assoc_count) def test_db_associate_disassociate_net(self): with self.network() as net: net_id = net['network']['id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] with self.assoc_net(id, net_id): bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, id) self.assertEqual([net_id], bgpvpn['networks']) bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, id) self.assertEqual([], bgpvpn['networks']) def test_db_associate_twice(self): with self.network() as net, self.bgpvpn() as bgpvpn: net_id = net['network']['id'] id = bgpvpn['bgpvpn']['id'] with self.assoc_net(id, net_id=net_id): self.assoc_net(id, net_id=net_id, do_disassociate=False) self.assertRaises(BGPVPNNetAssocAlreadyExists, self.plugin_db.create_net_assoc, self.ctx, id, {'tenant_id': self._tenant_id, 'network_id': net_id}) def test_db_find_bgpvpn_for_associated_network(self): with self.network() as net, \ self.bgpvpn(type=constants.BGPVPN_L2) as bgpvpn_l2, \ self.bgpvpn() as bgpvpn_l3, \ self.assoc_net(bgpvpn_l2['bgpvpn']['id'], net['network']['id']), \ self.assoc_net(bgpvpn_l3['bgpvpn']['id'], net['network']['id']): net_id = net['network']['id'] bgpvpn_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={'networks': [net_id]}, ) ) self.assertIn(bgpvpn_l2['bgpvpn']['id'], bgpvpn_id_list) self.assertIn(bgpvpn_l3['bgpvpn']['id'], bgpvpn_id_list) bgpvpn_l2_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'networks': [net_id], 'type': [constants.BGPVPN_L2], }, ) ) self.assertIn(bgpvpn_l2['bgpvpn']['id'], bgpvpn_l2_id_list) self.assertNotIn(bgpvpn_l3['bgpvpn']['id'], bgpvpn_l2_id_list) bgpvpn_l3_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'networks': [net_id], 'type': [constants.BGPVPN_L3], }, ) ) self.assertNotIn(bgpvpn_l2['bgpvpn']['id'], bgpvpn_l3_id_list[0]) self.assertIn(bgpvpn_l3['bgpvpn']['id'], bgpvpn_l3_id_list[0]) def test_db_delete_net(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] with self.network() as net: net_id = net['network']['id'] self.assoc_net(id, net_id=net_id, do_disassociate=False) bgpvpn_db = self.plugin_db.get_bgpvpn(self.ctx, id) self.assertEqual([], bgpvpn_db['networks']) def test_db_associate_disassociate_router(self): with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] with self.assoc_router(id, router_id): bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, id) self.assertEqual([router_id], bgpvpn['routers']) bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, id) self.assertEqual([], bgpvpn['routers']) def test_db_find_bgpvpn_for_associated_router(self): with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] with self.assoc_router(id, router_id=router_id): bgpvpn_list = self.plugin_db.get_bgpvpns( self.ctx, filters={'routers': [router_id]}, ) self.assertEqual(id, bgpvpn_list[0]['id']) def test_db_delete_router(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] self.assoc_router(id, router_id=router_id, do_disassociate=False) bgpvpn_db = self.plugin_db.get_bgpvpn(self.ctx, id) self.assertEqual([], bgpvpn_db['routers']) def test_db_list_bgpvpn_filtering_associated_resources(self): with self.network() as network1, \ self.network() as network2, \ self.router(tenant_id=self._tenant_id) as router1, \ self.router(tenant_id=self._tenant_id) as router2, \ self.bgpvpn() as bgpvpn1, \ self.bgpvpn() as bgpvpn2, \ self.bgpvpn() as bgpvpn3, \ self.assoc_net(bgpvpn1['bgpvpn']['id'], network1['network']['id']), \ self.assoc_router(bgpvpn3['bgpvpn']['id'], router1['router']['id']), \ self.assoc_net(bgpvpn2['bgpvpn']['id'], network2['network']['id']), \ self.assoc_router(bgpvpn2['bgpvpn']['id'], router2['router']['id']): network1_id = network1['network']['id'] network2_id = network2['network']['id'] router1_id = router1['router']['id'] router2_id = router2['router']['id'] bgpvpn_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'networks': [network1_id], }, ) ) self.assertIn(bgpvpn1['bgpvpn']['id'], bgpvpn_id_list) self.assertNotIn(bgpvpn2['bgpvpn']['id'], bgpvpn_id_list) self.assertNotIn(bgpvpn3['bgpvpn']['id'], bgpvpn_id_list) bgpvpn_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'networks': [network1_id, network2_id], }, ) ) self.assertIn(bgpvpn1['bgpvpn']['id'], bgpvpn_id_list) self.assertIn(bgpvpn2['bgpvpn']['id'], bgpvpn_id_list) self.assertNotIn(bgpvpn3['bgpvpn']['id'], bgpvpn_id_list) bgpvpn_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'routers': [router1_id], }, ) ) self.assertNotIn(bgpvpn1['bgpvpn']['id'], bgpvpn_id_list) self.assertNotIn(bgpvpn2['bgpvpn']['id'], bgpvpn_id_list) self.assertIn(bgpvpn3['bgpvpn']['id'], bgpvpn_id_list) bgpvpn_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'routers': [router1_id, router2_id], }, ) ) self.assertNotIn(bgpvpn1['bgpvpn']['id'], bgpvpn_id_list) self.assertIn(bgpvpn2['bgpvpn']['id'], bgpvpn_id_list) self.assertIn(bgpvpn3['bgpvpn']['id'], bgpvpn_id_list) bgpvpn_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'networks': [network1_id], 'routers': [router1_id], }, ) ) self.assertNotIn(bgpvpn1['bgpvpn']['id'], bgpvpn_id_list) self.assertNotIn(bgpvpn2['bgpvpn']['id'], bgpvpn_id_list) self.assertNotIn(bgpvpn3['bgpvpn']['id'], bgpvpn_id_list) bgpvpn_id_list = _id_list( self.plugin_db.get_bgpvpns( self.ctx, filters={ 'networks': [network2_id], 'routers': [router2_id], }, ) ) self.assertNotIn(bgpvpn1['bgpvpn']['id'], bgpvpn_id_list) self.assertIn(bgpvpn2['bgpvpn']['id'], bgpvpn_id_list) self.assertNotIn(bgpvpn3['bgpvpn']['id'], bgpvpn_id_list) def test_db_associate_disassociate_port(self): with self.port(tenant_id=self._tenant_id) as port, \ self.bgpvpn() as bgpvpn: port_id = port['port']['id'] bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.assoc_port(bgpvpn_id, port_id): bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, bgpvpn_id) self.assertEqual([port_id], bgpvpn['ports']) bgpvpn = self.plugin_db.get_bgpvpn(self.ctx, bgpvpn_id) self.assertEqual([], bgpvpn['ports']) def test_db_update_port_association(self): ROUTE_A = {'type': 'prefix', 'prefix': '12.1.0.0/16'} ROUTE_B = {'type': 'prefix', 'prefix': '14.0.0.0/8', 'local_pref': 200} ROUTE_Bbis = {'type': 'prefix', 'prefix': '14.0.0.0/8', 'local_pref': 100} ROUTE_C = {'type': 'prefix', 'prefix': '18.1.0.0/16'} def with_defaults(port_assoc_route): r = dict(local_pref=None) r.update(port_assoc_route) return r with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn() as bgpvpn, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id'], advertise_fixed_ips=False, routes=[ROUTE_A, ROUTE_B]) as port_assoc: bgpvpn_id = bgpvpn['bgpvpn']['id'] self._update('bgpvpn/bgpvpns/%s/port_associations' % bgpvpn_id, port_assoc['port_association']['id'], {'port_association': {'advertise_fixed_ips': True, 'routes': [ROUTE_Bbis, ROUTE_C]} }) assoc = self.show_port_assoc( bgpvpn['bgpvpn']['id'], port_assoc['port_association']['id']) assoc = assoc['port_association'] self.assertTrue(assoc['advertise_fixed_ips']) self.assertNotIn(with_defaults(ROUTE_A), assoc['routes']) self.assertIn(with_defaults(ROUTE_Bbis), assoc['routes']) self.assertIn(with_defaults(ROUTE_C), assoc['routes']) res = self._update( 'bgpvpn/bgpvpns/%s/port_associations' % bgpvpn_id, port_assoc['port_association']['id'], {'port_association': {'routes': []}} ) self.assertEqual(0, len(res['port_association']['routes'])) class BgpvpnDBTestCaseWithVNI(BgpvpnDBTestCase): def setUp(self): test_service_provider = ('networking_bgpvpn.tests.unit.services' '.test_plugin.TestBgpvpnDriverWithVni') super(BgpvpnDBTestCaseWithVNI, self).setUp( service_provider=test_service_provider) class BgpvpnDBTestCaseWithRC(BgpvpnDBTestCase): def setUp(self): test_service_provider = ('networking_bgpvpn.neutron.services.' 'service_drivers.driver_api.BGPVPNDriverRC') super(BgpvpnDBTestCaseWithRC, self).setUp( service_provider=test_service_provider) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5099788 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/extensions/0000775000175000017500000000000000000000000025361 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/extensions/__init__.py0000664000175000017500000000000000000000000027460 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/extensions/test_bgpvpn.py0000664000175000017500000003744600000000000030304 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from oslo_utils import uuidutils from neutron.api import extensions from neutron.tests.unit.api.v2 import test_base from neutron.tests.unit.extensions import base as test_extensions_base from neutron_lib.api.definitions import bgpvpn as bgpvpn_api_def from webob import exc from networking_bgpvpn.neutron import extensions as bgpvpn_extensions from networking_bgpvpn.neutron.extensions import bgpvpn _uuid = uuidutils.generate_uuid _get_path = test_base._get_path BGPVPN_PREFIX = 'bgpvpn' BGPVPN_URI = BGPVPN_PREFIX + '/' + 'bgpvpns' class BgpvpnExtensionTestCaseBase(test_extensions_base.ExtensionTestCase): def setUp(self): # NOTE(tmorin): this is already done in # networking_bgpvpn.neutron.extensions.bgpvpn at module loading time, # but for some reason I don't understand this is overridden later, # which is why we re-force this here: extensions.append_api_extensions_path(bgpvpn_extensions.__path__) super(BgpvpnExtensionTestCaseBase, self).setUp() plural_mappings = {'bgpvpn': 'bgpvpns'} self.setup_extension( '%s.%s' % (bgpvpn.BGPVPNPluginBase.__module__, bgpvpn.BGPVPNPluginBase.__name__), bgpvpn_api_def.ALIAS, bgpvpn.Bgpvpn, BGPVPN_PREFIX, plural_mappings=plural_mappings, translate_resource_name=True) self.instance = self.plugin.return_value self.bgpvpn_id = _uuid() self.net_id = _uuid() self.router_id = _uuid() self.net_assoc_id = _uuid() self.router_assoc_id = _uuid() self.NET_ASSOC_URI = BGPVPN_URI + '/' + self.bgpvpn_id + \ '/network_associations' self.ROUTER_ASSOC_URI = BGPVPN_URI + '/' + self.bgpvpn_id + \ '/router_associations' class BgpvpnExtensionTestCase(BgpvpnExtensionTestCaseBase): def test_bgpvpn_create(self): bgpvpn_id = _uuid() data = { 'bgpvpn': {'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['1234:56'], 'tenant_id': _uuid()} } expected_ret_val = copy.copy(data['bgpvpn']) expected_ret_val['import_targets'] = [] expected_ret_val['export_targets'] = [] expected_ret_val['route_distinguishers'] = [] expected_call_args = copy.copy(expected_ret_val) expected_ret_val.update({'id': bgpvpn_id}) self.instance.create_bgpvpn.return_value = expected_ret_val res = self.api.post(_get_path(BGPVPN_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertTrue(self.instance.create_bgpvpn.called) self.assertDictSupersetOf( expected_call_args, self.instance.create_bgpvpn.call_args[1]['bgpvpn']['bgpvpn']) self.assertEqual(res.status_int, exc.HTTPCreated.code) res = self.deserialize(res) self.assertIn('bgpvpn', res) self.assertDictSupersetOf(expected_ret_val, res['bgpvpn']) def test_bgpvpn_create_with_malformatted_route_target(self): data = { 'bgpvpn': {'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['ASN:NN'], 'tenant_id': _uuid()} } res = self.api.post(_get_path(BGPVPN_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertEqual(res.status_int, exc.HTTPBadRequest.code) def _data_for_invalid_rtdt(self, field): values = [[':1'], ['1:'], ['42'], ['65536:123456'], ['123.456.789.123:65535'], ['4294967296:65535'], ['1.1.1.1:655351'], ['4294967295:65536'], [''], ] for value in values: yield {'bgpvpn': {'name': 'bgpvpn1', 'type': 'l3', field: value, 'tenant_id': _uuid()} } def _data_for_valid_rtdt(self, field): values = [['1:1'], ['1:4294967295'], ['65535:0'], ['65535:4294967295'], ['1.1.1.1:1'], ['1.1.1.1:65535'], ['4294967295:0'], ['65536:65535'], ['4294967295:65535'], ] for value in values: yield {'bgpvpn': {'name': 'bgpvpn1', 'type': 'l3', field: value, 'tenant_id': _uuid()} } def _test_invalid_field(self, field): for data in self._data_for_invalid_rtdt(field): res = self.api.post(_get_path(BGPVPN_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertEqual(res.status_int, exc.HTTPBadRequest.code, "test failed for %s" % data) def _test_valid_field(self, field): for data in self._data_for_valid_rtdt(field): res = self.api.post(_get_path(BGPVPN_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=False) self.assertEqual(res.status_int, exc.HTTPCreated.code, "test failed for %s" % data) def test_bgpvpn_create_with_invalid_route_targets(self): self._test_invalid_field('route_targets') def test_bgpvpn_create_with_valid_route_targets(self): self._test_valid_field('route_targets') def test_bgpvpn_create_with_invalid_import_rts(self): self._test_invalid_field('import_targets') def test_bgpvpn_create_with_valid_import_rts(self): self._test_valid_field('import_targets') def test_bgpvpn_create_with_invalid_export_rts(self): self._test_invalid_field('export_targets') def test_bgpvpn_create_with_valid_export_rts(self): self._test_valid_field('export_targets') def test_bgpvpn_create_with_invalid_route_distinguishers(self): self._test_invalid_field('route_distinguishers') def test_bgpvpn_create_with_valid_route_distinguishers(self): self._test_valid_field('route_distinguishers') def test_bgpvpn_list(self): bgpvpn_id = _uuid() return_value = [{'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['1234:56'], 'id': bgpvpn_id}] self.instance.get_bgpvpns.return_value = return_value res = self.api.get( _get_path(BGPVPN_URI, fmt=self.fmt)) self.instance.get_bgpvpns.assert_called_with( mock.ANY, fields=mock.ANY, filters=mock.ANY ) self.assertEqual(res.status_int, exc.HTTPOk.code) def test_bgpvpn_update(self): bgpvpn_id = _uuid() update_data = {'bgpvpn': {'name': 'bgpvpn_updated'}} return_value = {'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['1234:56'], 'tenant_id': _uuid(), 'id': bgpvpn_id} self.instance.update_bgpvpn.return_value = return_value res = self.api.put(_get_path(BGPVPN_URI, id=bgpvpn_id, fmt=self.fmt), self.serialize(update_data), content_type='application/%s' % self.fmt) self.instance.update_bgpvpn.assert_called_with( mock.ANY, bgpvpn_id, bgpvpn=update_data ) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('bgpvpn', res) self.assertEqual(res['bgpvpn'], return_value) def test_bgpvpn_get(self): bgpvpn_id = _uuid() return_value = {'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['1234:56'], 'tenant_id': _uuid(), 'id': bgpvpn_id} self.instance.get_bgpvpn.return_value = return_value res = self.api.get(_get_path(BGPVPN_URI, id=bgpvpn_id, fmt=self.fmt)) self.instance.get_bgpvpn.assert_called_with( mock.ANY, bgpvpn_id, fields=mock.ANY ) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('bgpvpn', res) self.assertEqual(res['bgpvpn'], return_value) def test_bgpvpn_delete(self): self._test_entity_delete('bgpvpn') def test_bgpvpn_net_create(self): data = {'network_association': {'network_id': self.net_id, 'tenant_id': _uuid()}} return_value = copy.copy(data['network_association']) return_value.update({'id': self.net_assoc_id}) self.instance.create_bgpvpn_network_association.return_value = \ return_value res = self.api.post(_get_path(self.NET_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertTrue(self.instance.create_bgpvpn_network_association.called) self.assertEqual(self.bgpvpn_id, self.instance.create_bgpvpn_network_association. call_args[1]['bgpvpn_id']) self.assertDictSupersetOf( data['network_association'], self.instance.create_bgpvpn_network_association. call_args[1]['network_association']['network_association']) self.assertIn('network_association', res) res = self.deserialize(res) self.assertDictSupersetOf(return_value, res['network_association']) def _invalid_data_for_creation(self, target): return [None, {}, {target: None}, {target: {}}] def test_bgpvpn_net_create_with_invalid_data(self): for data in self._invalid_data_for_creation('network_association'): res = self.api.post(_get_path(self.NET_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertFalse( self.instance.create_bgpvpn_network_association.called) self.assertEqual(res.status_int, exc.HTTPBadRequest.code) def test_bgpvpn_net_get(self): return_value = {'id': self.net_assoc_id, 'network_id': self.net_id} self.instance.get_bgpvpn_network_association.return_value = \ return_value res = self.api.get(_get_path(self.NET_ASSOC_URI, id=self.net_assoc_id, fmt=self.fmt)) self.instance.get_bgpvpn_network_association.assert_called_with( mock.ANY, self.net_assoc_id, self.bgpvpn_id, fields=mock.ANY ) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('network_association', res) self.assertEqual(return_value, res['network_association']) def test_bgpvpn_net_update(self): pass def test_bgpvpn_net_delete(self): res = self.api.delete(_get_path(self.NET_ASSOC_URI, id=self.net_assoc_id, fmt=self.fmt)) self.instance.delete_bgpvpn_network_association.assert_called_with( mock.ANY, self.net_assoc_id, self.bgpvpn_id) self.assertEqual(res.status_int, exc.HTTPNoContent.code) def test_bgpvpn_router_create(self): data = { 'router_association': { 'router_id': self.router_id, 'tenant_id': _uuid() } } return_value = copy.copy(data['router_association']) return_value.update({'id': self.router_assoc_id}) self.instance.create_bgpvpn_router_association.return_value = \ return_value res = self.api.post(_get_path(self.ROUTER_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertTrue(self.instance.create_bgpvpn_router_association.called) self.assertEqual(self.bgpvpn_id, self.instance.create_bgpvpn_router_association. call_args[1]['bgpvpn_id']) self.assertDictSupersetOf( data['router_association'], self.instance.create_bgpvpn_router_association. call_args[1]['router_association']['router_association']) self.assertIn('router_association', res) res = self.deserialize(res) self.assertDictSupersetOf(return_value, res['router_association']) def test_bgpvpn_router_create_with_invalid_data(self): for data in self._invalid_data_for_creation('router_association'): res = self.api.post(_get_path(self.ROUTER_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertFalse( self.instance.create_bgpvpn_router_association.called) self.assertEqual(res.status_int, exc.HTTPBadRequest.code) def test_bgpvpn_router_get(self): return_value = {'id': self.router_assoc_id, 'router_id': self.router_id} self.instance.get_bgpvpn_router_association.return_value = \ return_value res = self.api.get(_get_path(self.ROUTER_ASSOC_URI, id=self.router_assoc_id, fmt=self.fmt)) self.instance.get_bgpvpn_router_association.assert_called_with( mock.ANY, self.router_assoc_id, self.bgpvpn_id, fields=mock.ANY ) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('router_association', res) self.assertEqual(return_value, res['router_association']) def test_bgpvpn_router_update(self): pass def test_bgpvpn_router_delete(self): res = self.api.delete(_get_path(self.ROUTER_ASSOC_URI, id=self.router_assoc_id, fmt=self.fmt)) self.instance.delete_bgpvpn_router_association.assert_called_with( mock.ANY, self.router_assoc_id, self.bgpvpn_id) self.assertEqual(res.status_int, exc.HTTPNoContent.code) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/extensions/test_bgpvpn_rc_base.py0000664000175000017500000001060700000000000031750 0ustar00zuulzuul00000000000000# Copyright 2014 Intel Corporation. # Copyright 2014 Isaku Yamahata # # 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 oslo_config import cfg import webtest from neutron_lib import fixture from neutron.api import extensions from neutron import manager from neutron import quota from neutron.tests.unit.api import test_extensions from neutron.tests.unit.extensions import base as test_extensions_base CORE_PLUGIN = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' class BGPVPNRCExtensionTestCase(test_extensions_base.ExtensionTestCase): # This is a modified copy of # n.t.u.extensions.base.ExtensionTestCase._setUpExtension # until the corresponding behavior, which consists in allowing # that more than one extension is setup, is pushed to neutron def _setUpExtension(self, plugin, service_type, _unused__resource_attribute_map, extension_class, *args, **kwargs): self._setUpExtensions(plugin, service_type, [extension_class], *args, **kwargs) def _setUpExtensions(self, plugin, service_type, extension_classes, resource_prefix, plural_mappings=None, translate_resource_name=False, allow_pagination=False, allow_sorting=False, supported_extension_aliases=None, use_quota=False, ): self._resource_prefix = resource_prefix self._plural_mappings = plural_mappings or {} self._translate_resource_name = translate_resource_name # Ensure existing ExtensionManager is not used extensions.PluginAwareExtensionManager._instance = None self.useFixture(fixture.APIDefinitionFixture()) # Create the default configurations self.config_parse() core_plugin = CORE_PLUGIN if service_type else plugin self.setup_coreplugin(core_plugin, load_plugins=False) if service_type: cfg.CONF.set_override('service_plugins', [plugin]) self._plugin_patcher = mock.patch(plugin, autospec=True) self.plugin = self._plugin_patcher.start() instance = self.plugin.return_value if service_type: instance.get_plugin_type.return_value = service_type manager.init() if supported_extension_aliases is not None: instance.supported_extension_aliases = supported_extension_aliases if allow_pagination: # instance.__native_pagination_support = True native_pagination_attr_name = ("_%s__native_pagination_support" % instance.__class__.__name__) setattr(instance, native_pagination_attr_name, True) if allow_sorting: # instance.__native_sorting_support = True native_sorting_attr_name = ("_%s__native_sorting_support" % instance.__class__.__name__) setattr(instance, native_sorting_attr_name, True) if use_quota: quota.QUOTAS._driver = None cfg.CONF.set_override( 'quota_driver', 'neutron.db.quota.driver.DbQuotaDriver', group='QUOTAS') setattr(instance, 'path_prefix', resource_prefix) #################################################################### ext_mgr = extensions.ExtensionManager('') for extension_class in extension_classes: ext = extension_class() ext_mgr.add_extension(ext) #################################################################### self.ext_mdw = test_extensions.setup_extensions_middleware(ext_mgr) self.api = webtest.TestApp(self.ext_mdw) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/extensions/test_bgpvpn_routes_control.py0000664000175000017500000002605300000000000033435 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from oslo_utils import uuidutils from neutron.extensions import l3 from neutron.tests.unit.api.v2 import test_base from neutron_lib.api.definitions import bgpvpn as bgpvpn_api_def from neutron_lib.api.definitions import bgpvpn_routes_control as rc_api_def from neutron_lib.utils import test from webob import exc from networking_bgpvpn.neutron.extensions import bgpvpn from networking_bgpvpn.neutron.extensions \ import bgpvpn_routes_control as bgpvpn_rc from networking_bgpvpn.tests.unit.extensions import test_bgpvpn_rc_base _uuid = uuidutils.generate_uuid _get_path = test_base._get_path BGPVPN_PREFIX = 'bgpvpn' BGPVPN_URI = BGPVPN_PREFIX + '/' + 'bgpvpns' class TestPlugin(bgpvpn.BGPVPNPluginBase, bgpvpn_rc.BGPVPNRoutesControlPluginBase): supported_exsupported_extension_aliases = [bgpvpn_api_def.ALIAS, rc_api_def.ALIAS] TEST_PLUGIN_CLASS = '%s.%s' % (TestPlugin.__module__, TestPlugin.__name__) class BgpvpnRoutesControlExtensionTestCase( test_bgpvpn_rc_base.BGPVPNRCExtensionTestCase): def setUp(self): super(BgpvpnRoutesControlExtensionTestCase, self).setUp() self._setUpExtensions( TEST_PLUGIN_CLASS, bgpvpn_api_def.ALIAS, [l3.L3, bgpvpn.Bgpvpn, bgpvpn_rc.Bgpvpn_routes_control], BGPVPN_PREFIX, translate_resource_name=True) self.instance = self.plugin.return_value self.bgpvpn_id = _uuid() self.net_id = _uuid() self.router_id = _uuid() self.net_assoc_id = _uuid() self.router_assoc_id = _uuid() self.port_id = _uuid() self.port_assoc_id = _uuid() self.NET_ASSOC_URI = BGPVPN_URI + '/' + self.bgpvpn_id + \ '/network_associations' self.ROUTER_ASSOC_URI = BGPVPN_URI + '/' + self.bgpvpn_id + \ '/router_associations' self.PORT_ASSOC_URI = BGPVPN_URI + '/' + self.bgpvpn_id + \ '/port_associations' def _invalid_data_for_creation(self, target): return [None, {}, {target: None}, {target: {}} ] @test.unstable_test("bug/1791256") def test_router_association_update(self): data = { 'router_association': { 'router_id': self.router_id, 'project_id': _uuid() } } self.api.post(_get_path(self.ROUTER_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) update_data = {'router_association': { 'advertise_extra_routes': False, }} return_value = { 'project_id': _uuid(), 'advertise_extra_routes': False, } self.instance.update_bgpvpn_router_association.return_value = ( return_value) res = self.api.put(_get_path(self.ROUTER_ASSOC_URI, id=self.router_assoc_id, fmt=self.fmt), self.serialize(update_data), content_type='application/%s' % self.fmt) self.instance.update_bgpvpn_router_association.assert_called_with( mock.ANY, self.router_assoc_id, bgpvpn_id=self.bgpvpn_id, router_association=update_data ) self.assertEqual(exc.HTTPOk.code, res.status_int) res = self.deserialize(res) self.assertIn('router_association', res) self.assertEqual(return_value, res['router_association']) def _invalid_data_for_port_assoc(self): return [ ({'advertise_fixed_ips': 'foo'}, "cannot be converted to boolean"), ({'routes': 'bla'}, "is not a list"), ({'routes': [{ 'type': 'flumox'}]}, "No valid key specs"), ({'routes': [{ 'type': 'prefix', 'something_else_than_prefix': 'foo'}]}, "No valid key specs"), ({'routes': [{ 'type': 'prefix', 'prefix': '1.1.1.352'}]}, "No valid key specs"), ({'routes': [{ 'type': 'prefix', 'something_else_than_bgpvpn_id': 'foo'}]}, "No valid key specs"), ({'routes': [{ 'type': 'prefix', 'prefix': '12.1.2.3', 'local_pref': -1, }]}, "No valid key specs"), ({'routes': [{ 'type': 'prefix', 'prefix': '12.1.2.3/20', 'local_pref': 2 ** 32, }]}, "No valid key specs") ] def test_port_association_create(self): data = { 'port_association': { 'port_id': self.port_id, 'tenant_id': _uuid() } } return_value = copy.copy(data['port_association']) return_value.update({'id': self.port_assoc_id}) self.instance.create_bgpvpn_port_association.return_value = \ return_value res = self.api.post(_get_path(self.PORT_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertTrue(self.instance.create_bgpvpn_port_association.called) self.assertEqual(self.bgpvpn_id, self.instance.create_bgpvpn_port_association. call_args[1]['bgpvpn_id']) self.assertDictSupersetOf( data['port_association'], self.instance.create_bgpvpn_port_association. call_args[1]['port_association']['port_association']) self.assertIn('port_association', res) res = self.deserialize(res) self.assertDictSupersetOf(return_value, res['port_association']) def _test_port_association_create_with_invalid_data(self, port_assoc, msg): res = self.api.post(_get_path(self.PORT_ASSOC_URI, fmt=self.fmt), self.serialize(port_assoc), content_type='application/%s' % self.fmt, expect_errors=True) self.assertEqual(res.status_int, exc.HTTPBadRequest.code) self.assertFalse( self.instance.create_bgpvpn_port_association.called) self.assertIn(msg, str(res.body)) def test_port_association_create_with_invalid_assoc(self): for data in self._invalid_data_for_creation('port_association'): res = self.api.post(_get_path(self.PORT_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) self.assertFalse( self.instance.create_bgpvpn_port_association.called) self.assertEqual(res.status_int, exc.HTTPBadRequest.code) def test_port_association_create_with_invalid_content(self): for port_assoc_attrs, msg in self._invalid_data_for_port_assoc(): data = {'port_association': { 'port_id': self.port_id, 'project_id': _uuid() }} data['port_association'].update(port_assoc_attrs) self._test_port_association_create_with_invalid_data(data, msg) def test_port_association_get(self): return_value = {'id': self.port_assoc_id, 'port_id': self.port_id} self.instance.get_bgpvpn_port_association.return_value = \ return_value res = self.api.get(_get_path(self.PORT_ASSOC_URI, id=self.port_assoc_id, fmt=self.fmt)) self.instance.get_bgpvpn_port_association.assert_called_with( mock.ANY, self.port_assoc_id, self.bgpvpn_id, fields=mock.ANY ) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('port_association', res) self.assertEqual(return_value, res['port_association']) def test_port_association_update(self): data = { 'port_association': { 'port_id': self.port_id, 'project_id': _uuid() } } self.api.post(_get_path(self.PORT_ASSOC_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt, expect_errors=True) update_data = {'port_association': { 'advertise_fixed_ips': False, 'routes': [ {'type': 'prefix', 'prefix': '1.2.3.0/24', 'local_pref': 42}, {'type': 'bgpvpn', 'bgpvpn_id': _uuid()}, ] }} return_value = { 'port_id': self.port_id, 'project_id': _uuid(), 'advertise_fixed_ips': False, 'routes': [ {'type': 'prefix', 'prefix': '1.2.3.0/24', 'local_pref': 42}, {'type': 'bgpvpn', 'prefix': '1.2.3.0/24'}, ] } self.instance.update_bgpvpn_port_association.return_value = ( return_value) res = self.api.put(_get_path(self.PORT_ASSOC_URI, id=self.port_assoc_id, fmt=self.fmt), self.serialize(update_data), content_type='application/%s' % self.fmt) self.instance.update_bgpvpn_port_association.assert_called_with( mock.ANY, self.port_assoc_id, bgpvpn_id=self.bgpvpn_id, port_association=update_data ) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('port_association', res) self.assertEqual(res['port_association'], return_value) def test_port_association_delete(self): res = self.api.delete(_get_path(self.PORT_ASSOC_URI, id=self.port_assoc_id, fmt=self.fmt)) self.instance.delete_bgpvpn_port_association.assert_called_with( mock.ANY, self.port_assoc_id, self.bgpvpn_id) self.assertEqual(res.status_int, exc.HTTPNoContent.code) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/extensions/test_bgpvpn_vni.py0000664000175000017500000001054300000000000031145 0ustar00zuulzuul00000000000000# # Copyright 2017 Ericsson India Global Services Pvt Ltd. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy from unittest import mock from oslo_utils import uuidutils from neutron.tests.unit.api.v2 import test_base from neutron.tests.unit.extensions import base as test_extensions_base from neutron_lib.api.definitions import bgpvpn as bgpvpn_def from neutron_lib.api.definitions import bgpvpn_vni as bgpvpn_vni_def from webob import exc from networking_bgpvpn.neutron.extensions import bgpvpn _uuid = uuidutils.generate_uuid _get_path = test_base._get_path BGPVPN_PREFIX = 'bgpvpn' BGPVPN_URI = BGPVPN_PREFIX + '/' + 'bgpvpns' BGPVPN_PLUGIN_BASE_NAME = (bgpvpn.BGPVPNPluginBase.__module__ + '.' + bgpvpn.BGPVPNPluginBase.__name__) class BgpvpnVniTestExtensionManager(object): def get_resources(self): bgpvpn_def.RESOURCE_ATTRIBUTE_MAP[bgpvpn_def.COLLECTION_NAME].update( bgpvpn_vni_def.RESOURCE_ATTRIBUTE_MAP[bgpvpn_def.COLLECTION_NAME]) return bgpvpn.Bgpvpn.get_resources() def get_actions(self): return [] def get_request_extensions(self): return [] class BgpvpnVniExtensionTestCase(test_extensions_base.ExtensionTestCase): fmt = 'json' def setUp(self): super(BgpvpnVniExtensionTestCase, self).setUp() plural_mappings = {'bgpvpn': 'bgpvpns'} self.setup_extension( BGPVPN_PLUGIN_BASE_NAME, bgpvpn_def.ALIAS, BgpvpnVniTestExtensionManager(), BGPVPN_PREFIX, plural_mappings=plural_mappings, translate_resource_name=True) self.instance = self.plugin.return_value def test_bgpvpn_create(self): bgpvpn_id = _uuid() data = { 'bgpvpn': {'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['1234:56'], 'vni': 1000, 'tenant_id': _uuid()} } expected_ret_val = copy.copy(data['bgpvpn']) expected_ret_val['import_targets'] = [] expected_ret_val['export_targets'] = [] expected_ret_val['route_distinguishers'] = [] expected_ret_val['vni'] = 1000 expected_call_args = copy.copy(expected_ret_val) expected_ret_val.update({'id': bgpvpn_id}) self.instance.create_bgpvpn.return_value = expected_ret_val res = self.api.post(_get_path(BGPVPN_URI, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.assertTrue(self.instance.create_bgpvpn.called) self.assertDictSupersetOf( expected_call_args, self.instance.create_bgpvpn.call_args[1]['bgpvpn']['bgpvpn']) self.assertEqual(res.status_int, exc.HTTPCreated.code) res = self.deserialize(res) self.assertIn('bgpvpn', res) self.assertDictSupersetOf(expected_ret_val, res['bgpvpn']) def test_bgpvpn_get(self): bgpvpn_id = _uuid() return_value = {'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['1234:56'], 'tenant_id': _uuid(), 'vni': 1000, 'id': bgpvpn_id} self.instance.get_bgpvpn.return_value = return_value res = self.api.get(_get_path(BGPVPN_URI, id=bgpvpn_id, fmt=self.fmt)) self.instance.get_bgpvpn.assert_called_with( mock.ANY, bgpvpn_id, fields=mock.ANY ) self.assertEqual(res.status_int, exc.HTTPOk.code) res = self.deserialize(res) self.assertIn('bgpvpn', res) self.assertEqual(res['bgpvpn'], return_value) def test_bgpvpn_delete(self): self._test_entity_delete('bgpvpn') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5099788 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/0000775000175000017500000000000000000000000025005 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/__init__.py0000664000175000017500000000000000000000000027104 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5099788 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/bagpipe/0000775000175000017500000000000000000000000026414 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/bagpipe/__init__.py0000664000175000017500000000000000000000000030513 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/bagpipe/test_bagpipe.py0000664000175000017500000013653400000000000031450 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock import webob.exc from oslo_config import cfg from neutron.api.rpc.handlers import resources_rpc from neutron.db import agents_db from neutron.db import db_base_plugin_v2 from neutron.plugins.ml2 import plugin as ml2_plugin from neutron.plugins.ml2 import rpc as ml2_rpc from neutron.tests.common import helpers from neutron_lib.api.definitions import portbindings from neutron_lib.callbacks import events from neutron_lib import constants as const from neutron_lib import context as n_context from neutron_lib.plugins import directory from networking_bgpvpn.neutron.services.service_drivers.bagpipe import bagpipe from networking_bgpvpn.tests.unit.services import test_plugin from networking_bagpipe.objects import bgpvpn as objs def _expected_formatted_bgpvpn(id, net_id, rt=None, gateway_mac=None): return {'id': id, 'network_id': net_id, 'l3vpn': {'import_rt': rt or mock.ANY, 'export_rt': rt or mock.ANY}, 'gateway_mac': gateway_mac or mock.ANY} class TestCorePluginWithAgents(db_base_plugin_v2.NeutronDbPluginV2, agents_db.AgentDbMixin): pass class TestBagpipeCommon(test_plugin.BgpvpnTestCaseMixin): def setUp(self, plugin=None, driver=('networking_bgpvpn.neutron.services.service_drivers.' 'bagpipe.bagpipe.BaGPipeBGPVPNDriver')): self.mocked_rpc = mock.patch( 'networking_bagpipe.agent.bgpvpn.rpc_client' '.BGPVPNAgentNotifyApi').start().return_value self.mock_attach_rpc = self.mocked_rpc.attach_port_on_bgpvpn self.mock_detach_rpc = self.mocked_rpc.detach_port_from_bgpvpn self.mock_update_rpc = self.mocked_rpc.update_bgpvpn self.mock_delete_rpc = self.mocked_rpc.delete_bgpvpn mock.patch( 'neutron_lib.rpc.get_client').start().return_value if not plugin: plugin = '%s.%s' % (__name__, TestCorePluginWithAgents.__name__) super(TestBagpipeCommon, self).setUp(service_provider=driver, core_plugin=plugin) self.ctxt = n_context.Context('fake_user', self._tenant_id) n_dict = {"name": "netfoo", "tenant_id": self._tenant_id, "admin_state_up": True, "router:external": True, "shared": True} self.external_net = {'network': self.plugin.create_network(self.ctxt, {'network': n_dict})} class AnyOfClass(object): def __init__(self, cls): self._class = cls def __eq__(self, other): return isinstance(other, self._class) def __repr__(self): return "AnyOfClass<%s>" % self._class.__name__ class TestBagpipeOVOPushPullMixin(object): # tests for OVO-based push notifications go here @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') def test_bgpvpn_update_name_only(self, mocked_push): with self.bgpvpn() as bgpvpn: self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], {'bgpvpn': {'name': 'newname'}}) # check that no RPC push is done for BGPVPN objects self.assertTrue( mocked_push.call_count == 0 or (not any([isinstance(ovo, objs.BGPVPNNetAssociation) for ovo in mocked_push.mock_calls[0][1][1]]) and not any([isinstance(ovo, objs.BGPVPNRouterAssociation) for ovo in mocked_push.mock_calls[0][1][1]]) ) ) @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') def test_bgpvpn_update_rts_no_assoc(self, mocked_push): with self.bgpvpn() as bgpvpn: self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], {'bgpvpn': {'route_targets': ['64512:43']}}, as_admin=True) # check that no RPC push is done for BGPVPN objects self.assertTrue( mocked_push.call_count == 0 or (not any([isinstance(ovo, objs.BGPVPNNetAssociation) for ovo in mocked_push.mock_calls[0][1][1]]) and not any([isinstance(ovo, objs.BGPVPNRouterAssociation) for ovo in mocked_push.mock_calls[0][1][1]]) ) ) @mock.patch.object(resources_rpc.ResourcesPushRpcApi, '_push') def test_bgpvpn_update_delete_rts_with_assocs(self, mocked_push): with self.bgpvpn(do_delete=False) as bgpvpn, \ self.network() as net, \ self.router(tenant_id=self._tenant_id) as router, \ self.assoc_net(bgpvpn['bgpvpn']['id'], net['network']['id'], do_disassociate=False), \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id'], do_disassociate=False): mocked_push.reset_mock() self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], {'bgpvpn': {'route_targets': ['64512:43']}}, as_admin=True) mocked_push.assert_any_call(mock.ANY, 'BGPVPNNetAssociation', mock.ANY, 'updated') mocked_push.assert_any_call(mock.ANY, 'BGPVPNRouterAssociation', mock.ANY, 'updated') mocked_push.reset_mock() # delete BGPVPN self._delete('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], as_admin=True) # after delete mocked_push.assert_any_call(mock.ANY, 'BGPVPNNetAssociation', mock.ANY, 'deleted') mocked_push.assert_any_call(mock.ANY, 'BGPVPNRouterAssociation', mock.ANY, 'deleted') @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') def test_net_assoc_create_delete(self, mocked_push): with self.network() as net, \ self.bgpvpn() as bgpvpn: mocked_push.reset_mock() with self.assoc_net(bgpvpn['bgpvpn']['id'], net['network']['id']): mocked_push.assert_called_once_with(mock.ANY, mock.ANY, 'created') ovos_in_call = mocked_push.mock_calls[0][1][1] self.assertEqual( [AnyOfClass(objs.BGPVPNNetAssociation)], ovos_in_call ) mocked_push.reset_mock() # after net assoc delete mocked_push.assert_called_once_with(mock.ANY, mock.ANY, 'deleted') ovos_in_call = mocked_push.mock_calls[0][1][1] self.assertEqual( [AnyOfClass(objs.BGPVPNNetAssociation)], ovos_in_call ) @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') def test_router_assoc_create_delete(self, mocked_push): with self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn: mocked_push.reset_mock() with self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']): mocked_push.assert_called_once_with(mock.ANY, mock.ANY, 'created') ovos_in_call = mocked_push.mock_calls[0][1][1] self.assertEqual( [AnyOfClass(objs.BGPVPNRouterAssociation)], ovos_in_call ) mocked_push.reset_mock() # after router assoc delete mocked_push.assert_called_once_with(mock.ANY, mock.ANY, 'deleted') ovos_in_call = mocked_push.mock_calls[0][1][1] self.assertEqual( [AnyOfClass(objs.BGPVPNRouterAssociation)], ovos_in_call ) @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') def test_port_assoc_crud(self, mocked_push): with self.port() as port, \ self.bgpvpn() as bgpvpn: mocked_push.reset_mock() with self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']) as port_assoc: mocked_push.assert_called_once_with(mock.ANY, mock.ANY, 'created') ovos_in_call = mocked_push.mock_calls[0][1][1] self.assertEqual( [AnyOfClass(objs.BGPVPNPortAssociation)], ovos_in_call ) mocked_push.reset_mock() self._update( ('bgpvpn/bgpvpns/%s/port_associations' % bgpvpn['bgpvpn']['id']), port_assoc['port_association']['id'], {'port_association': {'advertise_fixed_ips': False}}, as_admin=True) mocked_push.assert_called_once_with(mock.ANY, mock.ANY, 'updated') ovos_in_call = mocked_push.mock_calls[0][1][1] self.assertEqual( [AnyOfClass(objs.BGPVPNPortAssociation)], ovos_in_call ) mocked_push.reset_mock() # after port assoc delete mocked_push.assert_called_once_with(mock.ANY, mock.ANY, 'deleted') ovos_in_call = mocked_push.mock_calls[0][1][1] self.assertEqual( [AnyOfClass(objs.BGPVPNPortAssociation)], ovos_in_call ) class TestBagpipeServiceDriver(TestBagpipeCommon): def test_create_bgpvpn_l2_fails(self): bgpvpn_data = copy.copy(self.bgpvpn_data['bgpvpn']) bgpvpn_data.update({"type": "l2"}) # Assert that an error is returned to the client bgpvpn_req = self.new_create_request( 'bgpvpn/bgpvpns', bgpvpn_data) res = bgpvpn_req.get_response(self.ext_api) self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int) def test_create_bgpvpn_rds_fails(self): bgpvpn_data = copy.copy(self.bgpvpn_data) bgpvpn_data['bgpvpn'].update({"route_distinguishers": ["4444:55"]}) # Assert that an error is returned to the client bgpvpn_req = self.new_create_request( 'bgpvpn/bgpvpns', bgpvpn_data, as_admin=True) res = bgpvpn_req.get_response(self.ext_api) self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int) def test_bagpipe_update_bgpvpn_rds_fails(self): with self.bgpvpn() as bgpvpn: update_data = {'bgpvpn': {"route_distinguishers": ["4444:55"]}} self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], update_data, expected_code=webob.exc.HTTPBadRequest.code, as_admin=True) show_bgpvpn = self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], as_admin=True) self.assertEqual([], show_bgpvpn['bgpvpn']['route_distinguishers']) def test_bagpipe_associate_net(self): with self.port() as port1: net_id = port1['port']['network_id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] rt = bgpvpn['bgpvpn']['route_targets'] self.mock_update_rpc.reset_mock() with self.assoc_net(id, net_id): self.mock_update_rpc.assert_called_once_with( mock.ANY, _expected_formatted_bgpvpn(id, net_id, rt)) def test_bagpipe_associate_external_net_failed(self): net_id = self.external_net['network']['id'] with self.bgpvpn(tenant_id='another_tenant') as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'network_association': {'network_id': net_id, 'tenant_id': self._tenant_id}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='network_associations', as_admin=True) res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPForbidden.code) def test_bagpipe_associate_router(self): with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.subnet() as subnet: with self.port(subnet=subnet) as port: net_id = port['port']['network_id'] subnet_id = subnet['subnet']['id'] itf = self._router_interface_action('add', router_id, subnet_id, None) itf_port = self.plugin.get_port(self.ctxt, itf['port_id']) with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] rt = bgpvpn['bgpvpn']['route_targets'] self.mock_update_rpc.reset_mock() with self.assoc_router(id, router_id): self.mock_update_rpc.assert_called_once_with( mock.ANY, _expected_formatted_bgpvpn( id, net_id, rt, itf_port['mac_address'])) def test_bagpipe_disassociate_net(self): mocked_delete = self.mocked_rpc.delete_bgpvpn with self.port() as port1: net_id = port1['port']['network_id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] rt = bgpvpn['bgpvpn']['route_targets'] with self.assoc_net(id, net_id, do_disassociate=False) as assoc: mocked_delete.reset_mock() del_req = self.new_delete_request( 'bgpvpn/bgpvpns', id, fmt=self.fmt, subresource='network_associations', sub_id=assoc['network_association']['id']) res = del_req.get_response(self.ext_api) if res.status_int >= 400: raise webob.exc.HTTPClientError(code=res.status_int) mocked_delete.assert_called_once_with( mock.ANY, _expected_formatted_bgpvpn(id, net_id, rt)) def test_bagpipe_update_bgpvpn_rt(self): with self.port() as port1: net_id = port1['port']['network_id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] rt = ['6543:21'] with self.assoc_net(id, net_id): update_data = {'bgpvpn': {'route_targets': ['6543:21']}} self.mock_update_rpc.reset_mock() self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], update_data, as_admin=True) self.mock_update_rpc.assert_called_once_with( mock.ANY, _expected_formatted_bgpvpn(id, net_id, rt)) def test_bagpipe_update_bgpvpn_with_router_assoc(self): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']), \ self.port(subnet=subnet): self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) update_data = {'bgpvpn': {'route_targets': ['6543:21']}} self.mock_update_rpc.reset_mock() self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], update_data, as_admin=True) self.mock_update_rpc.assert_called_once_with( mock.ANY, _expected_formatted_bgpvpn(bgpvpn['bgpvpn']['id'], net['network']['id'])) def test_bagpipe_delete_bgpvpn(self): mocked_delete = self.mocked_rpc.delete_bgpvpn with self.port() as port1: net_id = port1['port']['network_id'] with self.bgpvpn(do_delete=False) as bgpvpn: id = bgpvpn['bgpvpn']['id'] rt = bgpvpn['bgpvpn']['route_targets'] mocked_delete.reset_mock() with self.assoc_net(id, net_id, do_disassociate=False): self._delete('bgpvpn/bgpvpns', id, as_admin=True) mocked_delete.assert_called_once_with( mock.ANY, _expected_formatted_bgpvpn(id, net_id, rt)) def test_bagpipe_delete_bgpvpn_with_router_assoc(self): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn(do_delete=False) as bgpvpn, \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id'], do_disassociate=False), \ self.port(subnet=subnet): self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) self.mock_delete_rpc.reset_mock() self._delete('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], as_admin=True) self.mocked_rpc.delete_bgpvpn.assert_called_once_with( mock.ANY, _expected_formatted_bgpvpn(bgpvpn['bgpvpn']['id'], net['network']['id'])) def test_bagpipe_callback_to_rpc_update_port_after_router_itf_added(self): driver = self.bgpvpn_plugin.driver with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn: itf = self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) with self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']), \ self.port(subnet=subnet) as port: mac_address = port['port']['mac_address'] formatted_ip = (port['port']['fixed_ips'][0]['ip_address'] + '/' + subnet['subnet']['cidr'].split('/')[-1]) itf_port = self.plugin.get_port(self.ctxt, itf['port_id']) expected = { 'gateway_ip': subnet['subnet']['gateway_ip'], 'mac_address': mac_address, 'ip_address': formatted_ip, 'gateway_mac': itf_port['mac_address'] } expected.update(driver._format_bgpvpn_network_route_targets( [bgpvpn['bgpvpn']])) actual = driver._retrieve_bgpvpn_network_info_for_port( self.ctxt, port['port']) self.assertEqual(expected, actual) def test_bagpipe_get_network_info_for_port(self): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.port(subnet=subnet) as port: itf = self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) itf_port = self.plugin.get_port(self.ctxt, itf['port_id']) r = bagpipe.get_network_info_for_port(self.ctxt, port['port']['id'], net['network']['id']) expected_ip = port['port']['fixed_ips'][0]['ip_address'] + "/24" self.assertEqual({ 'mac_address': port['port']['mac_address'], 'ip_address': expected_ip, 'gateway_ip': subnet['subnet']['gateway_ip'], 'gateway_mac': itf_port['mac_address'] }, r) RT = '12345:1' BGPVPN_INFO = {'mac_address': 'de:ad:00:00:be:ef', 'ip_address': '10.0.0.2', 'gateway_ip': '10.0.0.1', 'l3vpn': {'import_rt': [RT], 'export_rt': [RT] }, 'gateway_mac': None } class TestCorePluginML2WithAgents(ml2_plugin.Ml2Plugin, agents_db.AgentDbMixin): pass class TestBagpipeServiceDriverCallbacks(TestBagpipeCommon, TestBagpipeOVOPushPullMixin): '''Check that receiving callbacks results in RPC calls to the agent''' def setUp(self): cfg.CONF.set_override('mechanism_drivers', ['logger', 'fake_agent'], 'ml2') super(TestBagpipeServiceDriverCallbacks, self).setUp( "%s.%s" % (__name__, TestCorePluginML2WithAgents.__name__)) self.port_create_status = 'DOWN' self.plugin = directory.get_plugin() self.plugin.start_rpc_listeners() self.bagpipe_driver = self.bgpvpn_plugin.driver self.patched_driver = mock.patch.object( self.bgpvpn_plugin.driver, '_retrieve_bgpvpn_network_info_for_port', return_value=BGPVPN_INFO) self.patched_driver.start() # we choose an agent of type const.AGENT_TYPE_OFA # because this is the type used by the fake_agent mech driver helpers.register_ovs_agent(helpers.HOST, const.AGENT_TYPE_OFA) helpers.register_l3_agent() def _build_expected_return_active(self, port): bgpvpn_info_port = BGPVPN_INFO.copy() bgpvpn_info_port.update({'id': port['id'], 'network_id': port['network_id']}) return bgpvpn_info_port def _build_expected_return_down(self, port): return {'id': port['id'], 'network_id': port['network_id']} def _update_port_status(self, port, status): network_id = port['port']['network_id'] some_network = {'id': network_id} self.plugin.get_network = mock.Mock(return_value=some_network) self.plugin.update_port_status(self.ctxt, port['port']['id'], status, helpers.HOST) def test_bagpipe_callback_to_rpc_update_down2active(self): with self.port(arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.mock_attach_rpc.assert_called_once_with( mock.ANY, self._build_expected_return_active(port['port']), helpers.HOST) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_update_active2down(self): with self.port(arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_detach_rpc.assert_called_once_with( mock.ANY, self._build_expected_return_down(port['port']), helpers.HOST) self.assertFalse(self.mock_attach_rpc.called) def test_bagpipe_callback_to_rpc_update_active2active(self): with self.port(arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_update_down2down(self): with self.port(arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_DOWN) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_deleted(self): with self.port(arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self.plugin.delete_port(self.ctxt, port['port']['id']) self.mock_detach_rpc.assert_called_once_with( mock.ANY, self._build_expected_return_down(port['port']), helpers.HOST) self.assertFalse(self.mock_attach_rpc.called) def test_bagpipe_callback_to_rpc_update_active_ignore_net_ports(self): with self.port(device_owner=const.DEVICE_OWNER_NETWORK_PREFIX, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_update_down_ignore_net_ports(self): with self.port(device_owner=const.DEVICE_OWNER_NETWORK_PREFIX, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_deleted_ignore_net_ports(self): with self.port(device_owner=const.DEVICE_OWNER_NETWORK_PREFIX, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() payload = events.DBEventPayload( self.ctxt, resource_id=port['port']['id'], metadata={}, states=[port['port']]) self.bagpipe_driver.registry_port_deleted( None, None, None, payload) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_update_active_ignore_external_net(self): with self.subnet(network=self.external_net) as subnet, \ self.port(subnet=subnet, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_update_down_ignore_external_net(self): with self.subnet(network=self.external_net) as subnet, \ self.port(subnet=subnet, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_ACTIVE) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() self._update_port_status(port, const.PORT_STATUS_DOWN) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_bagpipe_callback_to_rpc_deleted_ignore_external_net(self): with self.subnet(network=self.external_net) as subnet, \ self.port(subnet=subnet, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port: self._update_port_status(port, const.PORT_STATUS_DOWN) self.mock_attach_rpc.reset_mock() self.mock_detach_rpc.reset_mock() payload = events.DBEventPayload( self.ctxt, resource_id=port['port']['id'], metadata={}, states=[port['port']]) self.bagpipe_driver.registry_port_deleted( None, None, None, payload) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) def test_delete_port_to_bgpvpn_rpc(self): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.port(subnet=subnet, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port, \ mock.patch.object(self.plugin, 'get_port', return_value=port['port']), \ mock.patch.object(self.plugin, 'get_network', return_value=net['network']): self.plugin.delete_port(self.ctxt, port['port']['id']) self.mock_detach_rpc.assert_called_once_with( mock.ANY, self._build_expected_return_down(port['port']), helpers.HOST) def test_bagpipe_callback_to_rpc_update_port_router_itf_added(self): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.port(subnet=subnet) as port, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ mock.patch.object(self.bagpipe_driver, 'get_bgpvpn', return_value=bgpvpn['bgpvpn']), \ mock.patch.object(bagpipe, 'get_router_bgpvpn_assocs', return_value=[{ 'bgpvpn_id': bgpvpn['bgpvpn']['id'] }]).start(): payload = events.DBEventPayload( self.ctxt, resource_id=router['router']['id'], metadata={'port': {'network_id': net['network']['id']}}) self.bagpipe_driver.registry_router_interface_created( None, None, None, payload=payload ) self.mock_update_rpc.assert_called_once_with( mock.ANY, self.bagpipe_driver._format_bgpvpn(self.ctxt, bgpvpn['bgpvpn'], port['port']['network_id'])) def test_bagpipe_callback_to_rpc_update_port_router_itf_removed(self): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.port(subnet=subnet, is_admin=True) as port, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ mock.patch.object(self.bagpipe_driver, 'get_bgpvpn', return_value=bgpvpn['bgpvpn']), \ mock.patch.object(bagpipe, 'get_router_bgpvpn_assocs', return_value=[{ 'bgpvpn_id': bgpvpn['bgpvpn']['id'] }]).start(): payload = events.DBEventPayload( self.ctxt, metadata={ 'network_id': port['port']['network_id'], 'port': { 'device_id': router['router']['id'], 'network_id': net['network']['id']} }) self.bagpipe_driver.registry_router_interface_deleted( None, None, None, payload=payload ) self.mock_delete_rpc.assert_called_once_with( mock.ANY, self.bagpipe_driver._format_bgpvpn(self.ctxt, bgpvpn['bgpvpn'], port['port']['network_id'])) def test_l3agent_add_remove_router_interface_to_bgpvpn_rpc(self): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ self.port(subnet=subnet, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True), \ mock.patch.object(bagpipe, 'get_router_bgpvpn_assocs', return_value=[{ 'bgpvpn_id': bgpvpn['bgpvpn']['id'] }]).start(): self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) self.mock_update_rpc.assert_called_once_with( mock.ANY, self.bagpipe_driver._format_bgpvpn(self.ctxt, bgpvpn['bgpvpn'], net['network']['id'])) self._router_interface_action('remove', router['router']['id'], subnet['subnet']['id'], None) self.mock_delete_rpc.assert_called_once_with( mock.ANY, self.bagpipe_driver._format_bgpvpn(self.ctxt, bgpvpn['bgpvpn'], net['network']['id'])) def test_gateway_mac_info_rpc(self): BGPVPN_INFO_GW_MAC = copy.copy(BGPVPN_INFO) BGPVPN_INFO_GW_MAC.update(gateway_mac='aa:bb:cc:dd:ee:ff') self.patched_driver.stop() with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn(route_targets=[RT]) as bgpvpn, \ self.port(subnet=subnet, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: helpers.HOST}, is_admin=True) as port, \ self.assoc_net(bgpvpn['bgpvpn']['id'], net['network']['id']), \ mock.patch.object(self.bgpvpn_plugin.driver, 'retrieve_bgpvpns_of_router_assocs' '_by_network', return_value=[{'type': 'l3', 'route_targets': [RT]}] ): self._update_port_status(port, const.PORT_STATUS_ACTIVE) itf = self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) itf_port = self.plugin.get_port(self.ctxt, itf['port_id']) self.mock_update_rpc.assert_called_with( mock.ANY, _expected_formatted_bgpvpn(bgpvpn['bgpvpn']['id'], net['network']['id'], [RT], gateway_mac=itf_port['mac_address']) ) self._router_interface_action('remove', router['router']['id'], subnet['subnet']['id'], None) self.mock_update_rpc.assert_called_with( mock.ANY, _expected_formatted_bgpvpn(bgpvpn['bgpvpn']['id'], net['network']['id'], [RT], gateway_mac=None) ) self.patched_driver.start() def test_l2agent_rpc_to_bgpvpn_rpc(self): # # Test that really simulate the ML2 codepath that # generate the registry events. ml2_rpc_callbacks = ml2_rpc.RpcCallbacks(mock.Mock(), mock.Mock()) n_dict = {"name": "netfoo", "tenant_id": self._tenant_id, "admin_state_up": True, "shared": False} net = self.plugin.create_network(self.ctxt, {'network': n_dict}) subnet_dict = {'name': 'test_subnet', 'tenant_id': self._tenant_id, 'ip_version': 4, 'cidr': '10.0.0.0/24', 'allocation_pools': [{'start': '10.0.0.2', 'end': '10.0.0.254'}], 'enable_dhcp': False, 'dns_nameservers': [], 'host_routes': [], 'network_id': net['id']} self.plugin.create_subnet(self.ctxt, {'subnet': subnet_dict}) p_dict = {'network_id': net['id'], 'tenant_id': self._tenant_id, 'name': 'fooport', "admin_state_up": True, "device_id": "tapfoo", "device_owner": "not_me", "mac_address": "de:ad:00:00:be:ef", "fixed_ips": [], "binding:host_id": helpers.HOST, } port = self.plugin.create_port(self.ctxt, {'port': p_dict}) ml2_rpc_callbacks.update_device_up(self.ctxt, host=helpers.HOST, agent_id='fooagent', device="de:ad:00:00:be:ef") self.mock_attach_rpc.assert_called_once_with( mock.ANY, self._build_expected_return_active(port), helpers.HOST) ml2_rpc_callbacks.update_device_down(self.ctxt, host=helpers.HOST, agent_id='fooagent', device="de:ad:00:00:be:ef") self.mock_detach_rpc.assert_called_once_with( mock.ANY, self._build_expected_return_down(port), helpers.HOST) self.mock_detach_rpc.reset_mock() self.plugin.delete_port(self.ctxt, port['id']) self.mock_detach_rpc.assert_called_once_with( mock.ANY, self._build_expected_return_down(port), helpers.HOST) def test_exception_on_callback(self): with mock.patch.object(bagpipe.LOG, 'exception') as log_exc: payload = events.DBEventPayload( self.ctxt, resource_id=None, metadata={}, states=[]) self.bagpipe_driver.registry_port_deleted( None, None, None, payload) self.assertFalse(self.mock_attach_rpc.called) self.assertFalse(self.mock_detach_rpc.called) self.assertTrue(log_exc.called) def test_format_bgpvpn_network_route_targets(self): driver = self.bgpvpn_plugin.driver bgpvpns = [{ 'type': 'l3', 'route_targets': ['12345:1', '12345:2', '12345:3'], 'import_targets': ['12345:2', '12345:3'], 'export_targets': ['12345:3', '12345:4'] }, { 'type': 'l3', 'route_targets': ['12345:3', '12346:1'] }, { 'type': 'l2', 'route_targets': ['12347:1'] }] result = driver._format_bgpvpn_network_route_targets(bgpvpns) expected = { 'l3vpn': { 'import_rt': ['12345:1', '12345:2', '12345:3', '12346:1'], 'export_rt': ['12345:1', '12345:2', '12345:3', '12345:4', '12346:1'] }, 'l2vpn': { 'import_rt': ['12347:1'], 'export_rt': ['12347:1'] } } self.assertCountEqual(result['l3vpn']['import_rt'], expected['l3vpn']['import_rt']) self.assertCountEqual(result['l3vpn']['export_rt'], expected['l3vpn']['export_rt']) self.assertCountEqual(result['l2vpn']['import_rt'], expected['l2vpn']['import_rt']) self.assertCountEqual(result['l2vpn']['export_rt'], expected['l2vpn']['export_rt']) class TestBagpipeServiceDriverV2RPCs(TestBagpipeCommon, TestBagpipeOVOPushPullMixin): '''Check RPC push/pull and local registry callback effects''' def setUp(self): cfg.CONF.set_override('mechanism_drivers', ['logger', 'fake_agent'], 'ml2') super(TestBagpipeServiceDriverV2RPCs, self).setUp( "%s.%s" % (__name__, TestCorePluginML2WithAgents.__name__), driver=('networking_bgpvpn.neutron.services.service_drivers.' 'bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver')) @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') def test_router_itf_event_router_assoc(self, mocked_push): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']): mocked_push.reset_mock() itf = self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) mocked_push.assert_any_call( mock.ANY, [AnyOfClass(objs.BGPVPNRouterAssociation)], 'updated') mocked_push.reset_mock() itf = self._router_interface_action('remove', router['router']['id'], subnet['subnet']['id'], itf['port_id']) mocked_push.assert_any_call( mock.ANY, [AnyOfClass(objs.BGPVPNRouterAssociation)], 'updated') @mock.patch.object(resources_rpc.ResourcesPushRpcApi, 'push') def test_router_itf_event_network_assoc(self, mocked_push): with self.network() as net, \ self.subnet(network=net) as subnet, \ self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ self.assoc_net(bgpvpn['bgpvpn']['id'], net['network']['id']): mocked_push.reset_mock() itf = self._router_interface_action('add', router['router']['id'], subnet['subnet']['id'], None) mocked_push.assert_any_call( mock.ANY, [AnyOfClass(objs.BGPVPNNetAssociation)], 'updated') mocked_push.reset_mock() itf = self._router_interface_action('remove', router['router']['id'], subnet['subnet']['id'], itf['port_id']) mocked_push.assert_any_call( mock.ANY, [AnyOfClass(objs.BGPVPNNetAssociation)], 'updated') ovos_in_call = mocked_push.mock_calls[0][1][1] for ovo in ovos_in_call: if not isinstance(ovo, objs.BGPVPNNetAssociation): continue for subnet in ovo.all_subnets(net['network']['id']): self.assertIsNone(subnet['gateway_mac']) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5099788 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/common/0000775000175000017500000000000000000000000026275 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/common/__init__.py0000664000175000017500000000000000000000000030374 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/common/test_utils.py0000664000175000017500000000446300000000000031055 0ustar00zuulzuul00000000000000# Copyright (c) 2017 Juniper Networks, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.tests import base from networking_bgpvpn.neutron.services.common.utils import filter_resource class TestFilterResource(base.BaseTestCase): _fake_resource_string = { 'fake_attribute': 'fake_value1', } _fake_resource_list = { 'fake_attribute': ['fake_value1', 'fake_value2', 'fake_value3'], } def test_filter_resource_succeeds_with_one_value(self): filters = { 'fake_attribute': 'fake_value1', } self.assertTrue(filter_resource(self._fake_resource_string, filters)) self.assertTrue(filter_resource(self._fake_resource_list, filters)) def test_filter_resource_fails_with_one_value(self): filters = { 'fake_attribute': 'wrong_fake_value1', } self.assertFalse(filter_resource(self._fake_resource_string, filters)) self.assertFalse(filter_resource(self._fake_resource_list, filters)) def test_filter_resource_succeeds_with_list_of_values(self): filters = { 'fake_attribute': ['fake_value1'], } self.assertTrue(filter_resource(self._fake_resource_string, filters)) filters = { 'fake_attribute': ['fake_value1', 'fake_value2'], } self.assertTrue(filter_resource(self._fake_resource_list, filters)) def test_filter_resource_fails_with_list_of_values(self): filters = { 'fake_attribute': ['wrong_fake_value1'], } self.assertFalse(filter_resource(self._fake_resource_string, filters)) filters = { 'fake_attribute': ['wrong_fake_value1', 'fake_value2'], } self.assertFalse(filter_resource(self._fake_resource_list, filters)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/tests/unit/services/test_plugin.py0000664000175000017500000020440000000000000027714 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Orange. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import copy from unittest import mock import webob.exc from neutron_lib.plugins import directory from oslo_utils import uuidutils from neutron.api import extensions as api_extensions from neutron.db import servicetype_db as sdb from neutron import extensions as n_extensions from neutron.services import provider_configuration as pconfig from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron.tests.unit.extensions import test_l3 from neutron.tests.unit.extensions.test_l3 import TestL3NatServicePlugin from neutron_lib.api.definitions import bgpvpn as bgpvpn_def from neutron_lib.api.definitions import bgpvpn_vni as bgpvpn_vni_def from networking_bgpvpn.neutron.db import bgpvpn_db from networking_bgpvpn.neutron import extensions from networking_bgpvpn.neutron.services.common import constants from networking_bgpvpn.neutron.services import plugin from networking_bgpvpn.neutron.services.service_drivers import driver_api _uuid = uuidutils.generate_uuid def http_client_error(req, res): explanation = "Request '%s %s %s' failed: %s" % (req.method, req.url, req.body, res.body) return webob.exc.HTTPClientError(code=res.status_int, explanation=explanation) class TestBgpvpnDriverWithVni(driver_api.BGPVPNDriverRC): more_supported_extension_aliases = ( driver_api.BGPVPNDriverRC.more_supported_extension_aliases + [ bgpvpn_vni_def.ALIAS]) def __init__(self, *args, **kwargs): super(TestBgpvpnDriverWithVni, self).__init__(*args, **kwargs) class BgpvpnTestCaseMixin(test_db_base_plugin_v2.NeutronDbPluginV2TestCase, test_l3.L3NatTestCaseMixin): def setUp(self, service_provider=None, core_plugin=None): if not service_provider: provider = (bgpvpn_def.ALIAS + ':dummy:networking_bgpvpn.neutron.services.' 'service_drivers.driver_api.BGPVPNDriverRC:default') else: provider = (bgpvpn_def.ALIAS + ':test:' + service_provider + ':default') bits = provider.split(':') provider = { 'service_type': bits[0], 'name': bits[1], 'driver': bits[2] } if len(bits) == 4: provider['default'] = True # override the default service provider self.service_providers = ( mock.patch.object(sdb.ServiceTypeManager, 'get_service_providers').start()) self.service_providers.return_value = [provider] self.provider_configuration = ( mock.patch.object(pconfig.ProviderConfiguration, '__init__').start()) self.provider_configuration.return_value = None bgpvpn_plugin_str = ('networking_bgpvpn.neutron.services.plugin.' 'BGPVPNPlugin') l3_plugin_str = ('neutron.tests.unit.extensions.test_l3.' 'TestL3NatServicePlugin') service_plugins = {'bgpvpn_plugin': bgpvpn_plugin_str, 'l3_plugin_name': l3_plugin_str} extensions_path = ':'.join(extensions.__path__ + n_extensions.__path__) # we need to provide a plugin instance, although # the extension manager will create a new instance # of the plugin ext_mgr = api_extensions.PluginAwareExtensionManager( extensions_path, {bgpvpn_def.ALIAS: plugin.BGPVPNPlugin(), 'l3_plugin_name': TestL3NatServicePlugin()}) super(BgpvpnTestCaseMixin, self).setUp( plugin=core_plugin, service_plugins=service_plugins, ext_mgr=ext_mgr) # find the BGPVPN plugin that was instantiated by the # extension manager: self.bgpvpn_plugin = directory.get_plugin(bgpvpn_def.ALIAS) self.bgpvpn_data = {'bgpvpn': {'name': 'bgpvpn1', 'type': 'l3', 'route_targets': ['1234:56'], 'tenant_id': self._tenant_id}} self.converted_data = copy.copy(self.bgpvpn_data) self.converted_data['bgpvpn'].update({'export_targets': [], 'import_targets': [], 'route_distinguishers': []}) def add_tenant(self, data): data.update({ "project_id": self._tenant_id, "tenant_id": self._tenant_id }) @contextlib.contextmanager def bgpvpn(self, do_delete=True, **kwargs): req_data = copy.deepcopy(self.bgpvpn_data) fmt = 'json' if kwargs.get('data'): req_data = kwargs.get('data') else: req_data['bgpvpn'].update(kwargs) req = self.new_create_request( 'bgpvpn/bgpvpns', req_data, fmt=fmt, as_admin=True) res = req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(req, res) bgpvpn = self.deserialize('json', res) yield bgpvpn if do_delete: self._delete('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], as_admin=True) @contextlib.contextmanager def assoc_net(self, bgpvpn_id, net_id, do_disassociate=True): fmt = 'json' data = {'network_association': {'network_id': net_id, 'tenant_id': self._tenant_id}} req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=fmt, id=bgpvpn_id, subresource='network_associations', as_admin=True) res = req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(req, res) assoc = self.deserialize('json', res) yield assoc if do_disassociate: del_req = self.new_delete_request( 'bgpvpn/bgpvpns', bgpvpn_id, fmt=self.fmt, subresource='network_associations', sub_id=assoc['network_association']['id'], as_admin=True) res = del_req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(del_req, res) @contextlib.contextmanager def assoc_router(self, bgpvpn_id, router_id, do_disassociate=True): fmt = 'json' data = {'router_association': {'router_id': router_id, 'tenant_id': self._tenant_id}} req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=fmt, id=bgpvpn_id, subresource='router_associations', as_admin=True) res = req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(req, res) assoc = self.deserialize('json', res) yield assoc if do_disassociate: del_req = self.new_delete_request( 'bgpvpn/bgpvpns', bgpvpn_id, fmt=self.fmt, subresource='router_associations', sub_id=assoc['router_association']['id'], as_admin=True) res = del_req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(del_req, res) @contextlib.contextmanager def assoc_port(self, bgpvpn_id, port_id, do_disassociate=True, **kwargs): fmt = 'json' data = {'port_association': {'port_id': port_id, 'tenant_id': self._tenant_id}} data['port_association'].update(kwargs) req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=fmt, id=bgpvpn_id, subresource='port_associations', as_admin=True) res = req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(req, res) assoc = self.deserialize('json', res) yield assoc if do_disassociate: del_req = self.new_delete_request( 'bgpvpn/bgpvpns', bgpvpn_id, fmt=self.fmt, subresource='port_associations', sub_id=assoc['port_association']['id'], as_admin=True) res = del_req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(del_req, res) def show_port_assoc(self, bgpvpn_id, port_assoc_id): req = self.new_show_request("bgpvpn/bgpvpns", bgpvpn_id, subresource="port_associations", sub_id=port_assoc_id) res = req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(req, res) return self.deserialize('json', res) class TestBGPVPNServicePlugin(BgpvpnTestCaseMixin): def test_bgpvpn_net_assoc_create(self): with self.network() as net, \ self.bgpvpn() as bgpvpn, \ mock.patch.object( self.bgpvpn_plugin, '_validate_network', return_value=net['network']) as mock_validate, \ self.assoc_net(bgpvpn['bgpvpn']['id'], net['network']['id']): mock_validate.assert_called_once_with( mock.ANY, net['network']['id']) def test_associate_empty_network(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='network_associations') res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) def test_associate_unknown_network(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] net_id = _uuid() data = {'network_association': {'network_id': net_id, 'tenant_id': self._tenant_id}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='network_associations') res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPNotFound.code) def test_associate_unauthorized_net(self): with self.network() as net: net_id = net['network']['id'] with self.bgpvpn(tenant_id='another_tenant') as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'network_association': {'network_id': net_id, 'tenant_id': self._tenant_id}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='network_associations', as_admin=True) res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPForbidden.code) def test_net_assoc_belong_to_diff_tenant(self): with self.network() as net: net_id = net['network']['id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'network_association': {'network_id': net_id, 'tenant_id': 'another_tenant'}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='network_associations', as_admin=True) res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPForbidden.code) def test_bgpvpn_router_assoc_create(self): with self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ mock.patch.object( self.bgpvpn_plugin, '_validate_router', return_value=router['router']) as mock_validate, \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']): mock_validate.assert_called_once_with( mock.ANY, router['router']['id']) def test_bgpvpn_router_assoc_update(self): with self.router(tenant_id=self._tenant_id) as router, \ self.bgpvpn() as bgpvpn, \ mock.patch.object( self.bgpvpn_plugin, '_validate_router', return_value=router['router']), \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']) as router_assoc: bgpvpn_id = bgpvpn['bgpvpn']['id'] updated = self._update('bgpvpn/bgpvpns/%s/router_associations' % bgpvpn_id, router_assoc['router_association']['id'], {'router_association': {'advertise_extra_routes': False}}, as_admin=True ) expected = {'router_association': { 'id': router_assoc['router_association']['id'], 'router_id': router['router']['id'], 'advertise_extra_routes': False }} self.add_tenant(expected['router_association']) self.assertEqual(expected, updated) def test_associate_empty_router(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {} bgpvpn_router_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='router_associations', as_admin=True) res = bgpvpn_router_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) def test_associate_unknown_router(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] router_id = _uuid() data = {'router_association': {'router_id': router_id, 'tenant_id': self._tenant_id}} bgpvpn_router_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='router_associations', as_admin=True) res = bgpvpn_router_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPNotFound.code) def test_associate_unauthorized_router(self): with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.bgpvpn(tenant_id='another_tenant') as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'router_association': {'router_id': router_id, 'tenant_id': self._tenant_id}} bgpvpn_router_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='router_associations', as_admin=True) res = bgpvpn_router_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPForbidden.code) def test_associate_router_incorrect_bgpvpn_type(self): with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.bgpvpn(tenant_id='another_tenant', type=constants.BGPVPN_L2) as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'router_association': {'router_id': router_id, 'tenant_id': self._tenant_id}} bgpvpn_router_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='router_associations', as_admin=True) res = bgpvpn_router_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) def test_router_assoc_belong_to_diff_tenant(self): with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'router_association': {'router_id': router_id, 'tenant_id': 'another_tenant'}} bgpvpn_router_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='router_associations', as_admin=True) res = bgpvpn_router_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPForbidden.code) def test_router_net_combination(self): with self.network() as net, \ self.bgpvpn() as bgpvpn, \ self.router(tenant_id=self._tenant_id) as router: self._test_router_net_combination_validation( net['network'], router['router'], bgpvpn['bgpvpn']) with self.network() as net, \ self.bgpvpn() as bgpvpn, \ self.router(tenant_id=self._tenant_id) as router: self._test_net_router_combination_validation( net['network'], router['router'], bgpvpn['bgpvpn']) def _test_router_net_combination_validation(self, network, router, bgpvpn): net_id = network['id'] bgpvpn_id = bgpvpn['id'] router_id = router['id'] data = {'router_association': {'router_id': router_id, 'tenant_id': self._tenant_id}} req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=bgpvpn_id, subresource='router_associations') res = req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(req, res) with self.subnet(network={'network': network}) as subnet: data = {"subnet_id": subnet['subnet']['id']} bgpvpn_rtr_intf_req = self.new_update_request( 'routers', data=data, fmt=self.fmt, id=router['id'], subresource='add_router_interface', as_admin=True) res = bgpvpn_rtr_intf_req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(bgpvpn_rtr_intf_req, res) data = {'network_association': {'network_id': net_id, 'tenant_id': self._tenant_id}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=bgpvpn_id, subresource='network_associations') res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) def _test_net_router_combination_validation(self, network, router, bgpvpn): net_id = network['id'] bgpvpn_id = bgpvpn['id'] router_id = router['id'] data = {'network_association': {'network_id': net_id, 'tenant_id': self._tenant_id}} req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=bgpvpn_id, subresource='network_associations') res = req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(req, res) with self.subnet(network={'network': network}) as subnet: data = {"subnet_id": subnet['subnet']['id']} bgpvpn_rtr_intf_req = self.new_update_request( 'routers', data=data, fmt=self.fmt, id=router['id'], subresource='add_router_interface', as_admin=True) res = bgpvpn_rtr_intf_req.get_response(self.ext_api) if res.status_int >= 400: raise http_client_error(bgpvpn_rtr_intf_req, res) data = {'router_association': {'router_id': router_id, 'tenant_id': self._tenant_id}} bgpvpn_router_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=bgpvpn_id, subresource='router_associations') res = bgpvpn_router_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) def test_attach_subnet_to_router_both_attached_to_bgpvpn(self): with self.network() as net, \ self.bgpvpn() as bgpvpn, \ self.router(tenant_id=self._tenant_id) as router, \ self.subnet(network={'network': net['network']}) as subnet, \ self.assoc_net(bgpvpn['bgpvpn']['id'], net['network']['id']), \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']): # Attach subnet to router data = {"subnet_id": subnet['subnet']['id']} bgpvpn_rtr_intf_req = self.new_update_request( 'routers', data=data, fmt=self.fmt, id=router['router']['id'], subresource='add_router_interface', as_admin=True) res = bgpvpn_rtr_intf_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPConflict.code) def test_attach_port_to_router_both_attached_to_bgpvpn(self): with self.network() as net, \ self.bgpvpn() as bgpvpn, \ self.router(tenant_id=self._tenant_id) as router, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.assoc_net(bgpvpn['bgpvpn']['id'], net['network']['id']), \ self.assoc_router(bgpvpn['bgpvpn']['id'], router['router']['id']): # Attach subnet to router data = {"port_id": port['port']['id']} bgpvpn_rtr_intf_req = self.new_update_request( 'routers', data=data, fmt=self.fmt, id=router['router']['id'], subresource='add_router_interface', as_admin=True) res = bgpvpn_rtr_intf_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPConflict.code) @mock.patch.object(plugin.BGPVPNPlugin, '_validate_port_association_routes_bgpvpn') def test_bgpvpn_port_assoc_create(self, mock_validate_port_assoc): with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn() as bgpvpn, \ mock.patch.object( self.bgpvpn_plugin, '_validate_port', return_value=port['port']) as mock_validate, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id'], advertise_fixed_ips=False, routes=[{ 'type': 'prefix', 'prefix': '12.1.3.0/24', }]): mock_validate.assert_called_once_with( mock.ANY, port['port']['id']) mock_validate_port_assoc.assert_called_once() def _test_bgpvpn_port_assoc_create_incorrect(self, **kwargs): with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn() as bgpvpn: data = {'port_association': {'port_id': port['port']['id'], 'tenant_id': self._tenant_id}} data['port_association'].update(kwargs) bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=bgpvpn['bgpvpn']['id'], subresource='port_associations', as_admin=True) res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) return res.body def test_bgpvpn_port_assoc_create_bgpvpn_route_non_existing(self): with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn() as bgpvpn: data = {'port_association': { 'port_id': port['port']['id'], 'tenant_id': self._tenant_id, 'routes': [{ 'type': 'bgpvpn', 'bgpvpn_id': '3aff9b6b-387b-4ffd-a9ff-a4bdffb349ff' }]}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=bgpvpn['bgpvpn']['id'], subresource='port_associations') res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) self.assertIn("bgpvpn specified in route does not exist", str(res.body)) def test_bgpvpn_port_assoc_create_bgpvpn_route_wrong_tenant(self): with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn() as bgpvpn, \ self.bgpvpn(tenant_id="notus") as bgpvpn_other: data = {'port_association': { 'port_id': port['port']['id'], 'tenant_id': self._tenant_id, 'routes': [{ 'type': 'bgpvpn', 'bgpvpn_id': bgpvpn_other['bgpvpn']['id'] }]}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=bgpvpn['bgpvpn']['id'], subresource='port_associations', as_admin=True) res = bgpvpn_net_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) self.assertIn("bgpvpn specified in route does not belong to " "the tenant", str(res.body)) def test_associate_empty_port(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {} bgpvpn_port_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='port_associations') res = bgpvpn_port_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) self.assertIn("Resource body required", str(res.body)) def test_associate_unknown_port(self): with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] port_id = _uuid() data = {'port_association': {'port_id': port_id, 'tenant_id': self._tenant_id}} bgpvpn_port_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='port_associations') res = bgpvpn_port_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPNotFound.code) def test_associate_unauthorized_port(self): with self.port(tenant_id=self._tenant_id) as port: port_id = port['port']['id'] with self.bgpvpn(tenant_id='another_tenant') as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'port_association': {'port_id': port_id, 'tenant_id': self._tenant_id}} bgpvpn_port_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='port_associations', as_admin=True) res = bgpvpn_port_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPForbidden.code) def test_port_assoc_belong_to_diff_tenant(self): with self.port(tenant_id=self._tenant_id) as port: port_id = port['port']['id'] with self.bgpvpn() as bgpvpn: id = bgpvpn['bgpvpn']['id'] data = {'port_association': {'port_id': port_id, 'tenant_id': 'another_tenant'}} bgpvpn_port_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=self.fmt, id=id, subresource='port_associations', as_admin=True) res = bgpvpn_port_req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPForbidden.code) @mock.patch.object(plugin.BGPVPNPlugin, '_validate_port_association_routes_bgpvpn') def test_bgpvpn_port_assoc_update( self, mock_validate): with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn() as bgpvpn, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']) as port_assoc: bgpvpn_id = bgpvpn['bgpvpn']['id'] self._update('bgpvpn/bgpvpns/%s/port_associations' % bgpvpn_id, port_assoc['port_association']['id'], {'port_association': {'advertise_fixed_ips': False}}, as_admin=True ) # one call for create, one call for update self.assertEqual(2, mock_validate.call_count) def test_bgpvpn_port_assoc_update_bgpvpn_route_wrong_tenant(self): with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn() as bgpvpn, \ self.bgpvpn(tenant_id="not-us") as bgpvpn_other, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']) as port_assoc: bgpvpn_id = bgpvpn['bgpvpn']['id'] req = self.new_update_request( 'bgpvpn/bgpvpns/%s/port_associations' % bgpvpn_id, {'port_association': { 'routes': [{ 'type': 'bgpvpn', 'bgpvpn_id': bgpvpn_other['bgpvpn']['id'] }] }}, port_assoc['port_association']['id'], as_admin=True ) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) self.assertIn( "bgpvpn specified in route does not belong to the tenant", str(res.body)) def test_bgpvpn_port_assoc_update_bgpvpn_route_wrong_type(self): with self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}) as port, \ self.bgpvpn(type='l2') as bgpvpn_l2, \ self.bgpvpn(type='l3') as bgpvpn_l3, \ self.assoc_port(bgpvpn_l2['bgpvpn']['id'], port['port']['id']) as port_assoc: bgpvpn_id = bgpvpn_l2['bgpvpn']['id'] req = self.new_update_request( 'bgpvpn/bgpvpns/%s/port_associations' % bgpvpn_id, {'port_association': { 'routes': [{ 'type': 'bgpvpn', 'bgpvpn_id': bgpvpn_l3['bgpvpn']['id'] }] }}, port_assoc['port_association']['id'], as_admin=True ) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code) self.assertIn("differing from type of associated BGPVPN", str(res.body)) class TestBGPVPNServiceDriverDB(BgpvpnTestCaseMixin): def setUp(self): super(TestBGPVPNServiceDriverDB, self).setUp() def _raise_bgpvpn_driver_precommit_exc(self, *args, **kwargs): raise extensions.bgpvpn.BGPVPNDriverError(method='precommit method') @mock.patch.object(driver_api.BGPVPNDriver, 'create_bgpvpn_postcommit') @mock.patch.object(driver_api.BGPVPNDriver, 'create_bgpvpn_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'create_bgpvpn') def test_create_bgpvpn(self, mock_create_db, mock_create_precommit, mock_create_postcommit): mock_create_db.return_value = self.converted_data['bgpvpn'] with self.bgpvpn(do_delete=False): self.assertTrue(mock_create_db.called) self.assertDictSupersetOf( self.converted_data['bgpvpn'], mock_create_db.call_args[0][1]) mock_create_precommit.assert_called_once_with( mock.ANY, self.converted_data['bgpvpn']) mock_create_postcommit.assert_called_once_with( mock.ANY, self.converted_data['bgpvpn']) def test_create_bgpvpn_precommit_fails(self): with mock.patch.object(driver_api.BGPVPNDriver, 'create_bgpvpn_precommit', new=self._raise_bgpvpn_driver_precommit_exc): # Assert that an error is returned to the client bgpvpn_req = self.new_create_request( 'bgpvpn/bgpvpns', self.bgpvpn_data, as_admin=True) res = bgpvpn_req.get_response(self.ext_api) self.assertEqual(webob.exc.HTTPError.code, res.status_int) # Assert that no bgpvpn has been created list = self._list('bgpvpn/bgpvpns', fmt='json') self.assertEqual([], list['bgpvpns']) def test_delete_bgpvpn_precommit_fails(self): with self.bgpvpn(do_delete=False) as bgpvpn, \ mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_bgpvpn', return_value=self.converted_data), \ mock.patch.object(driver_api.BGPVPNDriver, 'delete_bgpvpn_precommit', new=self._raise_bgpvpn_driver_precommit_exc): bgpvpn_req = self.new_delete_request('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], as_admin=True) res = bgpvpn_req.get_response(self.ext_api) self.assertEqual(webob.exc.HTTPError.code, res.status_int) # Assert that existing bgpvpn remains list = self._list('bgpvpn/bgpvpns', fmt='json') bgpvpn['bgpvpn'].pop('import_targets') bgpvpn['bgpvpn'].pop('project_id') bgpvpn['bgpvpn'].pop('tenant_id') bgpvpn['bgpvpn'].pop('route_distinguishers') bgpvpn['bgpvpn'].pop('route_targets') bgpvpn['bgpvpn'].pop('export_targets') self.assertEqual([bgpvpn['bgpvpn']], list['bgpvpns']) @mock.patch.object(driver_api.BGPVPNDriver, 'delete_bgpvpn_postcommit') def test_delete_bgpvpn(self, mock_delete_postcommit): with self.bgpvpn(do_delete=False) as bgpvpn, \ mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_bgpvpn') \ as mock_delete_db, \ mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_bgpvpn', return_value=self.converted_data): self._delete('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], as_admin=True) mock_delete_db.assert_called_once_with(mock.ANY, bgpvpn['bgpvpn']['id']) mock_delete_postcommit.assert_called_once_with(mock.ANY, self.converted_data) @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_bgpvpn') def test_get_bgpvpn(self, mock_get_db): with self.bgpvpn() as bgpvpn: self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], as_admin=True) mock_get_db.assert_called_once_with(mock.ANY, bgpvpn['bgpvpn']['id'], mock.ANY) def test_get_bgpvpn_with_net(self): with self.network() as net: net_id = net['network']['id'] with self.bgpvpn() as bgpvpn: with self.assoc_net(bgpvpn['bgpvpn']['id'], net_id=net_id): res = self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id']) self.assertIn('networks', res['bgpvpn']) self.assertEqual(net_id, res['bgpvpn']['networks'][0]) def test_get_bgpvpn_with_router(self): with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.bgpvpn() as bgpvpn: with self.assoc_router(bgpvpn['bgpvpn']['id'], router_id): res = self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id']) self.assertIn('routers', res['bgpvpn']) self.assertEqual(router_id, res['bgpvpn']['routers'][0]) @mock.patch.object(driver_api.BGPVPNDriver, 'update_bgpvpn_postcommit') @mock.patch.object(driver_api.BGPVPNDriver, 'update_bgpvpn_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'update_bgpvpn') def test_update_bgpvpn(self, mock_update_db, mock_update_precommit, mock_update_postcommit): with self.bgpvpn() as bgpvpn: old_bgpvpn = copy.copy(self.bgpvpn_data['bgpvpn']) old_bgpvpn['id'] = bgpvpn['bgpvpn']['id'] old_bgpvpn['networks'] = [] old_bgpvpn['routers'] = [] old_bgpvpn['ports'] = [] old_bgpvpn['project_id'] = old_bgpvpn['tenant_id'] old_bgpvpn['local_pref'] = None new_bgpvpn = copy.copy(old_bgpvpn) update = {'name': 'foo'} new_bgpvpn.update(update) mock_update_db.return_value = new_bgpvpn data = {"bgpvpn": {"name": new_bgpvpn['name']}} self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], data, as_admin=True) mock_update_db.assert_called_once_with( mock.ANY, bgpvpn['bgpvpn']['id'], data['bgpvpn']) mock_update_precommit.assert_called_once_with( mock.ANY, old_bgpvpn, new_bgpvpn) mock_update_postcommit.assert_called_once_with( mock.ANY, old_bgpvpn, new_bgpvpn) def test_update_bgpvpn_precommit_fails(self): with self.bgpvpn() as bgpvpn, \ mock.patch.object(driver_api.BGPVPNDriver, 'update_bgpvpn_precommit', new=self._raise_bgpvpn_driver_precommit_exc): new_data = {"bgpvpn": {"name": "foo"}} self._update('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id'], new_data, expected_code=webob.exc.HTTPError.code, as_admin=True) show_bgpvpn = self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id']) self.assertEqual(self.bgpvpn_data['bgpvpn']['name'], show_bgpvpn['bgpvpn']['name']) @mock.patch.object(driver_api.BGPVPNDriver, 'create_net_assoc_postcommit') @mock.patch.object(driver_api.BGPVPNDriver, 'create_net_assoc_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'create_net_assoc') def test_create_bgpvpn_net_assoc(self, mock_db_create_assoc, mock_pre_commit, mock_post_commit): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.network() as net: net_id = net['network']['id'] assoc_id = _uuid() data = {'tenant_id': self._tenant_id, 'network_id': net_id} net_assoc_dict = copy.copy(data) net_assoc_dict.update({'id': assoc_id, 'bgpvpn_id': bgpvpn_id}) mock_db_create_assoc.return_value = net_assoc_dict with self.assoc_net(bgpvpn_id, net_id=net_id, do_disassociate=False): self.assertTrue(mock_db_create_assoc.called) self.assertEqual( bgpvpn_id, mock_db_create_assoc.call_args[0][1]) self.assertDictSupersetOf( data, mock_db_create_assoc.call_args[0][2]) mock_pre_commit.assert_called_once_with(mock.ANY, net_assoc_dict) mock_post_commit.assert_called_once_with(mock.ANY, net_assoc_dict) def test_create_bgpvpn_net_assoc_precommit_fails(self): with self.bgpvpn() as bgpvpn, \ self.network() as net, \ mock.patch.object(driver_api.BGPVPNDriver, 'create_net_assoc_precommit', new=self._raise_bgpvpn_driver_precommit_exc): fmt = 'json' data = {'network_association': {'network_id': net['network']['id'], 'tenant_id': self._tenant_id}} bgpvpn_net_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=fmt, id=bgpvpn['bgpvpn']['id'], subresource='network_associations', as_admin=True) res = bgpvpn_net_req.get_response(self.ext_api) # Assert that driver failure returns an error self.assertEqual(webob.exc.HTTPError.code, res.status_int) # Assert that the bgpvpn is not associated to network bgpvpn_new = self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id']) self.assertEqual([], bgpvpn_new['bgpvpn']['networks']) @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_net_assoc') def test_get_bgpvpn_net_assoc(self, mock_get_db): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.network() as net: net_id = net['network']['id'] with self.assoc_net(bgpvpn_id, net_id=net_id) as assoc: assoc_id = assoc['network_association']['id'] res = 'bgpvpn/bgpvpns/' + bgpvpn_id + \ '/network_associations' self._show(res, assoc_id, as_admin=True) mock_get_db.assert_called_once_with(mock.ANY, assoc_id, bgpvpn_id, []) @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_net_assocs') def test_get_bgpvpn_net_assoc_list(self, mock_get_db): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.network() as net: net_id = net['network']['id'] with self.assoc_net(bgpvpn_id, net_id=net_id): res = 'bgpvpn/bgpvpns/' + bgpvpn_id + \ '/network_associations' self._list(res) mock_get_db.assert_called_once_with(mock.ANY, bgpvpn_id, mock.ANY, mock.ANY) @mock.patch.object(driver_api.BGPVPNDriver, 'delete_net_assoc_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_net_assoc') def test_delete_bgpvpn_net_assoc_precommit_fails(self, mock_db_del, mock_precommit): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.network() as net: net_id = net['network']['id'] with self.assoc_net(bgpvpn_id, net_id=net_id) as assoc: assoc_id = assoc['network_association']['id'] net_assoc = {'id': assoc_id, 'network_id': net_id, 'bgpvpn_id': bgpvpn_id} mock_db_del.return_value = net_assoc mock_precommit.return_value = \ self._raise_bgpvpn_driver_precommit_exc # Assert that existing bgpvpn and net-assoc remains list = self._list('bgpvpn/bgpvpns', fmt='json') bgpvpn['bgpvpn']['networks'] = [net_assoc['network_id']] bgpvpn['bgpvpn'].pop('import_targets') bgpvpn['bgpvpn'].pop('project_id') bgpvpn['bgpvpn'].pop('tenant_id') bgpvpn['bgpvpn'].pop('route_distinguishers') bgpvpn['bgpvpn'].pop('route_targets') bgpvpn['bgpvpn'].pop('export_targets') self.assertEqual([bgpvpn['bgpvpn']], list['bgpvpns']) @mock.patch.object(driver_api.BGPVPNDriver, 'delete_net_assoc_postcommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_net_assoc') def test_delete_bgpvpn_net_assoc(self, mock_db_del, mock_postcommit): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.network() as net: net_id = net['network']['id'] with self.assoc_net(bgpvpn_id, net_id=net_id) as assoc: assoc_id = assoc['network_association']['id'] net_assoc = {'id': assoc_id, 'network_id': net_id, 'bgpvpn_id': bgpvpn_id} self.add_tenant(net_assoc) mock_db_del.return_value = net_assoc mock_db_del.assert_called_once_with(mock.ANY, assoc_id, bgpvpn_id) mock_postcommit.assert_called_once_with(mock.ANY, net_assoc) @mock.patch.object(driver_api.BGPVPNDriver, 'create_router_assoc_postcommit') @mock.patch.object(driver_api.BGPVPNDriver, 'create_router_assoc_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'create_router_assoc') def test_create_bgpvpn_router_assoc(self, mock_db_create_assoc, mock_pre_commit, mock_post_commit): with self.bgpvpn() as bgpvpn, \ self.router(tenant_id=self._tenant_id) as router: bgpvpn_id = bgpvpn['bgpvpn']['id'] router_id = router['router']['id'] assoc_id = _uuid() data = {'tenant_id': self._tenant_id, 'router_id': router_id} router_assoc_dict = copy.copy(data) router_assoc_dict.update({'id': assoc_id, 'bgpvpn_id': bgpvpn_id}) mock_db_create_assoc.return_value = router_assoc_dict with self.assoc_router(bgpvpn_id, router_id=router_id, do_disassociate=False): self.assertTrue(mock_db_create_assoc.called) self.assertEqual( bgpvpn_id, mock_db_create_assoc.call_args[0][1]) self.assertDictSupersetOf( data, mock_db_create_assoc.call_args[0][2]) mock_pre_commit.assert_called_once_with(mock.ANY, router_assoc_dict) mock_post_commit.assert_called_once_with(mock.ANY, router_assoc_dict) def test_create_bgpvpn_router_assoc_precommit_fails(self): with self.bgpvpn() as bgpvpn, \ self.router(tenant_id=self._tenant_id) as router, \ mock.patch.object(driver_api.BGPVPNDriver, 'create_router_assoc_precommit', new=self._raise_bgpvpn_driver_precommit_exc): fmt = 'json' data = {'router_association': {'router_id': router['router']['id'], 'tenant_id': self._tenant_id}} bgpvpn_router_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=fmt, id=bgpvpn['bgpvpn']['id'], subresource='router_associations', as_admin=True) res = bgpvpn_router_req.get_response(self.ext_api) # Assert that driver failure returns an error self.assertEqual(webob.exc.HTTPError.code, res.status_int) # Assert that the bgpvpn is not associated to network bgpvpn_new = self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id']) self.assertEqual([], bgpvpn_new['bgpvpn']['routers']) @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_router_assoc') def test_get_bgpvpn_router_assoc(self, mock_get_db): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.router(tenant_id=self._tenant_id, as_admin=True) as router: router_id = router['router']['id'] with self.assoc_router(bgpvpn_id, router_id) as assoc: assoc_id = assoc['router_association']['id'] res = 'bgpvpn/bgpvpns/' + bgpvpn_id + \ '/router_associations' self._show(res, assoc_id, as_admin=True) mock_get_db.assert_called_once_with(mock.ANY, assoc_id, bgpvpn_id, []) @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_router_assocs') def test_get_bgpvpn_router_assoc_list(self, mock_get_db): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.assoc_router(bgpvpn_id, router_id): res = 'bgpvpn/bgpvpns/' + bgpvpn_id + \ '/router_associations' self._list(res) mock_get_db.assert_called_once_with(mock.ANY, bgpvpn_id, mock.ANY, mock.ANY) @mock.patch.object(driver_api.BGPVPNDriver, 'delete_router_assoc_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_router_assoc') def test_delete_bgpvpn_router_assoc_precommit_fails(self, mock_db_del, mock_precommit): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.assoc_router(bgpvpn_id, router_id) as assoc: assoc_id = assoc['router_association']['id'] router_assoc = {'id': assoc_id, 'router_id': router_id, 'bgpvpn_id': bgpvpn_id} mock_db_del.return_value = router_assoc mock_precommit.return_value = \ self._raise_bgpvpn_driver_precommit_exc # Assert that existing bgpvpn and router-assoc remains list = self._list('bgpvpn/bgpvpns', fmt='json') bgpvpn['bgpvpn']['routers'] = [router_assoc['router_id']] bgpvpn['bgpvpn'].pop('import_targets') bgpvpn['bgpvpn'].pop('project_id') bgpvpn['bgpvpn'].pop('tenant_id') bgpvpn['bgpvpn'].pop('route_distinguishers') bgpvpn['bgpvpn'].pop('route_targets') bgpvpn['bgpvpn'].pop('export_targets') self.assertEqual([bgpvpn['bgpvpn']], list['bgpvpns']) @mock.patch.object(driver_api.BGPVPNDriver, 'delete_router_assoc_postcommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_router_assoc') def test_delete_bgpvpn_router_assoc(self, mock_db_del, mock_postcommit): with self.bgpvpn() as bgpvpn: bgpvpn_id = bgpvpn['bgpvpn']['id'] with self.router(tenant_id=self._tenant_id) as router: router_id = router['router']['id'] with self.assoc_router(bgpvpn_id, router_id) as assoc: assoc_id = assoc['router_association']['id'] router_assoc = {'id': assoc_id, 'router_id': router_id, 'bgpvpn_id': bgpvpn_id, 'advertise_extra_routes': True} self.add_tenant(router_assoc) mock_db_del.return_value = router_assoc # (delete triggered by exit from with statement) mock_db_del.assert_called_once_with(mock.ANY, assoc_id, bgpvpn_id) mock_postcommit.assert_called_once_with(mock.ANY, router_assoc) @mock.patch.object(driver_api.BGPVPNDriverRC, 'create_port_assoc_postcommit') @mock.patch.object(driver_api.BGPVPNDriverRC, 'create_port_assoc_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'create_port_assoc') def test_create_bgpvpn_port_assoc(self, mock_db_create_assoc, mock_pre_commit, mock_post_commit): with self.bgpvpn() as bgpvpn, \ self.network() as net, \ self.subnet(network={'network': net['network']}) as subnet, \ self.port(subnet={'subnet': subnet['subnet']}, tenant_id=self._tenant_id) as port: bgpvpn_id = bgpvpn['bgpvpn']['id'] port_id = port['port']['id'] assoc_id = _uuid() data = {'tenant_id': self._tenant_id, 'port_id': port_id} port_assoc_dict = copy.copy(data) port_assoc_dict.update({'id': assoc_id, 'bgpvpn_id': bgpvpn_id}) mock_db_create_assoc.return_value = port_assoc_dict with self.assoc_port(bgpvpn_id, port_id=port_id, do_disassociate=False): self.assertTrue(mock_db_create_assoc.called) self.assertEqual( bgpvpn_id, mock_db_create_assoc.call_args[0][1]) self.assertDictSupersetOf( data, mock_db_create_assoc.call_args[0][2]) mock_pre_commit.assert_called_once_with(mock.ANY, port_assoc_dict) mock_post_commit.assert_called_once_with(mock.ANY, port_assoc_dict) def test_create_bgpvpn_port_assoc_precommit_fails(self): with self.bgpvpn() as bgpvpn, \ self.port(tenant_id=self._tenant_id) as port, \ mock.patch.object(driver_api.BGPVPNDriverRC, 'create_port_assoc_precommit', new=self._raise_bgpvpn_driver_precommit_exc): fmt = 'json' data = {'port_association': {'port_id': port['port']['id'], 'tenant_id': self._tenant_id}} bgpvpn_port_req = self.new_create_request( 'bgpvpn/bgpvpns', data=data, fmt=fmt, id=bgpvpn['bgpvpn']['id'], subresource='port_associations', as_admin=True) res = bgpvpn_port_req.get_response(self.ext_api) # Assert that driver failure returns an error self.assertEqual(webob.exc.HTTPError.code, res.status_int) # Assert that the bgpvpn is not associated to network bgpvpn_new = self._show('bgpvpn/bgpvpns', bgpvpn['bgpvpn']['id']) self.assertEqual([], bgpvpn_new['bgpvpn']['ports']) @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_port_assoc') def test_get_bgpvpn_port_assoc(self, mock_get_db): with self.bgpvpn() as bgpvpn, \ self.port(tenant_id=self._tenant_id, is_admin=True) as port, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']) as assoc: bgpvpn_id = bgpvpn['bgpvpn']['id'] assoc_id = assoc['port_association']['id'] res = 'bgpvpn/bgpvpns/' + bgpvpn_id + \ '/port_associations' self._show(res, assoc_id, as_admin=True) mock_get_db.assert_called_once_with(mock.ANY, assoc_id, bgpvpn_id, []) @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'get_port_assocs') def test_get_bgpvpn_port_assoc_list(self, mock_get_db): with self.bgpvpn() as bgpvpn, \ self.port(tenant_id=self._tenant_id) as port, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']): bgpvpn_id = bgpvpn['bgpvpn']['id'] res = 'bgpvpn/bgpvpns/' + bgpvpn_id + \ '/port_associations' self._list(res) mock_get_db.assert_called_once_with(mock.ANY, bgpvpn_id, mock.ANY, mock.ANY) @mock.patch.object(driver_api.BGPVPNDriverRC, 'delete_port_assoc_precommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_port_assoc') def test_delete_bgpvpn_port_assoc_precommit_fails(self, mock_db_del, mock_precommit): with self.bgpvpn() as bgpvpn, \ self.port(tenant_id=self._tenant_id, is_admin=True) as port, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']) as assoc: port_id = port['port']['id'] bgpvpn_id = bgpvpn['bgpvpn']['id'] assoc_id = assoc['port_association']['id'] port_assoc = {'id': assoc_id, 'port_id': port_id, 'bgpvpn_id': bgpvpn_id} mock_db_del.return_value = port_assoc mock_precommit.return_value = \ self._raise_bgpvpn_driver_precommit_exc # Assert that existing bgpvpn and port-assoc remains list = self._list('bgpvpn/bgpvpns', fmt='json') bgpvpn['bgpvpn']['ports'] = [port_assoc['port_id']] bgpvpn['bgpvpn'].pop('import_targets') bgpvpn['bgpvpn'].pop('project_id') bgpvpn['bgpvpn'].pop('tenant_id') bgpvpn['bgpvpn'].pop('route_distinguishers') bgpvpn['bgpvpn'].pop('route_targets') bgpvpn['bgpvpn'].pop('export_targets') self.assertEqual([bgpvpn['bgpvpn']], list['bgpvpns']) @mock.patch.object(driver_api.BGPVPNDriverRC, 'update_port_assoc_precommit') @mock.patch.object(driver_api.BGPVPNDriverRC, 'update_port_assoc_postcommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'update_port_assoc') def test_update_bgpvpn_port_assoc(self, mock_db_update, mock_postcommit, mock_precommit): with self.bgpvpn() as bgpvpn, \ self.port(tenant_id=self._tenant_id) as port, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']) as assoc: bgpvpn_id = bgpvpn['bgpvpn']['id'] assoc_id = assoc['port_association']['id'] assoc['port_association'].update({'bgpvpn_id': bgpvpn_id}) new_port_assoc = copy.deepcopy(assoc) changed = {'advertise_fixed_ips': False} new_port_assoc['port_association'].update(changed) mock_db_update.return_value = new_port_assoc['port_association'] data = {"port_association": changed} self._update('bgpvpn/bgpvpns/%s/port_associations' % bgpvpn_id, assoc['port_association']['id'], data, as_admin=True) mock_db_update.assert_called_once_with(mock.ANY, assoc_id, bgpvpn_id, data['port_association']) mock_precommit.assert_called_once_with( mock.ANY, assoc['port_association'], new_port_assoc['port_association'] ) mock_postcommit.assert_called_once_with( mock.ANY, assoc['port_association'], new_port_assoc['port_association'] ) @mock.patch.object(driver_api.BGPVPNDriverRC, 'delete_port_assoc_precommit') @mock.patch.object(driver_api.BGPVPNDriverRC, 'delete_port_assoc_postcommit') @mock.patch.object(bgpvpn_db.BGPVPNPluginDb, 'delete_port_assoc') def test_delete_bgpvpn_port_assoc(self, mock_db_del, mock_postcommit, mock_precommit): with self.bgpvpn() as bgpvpn, \ self.port(tenant_id=self._tenant_id) as port, \ self.assoc_port(bgpvpn['bgpvpn']['id'], port['port']['id']) as assoc: port_id = port['port']['id'] bgpvpn_id = bgpvpn['bgpvpn']['id'] assoc_id = assoc['port_association']['id'] port_assoc = {'id': assoc_id, 'bgpvpn_id': bgpvpn_id, 'port_id': port_id, 'routes': [], 'advertise_fixed_ips': True} self.add_tenant(port_assoc) mock_db_del.return_value = port_assoc # (delete triggered by exit from with statement) mock_db_del.assert_called_once_with(mock.ANY, assoc_id, bgpvpn_id) mock_precommit.assert_called_once_with(mock.ANY, port_assoc) mock_postcommit.assert_called_once_with(mock.ANY, port_assoc) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn/version.py0000664000175000017500000000133100000000000023076 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version # DNM: just test state of master version_info = pbr.version.VersionInfo('networking-bgpvpn') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4979787 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/0000775000175000017500000000000000000000000022533 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/PKG-INFO0000664000175000017500000000627300000000000023640 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: networking-bgpvpn Version: 21.0.0 Summary: API and Framework to interconnect bgpvpn to neutron networks Home-page: https://docs.openstack.org/networking-bgpvpn/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/networking-bgpvpn.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on =============================================== BGP-MPLS VPN Extension for OpenStack Networking =============================================== This project provides an API and Framework to interconnect BGP/MPLS VPNs to Openstack Neutron networks, routers and ports. The Border Gateway Protocol and Multi-Protocol Label Switching are widely used Wide Area Networking technologies. The primary purpose of this project is to allow attachment of Neutron networks and/or routers to VPNs built in carrier provided WANs using these standard protocols. An additional purpose of this project is to enable the use of these technologies within the Neutron networking environment. A vendor-neutral API and data model are provided such that multiple SDN controllers may be used as backends, while offering the same tenant facing API. A reference implementation working along with Neutron reference drivers is also provided. * Free software: Apache license * Source: https://opendev.org/openstack/networking-bgpvpn * Bugs: https://bugs.launchpad.net/bgpvpn * Doc: https://docs.openstack.org/networking-bgpvpn/latest/ * Release notes: https://docs.openstack.org/releasenotes/networking-bgpvpn/ =================== Introduction videos =================== The following videos are filmed presentations of talks given during the Barcelona OpenStack Summit (Oct' 2016). Although they do not cover the work done since, they can be a good introduction to the project: * https://www.youtube.com/watch?v=kGW5R8mtmRg * https://www.youtube.com/watch?v=LCDeR7MwTzE Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 Provides-Extra: bagpipe Provides-Extra: horizon Provides-Extra: test ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/SOURCES.txt0000664000175000017500000003264400000000000024430 0ustar00zuulzuul00000000000000.coveragerc .mailmap .pre-commit-config.yaml .pylintrc .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel-django.cfg babel-djangojs.cfg babel.cfg bindep.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini bgpvpn_dashboard/__init__.py bgpvpn_dashboard/api/__init__.py bgpvpn_dashboard/api/bgpvpn.py bgpvpn_dashboard/common/__init__.py bgpvpn_dashboard/common/bgpvpn.py bgpvpn_dashboard/dashboards/__init__.py bgpvpn_dashboard/dashboards/admin/__init__.py bgpvpn_dashboard/dashboards/admin/bgpvpn/__init__.py bgpvpn_dashboard/dashboards/admin/bgpvpn/forms.py bgpvpn_dashboard/dashboards/admin/bgpvpn/panel.py bgpvpn_dashboard/dashboards/admin/bgpvpn/tables.py bgpvpn_dashboard/dashboards/admin/bgpvpn/tabs.py bgpvpn_dashboard/dashboards/admin/bgpvpn/urls.py bgpvpn_dashboard/dashboards/admin/bgpvpn/views.py bgpvpn_dashboard/dashboards/admin/bgpvpn/workflows.py bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_create.html bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/_detail_overview.html bgpvpn_dashboard/dashboards/admin/bgpvpn/templates/bgpvpn/create.html bgpvpn_dashboard/dashboards/project/__init__.py bgpvpn_dashboard/dashboards/project/bgpvpn/__init__.py bgpvpn_dashboard/dashboards/project/bgpvpn/forms.py bgpvpn_dashboard/dashboards/project/bgpvpn/panel.py bgpvpn_dashboard/dashboards/project/bgpvpn/tables.py bgpvpn_dashboard/dashboards/project/bgpvpn/tabs.py bgpvpn_dashboard/dashboards/project/bgpvpn/urls.py bgpvpn_dashboard/dashboards/project/bgpvpn/views.py bgpvpn_dashboard/dashboards/project/bgpvpn/workflows.py bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/__init__.py bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/tables.py bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/tabs.py bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/urls.py bgpvpn_dashboard/dashboards/project/bgpvpn/network_associations/views.py bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/__init__.py bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/forms.py bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/tables.py bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/tabs.py bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/urls.py bgpvpn_dashboard/dashboards/project/bgpvpn/router_associations/views.py bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_associated_networks.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_associated_routers.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_create_network_association.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_detail_overview.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/_modify.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/create_network_association.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/index.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/modify.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/network_associations/_detail_overview.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associations/_detail_overview.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associations/_modify.html bgpvpn_dashboard/dashboards/project/bgpvpn/templates/bgpvpn/router_associations/modify.html bgpvpn_dashboard/enabled/_1495_project_bgpvpn_panel.py bgpvpn_dashboard/enabled/_2360_admin_bgpvpn_panel.py bgpvpn_dashboard/enabled/__init__.py bgpvpn_dashboard/etc/bgpvpn-horizon.conf bgpvpn_dashboard/locale/en_GB/LC_MESSAGES/django.po bgpvpn_dashboard/locale/fr/LC_MESSAGES/django.po bgpvpn_dashboard/test/__init__.py bgpvpn_dashboard/test/helpers.py bgpvpn_dashboard/test/settings.py bgpvpn_dashboard/test/urls.py bgpvpn_dashboard/test/admin/__init__.py bgpvpn_dashboard/test/admin/test_forms.py bgpvpn_dashboard/test/admin/test_tables.py bgpvpn_dashboard/test/admin/test_views.py bgpvpn_dashboard/test/api_tests/__init__.py bgpvpn_dashboard/test/api_tests/test_bgpvpn.py bgpvpn_dashboard/test/project/__init__.py bgpvpn_dashboard/test/project/test_forms.py bgpvpn_dashboard/test/project/test_tables.py bgpvpn_dashboard/test/project/test_views.py bgpvpn_dashboard/test/test_data/__init__.py bgpvpn_dashboard/test/test_data/bgpvpn_data.py bgpvpn_dashboard/test/test_data/utils.py devstack/devstack-gate-bagpipe-rc devstack/devstack-gate-rc devstack/plugin.sh devstack/settings doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/introduction.rst doc/source/_static/.placeholder doc/source/configuration/index.rst doc/source/configuration/networking-bgpvpn.rst doc/source/configuration/policy-sample.rst doc/source/configuration/policy.rst doc/source/configuration/samples/networking-bgpvpn.rst doc/source/contributor/contributing.rst doc/source/contributor/index.rst doc/source/contributor/specs.rst doc/source/contributor/future/attributes.rst doc/source/contributor/future/index.rst doc/source/install/index.rst doc/source/samples/__init__.py doc/source/samples/bgpvpn-sample01.py doc/source/user/api.rst doc/source/user/components-sdn.blockdiag doc/source/user/heat.rst doc/source/user/horizon.rst doc/source/user/index.rst doc/source/user/overview.rst doc/source/user/usage.rst doc/source/user/workflows.seqdiag doc/source/user/drivers/index.rst doc/source/user/drivers/bagpipe/index.rst doc/source/user/drivers/bagpipe/overview.blockdiag doc/source/user/drivers/nuage/index.rst doc/source/user/drivers/opencontrail/index.rst doc/source/user/drivers/opendaylight/index.rst doc/source/user/figures/components_sdn_blockdiag.png doc/source/user/figures/overview_blockdiag.png doc/source/user/figures/workflows_seqdiag.png etc/README.txt etc/neutron/networking_bgpvpn.conf etc/oslo-config-generator/networking-bgpvpn.conf etc/oslo-policy-generator/policy.conf networking_bgpvpn/__init__.py networking_bgpvpn/_i18n.py networking_bgpvpn/version.py networking_bgpvpn.egg-info/PKG-INFO networking_bgpvpn.egg-info/SOURCES.txt networking_bgpvpn.egg-info/dependency_links.txt networking_bgpvpn.egg-info/entry_points.txt networking_bgpvpn.egg-info/not-zip-safe networking_bgpvpn.egg-info/pbr.json networking_bgpvpn.egg-info/requires.txt networking_bgpvpn.egg-info/top_level.txt networking_bgpvpn/locale/en_GB/LC_MESSAGES/networking_bgpvpn.po networking_bgpvpn/neutron/__init__.py networking_bgpvpn/neutron/opts.py networking_bgpvpn/neutron/db/__init__.py networking_bgpvpn/neutron/db/bgpvpn_db.py networking_bgpvpn/neutron/db/head.py networking_bgpvpn/neutron/db/migration/__init__.py networking_bgpvpn/neutron/db/migration/alembic_migrations/README networking_bgpvpn/neutron/db/migration/alembic_migrations/__init__.py networking_bgpvpn/neutron/db/migration/alembic_migrations/env.py networking_bgpvpn/neutron/db/migration/alembic_migrations/script.py.mako networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/start_networking_bgpvpn.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/contract/180baa4183e0_initial.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/expand/17d9fd4fddee_initial.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/liberty/expand/3600132c6147_add_router_association_table.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/contract/23ce05e0a19f_rename_tenant_to_project.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/newton/expand/0ab4049986b8_add_indexes_to_tenant_id.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/39411aacf9b8_add_vni_to_bgpvpn_table.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/4610803bdf0d_router_assoc_add_advertise_extra_routes.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/666c706fea3b_bgpvpn_local_pref.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/queens/expand/9a6664f3b8d4_add_port_association_table.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/contract/9d7f1ae5fa56_add_standard_attributes.py networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/rocky/expand/7a9482036ecd_add_standard_attributes.py networking_bgpvpn/neutron/extensions/__init__.py networking_bgpvpn/neutron/extensions/bgpvpn.py networking_bgpvpn/neutron/extensions/bgpvpn_routes_control.py networking_bgpvpn/neutron/extensions/bgpvpn_vni.py networking_bgpvpn/neutron/services/__init__.py networking_bgpvpn/neutron/services/plugin.py networking_bgpvpn/neutron/services/common/__init__.py networking_bgpvpn/neutron/services/common/constants.py networking_bgpvpn/neutron/services/common/utils.py networking_bgpvpn/neutron/services/service_drivers/__init__.py networking_bgpvpn/neutron/services/service_drivers/driver_api.py networking_bgpvpn/neutron/services/service_drivers/bagpipe/__init__.py networking_bgpvpn/neutron/services/service_drivers/bagpipe/bagpipe.py networking_bgpvpn/neutron/services/service_drivers/bagpipe/bagpipe_v2.py networking_bgpvpn/neutronclient/__init__.py networking_bgpvpn/neutronclient/neutron/__init__.py networking_bgpvpn/neutronclient/neutron/v2_0/__init__.py networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/__init__.py networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/bgpvpn.py networking_bgpvpn/policies/__init__.py networking_bgpvpn/policies/bgpvpn.py networking_bgpvpn/policies/network_association.py networking_bgpvpn/policies/port_association.py networking_bgpvpn/policies/router_association.py networking_bgpvpn/tests/__init__.py networking_bgpvpn/tests/functional/__init__.py networking_bgpvpn/tests/functional/requirements.txt networking_bgpvpn/tests/functional/test_placeholder.py networking_bgpvpn/tests/functional/db/__init__.py networking_bgpvpn/tests/functional/db/test_migrations.py networking_bgpvpn/tests/unit/__init__.py networking_bgpvpn/tests/unit/client/__init__.py networking_bgpvpn/tests/unit/client/test_client.py networking_bgpvpn/tests/unit/db/__init__.py networking_bgpvpn/tests/unit/db/test_db.py networking_bgpvpn/tests/unit/extensions/__init__.py networking_bgpvpn/tests/unit/extensions/test_bgpvpn.py networking_bgpvpn/tests/unit/extensions/test_bgpvpn_rc_base.py networking_bgpvpn/tests/unit/extensions/test_bgpvpn_routes_control.py networking_bgpvpn/tests/unit/extensions/test_bgpvpn_vni.py networking_bgpvpn/tests/unit/services/__init__.py networking_bgpvpn/tests/unit/services/test_plugin.py networking_bgpvpn/tests/unit/services/bagpipe/__init__.py networking_bgpvpn/tests/unit/services/bagpipe/test_bagpipe.py networking_bgpvpn/tests/unit/services/common/__init__.py networking_bgpvpn/tests/unit/services/common/test_utils.py networking_bgpvpn_heat/__init__.py networking_bgpvpn_heat/_i18n.py networking_bgpvpn_heat/bgpvpnservice.py networking_bgpvpn_heat/examples/bgpvpn_test-00.yaml networking_bgpvpn_heat/examples/bgpvpn_test-01-admin.yaml networking_bgpvpn_heat/examples/bgpvpn_test-01-tenant.yaml networking_bgpvpn_heat/examples/bgpvpn_test-01bis_router-tenant.yaml networking_bgpvpn_heat/examples/bgpvpn_test-01ter_port-tenant.yaml networking_bgpvpn_heat/examples/bgpvpn_test-04-admin.yaml networking_bgpvpn_heat/examples/bgpvpn_test-04-tenant.yaml networking_bgpvpn_heat/locale/en_GB/LC_MESSAGES/networking_bgpvpn_heat.po releasenotes/notes/.placeholder releasenotes/notes/0_heat-support-ab233de7401aeb36.yaml releasenotes/notes/add-vni-to-bgpvpn-31d6eda7ba6d5047.yaml releasenotes/notes/bagpipe-driver-improvements-401a7ba59a6f5f45.yaml releasenotes/notes/bagpipe-router-compat-b53b6f3799cd23db.yaml releasenotes/notes/bagpipe_enable_evpn-ae64f77df89e069b.yaml releasenotes/notes/bagpipe_ovo_rpcs-380f7bd52969bef7.yaml releasenotes/notes/bgpvpn_service_declaration-6d9ecd2c397e4821.yaml releasenotes/notes/deprecate-old-opencontrail-driver-a598892ddf54c724.yaml releasenotes/notes/drop-py-2-7-4db5f2b1529bb09c.yaml releasenotes/notes/drop-python-3-6-and-3-7-97c0464e7a396023.yaml releasenotes/notes/filtering-on-resource-association-2acdbc5b59d1a40a.yaml releasenotes/notes/heat_bgpvpn_local_pref-a1cbfde10810b157.yaml releasenotes/notes/heat_port_associations-f2d316f3b8c755fe.yaml releasenotes/notes/horizon-in-extras-371d572b09437dc4.yaml releasenotes/notes/horizon-support-06a7b21286002949.yaml releasenotes/notes/mitaka-prelude-1675467c144a91ea.yaml releasenotes/notes/odl_router_association-fa2ed7c396531418.yaml releasenotes/notes/pre_commit_checks-b902ee19a3654a7b.yaml releasenotes/notes/remove_obsolete_drivers-3706e080098a5cb6.yaml releasenotes/notes/routes-control-api-ext-c0c4020e7370d833.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po specs/bgpvpn.rst tools/django-manage.py tools/generate_config_file_samples.sh././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/dependency_links.txt0000664000175000017500000000000100000000000026601 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/entry_points.txt0000664000175000017500000000142000000000000026026 0ustar00zuulzuul00000000000000[heat.constraints] neutron.bgpvpn = networking_bgpvpn_heat.bgpvpnservice:BGPVPNConstraint [neutron.db.alembic_migrations] networking-bgpvpn = networking_bgpvpn.neutron.db.migration:alembic_migrations [neutron.policies] networking-bgpvpn = networking_bgpvpn.policies:list_rules [neutron.service_plugins] bgpvpn = networking_bgpvpn.neutron.services.plugin:BGPVPNPlugin [neutronclient.extension] bgpvpn = networking_bgpvpn.neutronclient.neutron.v2_0.bgpvpn.bgpvpn [oslo.config.opts] networking-bgpvpn.service_provider = networking_bgpvpn.neutron.opts:list_service_provider [oslo.config.opts.defaults] networking-bgpvpn.service_provider = networking_bgpvpn.neutron.opts:set_service_provider_default [oslo.policy.policies] networking-bgpvpn = networking_bgpvpn.policies:list_rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/not-zip-safe0000664000175000017500000000000100000000000024761 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/pbr.json0000664000175000017500000000005600000000000024212 0ustar00zuulzuul00000000000000{"git_version": "b834f0c", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/requires.txt0000664000175000017500000000112300000000000025130 0ustar00zuulzuul00000000000000debtcollector>=1.19.0 horizon>=17.1.0 networking-bagpipe>=12.0.0 neutron-lib>=1.30.0 neutron>=23.0.0.0b2 oslo.config>=5.2.0 oslo.db>=4.37.0 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.utils>=3.33.0 pbr>=4.0.0 [bagpipe] networking-bagpipe>=9.0.0 [horizon] horizon>=17.1.0 [test] PyMySQL>=0.7.6 WebOb>=1.8.2 WebTest>=2.0.27 coverage!=4.4,>=4.0 hacking<6.2.0,>=6.1.0 horizon>=17.1.0 isort==4.3.21 networking-bagpipe>=12.0.0.0rc1 oslotest>=3.2.0 psycopg2>=2.8.5 pylint==2.17.4 pytest>=5.3.5 python-subunit>=1.0.0 stestr>=1.0.0 tempest>=17.1.0 testresources>=2.0.0 testscenarios>=0.4 testtools>=2.2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867199.0 networking-bgpvpn-21.0.0/networking_bgpvpn.egg-info/top_level.txt0000664000175000017500000000007200000000000025264 0ustar00zuulzuul00000000000000bgpvpn_dashboard networking_bgpvpn networking_bgpvpn_heat ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5099788 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/0000775000175000017500000000000000000000000022042 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/__init__.py0000664000175000017500000000000000000000000024141 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/_i18n.py0000664000175000017500000000211100000000000023325 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import oslo_i18n DOMAIN = "networking_bgpvpn_heat" _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" # requires oslo.i18n >=2.1.0 _C = _translators.contextual_form # The plural translation function using the name "_P" # requires oslo.i18n >=2.1.0 _P = _translators.plural_form def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/bgpvpnservice.py0000664000175000017500000003764000000000000025303 0ustar00zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from heat.common import exception from heat.engine import attributes from heat.engine.clients.os.neutron import neutron_constraints from heat.engine import constraints from heat.engine import properties from heat.engine.resources.openstack.neutron import neutron from networking_bgpvpn_heat._i18n import _ class BGPVPN(neutron.NeutronResource): """A resource for BGPVPN service in neutron. """ required_service_extension = 'bgpvpn' PROPERTIES = ( NAME, TYPE, DESCRIPTION, ROUTE_DISTINGUISHERS, IMPORT_TARGETS, EXPORT_TARGETS, ROUTE_TARGETS, TENANT_ID, LOCAL_PREF ) = ( 'name', 'type', 'description', 'route_distinguishers', 'import_targets', 'export_targets', 'route_targets', 'tenant_id', 'local_pref' ) ATTRIBUTES = ( SHOW, STATUS ) = ( 'show', 'status' ) properties_schema = { NAME: properties.Schema( properties.Schema.STRING, _('Name for the bgpvpn.'), ), TYPE: properties.Schema( properties.Schema.STRING, _('BGP VPN type selection between L3VPN (l3) and ' 'EVPN (l2), default:l3'), required=False, default='l3', constraints=[ constraints.AllowedValues(['l2', 'l3']) ] ), DESCRIPTION: properties.Schema( properties.Schema.STRING, _('Description for the bgpvpn.'), required=False, ), TENANT_ID: properties.Schema( properties.Schema.STRING, _('Tenant this bgpvpn belongs to (name or id).'), required=False, constraints=[ constraints.CustomConstraint('keystone.project') ] ), ROUTE_DISTINGUISHERS: properties.Schema( properties.Schema.LIST, _('List of RDs that will be used to advertize BGPVPN routes.'), required=False, # TODO(tmorin): add a pattern constraint ), IMPORT_TARGETS: properties.Schema( properties.Schema.LIST, _('List of additional Route Targets to import from.'), required=False, # TODO(tmorin): add a pattern constraint ), EXPORT_TARGETS: properties.Schema( properties.Schema.LIST, _('List of additional Route Targets to export to.'), required=False, # TODO(tmorin): add a pattern constraint ), ROUTE_TARGETS: properties.Schema( properties.Schema.LIST, _('Route Targets list to import/export for this BGPVPN.'), required=False, # TODO(tmorin): add a pattern constraint ), LOCAL_PREF: properties.Schema( properties.Schema.INTEGER, description=_('Default value of the BGP LOCAL_PREF for the ' 'route advertisement to this BGPVPN.'), constraints=[ constraints.Range(0, 2 ** 32 - 1), ], ) } attributes_schema = { STATUS: attributes.Schema( _('Status of bgpvpn.'), ), SHOW: attributes.Schema( _('All attributes.') ), } def validate(self): super(BGPVPN, self).validate() def handle_create(self): props = self.prepare_properties( self.properties, self.physical_resource_name()) # remove local-pref if unset, to let Neutron set a default if (self.LOCAL_PREF in props and props[self.LOCAL_PREF] is None): del props[self.LOCAL_PREF] if 'tenant_id' in props: tenant_id = self.client_plugin('keystone').get_project_id( props['tenant_id']) props['tenant_id'] = tenant_id bgpvpn = self.neutron().create_bgpvpn({'bgpvpn': props}) self.resource_id_set(bgpvpn['bgpvpn']['id']) def handle_update(self, json_snippet, tmpl_diff, prop_diff): raise NotImplementedError() def handle_delete(self): try: self.neutron().delete_bgpvpn(self.resource_id) except Exception as ex: self.client_plugin().ignore_not_found(ex) else: return True def _confirm_delete(self): while True: try: yield self._show_resource() except exception.NotFound: return def _show_resource(self): return self.neutron().show_bgpvpn(self.resource_id) # this class is registered to Heat via a setuptools entry point # (see setup.cfg) class BGPVPNConstraint(neutron_constraints.NeutronConstraint): resource_name = 'bgpvpn' class BGPVPNNetAssoc(neutron.NeutronResource): """A resource for BGPVPNNetAssoc in neutron. """ required_service_extension = 'bgpvpn' PROPERTIES = ( BGPVPN_ID, NETWORK_ID ) = ( 'bgpvpn_id', 'network_id' ) ATTRIBUTES = ( SHOW, STATUS ) = ( 'show', 'status' ) properties_schema = { BGPVPN_ID: properties.Schema( properties.Schema.STRING, _('name or ID of the BGPVPN.'), required=True, constraints=[ constraints.CustomConstraint('neutron.bgpvpn') ] ), NETWORK_ID: properties.Schema( properties.Schema.STRING, _('Network which shall be associated with the BGPVPN.'), required=True, constraints=[ constraints.CustomConstraint('neutron.network') ] ) } attributes_schema = { STATUS: attributes.Schema( _('Status of bgpvpn.'), ), SHOW: attributes.Schema( _('All attributes.') ), } def validate(self): super(BGPVPNNetAssoc, self).validate() def handle_create(self): self.props = self.prepare_properties(self.properties, self.physical_resource_name()) body = self.props.copy() body.pop('bgpvpn_id') bgpvpn_id = self.client_plugin().find_resourceid_by_name_or_id( 'bgpvpn', self.props['bgpvpn_id']) net_assoc = self.neutron().create_bgpvpn_network_assoc( bgpvpn_id, {'network_association': body}) self.resource_id_set(net_assoc['network_association']['id']) def handle_update(self, json_snippet, tmpl_diff, prop_diff): raise NotImplementedError() def handle_delete(self): try: self.neutron().delete_bgpvpn_network_assoc( self.properties['bgpvpn_id'], self.resource_id) except Exception as ex: self.client_plugin().ignore_not_found(ex) else: return True def _confirm_delete(self): while True: try: self._show_resource() except exception.NotFound: return def _show_resource(self): return self.neutron().show_bgpvpn_network_assoc( self.properties['bgpvpn_id'], self.resource_id) class BGPVPNRouterAssoc(neutron.NeutronResource): """A resource for BGPVPNRouterAssoc in neutron. """ required_service_extension = 'bgpvpn' PROPERTIES = ( BGPVPN_ID, ROUTER_ID ) = ( 'bgpvpn_id', 'router_id' ) ATTRIBUTES = ( SHOW, STATUS ) = ( 'show', 'status' ) properties_schema = { BGPVPN_ID: properties.Schema( properties.Schema.STRING, _('name or ID of the BGPVPN.'), required=True, constraints=[ constraints.CustomConstraint('neutron.bgpvpn') ] ), ROUTER_ID: properties.Schema( properties.Schema.STRING, _('Router which shall be associated with the BGPVPN.'), required=True, constraints=[ constraints.CustomConstraint('neutron.router') ] ) } attributes_schema = { STATUS: attributes.Schema( _('Status of bgpvpn.'), ), SHOW: attributes.Schema( _('All attributes.') ), } def validate(self): super(BGPVPNRouterAssoc, self).validate() def handle_create(self): self.props = self.prepare_properties(self.properties, self.physical_resource_name()) body = self.props.copy() body.pop('bgpvpn_id') bgpvpn_id = self.client_plugin().find_resourceid_by_name_or_id( 'bgpvpn', self.props['bgpvpn_id']) router_assoc = self.neutron().create_bgpvpn_router_assoc( bgpvpn_id, {'router_association': body}) self.resource_id_set(router_assoc['router_association']['id']) def handle_update(self, json_snippet, tmpl_diff, prop_diff): raise NotImplementedError() def handle_delete(self): try: self.neutron().delete_bgpvpn_router_assoc( self.properties['bgpvpn_id'], self.resource_id) except Exception as ex: self.client_plugin().ignore_not_found(ex) else: return True def _confirm_delete(self): while True: try: self._show_resource() except exception.NotFound: return def _show_resource(self): return self.neutron().show_bgpvpn_router_assoc( self.properties['bgpvpn_id'], self.resource_id) class BGPVPNPortAssoc(neutron.NeutronResource): """A resource for BGPVPNPortAssoc in neutron. """ required_service_extension = 'bgpvpn-routes-control' PROPERTIES = ( BGPVPN_ID, PORT_ID, ADVERTISE_FIXED_IPS, ROUTES, ) = ( 'bgpvpn_id', 'port_id', 'advertise_fixed_ips', 'routes', ) ATTRIBUTES = ( SHOW, STATUS ) = ( 'show', 'status' ) _ROUTE_DICT_KEYS = ( TYPE, PREFIX, FROM_BGPVPN, LOCAL_PREF, ) = ( 'type', 'prefix', 'bgpvpn_id', 'local_pref' ) _ROUTE_TYPE_VALUES = ( PREFIX, BGPVPN ) = ( PREFIX, 'bgpvpn' ) properties_schema = { BGPVPN_ID: properties.Schema( properties.Schema.STRING, _('name or ID of the BGPVPN.'), required=True, constraints=[ constraints.CustomConstraint('neutron.bgpvpn') ], ), PORT_ID: properties.Schema( properties.Schema.STRING, _('Port which shall be associated with the BGPVPN.'), required=True, constraints=[ constraints.CustomConstraint('neutron.port') ] ), ADVERTISE_FIXED_IPS: properties.Schema( properties.Schema.BOOLEAN, _('whether or not the fixed IPs of he port will be advertised ' 'into the BGPVPN.'), ), ROUTES: properties.Schema( properties.Schema.LIST, _('Defines routes to advertise into the BGPVPN, and for which ' 'this port will be the nexthop'), schema=properties.Schema( properties.Schema.MAP, schema={ TYPE: properties.Schema( properties.Schema.STRING, description=_('Type of the route.'), constraints=[ constraints.AllowedValues(_ROUTE_TYPE_VALUES) ], required=True ), PREFIX: properties.Schema( properties.Schema.STRING, description=_('Prefix to readvertise.'), constraints=[ constraints.CustomConstraint('net_cidr') ] ), FROM_BGPVPN: properties.Schema( properties.Schema.STRING, description=_('BGPVPN from which to readvertise routes' '(any route carrying an RT among ' 'route_targets or import_targets ' 'of this BGPVPN, will be readvertised).' ), constraints=[ constraints.CustomConstraint('neutron.bgpvpn') ] ), LOCAL_PREF: properties.Schema( properties.Schema.INTEGER, description=_('Value of the BGP LOCAL_PREF for the ' 'routes.'), constraints=[ constraints.Range(0, 2 ** 32 - 1), ], ) } ) ) } attributes_schema = { STATUS: attributes.Schema( _('Status of bgpvpn.'), ), SHOW: attributes.Schema( _('All attributes.') ), } def validate(self): super(BGPVPNPortAssoc, self).validate() def handle_create(self): self.props = self.prepare_properties(self.properties, self.physical_resource_name()) # clean-up/preparethe routes for route in self.props.get('routes', []): # remove local-pref if unset, to let Neutron set a default if (self.LOCAL_PREF in route and route[self.LOCAL_PREF] is None): del route[self.LOCAL_PREF] if route[self.TYPE] == self.PREFIX: # routes of type 'prefix' should not have a bgpvpn_id attribute del route[self.FROM_BGPVPN] elif route[self.TYPE] == self.BGPVPN: # routes of type 'bgpvpn' should not have a 'prefix' attribute del route[self.PREFIX] # need to lookup the BGPVPN by name or id route[self.FROM_BGPVPN] = ( self.client_plugin().find_resourceid_by_name_or_id( 'bgpvpn', route[self.FROM_BGPVPN]) ) body = self.props.copy() body.pop('bgpvpn_id') bgpvpn_id = self.client_plugin().find_resourceid_by_name_or_id( 'bgpvpn', self.props['bgpvpn_id']) port_assoc = self.neutron().create_bgpvpn_port_assoc( bgpvpn_id, {'port_association': body}) self.resource_id_set(port_assoc['port_association']['id']) def handle_update(self, json_snippet, tmpl_diff, prop_diff): raise NotImplementedError() def handle_delete(self): try: self.neutron().delete_bgpvpn_port_assoc( self.properties['bgpvpn_id'], self.resource_id) except Exception as ex: self.client_plugin().ignore_not_found(ex) else: return True def _confirm_delete(self): while True: try: self._show_resource() except exception.NotFound: return def _show_resource(self): return self.neutron().show_port_association( self.resource_id, self.properties['bgpvpn_id']) def resource_mapping(): return { 'OS::Neutron::BGPVPN': BGPVPN, 'OS::Neutron::BGPVPN-NET-ASSOCIATION': BGPVPNNetAssoc, 'OS::Neutron::BGPVPN-ROUTER-ASSOCIATION': BGPVPNRouterAssoc, 'OS::Neutron::BGPVPN-PORT-ASSOCIATION': BGPVPNPortAssoc, } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5099788 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/0000775000175000017500000000000000000000000023660 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/bgpvpn_test-00.yaml0000664000175000017500000000116700000000000027321 0ustar00zuulzuul00000000000000description: BGPVPN networking example (admin) heat_template_version: '2013-05-23' resources: BGPVPN1: type: OS::Neutron::BGPVPN properties: import_targets: [ "100:1001"] export_targets: [ "100:1002"] route_targets: [ "100:1000" ] name: "default VPN" Net1: type: OS::Neutron::Net SubNet1: type: OS::Neutron::Subnet properties: network: { get_resource: Net1 } cidr: 192.168.10.0/24 BGPVPN_NET_assoc1: type: OS::Neutron::BGPVPN-NET-ASSOCIATION properties: bgpvpn_id: { get_resource: BGPVPN1 } network_id: { get_resource: Net1 } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/bgpvpn_test-01-admin.yaml0000664000175000017500000000044700000000000030410 0ustar00zuulzuul00000000000000description: BGPVPN networking example (admin) heat_template_version: '2013-05-23' resources: BGPVPN1: type: OS::Neutron::BGPVPN properties: import_targets: [ "100:1001"] export_targets: [ "100:1002"] route_targets: [ "100:1000" ] name: "default_vpn" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/bgpvpn_test-01-tenant.yaml0000664000175000017500000000107200000000000030604 0ustar00zuulzuul00000000000000description: BGPVPN networking example (tenant) heat_template_version: '2013-05-23' parameters: bgpvpn: type: string description: id of BGPVPN to bind the network to resources: Net1: type: OS::Neutron::Net SubNet1: type: OS::Neutron::Subnet properties: network: { get_resource: Net1 } cidr: 192.168.10.0/24 BGPVPN_NET_assoc1: type: OS::Neutron::BGPVPN-NET-ASSOCIATION properties: bgpvpn_id: { get_param: bgpvpn } network_id: { get_resource: Net1 } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/bgpvpn_test-01bis_router-tenant.yaml0000664000175000017500000000145100000000000032703 0ustar00zuulzuul00000000000000description: BGPVPN networking example (tenant) heat_template_version: '2013-05-23' parameters: bgpvpn: type: string description: id of BGPVPN to bind the network to resources: Net1: type: OS::Neutron::Net SubNet1: type: OS::Neutron::Subnet properties: network: { get_resource: Net1 } cidr: 192.168.10.0/24 Router1: type: OS::Neutron::Router router_interface1: type: OS::Neutron::RouterInterface properties: router_id: { get_resource: Router1 } subnet_id: { get_resource: SubNet1 } BGPVPN_router_assoc1: type: OS::Neutron::BGPVPN-ROUTER-ASSOCIATION properties: bgpvpn_id: { get_param: bgpvpn } router_id: { get_resource: Router1 } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/bgpvpn_test-01ter_port-tenant.yaml0000664000175000017500000000244500000000000032370 0ustar00zuulzuul00000000000000description: BGPVPN networking example (tenant) heat_template_version: '2013-05-23' parameters: bgpvpn: type: string description: id of BGPVPN to bind the network to bgpvpn_bis: type: string description: id of BGPVPN from which to readvertise a route resources: Net1: type: OS::Neutron::Net SubNet1: type: OS::Neutron::Subnet properties: network: { get_resource: Net1 } cidr: 192.168.10.0/24 Port1: type: OS::Neutron::Port properties: network: { get_resource: Net1 } Port2: type: OS::Neutron::Port properties: network: { get_resource: Net1 } BGPVPN_port_assoc1: type: OS::Neutron::BGPVPN-PORT-ASSOCIATION properties: bgpvpn_id: { get_param: bgpvpn } port_id: { get_resource: Port1 } BGPVPN_port_assoc2: type: OS::Neutron::BGPVPN-PORT-ASSOCIATION properties: bgpvpn_id: { get_param: bgpvpn } port_id: { get_resource: Port2 } advertise_fixed_ips: false routes: - type: prefix prefix: 1.1.1.1/32 local_pref: 42 - type: bgpvpn bgpvpn_id: { get_param: bgpvpn_bis } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/bgpvpn_test-04-admin.yaml0000664000175000017500000000050200000000000030403 0ustar00zuulzuul00000000000000description: BGPVPN networking example (admin) heat_template_version: '2013-05-23' resources: BGPVPN1: type: OS::Neutron::BGPVPN properties: import_targets: [ "100:1001"] export_targets: [ "100:1002"] route_targets: [ "100:1000" ] name: "default_vpn" tenant_id: "demo" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/examples/bgpvpn_test-04-tenant.yaml0000664000175000017500000000276700000000000030623 0ustar00zuulzuul00000000000000description: BGPVPN networking example (tenant) heat_template_version: '2013-05-23' resources: Net1: type: OS::Neutron::Net SubNet1: type: OS::Neutron::Subnet properties: network: { get_resource: Net1 } cidr: 192.168.10.0/24 BGPVPN_NET_assoc1: type: OS::Neutron::BGPVPN-NET-ASSOCIATION properties: bgpvpn_id: "default_vpn" network_id: { get_resource: Net1 } Net2: type: OS::Neutron::Net SubNet2: type: OS::Neutron::Subnet properties: network: { get_resource: Net2 } cidr: 192.168.10.0/24 Router: type: OS::Neutron::Router router_interface: type: OS::Neutron::RouterInterface properties: router_id: { get_resource: Router } subnet_id: { get_resource: SubNet2 } BGPVPN_router_assoc1: type: OS::Neutron::BGPVPN-ROUTER-ASSOCIATION properties: bgpvpn_id: "default_vpn" router_id: { get_resource: Router } Net3: type: OS::Neutron::Net SubNet3: type: OS::Neutron::Subnet properties: network: { get_resource: Net3 } cidr: 192.168.10.0/24 Port: type: OS::Neutron::Port properties: network: { get_resource: Net3 } BGPVPN_port_assoc1: type: OS::Neutron::BGPVPN-PORT-ASSOCIATION properties: bgpvpn_id: "default_vpn" port_id: { get_resource: Port } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4659784 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/locale/0000775000175000017500000000000000000000000023301 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4659784 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/locale/en_GB/0000775000175000017500000000000000000000000024253 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5099788 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000026040 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/networking_bgpvpn_heat/locale/en_GB/LC_MESSAGES/networking_bgpvpn_heat.po0000664000175000017500000000610700000000000033150 0ustar00zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2019. #zanata msgid "" msgstr "" "Project-Id-Version: networking-bgpvpn VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2019-12-05 06:33+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2019-12-21 02:47+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "All attributes." msgstr "All attributes." msgid "BGP VPN type selection between L3VPN (l3) and EVPN (l2), default:l3" msgstr "BGP VPN type selection between L3VPN (l3) and EVPN (l2), default:l3" msgid "" "BGPVPN from which to readvertise routes(any route carrying an RT among " "route_targets or import_targets of this BGPVPN, will be readvertised)." msgstr "" "BGPVPN from which to readvertise routes(any route carrying an RT among " "route_targets or import_targets of this BGPVPN, will be readvertised)." msgid "" "Default value of the BGP LOCAL_PREF for the route advertisement to this " "BGPVPN." msgstr "" "Default value of the BGP LOCAL_PREF for the route advertisement to this " "BGPVPN." msgid "" "Defines routes to advertise into the BGPVPN, and for which this port will be " "the nexthop" msgstr "" "Defines routes to advertise into the BGPVPN, and for which this port will be " "the nexthop" msgid "Description for the bgpvpn." msgstr "Description for the BGPVPN." msgid "List of RDs that will be used to advertize BGPVPN routes." msgstr "List of RDs that will be used to advertise BGPVPN routes." msgid "List of additional Route Targets to export to." msgstr "List of additional Route Targets to export to." msgid "List of additional Route Targets to import from." msgstr "List of additional Route Targets to import from." msgid "Name for the bgpvpn." msgstr "Name for the BGPVPN." msgid "Network which shall be associated with the BGPVPN." msgstr "Network which shall be associated with the BGPVPN." msgid "Port which shall be associated with the BGPVPN." msgstr "Port which shall be associated with the BGPVPN." msgid "Prefix to readvertise." msgstr "Prefix to readvertise." msgid "Route Targets list to import/export for this BGPVPN." msgstr "Route Targets list to import/export for this BGPVPN." msgid "Router which shall be associated with the BGPVPN." msgstr "Router which shall be associated with the BGPVPN." msgid "Status of bgpvpn." msgstr "Status of BGPVPN." msgid "Tenant this bgpvpn belongs to (name or id)." msgstr "Tenant this BGPVPN belongs to (name or id)." msgid "Type of the route." msgstr "Type of the route." msgid "Value of the BGP LOCAL_PREF for the routes." msgstr "Value of the BGP LOCAL_PREF for the routes." msgid "name or ID of the BGPVPN." msgstr "name or ID of the BGPVPN." msgid "" "whether or not the fixed IPs of he port will be advertised into the BGPVPN." msgstr "" "whether or not the fixed IPs of he port will be advertised into the BGPVPN." ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4659784 networking-bgpvpn-21.0.0/releasenotes/0000775000175000017500000000000000000000000017767 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5139787 networking-bgpvpn-21.0.0/releasenotes/notes/0000775000175000017500000000000000000000000021117 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023370 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/0_heat-support-ab233de7401aeb36.yaml0000664000175000017500000000011000000000000027123 0ustar00zuulzuul00000000000000--- features: - Heat support for the whole BGPVPN Interconnection API ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/add-vni-to-bgpvpn-31d6eda7ba6d5047.yaml0000664000175000017500000000021500000000000027526 0ustar00zuulzuul00000000000000--- features: - | Add ``vni`` optional attribute to ``bgpvpn`` resource to control the VXLAN VNI when VXLAN encapsulation is used. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/bagpipe-driver-improvements-401a7ba59a6f5f45.yaml0000664000175000017500000000065700000000000031655 0ustar00zuulzuul00000000000000--- features: - BaGPipe driver improvement for a clean integration in the Neutron OpenVSwitch agent (see Bug `1492021 `_). Instead of requiring to use a modified OVS agent, we now provide an extension that is loaded into the unmodified OVS agent. fixes: - with BaGPipe driver, the OVS agent does not lose BGPVPN flows on restart (Bug `1531459 `_) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/bagpipe-router-compat-b53b6f3799cd23db.yaml0000664000175000017500000000100600000000000030511 0ustar00zuulzuul00000000000000--- prelude: > The ovs/bagpipe driver now let you use both a Neutron router and a BGPVPN association simultaneously on a given Port. features: - The bagpipe driver now let happily coexist a BGPVPN association and a Neutron router. Traffic that does not match any VPN route will be handled by the Neutron router. This evolution depends on corresponding evolutions in networking-bagpipe and bagpipe-bgp. (`see bug 1627645 `_) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/bagpipe_enable_evpn-ae64f77df89e069b.yaml0000664000175000017500000000017300000000000030273 0ustar00zuulzuul00000000000000--- features: - | BGPVPNs of type L2 are now supported with Neutron ML2 reference drivers (OVS and linuxbridge). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/bagpipe_ovo_rpcs-380f7bd52969bef7.yaml0000664000175000017500000000033600000000000027564 0ustar00zuulzuul00000000000000--- other: - | The BGPVPN reference driver `bagpipe`, for use with Neutron linuxbridge or OVS reference drivers, has adopted OVO-based RPCs. A v2 driver is provided to avoid the production of old-style RPCs. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/bgpvpn_service_declaration-6d9ecd2c397e4821.yaml0000664000175000017500000000034300000000000031614 0ustar00zuulzuul00000000000000features: - | The BGPVPN Interconnection API can now be enabled by adding ``bgpvpn`` to ``service_plugins`` in ``neutron.conf``, instead of the verbose ``networking_bgpvpn.neutron.services.plugin.BGPVPNPlugin``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/deprecate-old-opencontrail-driver-a598892ddf54c724.yaml0000664000175000017500000000045400000000000032660 0ustar00zuulzuul00000000000000--- deprecations: - | The first OpenContrail Driver was not developed with production ready in mind, it was more a proof of concept. We do not recommend to use it in production. Instead a production ready driver is available in the OpenContrail monolithic Neutron core plugin tree. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/drop-py-2-7-4db5f2b1529bb09c.yaml0000664000175000017500000000034000000000000026164 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of networking-bgpvpn to support Python 2.7 is OpenStack Train. The minimum version of Python now supported by networking-bgpvpn is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/drop-python-3-6-and-3-7-97c0464e7a396023.yaml0000664000175000017500000000020100000000000027630 0ustar00zuulzuul00000000000000--- upgrade: - | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now supported is Python 3.8. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/filtering-on-resource-association-2acdbc5b59d1a40a.yaml0000664000175000017500000000020100000000000033144 0ustar00zuulzuul00000000000000--- features: - The API now supports filtering BGPVPN resources based on the networks or routers they are associated with. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/heat_bgpvpn_local_pref-a1cbfde10810b157.yaml0000664000175000017500000000017700000000000030747 0ustar00zuulzuul00000000000000--- features: - | The the 'local_pref' attribute of a Heat BGPVPN resource can now be controlled in a Heat template. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/heat_port_associations-f2d316f3b8c755fe.yaml0000664000175000017500000000021100000000000031046 0ustar00zuulzuul00000000000000--- features: - | The Heat plugin for the BGPVPN interconnection API extension now supports BGPVPN Port Association resources. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/horizon-in-extras-371d572b09437dc4.yaml0000664000175000017500000000073300000000000027464 0ustar00zuulzuul00000000000000--- upgrade: - | Horizon is now an optional dependency of networking-bgpvpn as the GUI support is optional. This means horizon will not be installed automatically. The horizon dependency is now declared in ``extras`` section in ``setup.cfg``. If you would like to enable the GUI support, you can install the dependencies using ``python -m pip install networking-bgpvpn[horizon]`` (or ``.[horizon]`` in case you install it from the source code). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/horizon-support-06a7b21286002949.yaml0000664000175000017500000000120200000000000027110 0ustar00zuulzuul00000000000000--- prelude: > New Horizon panels for BGPVPN resources, allowing you to create a bgpvpn and to associate related resources such as a network or a router. features: - | Horizon: * a view of all the existing BGPVPNs. * ability to view details of a BGPVPN. * ability to create, update and delete BGPVPN resources for an admin user. * ability to update BGPVPN resources for a tenant user. (with restrictions, compared to what an admin user can change) * abiity to associate/disassociate BGPVPN to/from networks and routers (for both tenant and admin users) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/mitaka-prelude-1675467c144a91ea.yaml0000664000175000017500000000025300000000000026765 0ustar00zuulzuul00000000000000--- prelude: > Mitaka release is a short-cycle release to compensate for the delayed Liberty release and get the project in sync with Openstack release cycles ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/odl_router_association-fa2ed7c396531418.yaml0000664000175000017500000000010700000000000030714 0ustar00zuulzuul00000000000000--- features: - OpenDaylight driver now supports Router associations ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/pre_commit_checks-b902ee19a3654a7b.yaml0000664000175000017500000000017700000000000027701 0ustar00zuulzuul00000000000000--- features: - Pre-commit hooks were added in the driver framework, and then leveraged in BaGPipe and OpenDaylight drivers ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/remove_obsolete_drivers-3706e080098a5cb6.yaml0000664000175000017500000000025300000000000031006 0ustar00zuulzuul00000000000000--- other: - | The obsolete in-tree drivers for OpenContrail and OpenDaylight have been removed, in favor of the out-of-tree drivers provided by these projects. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/notes/routes-control-api-ext-c0c4020e7370d833.yaml0000664000175000017500000000037200000000000030412 0ustar00zuulzuul00000000000000--- features: - | The `bgpvpn-routes-control` API extension is introduced, allowing control of BGPVPN routing with a finer grain, including API-defined static routes pointing to Neutron ports, or BGPVPN route leaking via Neutron ports. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5179787 networking-bgpvpn-21.0.0/releasenotes/source/0000775000175000017500000000000000000000000021267 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/2023.1.rst0000664000175000017500000000020200000000000022540 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022541 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000022541 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5179787 networking-bgpvpn-21.0.0/releasenotes/source/_static/0000775000175000017500000000000000000000000022715 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000025166 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5179787 networking-bgpvpn-21.0.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000023424 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000025675 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/conf.py0000664000175000017500000002144300000000000022572 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Networking-bgpvpn Release Notes documentation build configuration file # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/networking-bgpvpn' openstackdocs_auto_name = False openstackdocs_bug_project = 'bgpvpn' openstackdocs_bug_tag = '' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Networking-bgpvpn Release Notes' copyright = '2016, Networking-bgpvpn Developers' # Release notes are version independent. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'NetworkingBgpvpnReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'NetworkingBgpvpnReleaseNotes.tex', 'Networking-bgpvpn Release Notes Documentation', 'Networking-bgpvpn Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'networkingbgpvpnreleasenotes', 'Networking-bgpvpn Release Notes Documentation', ['Networking-bgpvpn Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'NetworkingBgpvpnReleaseNotes', 'Networking-bgpvpn Release Notes Documentation', 'Networking-bgpvpn Developers', 'NetworkingBgpvpnReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/index.rst0000664000175000017500000000047300000000000023134 0ustar00zuulzuul00000000000000================================ Networking-bgpvpn Release Notes ================================ .. toctree:: :maxdepth: 1 unreleased 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata newton mitaka liberty ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/liberty.rst0000664000175000017500000000022200000000000023467 0ustar00zuulzuul00000000000000============================== Liberty Series Release Notes ============================== .. release-notes:: :branch: origin/stable/liberty ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4699786 networking-bgpvpn-21.0.0/releasenotes/source/locale/0000775000175000017500000000000000000000000022526 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.4699786 networking-bgpvpn-21.0.0/releasenotes/source/locale/en_GB/0000775000175000017500000000000000000000000023500 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5179787 networking-bgpvpn-21.0.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000025265 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175000017500000002674000000000000030327 0ustar00zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata # Andi Chandler , 2019. #zanata # Andi Chandler , 2020. #zanata # Andi Chandler , 2022. #zanata # Andi Chandler , 2023. #zanata # Andi Chandler , 2024. #zanata msgid "" msgstr "" "Project-Id-Version: Networking-bgpvpn Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-06 01:10+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2024-04-18 12:41+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "10.0.0" msgstr "10.0.0" msgid "12.0.0" msgstr "12.0.0" msgid "17.0.0" msgstr "17.0.0" msgid "2023.1 Series Release Notes" msgstr "2023.1 Series Release Notes" msgid "2023.2 Series Release Notes" msgstr "2023.2 Series Release Notes" msgid "2024.1 Series Release Notes" msgstr "2024.1 Series Release Notes" msgid "4.0.0" msgstr "4.0.0" msgid "5.0.0" msgstr "5.0.0" msgid "6.0.0" msgstr "6.0.0" msgid "7.0.0" msgstr "7.0.0" msgid "7.0.0.0b1" msgstr "7.0.0.0b1" msgid "8.0.0" msgstr "8.0.0" msgid "8.0.1" msgstr "8.0.1" msgid "9.0.0" msgstr "9.0.0" msgid "" "Add ``vni`` optional attribute to ``bgpvpn`` resource to control the VXLAN " "VNI when VXLAN encapsulation is used." msgstr "" "Add ``vni`` optional attribute to ``bgpvpn`` resource to control the VXLAN " "VNI when VXLAN encapsulation is used." msgid "" "BGPVPNs of type L2 are now supported with Neutron ML2 reference drivers (OVS " "and linuxbridge)." msgstr "" "BGPVPNs of type L2 are now supported with Neutron ML2 reference drivers (OVS " "and linuxbridge)." msgid "" "BaGPipe driver improvement for a clean integration in the Neutron " "OpenVSwitch agent (see Bug `1492021 `_). " "Instead of requiring to use a modified OVS agent, we now provide an " "extension that is loaded into the unmodified OVS agent." msgstr "" "BaGPipe driver improvement for a clean integration in the Neutron " "OpenVSwitch agent (see Bug `1492021 `_). " "Instead of requiring to use a modified OVS agent, we now provide an " "extension that is loaded into the unmodified OVS agent." msgid "Bug Fixes" msgstr "Bug Fixes" msgid "Current Series Release Notes" msgstr "Current Series Release Notes" msgid "Deprecation Notes" msgstr "Deprecation Notes" msgid "Heat support for the whole BGPVPN Interconnection API" msgstr "Heat support for the whole BGPVPN Interconnection API" msgid "" "Horizon is now an optional dependency of networking-bgpvpn as the GUI " "support is optional. This means horizon will not be installed automatically. " "The horizon dependency is now declared in ``extras`` section in ``setup." "cfg``. If you would like to enable the GUI support, you can install the " "dependencies using ``python -m pip install networking-bgpvpn[horizon]`` (or " "``.[horizon]`` in case you install it from the source code)." msgstr "" "Horizon is now an optional dependency of networking-bgpvpn as the GUI " "support is optional. This means Horizon will not be installed automatically. " "The horizon dependency is now declared in ``extras`` section in ``setup." "cfg``. If you would like to enable the GUI support, you can install the " "dependencies using ``python -m pip install networking-bgpvpn[horizon]`` (or " "``.[horizon]`` in case you install it from the source code)." msgid "Horizon:" msgstr "Horizon:" msgid "Liberty Series Release Notes" msgstr "Liberty Series Release Notes" msgid "Mitaka Series Release Notes" msgstr "Mitaka Series Release Notes" msgid "" "Mitaka release is a short-cycle release to compensate for the delayed " "Liberty release and get the project in sync with Openstack release cycles" msgstr "" "Mitaka release is a short-cycle release to compensate for the delayed " "Liberty release and get the project in sync with OpenStack release cycles" msgid "Networking-bgpvpn Release Notes" msgstr "Networking-bgpvpn Release Notes" msgid "New Features" msgstr "New Features" msgid "" "New Horizon panels for BGPVPN resources, allowing you to create a bgpvpn and " "to associate related resources such as a network or a router." msgstr "" "New Horizon panels for BGPVPN resources, allowing you to create a BGPVPN and " "to associate related resources such as a network or a router." msgid "Newton Series Release Notes" msgstr "Newton Series Release Notes" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "OpenDaylight driver now supports Router associations" msgstr "OpenDaylight driver now supports Router associations" msgid "Other Notes" msgstr "Other Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "" "Pre-commit hooks were added in the driver framework, and then leveraged in " "BaGPipe and OpenDaylight drivers" msgstr "" "Pre-commit hooks were added in the driver framework, and then leveraged in " "BaGPipe and OpenDaylight drivers" msgid "Prelude" msgstr "Prelude" msgid "" "Python 2.7 support has been dropped. Last release of networking-bgpvpn to " "support Python 2.7 is OpenStack Train. The minimum version of Python now " "supported by networking-bgpvpn is Python 3.6." msgstr "" "Python 2.7 support has been dropped. Last release of Networking-bgpvpn to " "support Python 2.7 is OpenStack Train. The minimum version of Python now " "supported by Networking-bgpvpn is Python 3.6." msgid "" "Python 3.6 & 3.7 support has been dropped. The minimum version of Python now " "supported is Python 3.8." msgstr "" "Python 3.6 & 3.7 support has been dropped. The minimum version of Python now " "supported is Python 3.8." msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" msgid "Rocky Series Release Notes" msgstr "Rocky Series Release Notes" msgid "Stein Series Release Notes" msgstr "Stein Series Release Notes" msgid "" "The API now supports filtering BGPVPN resources based on the networks or " "routers they are associated with." msgstr "" "The API now supports filtering BGPVPN resources based on the networks or " "routers they are associated with." msgid "" "The BGPVPN Interconnection API can now be enabled by adding ``bgpvpn`` to " "``service_plugins`` in ``neutron.conf``, instead of the verbose " "``networking_bgpvpn.neutron.services.plugin.BGPVPNPlugin``." msgstr "" "The BGPVPN Interconnection API can now be enabled by adding ``bgpvpn`` to " "``service_plugins`` in ``neutron.conf``, instead of the verbose " "``networking_bgpvpn.neutron.services.plugin.BGPVPNPlugin``." msgid "" "The BGPVPN reference driver `bagpipe`, for use with Neutron linuxbridge or " "OVS reference drivers, has adopted OVO-based RPCs. A v2 driver is provided " "to avoid the production of old-style RPCs." msgstr "" "The BGPVPN reference driver `bagpipe`, for use with Neutron linuxbridge or " "OVS reference drivers, has adopted OVO-based RPCs. A v2 driver is provided " "to avoid the production of old-style RPCs." msgid "" "The Heat plugin for the BGPVPN interconnection API extension now supports " "BGPVPN Port Association resources." msgstr "" "The Heat plugin for the BGPVPN interconnection API extension now supports " "BGPVPN Port Association resources." msgid "" "The `bgpvpn-routes-control` API extension is introduced, allowing control of " "BGPVPN routing with a finer grain, including API-defined static routes " "pointing to Neutron ports, or BGPVPN route leaking via Neutron ports." msgstr "" "The `bgpvpn-routes-control` API extension is introduced, allowing control of " "BGPVPN routing with a finer grain, including API-defined static routes " "pointing to Neutron ports, or BGPVPN route leaking via Neutron ports." msgid "" "The bagpipe driver now let happily coexist a BGPVPN association and a " "Neutron router. Traffic that does not match any VPN route will be handled by " "the Neutron router. This evolution depends on corresponding evolutions in " "networking-bagpipe and bagpipe-bgp. (`see bug 1627645 `_)" msgstr "" "The bagpipe driver now let happily coexist a BGPVPN association and a " "Neutron router. Traffic that does not match any VPN route will be handled by " "the Neutron router. This evolution depends on corresponding evolutions in " "networking-bagpipe and bagpipe-bgp. (`see bug 1627645 `_)" msgid "" "The first OpenContrail Driver was not developed with production ready in " "mind, it was more a proof of concept. We do not recommend to use it in " "production. Instead a production ready driver is available in the " "OpenContrail monolithic Neutron core plugin tree." msgstr "" "The first OpenContrail Driver was not developed with production ready in " "mind, it was more a proof of concept. We do not recommend to use it in " "production. Instead a production ready driver is available in the " "OpenContrail monolithic Neutron core plugin tree." msgid "" "The obsolete in-tree drivers for OpenContrail and OpenDaylight have been " "removed, in favor of the out-of-tree drivers provided by these projects." msgstr "" "The obsolete in-tree drivers for OpenContrail and OpenDaylight have been " "removed, in favor of the out-of-tree drivers provided by these projects." msgid "" "The ovs/bagpipe driver now let you use both a Neutron router and a BGPVPN " "association simultaneously on a given Port." msgstr "" "The ovs/bagpipe driver now let you use both a Neutron router and a BGPVPN " "association simultaneously on a given Port." msgid "" "The the 'local_pref' attribute of a Heat BGPVPN resource can now be " "controlled in a Heat template." msgstr "" "The the 'local_pref' attribute of a Heat BGPVPN resource can now be " "controlled in a Heat template." msgid "Train Series Release Notes" msgstr "Train Series Release Notes" msgid "Upgrade Notes" msgstr "Upgrade Notes" msgid "Ussuri Series Release Notes" msgstr "Ussuri Series Release Notes" msgid "Victoria Series Release Notes" msgstr "Victoria Series Release Notes" msgid "Wallaby Series Release Notes" msgstr "Wallaby Series Release Notes" msgid "Xena Series Release Notes" msgstr "Xena Series Release Notes" msgid "Yoga Series Release Notes" msgstr "Yoga Series Release Notes" msgid "Zed Series Release Notes" msgstr "Zed Series Release Notes" msgid "a view of all the existing BGPVPNs." msgstr "a view of all the existing BGPVPNs." msgid "" "abiity to associate/disassociate BGPVPN to/from networks and routers (for " "both tenant and admin users)" msgstr "" "ability to associate/disassociate BGPVPN to/from networks and routers (for " "both tenant and admin users)" msgid "" "ability to create, update and delete BGPVPN resources for an admin user." msgstr "" "ability to create, update and delete BGPVPN resources for an admin user." msgid "" "ability to update BGPVPN resources for a tenant user. (with restrictions, " "compared to what an admin user can change)" msgstr "" "ability to update BGPVPN resources for a tenant user. (with restrictions, " "compared to what an admin user can change)" msgid "ability to view details of a BGPVPN." msgstr "ability to view details of a BGPVPN." msgid "" "with BaGPipe driver, the OVS agent does not lose BGPVPN flows on restart " "(Bug `1531459 `_)" msgstr "" "with BaGPipe driver, the OVS agent does not lose BGPVPN flows on restart " "(Bug `1531459 `_)" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/mitaka.rst0000664000175000017500000000022300000000000023264 0ustar00zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: stable/mitaka ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/newton.rst0000664000175000017500000000022300000000000023330 0ustar00zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/ocata.rst0000664000175000017500000000023000000000000023103 0ustar00zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/pike.rst0000664000175000017500000000021700000000000022751 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000023316 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023143 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000023136 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/train.rst0000664000175000017500000000017600000000000023142 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/unreleased.rst0000664000175000017500000000015300000000000024147 0ustar00zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023345 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000023633 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000023451 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000022744 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000022750 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/releasenotes/source/zed.rst0000664000175000017500000000016600000000000022606 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: stable/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/requirements.txt0000664000175000017500000000174700000000000020573 0ustar00zuulzuul00000000000000# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. pbr>=4.0.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.db>=4.37.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 neutron-lib>=1.30.0 # Apache-2.0 debtcollector>=1.19.0 # Apache-2.0 # OpenStack CI will install the following projects from git # if they are in the required-projects list for a job: neutron>=23.0.0.0b2 # Apache-2.0 networking-bagpipe>=12.0.0 # Apache-2.0 horizon>=17.1.0 # Apache-2.0 # The comment below indicates this project repo is current with neutron-lib # and should receive neutron-lib consumption patches as they are released # in neutron-lib. It also implies the project will stay current with TC # and infra initiatives ensuring consumption patches can land. # neutron-lib-current ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5219789 networking-bgpvpn-21.0.0/setup.cfg0000664000175000017500000000461200000000000017122 0ustar00zuulzuul00000000000000[metadata] name = networking-bgpvpn summary = API and Framework to interconnect bgpvpn to neutron networks description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/networking-bgpvpn/latest/ python_requires = >=3.8 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 [files] packages = networking_bgpvpn networking_bgpvpn_heat bgpvpn_dashboard data_files = etc/neutron = etc/neutron/networking_bgpvpn.conf [extras] bagpipe = networking-bagpipe>=9.0.0 # Apache-2.0 horizon = horizon>=17.1.0 # Apache-2.0 [entry_points] neutronclient.extension = bgpvpn = networking_bgpvpn.neutronclient.neutron.v2_0.bgpvpn.bgpvpn neutron.db.alembic_migrations = networking-bgpvpn = networking_bgpvpn.neutron.db.migration:alembic_migrations heat.constraints = neutron.bgpvpn = networking_bgpvpn_heat.bgpvpnservice:BGPVPNConstraint neutron.service_plugins = bgpvpn = networking_bgpvpn.neutron.services.plugin:BGPVPNPlugin oslo.config.opts = networking-bgpvpn.service_provider = networking_bgpvpn.neutron.opts:list_service_provider oslo.config.opts.defaults = networking-bgpvpn.service_provider = networking_bgpvpn.neutron.opts:set_service_provider_default oslo.policy.policies = networking-bgpvpn = networking_bgpvpn.policies:list_rules neutron.policies = networking-bgpvpn = networking_bgpvpn.policies:list_rules [compile_catalog] directory = networking_bgpvpn/locale domain = networking_bgpvpn [update_catalog] domain = networking-bgpvpn output_dir = networking_bgpvpn/locale input_file = networking_bgpvpn/locale/networking-bgpvpn.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = networking_bgpvpn/locale/networking-bgpvpn.pot [openstack_translations] django_modules = bgpvpn_dashboard python_modules = networking_bgpvpn networking_bgpvpn_heat [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/setup.py0000664000175000017500000000127100000000000017011 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5179787 networking-bgpvpn-21.0.0/specs/0000775000175000017500000000000000000000000016413 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/specs/bgpvpn.rst0000664000175000017500000000016400000000000020442 0ustar00zuulzuul00000000000000This is a work in progress to implement the specs currently discussed at: https://review.openstack.org/#/c/177740/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/test-requirements.txt0000664000175000017500000000156700000000000021550 0ustar00zuulzuul00000000000000hacking>=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 WebOb>=1.8.2 # MIT oslotest>=3.2.0 # Apache-2.0 pylint==2.17.4 # GPLv2 pytest>=5.3.5 # MIT stestr>=1.0.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 isort==4.3.21 # MIT # OpenStack CI will install the following projects from git # if they are in the required-projects list for a job. # Installation by 'extras' in tox.ini does not honor upper-constraints, # so we specify the same here to ensure upper-constraints. networking-bagpipe>=12.0.0.0rc1 # Apache-2.0 horizon>=17.1.0 # Apache-2.0 # This is necessary as pecan dropped this dependency # see https://review.opendev.org/c/openstack/neutron/+/848706 WebTest>=2.0.27 # MIT ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1727867199.5219789 networking-bgpvpn-21.0.0/tools/0000775000175000017500000000000000000000000016436 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/tools/django-manage.py0000775000175000017500000000150000000000000021477 0ustar00zuulzuul00000000000000#!/usr/bin/env python # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from django.core.management import execute_from_command_line if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bgpvpn_dashboard.test.settings") execute_from_command_line(sys.argv) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/tools/generate_config_file_samples.sh0000775000175000017500000000144000000000000024636 0ustar00zuulzuul00000000000000#!/bin/sh # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. set -e GEN_CMD=oslo-config-generator if ! type "$GEN_CMD" > /dev/null; then echo "ERROR: $GEN_CMD not installed on the system." exit 1 fi for file in `ls etc/oslo-config-generator/*`; do $GEN_CMD --config-file=$file done set -x ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1727867169.0 networking-bgpvpn-21.0.0/tox.ini0000664000175000017500000001161000000000000016610 0ustar00zuulzuul00000000000000[tox] minversion = 3.18.0 envlist = py38,pep8 skipsdist = False ignore_basepython_conflict = True [testenv] usedevelop = True basepython = python3 setenv = OS_LOG_CAPTURE={env:OS_LOG_CAPTURE:true} OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:true} OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:true} PYTHONWARNINGS=default::DeprecationWarning deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} python {toxinidir}/tools/django-manage.py test bgpvpn_dashboard [testenv:releasenotes] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -a -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:pep8] deps = {[testenv]deps} commands = flake8 flake8 doc/source/samples pylint --version pylint --rcfile=.pylintrc --output-format=colorized {posargs:networking_bgpvpn} pylint --rcfile=.pylintrc --output-format=colorized doc/source/samples neutron-db-manage --subproject networking-bgpvpn --database-connection sqlite:// check_migration {[testenv:genconfig]commands} {[testenv:genpolicy]commands} allowlist_externals = {toxinidir}/tools/generate_config_file_samples.sh [testenv:dsvm] setenv = OS_FAIL_ON_MISSING_DEPS=1 OS_LOG_PATH={env:OS_LOG_PATH:/opt/stack/logs} [testenv:functional] setenv = {[testenv]setenv} OS_TEST_TIMEOUT=180 OS_TEST_PATH=./networking_bgpvpn/tests/functional OS_LOG_PATH={env:OS_LOG_PATH:/opt/stack/logs} deps = {[testenv]deps} -r{toxinidir}/networking_bgpvpn/tests/functional/requirements.txt commands = stestr run {posargs} [testenv:dsvm-functional-gate] setenv = {[testenv:functional]setenv} {[testenv:dsvm]setenv} deps = {[testenv:functional]deps} commands = stestr run {posargs} [testenv:venv] commands = {posargs} [testenv:cover] setenv = PYTHON = coverage run --source networking_bgpvpn --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -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 [testenv:debug] commands = oslo_debug_helper -t networking_bgpvpn/tests/unit {posargs} [testenv:genconfig] commands = {toxinidir}/tools/generate_config_file_samples.sh allowlist_externals = {toxinidir}/tools/generate_config_file_samples.sh [testenv:genpolicy] commands = oslopolicy-sample-generator --config-file=etc/oslo-policy-generator/policy.conf [flake8] show-source = True # 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 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,.tmp # 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 [flake8:local-plugins] extension = # Checks from neutron-lib N521 = neutron_lib.hacking.checks:use_jsonutils N524 = neutron_lib.hacking.checks:check_no_contextlib_nested N529 = neutron_lib.hacking.checks:no_mutable_default_args N532 = neutron_lib.hacking.translation_checks:check_log_warn_deprecated N534 = neutron_lib.hacking.translation_checks:check_raised_localized_exceptions N536 = neutron_lib.hacking.checks:assert_equal_none N537 = neutron_lib.hacking.translation_checks:no_translate_logs [testenv:dev] # run locally (not in the gate) using editable mode # https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs # note that order is important to ensure dependencies don't override commands = pip install -q -e "git+https://opendev.org/openstack/networking-bagpipe#egg=networking_bagpipe" 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:pep8]deps} commands = {[testenv:dev]commands} {[testenv:pep8]commands}