././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/0000775000175000017500000000000000000000000015107 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/.coveragerc0000664000175000017500000000014500000000000017230 0ustar00zuulzuul00000000000000[run] branch = True source = neutron_taas omit = neutron_taas/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/.mailmap0000664000175000017500000000013100000000000016523 0ustar00zuulzuul00000000000000# Format is: # # ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/.pre-commit-config.yaml0000664000175000017500000000243000000000000021367 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)/.*$' - id: pylint name: pylint entry: .tox/pep8/bin/pylint files: '^.*\.py$' language: system types: [python] args: ['--rcfile=.pylintrc', '--output-format=colorized'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/.pylintrc0000664000175000017500000000653300000000000016763 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 c-extension-no-member, locally-disabled, # "E" Error for important programming issues (likely bugs) access-member-before-definition, no-member, no-method-argument, no-self-argument, not-an-iterable, # "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, keyword-arg-before-vararg, literal-comparison, not-callable, protected-access, raise-missing-from, redefined-builtin, redefined-outer-name, signature-differs, super-init-not-called, unpacking-non-sequence, unused-argument, unused-import, unused-variable, useless-super-delegation, # TODO(dougwig) - disable nonstandard-exception while we have neutron_lib shims # "C" Coding convention violations consider-iterating-dictionary, consider-using-enumerate, consider-using-f-string, invalid-name, len-as-condition, missing-docstring, multiple-statements, singleton-comparison, superfluous-parens, ungrouped-imports, wrong-import-order, # "R" Refactor recommendations consider-merging-isinstance, consider-using-ternary, duplicate-code, inconsistent-return-statements, no-else-return, redefined-argument-from-local, simplifiable-if-statement, 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-nested-blocks, too-many-public-methods, too-many-return-statements, too-many-statements, # new for python3 version of pylint consider-using-set-comprehension, unnecessary-pass, [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 lowercased 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= # should use oslo_serialization.jsonutils json, six [TYPECHECK] # List of module names for which member attributes should not be checked ignored-modules=_MovedItems [REPORTS] # Tells whether to display a full report or only the messages reports=no ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/.stestr.conf0000664000175000017500000000011200000000000017352 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./neutron_taas/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/.zuul.yaml0000664000175000017500000000140500000000000017050 0ustar00zuulzuul00000000000000- job: name: neutron-tempest-plugin-tap-as-a-service-wsgi parent: neutron-tempest-plugin-tap-as-a-service vars: devstack_localrc: NEUTRON_DEPLOY_MOD_WSGI: true - project: templates: - openstack-cover-jobs-neutron - release-notes-jobs-python3 - build-openstack-docs-pti - check-requirements - openstack-python3-jobs-neutron check: jobs: - neutron-tempest-plugin-tap-as-a-service - neutron-tempest-plugin-tap-as-a-service-wsgi gate: jobs: - neutron-tempest-plugin-tap-as-a-service periodic-weekly: jobs: - openstack-tox-py39 - openstack-tox-py312 - openstack-tox-py312-with-oslo-master - neutron-tempest-plugin-tap-as-a-service ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/API_REFERENCE.rst0000664000175000017500000002064500000000000017637 0ustar00zuulzuul00000000000000============================== Tap as a Service API REFERENCE ============================== This documents is an API REFERENCE for Tap-as-a-Service Neutron extension. The documents is organized into the following sections: * TaaS Resources * API Reference * TaaS CLI Reference * Workflow TaaS Resources ============== TaaS consists of two resources, TapService and TapFlow. TapService ---------- TapService Represents the port on which the mirrored traffic is delivered. Any service (VM) that uses the mirrored data is attached to the port. .. code-block:: python 'tap_services': { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, 'primary_key': True}, 'tenant_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'port_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True}, } TapFlow ------- TapFlow Represents the port from which the traffic needs to be mirrored. .. code-block:: python 'tap_flows': { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, 'primary_key': True}, 'tenant_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'tap_service_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid': None}, 'required_by_policy': True, 'is_visible': True}, 'source_port': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid': None}, 'required_by_policy': True, 'is_visible': True}, 'direction': {'allow_post': True, 'allow_put': False, 'validate': {'type:values': direction_enum}, 'is_visible': True}, 'vlan_filter': {'allow_post': True, 'allow_put': False, 'validate': {'type:regex_or_none': RANGE_REGEX}, 'is_visible': True, 'default': None} } direction_enum = ['IN', 'OUT', 'BOTH'] Multiple TapFlow instances can be associated with a single TapService instance. Tap Mirror ---------- A ``tapmirror`` mirrors the traffic of a Neutron port using ``gre`` or ``erspan v1`` tunnels. .. code-block:: python 'tap_mirrors': { 'id': { 'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, 'primary_key': True}, 'project_id': { 'allow_post': True, 'allow_put': False, 'validate': {'type:string': db_const.PROJECT_ID_FIELD_SIZE}, 'required_by_policy': True, 'is_filter': True, 'is_sort_key': True, 'is_visible': True}, 'name': { 'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'description': { 'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'port_id': { 'allow_post': True, 'allow_put': False, 'validate': {'type:uuid': None}, 'enforce_policy': True, 'is_visible': True}, 'directions': { 'allow_post': True, 'allow_put': False, 'validate': DIRECTION_SPEC, 'is_visible': True}, 'remote_ip': { 'allow_post': True, 'allow_put': False, 'validate': {'type:ip_address': None}, 'is_visible': True}, 'mirror_type': { 'allow_post': True, 'allow_put': False, 'validate': {'type:values': mirror_types_list}, 'is_visible': True}, } mirror_types_list = ['erspanv1', 'gre'] DIRECTION_SPEC = { 'type:dict': { 'IN': {'type:integer': None, 'default': None, 'required': False}, 'OUT': {'type:integer': None, 'default': None, 'required': False} } } API REFERENCE ============= https://docs.openstack.org/api-ref/network/v2/index.html#tap-as-a-service TaaS CLI Reference ================== Openstack CLI ------------- OpenStackClient provides `the basic network commands `__ and tap-as-a-service has an extension for taas related commands. * Create tap service: **openstack tap service create** --name --port * List tap services: **openstack tap service list** * Show tap service: **openstack tap service show** * Delete tap service: **openstack tap service delete** * Update tap service: **openstack tap service update** --name --description * Create tap flow: **openstack tap flow create** --name --port --tap-service --direction --vlan-filter * List tap flows **openstack tap flow list** * Show tap flow **openstack tap flow show** * Delete tap flow **openstack tap flow delete** * Update tap flow **openstack tap flow update** --name --description Openstack CLI for tap mirrors ----------------------------- * Create tap mirror: **openstack tap mirror create** --name --description --port --directions --remote-ip --mirror-type * List tap mirrors: **openstack tap mirror list** * Show tap mirrors: **openstack tap mirror show** * Delete tap mirror: **openstack tap mirror delete** * Update tap mirror: **openstack tap mirror update** --name --description Workflow ========= In this section we describe a simple sequence of steps to use TaaS. Workflow Sequence for tap services and tap flows ------------------------------------------------ 1. Create a Neutron port with 'port_security_enabled' set to 'false'. 2. Launch a VM (VM on which you want to monitor/receive the mirrored data). Associate the Neutron port created in step 1 while creating the VM. 3. Using Neutron Client command for TaaS **neutron tap-service-create** or via REST APIs create a Tap Service instance by associating the port created in step 1. 4. Using Neutron Client command for TaaS **neutron tap-flow-create** or via REST APIs create a Tap Flow instance by associating the Tap Service instance created in step 3 and the target Neutron port from which you want to mirror traffic (assuming the Neutron port from which the traffic needs to be monitored already exists.) Mirroring can be done for both incoming and/or outgoing traffic from the target Neutron port. 5. Observe the mirrored traffic on the monitoring VM by running tools such as tcpdump. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/AUTHORS0000664000175000017500000000433300000000000016162 0ustar00zuulzuul00000000000000Abhishek Raut Andrea Frittoli Andreas Jaeger Anh Tran Anil Rao Boden R Corey Bryant Deepak Tiwari Dongcan Ye Doug Hellmann Duong Ha-Quang Elod Illes Fawad Khaliq Felipe Figueroa Gary Kotton Ghanshyam Mann Hangdong Zhang Henry Gessau Henry Gessau Ian Wienand James Page Jeremy Stanley Joel Capitao Juan Pablo Suazo Kazuhiro Suzuki Monty Taylor Nguyen Hung Phuong Ondřej Nový OpenStack Release Bot Reedip Reedip Reedip Reedip Banerjee Robert Simon Rodolfo Alonso Hernandez Rui Zang Slawek Kaplonski Takashi Kajinami Thomas Morin Trevor McCasland Viktor Krivak YAMAMOTO Takashi Yatin Kumbhare Yoichiro Iura bhavani.cr bk160f cheng daohanli elajkat ghanshyam huang.zhiping jessegler liyou01 reedip ricolin sunqingliang6 venkatamahesh vnyyad yatinkarel zitptan zoushilin ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/CONTRIBUTING.rst0000664000175000017500000000122600000000000017551 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 If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: 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/tap-as-a-service ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/ChangeLog0000664000175000017500000002562400000000000016672 0ustar00zuulzuul00000000000000CHANGES ======= 15.0.0 ------ * Use generated config files in devstack * Documentation for tap-mirrors * CLI for Tap Mirrors * TAAS tap-mirror OVN driver * Tap Mirror OVS driver * Remove unused Babel * GRE/ERSPAN mirroring for taas * Prohibit installation in too old python versions * Doc: add documentation for usage and driver details for SRIOV driver * Doc: add documentation for usage and flow examples for OVS * Do not set ageing in case of system datapath type * pep8: Issue on Noble (U. 24.04) with pylint * Fix reference to \`\`TestModelsMigrations\`\` class * reno: Update master for unmaintained/2023.1 * pyupgrade changes for Python3.9+ * Update jobs based on testing runtime for 2025.1 * Remove unused sections from setup.cfg * Remove unused openstack-common.conf * Squash tass.ini and taas\_plugin.ini * Generate plugin config file * Update master for stable/2024.2 * Doc: remove sphinxcontrib-\*diag from doc dependencies 14.0.0 ------ * Devstack: Make sure that taas ini file used by Neutron * do not use str(url) to stringify a URL for subsequent use * SQLAlchemy2: fix remaining issues * Add pre-commit configuration * reno: Update master for unmaintained/zed * Add py312 job to periodic based on testing runtime for 2024.2 * Update master for stable/2024.1 13.0.0 ------ * Drop removed options from sample config * Bump hacking * reno: Update master for unmaintained/yoga * Bump hacking * Enables external network mirroring * Add pyproject.toml to support pip 23.1 * docs: Fix releasenote building * Docs: Point to apiref and remove old CLI examples * Update master for stable/2023.2 12.0.0 ------ * Fix bindep.txt for python 3.11 job(Debian Bookworm) * Replaces Deprecated Bridge-utils Command * Support for ovs-dpdk * Remove the old CLI code from taas * OSC: Remove calls to neutronclient * Update master for stable/2023.1 11.0.0 ------ * CI: Add openstack-tox-py39-with-oslo-master to periodic weekly queue * Add pylint to pep8 and adopt code to the results * Switch away from Mock auto\_spec=True * Fix tox.ini for tox4 * Cleanup tox.ini * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed 10.0.0 ------ * Py3: Remove six * Add WebTest as dependency for testing * Modify taas extensions to use neutron-lib API def * setup.cfg: Replace dashes with underscores * Make neutron tempest plugin job voting again * Add context for all SQL transactions * Update python testing as per zed cycle teting runtime * test: Make py310 passing * py310: Add rpm packages to bindep.txt * Code cleaning: make RPC method signatures more meaningful * Add weekly jobs * tests: Use Zed job definitions * Update master for stable/yoga * Do not try to call status setting methods in case of periodic task * Remove ovs-vsctl direct calls 9.0.0 ----- * Use ovs TUNNEL\_ constants from new location * Execute neutron-db-manage only if q-svc is enabled * CLI: remove leftover logs from tap-flow create * Update leftover x namespace to openstack 8.0.0 ----- * Tests: remove tempest plugin from taas repository * Use payload for PORT PRECOMMIT\_DELETE event * Use ovs constants from neutron-lib * CFG: Remove unnused cfg options * taas OSC commands * Add condition for the create\_tap\_service method in ovs\_taas.py * Make master working again 7.0.0 ----- * Change tempest job to run on Ubuntu Focal * Switch to hacking 3.0.1 and to testing on focal * Enhancements to TaaS agent driver failure handling * Enable doc building job * Complete removal of dependency on the "mock" package * Update master for stable/train * Remove python2 jobs for tap-as-a-service * Adds policy in code to Tap-as-a-Service * Update deliverables for 6.0.0/train 6.0.0 ----- * Add local bindep.txt * Switch to stestr * Update URLs after opendev.org migration * OpenDev Migration Patch * DB migration milestone for Stein * Update master for stable/stein * Update deliverables for 5.0.0/stein 5.0.0 ----- * Add SRIOV mirroring support to Tap as a Service * Add a job from tap-as-a-service-tempest-plugin * Update psycopg2 to fix lower constraint issue * Replace openstack.org git:// URLs with https:// * stop using common db mixin * Clear associated tap resources on port delete * use rpc from neutron-lib * Change openstack-dev to openstack-discuss * Use template for lower-constraints * Changes to tap service and flow update tests in taas Client * use neutron-lib for \_model\_query * use common rpc and exceptions from neutron-lib * DB migration milestone for Rocky * Update deliverables for 4.0.0/rocky 4.0.0 ----- * Fix non-absolute imports * add local tox targets for pep8 and py3 * Opt-in for neutron-lib consumption patches * Require neutron-lib explicitly * Update to use setup\_extension * add lower-constraints job * modify grammer mistake * fix a code annotation * Avoid tox-install.sh * remove unused plugin.get\_plugin\_name() * Updated from global requirements * DB migration milestone for Queens * Update reno for stable/queens * Add deliverables info * Zuul: Remove project name 3.0.0 ----- * Add release notes * Update for os-testr 1.0.0 / stestr * Switch to tempest.common.utils.requires\_ext * Updated from global requirements * Move legacy jobs to project * Migration of Agent Extension to Neutron-Lib * Shrink Tempest scenario manager copy * DB migration milestone for Pike 2.0.0 ----- * Add coverage threshold * tox.ini: Fix cover target * Update the documentation link for doc migration * Update neutron cli unit tests * L2 agent extension support for Tap-as-a-Service * Replace the usage of 'admin\_manager' with 'os\_admin' * Remove MANIFEST.in * devstackgaterc: Enable n-api-meta * Updated from global requirements * Fixes enable taas id reuse * Enable to stop taas\_openvswitch\_agent on unstack * Updated from global requirements * devstack: Update after systemd change * Remove get\_namespace from API extensions * Drop py34 target in tox.ini and setup.cfg * Undefined name '\_' * Remove log translations * Fix breakage due to neutron-lib update * Use data\_utils from tempest.lib * Use get\_random\_mac from neutron-lib * Updated from global requirements * [Fix gate]Update test requirement * Use neutron-lib's context module * tempest: Switch to local copy of tempset/scenario/manager.py * devstack: Adapt to lib/neutron * Remove unused logging import * Updated from global requirements 1.0.0 ----- * Prepare for using standard python tests * devstack: Stop using Q\_PLUGIN\_EXTRA\_CONF\_FILES * Added link for modindex * Add DB migration milestone for Ocata * Enable placement on gate * Switch to decorators.idempotent\_id * Add tap-as-a-service spec * Remove unnecessary executable mode of some source files * Updated from global requirements * Updated from global requirements * Use ExtensionDescriptor from neutron-lib * Use tox\_install convention * Remove PLURALS * Use the new neutron manager * Import model\_base from neutron-lib rather than neutron * Include alembic migrations in module * Updated from global requirements * Updated from global requirements * Add status field in the TaaS API * Use neutron-lib modules for neutron\_taas * Disable VLAN id checks in create\_tap\_flow and delete\_tap\_flow * Fix i18n translation imports * Tag the alembic migration revisions for Newton * Fix Bug 1623457 * Switch to internal \_i18n pattern, as per oslo\_i18n guidelines * Updated from global requirements * Rename DB columns: tenant -> project * Enable DeprecationWarning in test environments * Bring models in sync with migrations, add test * Fix a few db vs migration mismatch * UT: Fix "RuntimeError: stop called on unstarted patcher" * presentations.rst: Add another TaaS related Austin presentation * Fix can't delete tap service on agent site * Add Python 3.5 classifier and venv * Add more unit tests to test DB mixin class for TaaS * Add testresources to test-requirements * Make get\_plugin\_type classmethod * Provide support for tap-\*-update * Updated from global requirements * Fix validation of direction on TapFlow * Add unit tests to test DB mixin class for TaaS * Fix typo in class name * Updated from global requirements * Support service framework to separate plugin logic * Remove network-id from tap-service * Use call\_and\_ignore\_notfound\_exc directly * doc: reference OpenStack Austin presentation * CLI: Turn comments into docstring * Fix tap-flow CLI to take service parameter * Fix help text for tap flow command * Remove new= argument from create\_connection * Initialize alembic branches for TaaS repo * devstackgaterc: Remove stale reference to n-obj * Dont pass tenant-id in request unless given * Improve exception messages * Fix API end point for tapservice/tapflow * Updated from global requirements * devstackgaterc: Disable unrelated services * Update tempest plugin after tempest-lib deprecation * Fix Gate tests * Avoid using \_get\_tenant\_id\_for\_create * Add report to coverage * Deprecated tox -downloadcache option removed * Change stackforge to openstack * Fix in install and api guide * Remove old TaaS CLI repo, update API reference * Re-implement TaaS CLI as a NeutronClient extension * Fix db error when running python34 unit tests * Fix TempestPlugin function signature * Delete python bytecode before every test run * Create specs directory * devstackgaterc: Specify tests to run on gate * Add a few dumb api tests * Add TaaS services client for tempest * Add a skeleton tempest plugin * Remove old install instructions and script * Add a minimum documentation for devstack plugin * Add devstack plugin * unit tests: Use the correct base class * Add alembic migration * Add an entry point for the service plugin * Add entry points for cli commands * Fix taas\_init\_ops * INSTALL.rst: Tweak the description of ML2 extension driver * Updated from global requirements * Remove repos.py replacement * Move constants.TAAS to our local module for now * Update .gitreview for new namespace * Add tests for the rest of plugin methods * Add some more tests * tox.ini: Fix cover by giving the source directory explicitly * tox.ini: Fix a typo in cover env * Add a few more tests * setup.cfg: Drop python 2.6 and 3.3 * Start writing tests * Move topics definitions to our own module for now * Python3 support improvement * test-requirements: Remove discover * Remove some commented out code * Misc documentation improvements * Change ignore-errors to ignore\_errors * Improved install script and updated the install readme file * Removing the reference shell script as now its part of the taas linux ovs driver * Deleting neutron configuration file as its no longer needed in the dependencies folder * Edited the READEM.rst file to refer API Reference document * Adding the API Reference guide for TaaS * Bug fix in the \_\_init\_\_.py file * Fixed misssing path prefix in taas\_plugin.py * Changing all oslo.config to oslo\_config * This is the initial commit of TaaS sources code * Changed tox.ini file to reflect only py27 and pep8 * Adding an install script to install TaaS * Changing the README.rst file for tap-as-a-service * Changed the Readme file and Added INSTALL.rst file * Initial Cookiecutter Commit * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/HACKING.rst0000664000175000017500000000024600000000000016707 0ustar00zuulzuul00000000000000tap-as-a-service Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/INSTALL.rst0000664000175000017500000000161500000000000016752 0ustar00zuulzuul00000000000000=================================== Tap as a Service installation guide =================================== This is the installation guide for enabling Tap-as-a-Service(TaaS) feature in OpenStack Neutron We have tested TaaS with latest version DevStack running on Ubuntu 12.04 and 14.04. TaaS is currently under active development and we will update you of new features and capabilities as and when they become available. Feel free to approach us with any issues related to installing or using TaaS. Dependencies ============ TaaS requires the 'Port Security' Neutron ML2 extension. Please make sure that this extension has been enabled. Adding the following to 'local.conf' while installing DevStack will enable 'Port Security' extension. (It's enabled by default) Q_ML2_PLUGIN_EXT_DRIVERS=port_security Installation ============ You can use DevStack external plugin. See `devstack/README.rst`. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/LICENSE0000664000175000017500000002363700000000000016127 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=1743591513.8592288 tap_as_a_service-15.0.0/PKG-INFO0000644000175000017500000000447000000000000016207 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: tap-as-a-service Version: 15.0.0 Summary: Tap-as-a-Service (TaaS) is an extension to the OpenStack network service (Neutron), it provides remote port mirroring capability for tenant virtual networks. Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr>=5.5.0 Requires-Dist: neutron>=16.0.0.0b1 Requires-Dist: neutron-lib>=2.11.0 Requires-Dist: openstacksdk>=0.102.0 Requires-Dist: python-openstackclient>=3.12.0 Requires-Dist: osc-lib>=2.3.0 ================ Tap as a Service ================ Tap-as-a-Service (TaaS) is an extension to the OpenStack network service (Neutron). It provides remote port mirroring capability for tenant virtual networks. Port mirroring involves sending a copy of packets entering and/or leaving one port to another port, which is usually different from the original destinations of the packets being mirrored. This service has been primarily designed to help tenants (or the cloud administrator) debug complex virtual networks and gain visibility into their VMs, by monitoring the network traffic associated with them. TaaS honors tenant boundaries and its mirror sessions are capable of spanning across multiple compute and network nodes. It serves as an essential infrastructure component that can be utilized for supplying data to a variety of network analytics and security applications (e.g. IDS). * Free software: Apache license * API Reference: https://opendev.org/openstack/tap-as-a-service/src/branch/master/API_REFERENCE.rst * Source: https://opendev.org/openstack/tap-as-a-service/ * Bugs: https://bugs.launchpad.net/tap-as-a-service For installing Tap-as-a-Service with Devstack please read the INSTALL.rst file ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/README.rst0000664000175000017500000000230100000000000016572 0ustar00zuulzuul00000000000000================ Tap as a Service ================ Tap-as-a-Service (TaaS) is an extension to the OpenStack network service (Neutron). It provides remote port mirroring capability for tenant virtual networks. Port mirroring involves sending a copy of packets entering and/or leaving one port to another port, which is usually different from the original destinations of the packets being mirrored. This service has been primarily designed to help tenants (or the cloud administrator) debug complex virtual networks and gain visibility into their VMs, by monitoring the network traffic associated with them. TaaS honors tenant boundaries and its mirror sessions are capable of spanning across multiple compute and network nodes. It serves as an essential infrastructure component that can be utilized for supplying data to a variety of network analytics and security applications (e.g. IDS). * Free software: Apache license * API Reference: https://opendev.org/openstack/tap-as-a-service/src/branch/master/API_REFERENCE.rst * Source: https://opendev.org/openstack/tap-as-a-service/ * Bugs: https://bugs.launchpad.net/tap-as-a-service For installing Tap-as-a-Service with Devstack please read the INSTALL.rst file ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8312287 tap_as_a_service-15.0.0/bin/0000775000175000017500000000000000000000000015657 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/bin/i40e_sysfs_command0000664000175000017500000000231700000000000021273 0ustar00zuulzuul00000000000000#!/usr/bin/env bash # Copyright (c) 2018 AT&T Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. help_msg="Incorrect arguments supplied!! Aborting....\nUsage: 1. VLANs to VF mirroring: $0 <'vlan_mirror'> <'add'|'rem'> \nUsage: 2. VF to VF mirroring: $0 <'ingress_mirror'|'egress_mirror'> <'add'|'rem'> " (($#!=5)) && { echo -e ${help_msg} && exit 1; } if [ -f /sys/class/net/${1}/device/sriov/${2}/${3} ] then echo ${4} ${5} > /sys/class/net/${1}/device/sriov/${2}/${3} else echo "Invalid sysfs path: /sys/class/net/${1}/device/sriov/${2}/${3}" exit 1 fi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/bindep.txt0000664000175000017500000000110400000000000017105 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 test] mysql-client [platform:dpkg test !platform:debian] mysql-server [platform:dpkg test !platform:debian] postgresql [test] postgresql-client [platform:dpkg test] libpq-devel [platform:rpm test] mariadb [platform:rpm test] mariadb-devel [platform:rpm test] mariadb-server [platform:rpm test platform:debian] postgresql-devel [platform:rpm test] postgresql-server [platform:rpm test] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8152287 tap_as_a_service-15.0.0/deliverables/0000775000175000017500000000000000000000000017550 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8312287 tap_as_a_service-15.0.0/deliverables/queens/0000775000175000017500000000000000000000000021050 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/deliverables/queens/tap-as-a-service.yaml0000664000175000017500000000035500000000000025000 0ustar00zuulzuul00000000000000--- launchpad: tap-as-a-service type: other releases: - projects: - hash: e528a0ba81f24d0da178bbfac94d517f664aa149 repo: openstack/tap-as-a-service version: 3.0.0 branches: - name: stable/queens location: 3.0.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8312287 tap_as_a_service-15.0.0/deliverables/rocky/0000775000175000017500000000000000000000000020677 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/deliverables/rocky/tap-as-a-service.yaml0000664000175000017500000000035400000000000024626 0ustar00zuulzuul00000000000000--- launchpad: tap-as-a-service type: other releases: - projects: - hash: 57dfbdb8ebbf8d42d0715f7753626ed2ddf38cef repo: openstack/tap-as-a-service version: 4.0.0 branches: - name: stable/rocky location: 4.0.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8312287 tap_as_a_service-15.0.0/deliverables/stein/0000775000175000017500000000000000000000000020672 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/deliverables/stein/tap-as-a-service.yaml0000664000175000017500000000035400000000000024621 0ustar00zuulzuul00000000000000--- launchpad: tap-as-a-service type: other releases: - projects: - hash: 3f175b3f2741a43efec7c1f801d0cd1c97132abc repo: openstack/tap-as-a-service version: 5.0.0 branches: - name: stable/stein location: 5.0.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8312287 tap_as_a_service-15.0.0/deliverables/train/0000775000175000017500000000000000000000000020665 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/deliverables/train/tap-as-a-service.yaml0000664000175000017500000000035400000000000024614 0ustar00zuulzuul00000000000000--- launchpad: tap-as-a-service type: other releases: - projects: - hash: 21aa47cbfad2320d9adf08698d13ed631bfa10f5 repo: openstack/tap-as-a-service version: 6.0.0 branches: - name: stable/train location: 6.0.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8312287 tap_as_a_service-15.0.0/devstack/0000775000175000017500000000000000000000000016713 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/devstack/README.rst0000664000175000017500000000140000000000000020375 0ustar00zuulzuul00000000000000======================== DevStack external plugin ======================== A `local.conf` recipe to enable tap-as-a-service:: [[local|localrc]] enable_plugin tap-as-a-service https://opendev.org/openstack/tap-as-a-service enable_service taas To enable mirroring via GRE or ERSPAN tunnels:: [[local|localrc]] enable_plugin tap-as-a-service https://opendev.org/openstack/tap-as-a-service enable_service taas enable_service tap_mirror To enable the mirroring with OVS driver:: TAAS_SERVICE_DRIVER=TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default To enable mirroring with OVN driver:: TAAS_SERVICE_DRIVER=TAAS:TAAS:neutron_taas.services.taas.service_drivers.ovn.taas_ovn.TaasOvnDriver:default ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/devstack/devstackgaterc0000664000175000017500000000273200000000000021634 0ustar00zuulzuul00000000000000# Copyright 2015 Midokura SARL # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This script is executed in the OpenStack CI job that runs DevStack + tempest. # You can find the CI job configuration here: # # http://git.openstack.org/cgit/openstack-infra/project-config/tree/jenkins/jobs/tap-as-a-service.yaml # OVERRIDE_ENABLED_SERVICES=key,mysql,rabbit OVERRIDE_ENABLED_SERVICES+=,g-api,g-reg OVERRIDE_ENABLED_SERVICES+=,n-api,n-cond,n-cpu,n-crt,n-sch,placement-api OVERRIDE_ENABLED_SERVICES+=,n-api-meta OVERRIDE_ENABLED_SERVICES+=,q-agt,q-dhcp,q-l3,q-meta,q-metering,q-svc,quantum OVERRIDE_ENABLED_SERVICES+=,taas,taas_agent OVERRIDE_ENABLED_SERVICES+=,tempest,dstat export OVERRIDE_ENABLED_SERVICES # Begin list of exclusions. r="^(?!.*" # exclude the slow tag (part of the default for 'full') r="$r(?:.*\[.*\bslow\b.*\])" # End list of exclusions. r="$r)" r="$r(tempest\.(api.network\.|scenario.test_network)|neutron_taas_tempest_plugin\.).*$" export DEVSTACK_GATE_TEMPEST_REGEX="$r" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/devstack/plugin.sh0000664000175000017500000000530600000000000020551 0ustar00zuulzuul00000000000000#!/bin/bash # Copyright 2015 Midokura SARL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This script is meant to be sourced from devstack. It is a wrapper of # devmido scripts that allows proper exporting of environment variables. function install_taas { setup_develop $NEUTRON_TAAS_DIR } function generate_taas_config_files { # Uses oslo config generator to generate TaaS sample configuration files (cd $NEUTRON_TAAS_DIR && exec ./tools/generate_config_file_samples.sh) } function configure_taas_plugin { echo "Configuring taas" cp $NEUTRON_TAAS_DIR/etc/taas_plugin.ini.sample $TAAS_PLUGIN_CONF_FILE neutron_server_config_add $TAAS_PLUGIN_CONF_FILE neutron_service_plugin_class_add taas if is_service_enabled tap_mirror; then neutron_service_plugin_class_add tapmirror fi inicomment $TAAS_PLUGIN_CONF_FILE service_providers service_provider iniadd $TAAS_PLUGIN_CONF_FILE service_providers service_provider $TAAS_SERVICE_DRIVER } function configure_taas_agent { echo "Configuring taas agent" source $NEUTRON_DIR/devstack/lib/l2_agent plugin_agent_add_l2_agent_extension taas configure_l2_agent } if is_service_enabled taas; then if [[ "$1" == "stack" ]]; then if [[ "$2" == "pre-install" ]]; then : elif [[ "$2" == "install" ]]; then install_taas elif [[ "$2" == "post-config" ]]; then generate_taas_config_files configure_taas_plugin if is_service_enabled q-svc neutron-api; then neutron-db-manage --subproject tap-as-a-service upgrade head fi elif [[ "$2" == "extra" ]]; then : fi elif [[ "$1" == "unstack" ]]; then : fi fi if is_service_enabled q-agt neutron-agent; then if [[ "$1" == "stack" ]]; then if [[ "$2" == "pre-install" ]]; then : elif [[ "$2" == "install" ]]; then install_taas elif [[ "$2" == "post-config" ]]; then if is_service_enabled q-agt neutron-agent; then configure_taas_agent fi elif [[ "$2" == "extra" ]]; then : fi elif [[ "$1" == "unstack" ]]; then : fi fi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/devstack/settings0000664000175000017500000000063200000000000020477 0ustar00zuulzuul00000000000000# Devstack settings ABSOLUTE_PATH=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd) NEUTRON_CONF_DIR=${NEUTRON_CONF_DIR:-"/etc/neutron"} NEUTRON_TAAS_DIR=${DEST}/tap-as-a-service NEUTRON_TAAS_CONF_FILE=neutron_taas.conf TAAS_PLUGIN_CONF_FILE=$NEUTRON_CONF_DIR/$NEUTRON_TAAS_CONF_FILE TAAS_SERVICE_DRIVER=${TAAS_SERVICE_DRIVER:-"TAAS:taas:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default"} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8312287 tap_as_a_service-15.0.0/doc/0000775000175000017500000000000000000000000015654 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/requirements.txt0000664000175000017500000000045600000000000021145 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/doc/source/0000775000175000017500000000000000000000000017154 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/api_reference.rst0000664000175000017500000000004500000000000022474 0ustar00zuulzuul00000000000000.. include:: ../../API_REFERENCE.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/conf.py0000664000175000017500000000544100000000000020457 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', 'openstackdocstheme' ] # openstackdocstheme options repository_name = 'openstack/tap-as-a-service' bug_project = 'tap-as-a-service' bug_tag = '' html_theme = 'openstackdocs' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'tap-as-a-service' 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 = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-tap-as-a-service.tex', 'Tap as a Service Documentation', 'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} latex_elements = { 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', # openany: Skip blank pages in generated PDFs # oneside: Use the same page layout for both even and odd pages 'extraclassoptions': 'openany,oneside', } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/contributing.rst0000664000175000017500000000011300000000000022410 0ustar00zuulzuul00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/index.rst0000664000175000017500000000121000000000000021007 0ustar00zuulzuul00000000000000.. tap-as-a-service documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to tap-as-a-service's documentation! ======================================================== Contents: .. toctree:: :maxdepth: 2 readme installation api_reference contributing specs/index presentations usage mirroring_with_ovs_driver mirroring_sriov_ports tap_mirrors_under_the_hood Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/installation.rst0000664000175000017500000000003700000000000022407 0ustar00zuulzuul00000000000000.. include:: ../../INSTALL.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/mirroring_sriov_ports.rst0000664000175000017500000000564500000000000024401 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. Convention for heading levels in Neutron devref: ======= Heading 0 (reserved for the title in a document) ------- Heading 1 ~~~~~~~ Heading 2 +++++++ Heading 3 ''''''' Heading 4 (Avoid deeper levels because they do not render well.) SriovNic taas driver ==================== The SRIOV mirroring for tap-as-a-service was proposed in Stein cycle, in `Port Mirroring API for VF Mirroring `_. The proposal was to use the ``Intel Ethernet Network Adapter XXV710`` capabilities with at least ``Intel i40e v4.16.0`` driver for mirroring traffic on VFs. Configuration ------------- To enable the OVS taas driver you need these config options in ``ml2_conf.ini`` on all hosts where ovs-neutron-agent is running:: [agent] extensions = taas You also need a ``taas_plugin.ini`` with the necessary service_provider setting:: [service_providers] service_provider = TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default Driver ------ The driver works in a way that the traffic on the port of the ``tap-flow``, the source of the mirroring is mirrored to the port of the ``tap-service``. Both the tap-flow port and the tap-service port must be on the same host and on the same Physical Function (PF). The actual mirroring with the SRIOV driver is done by ``sysfs`` commands, for example (from `Port Mirroring API for VF Mirroring of specific VLANs to VF `_):: # # Mirror VLANs 2,18-22 of VF3 to p1p1 # echo add 2,18-22>/sys/class/net/p1p1/device/sriov/3/vlan_mirror # # Remove VLAN mirroring of VLANs 2,18 of VF3 from p1p1 # echo rem 2,18>/sys/class/net/p1p1/device/sriov/3/vlan_mirror The driver allows the selection of specific VLANs for mirroring, and taas API was also adopted to it, with the extra ``vlan_filter`` field for tap-flows, see the `API ref `_. To achieve the above mirroring the following CLI command can be used:: $ openstack tap service create --name tap_service --port port_ts $ openstack tap flow create --name tap_flow0 --port port0 --vlan-filter 2,18-22 --tap-service tap_service ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/mirroring_with_ovs_driver.rst0000664000175000017500000002734200000000000025223 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. Convention for heading levels in Neutron devref: ======= Heading 0 (reserved for the title in a document) ------- Heading 1 ~~~~~~~ Heading 2 +++++++ Heading 3 ''''''' Heading 4 (Avoid deeper levels because they do not render well.) Open vSwitch taas driver ======================== The OVS driver for taas uses the existing infrastructure created by Neutron: br-int, br-tun, and adds an extra bridge br-tap for mirroring. For ``ingress``/``egress`` terminology please check out: `Open vSwitch Firewall Driver Ingress/Egress Terminology `_. Configuration ------------- To enable the OVS taas driver you need these config options in ``ml2_conf.ini`` on all hosts where ovs-neutron-agent is running:: [agent] extensions = taas You also need a ``taas_plugin.ini`` with the necessary service_provider setting:: [service_providers] service_provider = TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default Openflow rules -------------- Topology ~~~~~~~~ TAAS (``OvsTaasDriver`` class) creates ``br-tap`` when the driver is starting, then created patch ports to connect it with br-int and br-tun. :: +----------+ +---------+ +--------+ | |(patch-int-tap) | |(patch-tap-tun) | | | br-int |------------------| br-tap |---------------------| br-tun | | | (patch-tap-int)| | (patch-tun-tap)| | +----------+ +---------+ +--------+ Installed flows on br-int ~~~~~~~~~~~~~~~~~~~~~~~~~ Create tap service ++++++++++++++++++ Add flow to table 0 to match packets coming from ``patch_int_tap`` to mod_vlan to the original port (associated to tap service) VLAN, and output on the port. Create tap flow +++++++++++++++ If the direction of the tap flow is ``OUT`` or ``BOTH`` match packets coming from the port associated with the tap flow, mod_vlan to the tap service VLAN (taas_id in the code) and output to ``patch_int_tap``. If direction is ``IN`` or ``BOTH`` match flows with the mac of the port associated with the tap flow, mod_vlan to the tap service VLAN (taas_id in the code) and output to ``patch_int_tap``. Installed flows on br-tun ~~~~~~~~~~~~~~~~~~~~~~~~~ The tables used on br-tun are in [1]_ OvsTaasDriver during initialization uses table 0 to filter out packets coming from ``patch_tun_tap`` and resubmit them to ``TAAS_SEND_UCAST``, and to ``TAAS_SEND_FLOOD``. In table ``TAAS_SEND_FLOOD`` flood flow actions are installed for all ports except ``patch-int`` and ``patch-tun-tap``. Table ``TAAS_CLASSIFY`` is used to filter packets based on ``reg0`` value (which is set during tap service or tap flow creation, to have the same rules on both the tap service and tap flow hosts): * reg0=0 or 1 resubmit to ``TAAS_DST_CHECK`` * reg0=2 resubmit to ``TAAS_SRC_CHECK`` In table ``TAAS_DST_RESPOND`` flow filters for ``reg0=1``, VLAN is modified to 2, and VLAN_TCI least significant bits moved to TUN_ID, and packet is sent out in in_port. Flow to send packets to table ``TAAS_DST_RESPOND`` is installed during tap service creation. In table ``TAAS_SRC_RESPOND`` a learn action is installed. Create tap service ++++++++++++++++++ For every tunnel type (GRE, VXLAN, GENEVE) a flow is installed to match packets whose ``tun_id`` equals to the tap service VLAN (taas_id in the code) and resubmit to ``TAAS_CLASSIFY``. This is the flow that moves VLAN_TCI least significant 11 bits to ``REG0``, that is the VID. To table ``TAAS_DST_CHECK`` a flow is installed to match flows which tun_id equals with the tap service VLAN (taas_id in the code) and resubmit to ``TAAS_DST_RESPOND``. Create tap flow +++++++++++++++ For every tunnel type (GRE, VXLAN, GENEVE) a flow is installed to match packets whose ``tun_id`` equals to the tap service VLAN (taas_id in the code) and resubmit to ``TAAS_CLASSIFY``. This is the flow that moves VLAN_TCI least significant 11 bits to ``REG0``, that is the VID. To table ``TAAS_SRC_CHECK`` a flow is installed to match flows which tun_id equals with the tap service VLAN (taas_id in the code) and resubmit to ``TAAS_SRC_RESPOND``. Installed flows on br-tap ~~~~~~~~~~~~~~~~~~~~~~~~~ The tables used on br-tap are in [2]_. OvsTaasDriver uses table 0 to filter out packets coming from ``patch_tap_int`` and resubmit them to table ``TAAS_RECV_LOC``, and from there send out on patch_tap_tun. On the other direction if packets in table 0 are coming from ``patch_tap_tun`` then those are resubmitted to ``TAAS_RECV_REM``. Create tap service ++++++++++++++++++ When tap service is created the OVS TaaS driver filters out packets in ``TAAS_RECV_LOC`` with the VLAN dedicated to the tap service from the ``vlan_range`` (see [3]_) and sends them back. On the other direction if packets coming from table ``TAAS_RECV_REM`` and they have the tap service VLAN (taas_id in the driver code) output them to ``patch_tap_int`` towards br-int. Flow rules example ------------------ :: +---------+ +----------+ | | | Monitor | | VM0 | | VM | | | | | +---------+ +----------+ | |Port0 (associated to tap flow) | |Port_ts (associated to +--+ ----------- +--+ tap service) | net0 \ monitor_net | -------+ ---+------------ | ------+-------- \ | / +---------+-+ +---------+ / \ / \ | tap_flow0 | -------------------- | tap_service | \ / \ / +------------+ +----------+ Flows after the driver is started ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``br-tun`` :: table=0, priority=1,in_port="patch-tun-tap" actions=resubmit(,30) table=30, priority=0 actions=resubmit(,31) table=31, hard_age=1, actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID[0..11],mod_vlan_vid:1,output:"vxlan-646d00d9" table=35, priority=1,reg0=0x1 actions=resubmit(,36) table=35, priority=1,reg0=0x2 actions=resubmit(,37) table=35, priority=2,reg0=0 actions=resubmit(,36) table=36, priority=0 actions=drop table=37, priority=0 actions=drop table=38, priority=1,reg0=0x1 actions=output:"patch-tun-tap",move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID[0..11],mod_vlan_vid:2,IN_PORT table=38, priority=2,reg0=0 actions=output:"patch-tun-tap" table=39, priority=1 actions=learn(table=30,hard_timeout=60,priority=1,NXM_OF_VLAN_TCI[0..11],load:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID[0..11],load:0->NXM_OF_VLAN_TCI[0..11],output:NXM_OF_IN_PORT[]) * table 30: TAAS_SEND_UCAST * table 31: TAAS_SEND_FLOOD * table 35: TAAS_CLASSIFY * table 36: TAAS_DST_CHECK * table 37: TAAS_SRC_CHECK * table 38: TAAS_DST_RESPOND * table 39: TAAS_SRC_RESPOND * vxlan-646d00d9: vxlan port on the host ``br-tap`` :: table=0, priority=0 actions=drop table=0, priority=1,in_port="patch-tap-int" actions=resubmit(,1) table=0, priority=1,in_port="patch-tap-tun" actions=resubmit(,2) table=1, priority=0 actions=output:"patch-tap-tun" table=2, priority=0 actions=drop * table 1: TAAS_RECV_LOC * table 2: TAAS_RECV_REM Create tap service ~~~~~~~~~~~~~~~~~~ The used command: .. code-block:: console $ openstack tap service create --name tap_service --port port_ts ``br-int`` :: table=0, priority=25,in_port="patch-int-tap",dl_vlan=3900 actions=mod_vlan_vid:5,output:"tap12df65fe-ce" * VLAN 3900: taas id (vlan_range_start default value) * tap12df65fe-ce: tap port id in br-int for the port associated with the tap service ``br-tun`` :: table=3, priority=1,tun_id=0xf3c actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35) table=4, priority=1,tun_id=0xf3c actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35) table=6, priority=1,tun_id=0xf3c actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35) table=36, priority=1,tun_id=0xf3c actions=resubmit(,38) * table 3: GRE_TUN_TO_LV * table 4: VXLAN_TUN_TO_LV * table 6: GENEVE_TUN_TO_LV * table 36: TAAS_DST_CHECK * table 38: TAAS_DST_RESPOND * tun_id=0xf3c => VLAN 3900 (see taas_id, vlan_range_start default value) ``br-tap`` :: table=1, priority=1,dl_vlan=3900 actions=IN_PORT table=2, priority=1,dl_vlan=3900 actions=output:"patch-tap-int" * VLAN 3900 (see taas_id, vlan_range_start default value) Create tap flow ~~~~~~~~~~~~~~~ The used command: .. code-block:: console $ openstack tap flow create --name tap_flow0 --port port0 --tap-service tap_service --direction BOTH ``br-int`` :: table=0, priority=20,dl_dst=fa:16:3e:fc:c5:71 actions=NORMAL,mod_vlan_vid:3900,output:"patch-int-tap" table=0, priority=20,in_port="tap4bd58b41-2b" actions=NORMAL,mod_vlan_vid:3900,output:"patch-int-tap" * fa:16:3e:fc:c5:71 : mac address of the port associated with the tap flow * tap4bd58b41-2b: tap port id in br-int for the port associated with the tap flow ``br-tun`` :: table=3, priority=1,tun_id=0xf3c actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35) table=4, priority=1,tun_id=0xf3c actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35) table=6, priority=1,tun_id=0xf3c actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35) table=37, priority=1,tun_id=0xf3c actions=resubmit(,39) From learn action (see table 39): :: table=30, priority=1,vlan_tci=0x0f3c/0x0fff actions=load:0xf3c->NXM_NX_TUN_ID[0..11],load:0->NXM_OF_VLAN_TCI[0..11],output: * table 3: GRE_TUN_TO_LV * table 4: VXLAN_TUN_TO_LV * table 6: GENEVE_TUN_TO_LV * table 30: TAAS_SEND_UCAST * table 37: TAAS_SRC_CHECK * table 39: TAAS_SRC_RESPOND * tun_id=0xf3c => VLAN 3900 (see taas_id, vlan_range_start default value) * port 3: vxlan port on the host .. [1] ``_ .. [2] ``_ .. [3] ``_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/presentations.rst0000664000175000017500000000157100000000000022610 0ustar00zuulzuul00000000000000============= Presentations ============= - `Tap-As-A-Service What You Need to Know Now `_ 40 min presentation at OpenStack Summit Austin, April 2016, including a demo with Horizon. - `Using Open Source Security Architecture to Defend against Targeted Attacks` 40 min presentation at OpenStack Summit Austin, April 2016, including IDS/IPS use cases and a demo with snort. - `Tap-as-a-Service (TaaS): Port Monitoring for Neutron Networks `_ 40 min presentation at OpenStack Summit Vancouver, May 2015, including a demo. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/readme.rst0000664000175000017500000000003600000000000021142 0ustar00zuulzuul00000000000000.. include:: ../../README.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/tap_mirrors_under_the_hood.rst0000664000175000017500000002124300000000000025317 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. Convention for heading levels in Neutron devref: ======= Heading 0 (reserved for the title in a document) ------- Heading 1 ~~~~~~~ Heading 2 +++++++ Heading 3 ''''''' Heading 4 (Avoid deeper levels because they do not render well.) Open vSwitch tap mirror driver ============================== Since OVS v2.10 it is possible to create GRE or ERSPAN mirroring ports. .. code-block:: console $ ovs-vsctl add-port br0 at_erspan0 -- set int at_erspan0 type=erspan options:key=1 options:remote_ip=172.31.1.1 options:erspan_ver=1 options:erspan_idx=1 $ # type can be erspan or gre, and $ # options:erspan_ver=1 or 2 selects the version of ERSPAN. $ # Note that tap mirroring uses erspan_ver=1 To create a tap mirror with the OVS driver you have to enable ``TaasRpcDriver`` in the ``taas_plugin.ini`` configuration file: .. code-block:: ini [service_providers] service_provider = TAAS:TAAS:neutron_taas.services.taas.service_drivers.taas_rpc.TaasRpcDriver:default .. note:: The same driver must be set to use taas with tap-services and tap-flows. The Openstack CLI workflow is the following to create an ERSPANv1 mirror: .. code-block:: bash $ openstack network create net0 $ openstack subnet create subnet0 --subnet-range --network net0 $ openstack port create mirror_port --network net0 $ openstack server create --flavor --image --nic port-id=mirror_port mirror_vm0 $ openstack tap mirror create --port mirror_port --name mirror1 --directions IN=102 --remote-ip 100.109.0.221 --mirror-type erspanv1 +-------------+--------------------------------------+ | Field | Value | +-------------+--------------------------------------+ | description | | | directions | {'IN': '102'} | | id | 7171328e-fcfe-40ab-8e27-84ce7d57a5cd | | mirror_type | erspanv1 | | name | mirror1 | | port_id | 88316ec8-38ca-4115-912a-3d7fab2d6cf7 | | project_id | fe7c0b79c37b439490d2274405ebf483 | | remote_ip | 100.109.0.221 | +-------------+--------------------------------------+ The result of the above commands will result in a new port on ``br-tap``: .. code-block:: bash $ sudo ovs-vsctl show ... Bridge br-tap datapath_type: system Port br-tap Interface br-tap type: internal Port tm_in_c00403 Interface tm_in_c00403 type: erspan options: {erspan_idx="102", erspan_ver="1", key="102", remote_ip="100.109.0.221"} Port patch-tap-int Interface patch-tap-int type: patch options: {peer=patch-int-tap} Port patch-tap-tun Interface patch-tap-tun type: patch options: {peer=patch-tun-tap} On ``br-int`` new flows are installed to direct the traffic (in this case only ingress) towards ``br-tap``: .. code-block:: bash $ sudo ovs-ofctl dump-flows br-int ... cookie=0x8f7b2f67055cd027, duration=1282.245s, table=0, n_packets=0, n_bytes=0, idle_age=1282, priority=20,dl_dst= actions=output:4,resubmit(,58) .. note:: output:4 points to patch-tap-int. The resulting packet will be like this: .. code-block:: bash Frame 1: 148 bytes on wire (1184 bits), 148 bytes captured (1184 bits) Ethernet II, Src: RealtekU_16:01:cb (52:54:00:16:01:cb), Dst: RealtekU_8e:0e:4b (52:54:00:8e:0e:4b) Internet Protocol Version 4, Src: 100.109.0.82, Dst: 100.109.0.221 Generic Routing Encapsulation (ERSPAN) Encapsulated Remote Switch Packet ANalysis Type II 0001 .... .... .... = Version: Type II (1) .... 0000 0000 0000 = Vlan: 0 000. .... .... .... = COS: 0 ...0 0... .... .... = Encap: Originally without VLAN tag (0) .... .0.. .... .... = Truncated: Not truncated (0) .... ..00 0110 0110 = SpanID: 102 0000 0000 0000 .... .... .... .... .... = Reserved: 0 .... .... .... 0000 0000 0001 0000 0010 = Index: 258 Ethernet II, Src: fa:16:3e:4c:0c:be (fa:16:3e:4c:0c:be), Dst: fa:16:3e:1d:e4:f4 (fa:16:3e:1d:e4:f4) Internet Protocol Version 4, Src: 192.171.0.23, Dst: 192.171.0.6 Internet Control Message Protocol ``SpanID`` is ``102`` as expected but the ``Index`` is ``258`` which is ``0x102`` OVN tap mirror driver ===================== Since OVN v22.12.0 it is possible to create mirrors: .. code-block:: console $ ovn-nbctl mirror-add mirror1 erspan 0 from-lport 100.109.0.48 $ # type (2nd parameter after name) can be erspan or gre or local (from a later version) $ # index (3rd parameter) is the tunnel id and the base of ERSPAN idx $ # filter (4th parameter) can be from-lport, to-lport or both (from a later version) $ # sink (5th parameter) is the remote IP of the mirroring. To create a tap mirror with the OVN driver you have to enable ``TaasOvnDriver`` in the ``taas_plugin.ini`` configuration file: .. code-block:: ini [service_providers] service_provider = TAAS:TAAS:neutron_taas.services.taas.service_drivers.ovn.taas_ovn.TaasOvnDriver:default The Openstack CLI workflow is the following to create an ERSPANv1 mirror: .. code-block:: bash $ openstack network create net0 $ openstack subnet create subnet0 --subnet-range --network net0 $ openstack port create mirror_port --network net0 $ openstack server create --flavor --image --nic port-id=mirror_port mirror_vm0 $ openstack tap mirror create --port mirror_port --name mirror1 --directions IN=102 --remote-ip 100.109.0.221 --mirror-type erspanv1 +-------------+--------------------------------------+ | Field | Value | +-------------+--------------------------------------+ | description | | | directions | {'IN': '102'} | | id | 7171328e-fcfe-40ab-8e27-84ce7d57a5cd | | mirror_type | erspanv1 | | name | mirror1 | | port_id | 88316ec8-38ca-4115-912a-3d7fab2d6cf7 | | project_id | fe7c0b79c37b439490d2274405ebf483 | | remote_ip | 100.109.0.221 | +-------------+--------------------------------------+ The result of the above commands will result a new mirror in the ovn nbdb: .. code-block:: bash $ ovn-nbctl mirror-list tm_in_717132: Type : erspan Sink : 100.109.0.221 Filter : to-lport Index/Key: 102 Note the "translation" of the parameters. Directions IN=102 will Filter=to-lport, and Index/Key:102. (OUT direction of course will be from-lport in OVN NBDB) And of course the port will appear on the integration bridge also: .. code-block:: bash $ ovs-vsctl show ... Bridge br-int .... Port ovn-tm_in_717132 Interface ovn-tm_in_717132 type: erspan options: {erspan_idx="102", erspan_ver="1", key="102", remote_ip="100.109.0.221"} Please note the ERSPAN header fields also: .. code-block:: bash Frame 1: 148 bytes on wire (1184 bits), 148 bytes captured (1184 bits) Ethernet II, Src: RealtekU_3d:93:57 (52:54:00:3d:93:57), Dst: RealtekU_8e:0e:4b (52:54:00:8e:0e:4b) Internet Protocol Version 4, Src: 100.109.0.48, Dst: 100.109.0.221 Generic Routing Encapsulation (ERSPAN) Encapsulated Remote Switch Packet ANalysis Type II 0001 .... .... .... = Version: Type II (1) .... 0000 0000 0000 = Vlan: 0 000. .... .... .... = COS: 0 ...0 0... .... .... = Encap: Originally without VLAN tag (0) .... .0.. .... .... = Truncated: Not truncated (0) .... ..00 0110 0110 = SpanID: 102 0000 0000 0000 .... .... .... .... .... = Reserved: 0 .... .... .... 0000 0000 0001 0000 0010 = Index: 258 Ethernet II, Src: fa:16:3e:50:ed:fd (fa:16:3e:50:ed:fd), Dst: fa:16:3e:6a:49:13 (fa:16:3e:6a:49:13) Internet Protocol Version 4, Src: 192.171.0.25, Dst: 192.171.0.27 Internet Control Message Protocol ``SpanID`` is ``102`` as expected but the ``Index`` is ``258`` which is ``0x102`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/doc/source/usage.rst0000664000175000017500000001015700000000000021016 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. Convention for heading levels in Neutron devref: ======= Heading 0 (reserved for the title in a document) ------- Heading 1 ~~~~~~~ Heading 2 +++++++ Heading 3 ''''''' Heading 4 (Avoid deeper levels because they do not render well.) Usage Workflow ============== 1. Create a Neutron port with 'port_security_enabled' set to 'false'. .. note:: If you already have a port attached to a virtual machine you can disable port-security on that port and use that for mirroring also. .. code-block:: console $ openstack port create port_tap_service --disable-port-security 2. Launch a VM (VM on which you want to monitor/receive the mirrored data). Associate the Neutron port created in step 1 while creating the VM. .. note:: To capture and analize traffic it is suggested to use VM image that has advanced tools like tcpdump. .. code-block:: console $ openstack server create --nic port-id=port_tap_service monitor_vm 3. Using Openstack Client command for TaaS **openstack tap service create** or via REST APIs create a Tap Service instance by associating the port created in step 1. .. code-block:: console $ openstack tap service create --name ts_0 --port port_tap_service 4. Using Openstack Client command for TaaS **openstack tap flow create** or via REST APIs create a Tap Flow instance by associating the Tap Service instance created in step 3 and the target Neutron port from which you want to mirror traffic (assuming the Neutron port from which the traffic needs to be monitored already exists.) Mirroring can be done for both incoming and/or outgoing traffic from the target Neutron port. .. code-block:: console $ openstack tap flow create --name tf_0 --port source_port --tap-service ts_0 --direction BOTH 5. Observe the mirrored traffic on the monitoring VM by running tools such as tcpdump. Worklow for tap mirrors -------------------------------- 1. Make sure that the destination of your mirroring is ready. This can be a Host outside of your cloud, or a virtual machine with port_security_enabled=False and with a Floating IP. .. code-block:: console $ openstack network create monitor_net $ openstack subnet create monitor_subnet --subnet-range 192.171.0.0/27 --network monitor_net $ openstack port create vxlan_monitor_port --network monitor_net --disable-port-security --no-security-group $ openstack server create --flavor d1 --image --nic port-id=monitor_port monitor_vm --key-name mykey $ openstack server add floating ip monitor_vm 100.109.0.221 2. Create another Neutron port (the source of the mirroring). .. code-block:: console $ openstack network create mirror_net $ openstack subnet create mirror_subnet --subnet-range 192.170.0.0/27 --network mirror_net $ openstack port create mirror_port --network mirror_net --security-group 3. Boot a VM on the previous port. .. code-block:: console $ openstack server create --security-group --flavor c1 --image cirros-0.6.2-x86_64-disk --nic port-id=mirror_port mirror_vm 4. Create a tap mirror with the source port Id as the port field and the FIP or the IP of the remote host as the remote_ip field of the tap mirror. Make sure that the remote end can be the endpoint of the GRE or ERSPANv1 tunnel. .. code-block:: console $ openstack tap mirror create --port mirror_port --name mirror1 --directions IN=102 --remte-ip 100.109.0.221 --mirror-type erspanv1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/etc/0000775000175000017500000000000000000000000015662 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/etc/neutron/0000775000175000017500000000000000000000000017354 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/etc/neutron/policy.yaml.sample0000664000175000017500000000266000000000000023023 0ustar00zuulzuul00000000000000# Create a tap flow # POST /taas/tap_flows #"create_tap_flow": "rule:admin_or_owner" # Update a tap flow # PUT /taas/tap_flows/{id} #"update_tap_flow": "rule:admin_or_owner" # Show a tap flow # GET /taas/tap_flows/{id} #"get_tap_flow": "rule:admin_or_owner" # Delete a tap flow # DELETE /taas/tap_flows/{id} #"delete_tap_flow": "rule:admin_or_owner" # Create a tap service # POST /taas/tap_services #"create_tap_service": "rule:admin_or_owner" # Updates a tap service # PUT /taas/tap_services/{id} #"update_tap_service": "rule:admin_or_owner" # Show a tap service # GET /taas/tap_services/{id} #"get_tap_service": "rule:admin_or_owner" # Delete a tap service # DELETE /taas/tap_services/{id} #"delete_tap_service": "rule:admin_or_owner" # Create a Tap Mirror # POST /taas/tap_mirrors # Intended scope(s): project #"create_tap_mirror": "(rule:admin_only) or (role:member and project_id:%(project_id)s)" # Update a Tap Mirror # PUT /taas/tap_mirrors/{id} # Intended scope(s): project #"update_tap_mirror": "(rule:admin_only) or (role:member and project_id:%(project_id)s)" # Show a Tap Mirror # GET /taas/tap_mirrors # GET /taas/tap_mirrors/{id} # Intended scope(s): project #"get_tap_mirror": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)" # Delete a Tap Mirror # DELETE /taas/tap_mirrors/{id} # Intended scope(s): project #"delete_tap_mirror": "(rule:admin_only) or (role:member and project_id:%(project_id)s)" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/etc/neutron/rootwrap.d/0000775000175000017500000000000000000000000021453 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/etc/neutron/rootwrap.d/taas-i40e-sysfs.filters0000664000175000017500000000063200000000000025702 0ustar00zuulzuul00000000000000# taas-i40e-sysfs filters # This file should be owned by (and only-writeable by) the root user [Filters] # This is needed to allow taas to insert/remove vlan id to the # target vf under /sys/class/net/[device-name]/device/sriov/[vf-index]/[mirror] i40e_sysfs_command: RegExpFilter, i40e_sysfs_command, root, i40e_sysfs_command, (?!.*\.\..*|.*\/.*).*, [0-9]+, (vlan|egress|ingress)_mirror, (?i)(add|rem), .* ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/etc/oslo-config-generator/0000775000175000017500000000000000000000000022065 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/etc/oslo-config-generator/taas_agent.ini0000664000175000017500000000014200000000000024671 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/taas_agent.ini.sample wrap_width = 79 namespace = neutron.taas.agent ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/etc/oslo-config-generator/taas_plugin.ini0000664000175000017500000000013500000000000025073 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/taas_plugin.ini.sample wrap_width = 79 namespace = neutron.taas ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/etc/policy-generator.conf0000664000175000017500000000012400000000000022011 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/neutron/policy.yaml.sample namespace = tap-as-a-service ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/neutron_taas/0000775000175000017500000000000000000000000017611 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/__init__.py0000664000175000017500000000000000000000000021710 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/_i18n.py0000664000175000017500000000200600000000000021077 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 = 'neutron_taas' _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary # The contextual translation function using the name "_C" _C = _translators.contextual_form # The plural translation function using the name "_P" _P = _translators.plural_form def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/neutron_taas/common/0000775000175000017500000000000000000000000021101 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/common/__init__.py0000664000175000017500000000000000000000000023200 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/common/config.py0000664000175000017500000000244300000000000022723 0ustar00zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg from neutron_taas._i18n import _ taas_quota_opts = [ cfg.IntOpt('quota_tap_service', default=1, help=_('Number of Tap Service instances allowed per tenant')), cfg.IntOpt('quota_tap_flow', default=10, help=_('Number of Tap flows allowed per tenant')) ] taas_opts = [ cfg.IntOpt( 'vlan_range_start', default=3900, help=_("Starting range of TAAS VLAN IDs")), cfg.IntOpt( 'vlan_range_end', default=4000, help=_("End range of TAAS VLAN IDs")), ] def register(): cfg.CONF.register_opts(taas_quota_opts, 'QUOTAS') cfg.CONF.register_opts(taas_opts, 'taas') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/common/constants.py0000664000175000017500000000132200000000000023465 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # Copyright (C) 2015 Midokura SARL. # 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. TAAS = 'TAAS' # Complete VLAN Id Range VLAN_RANGE = '0-4095' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/common/topics.py0000664000175000017500000000135300000000000022756 0ustar00zuulzuul00000000000000# Copyright (C) 2015 Midokura SARL. # 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. # TODO(yamamoto): Move these to neutron.common.topics TAAS_PLUGIN = 'n-taas-plugin' TAAS_AGENT = 'n-taas_agent' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/common/utils.py0000664000175000017500000000362100000000000022615 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def get_list_from_ranges_str(ranges_str): """Convert the range in string format to ranges list And yield the merged ranges in order. The argument must be a string having comma separated vlan and vlan-ranges. get_list_from_ranges_str("4,6,10-13,25-27,100-103") [4, 6, 10, 11, 12, 13, 25, 26, 27, 100, 101, 102, 103] """ return sum(((list(range(*[int(range_start) + range_index for range_index, range_start in enumerate(range_item.split('-'))])) if '-' in range_item else [int(range_item)]) for range_item in ranges_str.split(',')), []) def get_ranges_str_from_list(ranges): """Convert the ranges list to string format And yield the merged ranges in order in string format. The argument must be an iterable of pairs (start, stop). get_ranges_str_from_list([4, 11, 12, 13, 25, 26, 27, 101, 102, 103]) "4,11-13,25-27,101-103" """ ranges_str = [] for val in sorted(ranges): if not ranges_str or ranges_str[-1][-1] + 1 != val: ranges_str.append([val]) else: ranges_str[-1].append(val) return ",".join([str(range_item[0]) if len(range_item) == 1 else str(range_item[0]) + "-" + str(range_item[-1]) for range_item in ranges_str]) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8352287 tap_as_a_service-15.0.0/neutron_taas/db/0000775000175000017500000000000000000000000020176 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/__init__.py0000664000175000017500000000000000000000000022275 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/head.py0000664000175000017500000000136700000000000021460 0ustar00zuulzuul00000000000000# Copyright 2016 VMware, Inc # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.db.migration.models import head from neutron_taas.db import taas_db # noqa def get_metadata(): return head.model_base.BASEV2.metadata ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/0000775000175000017500000000000000000000000022167 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/__init__.py0000664000175000017500000000000000000000000024266 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/0000775000175000017500000000000000000000000025634 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/README0000664000175000017500000000004600000000000026514 0ustar00zuulzuul00000000000000Generic single-database configuration.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/__init__.py0000664000175000017500000000000000000000000027733 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/env.py0000664000175000017500000000460600000000000027004 0ustar00zuulzuul00000000000000# Copyright 2015 Midokura SARL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 neutron_lib.db import model_base from alembic import context from oslo_config import cfg from oslo_db.sqlalchemy import session import sqlalchemy as sa from sqlalchemy import event MYSQL_ENGINE = None TAAS_VERSION_TABLE = 'alembic_version_taas' 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'] = TAAS_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=TAAS_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=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/script.py.mako0000664000175000017500000000063700000000000030446 0ustar00zuulzuul00000000000000"""${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=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/0000775000175000017500000000000000000000000027504 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8152287 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/2025.1/0000775000175000017500000000000000000000000030233 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/2025.1/expand/0000775000175000017500000000000000000000000031512 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000023500000000000011455 xustar0000000000000000135 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/2025.1/expand/f8f1f10ebaf9_mirroring_for_taas.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/2025.1/expand/f8f1f10eb0000664000175000017500000000336200000000000033023 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 alembic import op from neutron_lib.db import constants as db_const import sqlalchemy as sa # ERSPAN or GRE mirroring for taas # Revision ID: f8f1f10ebaf9 # Revises: ccbcc559d175 # Create Date: 2023-05-05 11:59:10.007052 # revision identifiers, used by Alembic. revision = 'f8f1f10ebaf9' down_revision = 'ccbcc559d175' mirror_type_enum = sa.Enum('erspanv1', 'gre', name='tapmirrors_type') def upgrade(): op.create_table( 'tap_mirrors', sa.Column('id', sa.String(length=db_const.UUID_FIELD_SIZE), primary_key=True), sa.Column('project_id', sa.String( length=db_const.PROJECT_ID_FIELD_SIZE), nullable=True), sa.Column('name', sa.String(length=db_const.NAME_FIELD_SIZE), nullable=True), sa.Column('description', sa.String( length=db_const.DESCRIPTION_FIELD_SIZE), nullable=True), sa.Column('port_id', sa.String(db_const.UUID_FIELD_SIZE), nullable=False), sa.Column('directions', sa.String(255), nullable=False), sa.Column('remote_ip', sa.String(db_const.IP_ADDR_FIELD_SIZE)), sa.Column('mirror_type', mirror_type_enum, nullable=False) ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/CONTRACT_HEAD0000664000175000017500000000001500000000000031421 0ustar00zuulzuul00000000000000bac61f603e39 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/EXPAND_HEAD0000664000175000017500000000001500000000000031163 0ustar00zuulzuul00000000000000f8f1f10ebaf9 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8152287 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/0000775000175000017500000000000000000000000031016 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/0000775000175000017500000000000000000000000032633 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000025700000000000011461 xustar0000000000000000153 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/1817af933379_remove_network_id_from_tap_service.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/1817af90000664000175000017500000000167300000000000033565 0ustar00zuulzuul00000000000000# Copyright (c) 2016 Midokura SARL # 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 """Remove network-id from tap-service Revision ID: 1817af933379 Revises: 80c85b675b6e Create Date: 2016-04-05 21:59:28.829793 """ # revision identifiers, used by Alembic. revision = '1817af933379' down_revision = '80c85b675b6e' def upgrade(): op.drop_column('tap_services', 'network_id') ././@PaxHeader0000000000000000000000000000027500000000000011461 xustar0000000000000000167 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/2ecce0368a62_add_foreign_key_constraint_on_tap_id_association.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/2ecce030000664000175000017500000000211300000000000033677 0ustar00zuulzuul00000000000000# Copyright 2016 Midokura SARL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 """add foreign key constraint on tap id association Revision ID: 2ecce0368a62 Revises: 1817af933379 Create Date: 2016-05-19 11:39:52.892610 """ # revision identifiers, used by Alembic. revision = '2ecce0368a62' down_revision = '1817af933379' def upgrade(): op.create_foreign_key( constraint_name=None, source_table='tap_id_associations', referent_table='tap_services', local_cols=['tap_service_id'], remote_cols=['id'], ondelete='CASCADE') ././@PaxHeader0000000000000000000000000000024500000000000011456 xustar0000000000000000143 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/4086b3cffc01_rename_tenant_to_project.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/4086b3c0000664000175000017500000000547000000000000033555 0ustar00zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from alembic import op import sqlalchemy as sa from sqlalchemy.engine import reflection from neutron.db import migration """rename tenant to project Revision ID: 4086b3cffc01 Revises: 2ecce0368a62 Create Date: 2016-07-30 22:09:16.372917 """ # revision identifiers, used by Alembic. revision = '4086b3cffc01' down_revision = '2ecce0368a62' _INSPECTOR = None # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.NEWTON, migration.OCATA] def get_inspector(): """Reuse inspector.""" global _INSPECTOR if _INSPECTOR: return _INSPECTOR else: bind = op.get_bind() _INSPECTOR = reflection.Inspector.from_engine(bind) return _INSPECTOR def get_tables(): """Returns hardcoded list of tables which have ``tenant_id`` column. The list is hard-coded to match the state of the schema when this upgrade script is run. """ tables = [ 'tap_services', 'tap_flows', ] 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 upgrade(): data = get_data() for table, column in data: alter_column(table, column) 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() } ././@PaxHeader0000000000000000000000000000026100000000000011454 xustar0000000000000000155 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/80c85b675b6e_initial_newton_no_op_contract_script.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/contract/80c85b60000664000175000017500000000167300000000000033564 0ustar00zuulzuul00000000000000# Copyright 2016 VMware, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.db.migration import cli """initial Newton no op contract script Revision ID: 80c85b675b6e Revises: start_neutron_taas Create Date: 2016-05-06 04:58:04.510568 """ # revision identifiers, used by Alembic. revision = '80c85b675b6e' down_revision = 'start_neutron_taas' branch_labels = (cli.CONTRACT_BRANCH,) def upgrade(): pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/expand/0000775000175000017500000000000000000000000032275 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000025500000000000011457 xustar0000000000000000151 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/expand/04625466c6fa_initial_newton_no_op_expand_script.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/expand/04625466c0000664000175000017500000000166700000000000033315 0ustar00zuulzuul00000000000000# Copyright 2016 VMware, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.db.migration import cli """initial Newton no op expand script Revision ID: 04625466c6fa Revises: start_neutron_taas Create Date: 2016-05-06 05:17:30.172181 """ # revision identifiers, used by Alembic. revision = '04625466c6fa' down_revision = 'start_neutron_taas' branch_labels = (cli.EXPAND_BRANCH,) def upgrade(): pass ././@PaxHeader0000000000000000000000000000022500000000000011454 xustar0000000000000000127 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/expand/fddbdec8711a_add_status.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/newton/expand/fddbdec870000664000175000017500000000301100000000000033745 0ustar00zuulzuul00000000000000# Copyright 2016 FUJITSU LABORATORIES LTD. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from neutron.db import migration from neutron_lib import constants import sqlalchemy as sa """add status Revision ID: fddbdec8711a Revises: 04625466c6fa Create Date: 2016-06-06 10:54:42.252898 """ # revision identifiers, used by Alembic. revision = 'fddbdec8711a' down_revision = '04625466c6fa' # milestone identifier, used by neutron-db-manage neutron_milestone = [ migration.NEWTON, migration.OCATA, migration.PIKE, migration.QUEENS, migration.ROCKY, ] def upgrade(): op.add_column('tap_services', sa.Column('status', sa.String(16), server_default=constants.ACTIVE, nullable=False)) op.add_column('tap_flows', sa.Column('status', sa.String(16), server_default=constants.ACTIVE, nullable=False)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8192286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/pike/0000775000175000017500000000000000000000000030434 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/pike/contract/0000775000175000017500000000000000000000000032251 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000027400000000000011460 xustar0000000000000000166 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/pike/contract/bac61f603e39_alter_tap_id_associations_to_support_tap_id_reuse.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/pike/contract/bac61f6030000664000175000017500000000356300000000000033476 0ustar00zuulzuul00000000000000# Copyright 2016-17 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 from sqlalchemy.engine import reflection import sqlalchemy as sa from neutron.db import migration """Alter TapIdAssociations to support tap id reuse Revision ID: bac61f603e39 Revises: 4086b3cffc01 Create Date: 2016-07-27 09:31:54.200165 """ # revision identifiers, used by Alembic. revision = 'bac61f603e39' down_revision = '4086b3cffc01' # milestone identifier, used by neutron-db-manage neutron_milestone = [ migration.PIKE, migration.QUEENS, migration.ROCKY, migration.STEIN, ] TABLE_NAME = 'tap_id_associations' def upgrade(): inspector = reflection.Inspector.from_engine(op.get_bind()) fk_constraints = inspector.get_foreign_keys(TABLE_NAME) for fk in fk_constraints: op.drop_constraint(fk['name'], TABLE_NAME, type_='foreignkey') op.create_foreign_key('fk_tap_id_assoc_tap_service', TABLE_NAME, 'tap_services', ['tap_service_id'], ['id'], ondelete='SET NULL') op.alter_column(TABLE_NAME, 'taas_id', autoincrement=False, existing_type=sa.INTEGER, nullable=False) op.alter_column(TABLE_NAME, 'tap_service_id', existing_type=sa.String(36), nullable=True) op.create_unique_constraint('unique_taas_id', TABLE_NAME, ['taas_id']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/start_neutron_taas.py0000664000175000017500000000157300000000000034003 0ustar00zuulzuul00000000000000# Copyright 2015 Midokura SARL # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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_taas.db.migration import taas_init_ops """start neutron-taas chain Revision ID: start_neutron_taas Revises: None Create Date: 2015-11-11 02:36:00.209301 """ # revision identifiers, used by Alembic. revision = 'start_neutron_taas' down_revision = None def upgrade(): taas_init_ops.upgrade() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8192286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/stein/0000775000175000017500000000000000000000000030626 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8392286 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/stein/expand/0000775000175000017500000000000000000000000032105 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000024500000000000011456 xustar0000000000000000143 path=tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/stein/expand/ccbcc559d175_add_vlan_filter_to_tap_flow.py 22 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/alembic_migration/versions/stein/expand/ccbcc559d10000664000175000017500000000222100000000000033552 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 neutron.db import migration """add_vlan_filter_to_tap_flow Revision ID: ccbcc559d175 Revises: fddbdec8711a Create Date: 2018-09-18 19:33:32.119458 """ # revision identifiers, used by Alembic. revision = 'ccbcc559d175' down_revision = 'fddbdec8711a' # milestone identifier, used by neutron-db-manage neutron_milestone = [migration.STEIN] TABLE_NAME = 'tap_flows' def upgrade(): op.add_column(TABLE_NAME, sa.Column('vlan_filter', sa.String(1024), nullable=True)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/migration/taas_init_ops.py0000664000175000017500000000417400000000000025403 0ustar00zuulzuul00000000000000# Copyright 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Initial schema operations for Tap-as-a-Service service plugin from alembic import op import sqlalchemy as sa direction_types = sa.Enum('IN', 'OUT', 'BOTH', name='tapflows_direction') def upgrade(): op.create_table( 'tap_services', sa.Column('id', sa.String(length=36), primary_key=True, nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=True), sa.Column('name', sa.String(length=255), nullable=True), sa.Column('description', sa.String(length=1024), nullable=True), sa.Column('port_id', sa.String(36), nullable=False), sa.Column('network_id', sa.String(36), nullable=True)) op.create_table( 'tap_flows', sa.Column('id', sa.String(length=36), primary_key=True, nullable=False), sa.Column('tenant_id', sa.String(length=255), nullable=True), sa.Column('name', sa.String(length=255), nullable=True), sa.Column('description', sa.String(length=1024), nullable=True), sa.Column('tap_service_id', sa.String(length=36), sa.ForeignKey("tap_services.id", ondelete="CASCADE"), nullable=False), sa.Column('source_port', sa.String(length=36), nullable=False), sa.Column('direction', direction_types, nullable=False)) op.create_table( 'tap_id_associations', sa.Column('tap_service_id', sa.String(length=36)), sa.Column('taas_id', sa.INTEGER, primary_key=True, autoincrement=True)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/taas_db.py0000664000175000017500000003065300000000000022154 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.orm import exc from neutron.db.models import segment from neutron_lib import constants from neutron_lib.db import api as db_api from neutron_lib.db import model_base from neutron_lib.db import model_query from neutron_lib.db import utils as db_utils from neutron_lib.exceptions import taas as taas_exc from neutron_lib.plugins import directory from oslo_config import cfg from oslo_log import log as logging from oslo_utils import uuidutils from neutron_taas.extensions import taas as taas_extension LOG = logging.getLogger(__name__) class TapService(model_base.BASEV2, model_base.HasId, model_base.HasProjectNoIndex): # Represents a V2 TapService Object __tablename__ = 'tap_services' name = sa.Column(sa.String(255), nullable=True) description = sa.Column(sa.String(1024), nullable=True) port_id = sa.Column(sa.String(36), nullable=False) status = sa.Column(sa.String(16), nullable=False, server_default=constants.ACTIVE) class TapFlow(model_base.BASEV2, model_base.HasId, model_base.HasProjectNoIndex): # Represents a V2 TapFlow Object __tablename__ = 'tap_flows' name = sa.Column(sa.String(255), nullable=True) description = sa.Column(sa.String(1024), nullable=True) tap_service_id = sa.Column(sa.String(36), sa.ForeignKey("tap_services.id", ondelete="CASCADE"), nullable=False) source_port = sa.Column(sa.String(36), nullable=False) direction = sa.Column(sa.Enum('IN', 'OUT', 'BOTH', name='tapflows_direction'), nullable=False) status = sa.Column(sa.String(16), nullable=False, server_default=constants.ACTIVE) vlan_filter = sa.Column(sa.String(1024), nullable=True) class TapIdAssociation(model_base.BASEV2): # Internal mapping between a TAP Service and # id to be used by the Agents __tablename__ = 'tap_id_associations' tap_service_id = sa.Column(sa.String(36), sa.ForeignKey("tap_services.id", ondelete='SET NULL'), nullable=True) taas_id = sa.Column(sa.Integer, primary_key=True, unique=True) tap_service = orm.relationship( TapService, backref=orm.backref("tap_service_id", lazy="joined"), primaryjoin='TapService.id==TapIdAssociation.tap_service_id') class Taas_db_Mixin(taas_extension.TaasPluginBase): def _core_plugin(self): return directory.get_plugin() @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def _get_tap_service(self, context, id): try: return model_query.get_by_id(context, TapService, id) except exc.NoResultFound: raise taas_exc.TapServiceNotFound(tap_id=id) @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def _get_tap_id_association(self, context, tap_service_id): try: query = model_query.query_with_hooks(context, TapIdAssociation) return query.filter(TapIdAssociation.tap_service_id == tap_service_id).one() except exc.NoResultFound: raise taas_exc.TapServiceNotFound(tap_id=tap_service_id) @db_api.CONTEXT_READER def _get_tap_flow(self, context, id): try: return model_query.get_by_id(context, TapFlow, id) except exc.NoResultFound: raise taas_exc.TapFlowNotFound(flow_id=id) def _make_tap_service_dict(self, tap_service, fields=None): res = {'id': tap_service['id'], 'tenant_id': tap_service['tenant_id'], 'name': tap_service['name'], 'description': tap_service['description'], 'port_id': tap_service['port_id'], 'status': tap_service['status']} return db_utils.resource_fields(res, fields) def _make_tap_id_association_dict(self, tap_id_association): res = {'tap_service_id': tap_id_association['tap_service_id'], 'taas_id': tap_id_association['taas_id']} return res def _make_tap_flow_dict(self, tap_flow, fields=None): res = {'id': tap_flow['id'], 'tenant_id': tap_flow['tenant_id'], 'tap_service_id': tap_flow['tap_service_id'], 'name': tap_flow['name'], 'description': tap_flow['description'], 'source_port': tap_flow['source_port'], 'direction': tap_flow['direction'], 'status': tap_flow['status'], 'vlan_filter': tap_flow['vlan_filter']} return db_utils.resource_fields(res, fields) @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def get_port_network_data(self, context, port): ns = context.session.query( segment.NetworkSegment.network_id, segment.NetworkSegment.physical_network, segment.NetworkSegment.network_type ).filter( segment.NetworkSegment.network_id == port['network_id'] ).first() if not ns: LOG.debug("No Network Segment found for network_id %s", port['network_id']) return ns_data = { 'physical_network': ns[1], 'network_type': ns[2] } return ns_data @db_api.retry_if_session_inactive() @db_api.CONTEXT_WRITER def create_tap_service(self, context, tap_service): LOG.debug("create_tap_service() called") t_s = tap_service['tap_service'] tenant_id = t_s['tenant_id'] tap_service_db = TapService( id=uuidutils.generate_uuid(), tenant_id=tenant_id, name=t_s['name'], description=t_s['description'], port_id=t_s['port_id'], status=constants.DOWN, ) context.session.add(tap_service_db) return self._make_tap_service_dict(tap_service_db) def _rebuild_taas_id_allocation_range(self, context): query = context.session.query( TapIdAssociation).all() allocate_taas_id_list = [_q.taas_id for _q in query] first_taas_id = cfg.CONF.taas.vlan_range_start # Exclude range end last_taas_id = cfg.CONF.taas.vlan_range_end all_taas_id_set = set(range(first_taas_id, last_taas_id)) vaild_taas_id_set = all_taas_id_set - set(allocate_taas_id_list) for _id in vaild_taas_id_set: # new taas id context.session.add(TapIdAssociation( taas_id=_id)) def _allocate_taas_id_with_tap_service_id(self, context, tap_service_id): query = context.session.query(TapIdAssociation).filter_by( tap_service_id=None).first() if not query: self._rebuild_taas_id_allocation_range(context) # try again query = context.session.query(TapIdAssociation).filter_by( tap_service_id=None).first() if query: query.update({"tap_service_id": tap_service_id}) return query # not found raise taas_exc.TapServiceLimitReached() @db_api.retry_if_session_inactive() @db_api.CONTEXT_WRITER def create_tap_id_association(self, context, tap_service_id): LOG.debug("create_tap_id_association() called") # create the TapIdAssociation object # allocate Taas id. # if conflict happened, it will raise db.DBDuplicateEntry. # this will be retry request again in neutron controller framework. # so we just make sure TapIdAssociation field taas_id is unique tap_id_association_db = self._allocate_taas_id_with_tap_service_id( context, tap_service_id) return self._make_tap_id_association_dict(tap_id_association_db) @db_api.retry_if_session_inactive() @db_api.CONTEXT_WRITER def create_tap_flow(self, context, tap_flow): LOG.debug("create_tap_flow() called") t_f = tap_flow['tap_flow'] tenant_id = t_f['tenant_id'] # TODO(Vinay): Check for the tenant_id validation # TODO(Vinay): Check for the source port validation tap_flow_db = TapFlow( id=uuidutils.generate_uuid(), tenant_id=tenant_id, name=t_f['name'], description=t_f['description'], tap_service_id=t_f['tap_service_id'], source_port=t_f['source_port'], direction=t_f['direction'], status=constants.DOWN, vlan_filter=t_f['vlan_filter'], ) context.session.add(tap_flow_db) return self._make_tap_flow_dict(tap_flow_db) @db_api.retry_if_session_inactive() @db_api.CONTEXT_WRITER def delete_tap_service(self, context, id): LOG.debug("delete_tap_service() called") count = context.session.query(TapService).filter_by(id=id).delete() if not count: raise taas_exc.TapServiceNotFound(tap_id=id) @db_api.CONTEXT_WRITER def delete_tap_flow(self, context, id): LOG.debug("delete_tap_flow() called") count = context.session.query(TapFlow).filter_by(id=id).delete() if not count: raise taas_exc.TapFlowNotFound(flow_id=id) def get_tap_service(self, context, id, fields=None): LOG.debug("get_tap_service() called") t_s = self._get_tap_service(context, id) return self._make_tap_service_dict(t_s, fields) def get_tap_id_association(self, context, tap_service_id): LOG.debug("get_tap_id_association() called") t_a = self._get_tap_id_association(context, tap_service_id) return self._make_tap_id_association_dict(t_a) @db_api.CONTEXT_READER def get_tap_flow(self, context, id, fields=None): LOG.debug("get_tap_flow() called") t_f = self._get_tap_flow(context, id) return self._make_tap_flow_dict(t_f, fields) @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def get_tap_services(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): LOG.debug("get_tap_services() called") return model_query.get_collection(context, TapService, self._make_tap_service_dict, filters=filters, fields=fields) @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def get_tap_flows(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): LOG.debug("get_tap_flows() called") return model_query.get_collection(context, TapFlow, self._make_tap_flow_dict, filters=filters, fields=fields) @db_api.retry_if_session_inactive() @db_api.CONTEXT_READER def get_port_details(self, context, port_id): port = self._core_plugin().get_port(context, port_id) return port @db_api.retry_if_session_inactive() @db_api.CONTEXT_WRITER def update_tap_service(self, context, id, tap_service): LOG.debug("update_tap_service() called") t_s = tap_service['tap_service'] tap_service_db = self._get_tap_service(context, id) tap_service_db.update(t_s) return self._make_tap_service_dict(tap_service_db) @db_api.retry_if_session_inactive() @db_api.CONTEXT_WRITER def update_tap_flow(self, context, id, tap_flow): LOG.debug("update_tap_flow() called") t_f = tap_flow['tap_flow'] tap_flow_db = self._get_tap_flow(context, id) tap_flow_db.update(t_f) return self._make_tap_flow_dict(tap_flow_db) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/db/tap_mirror_db.py0000664000175000017500000001307000000000000023374 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 sqlalchemy as sa from sqlalchemy.orm import exc from neutron_lib.api.definitions import tap_mirror as mirror_extension 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 utils as db_utils from neutron_lib.exceptions import taas as taas_exc from neutron_lib.plugins import directory from oslo_log import helpers as log_helpers from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import uuidutils from neutron_taas.extensions import tap_mirror as tap_m_extension LOG = logging.getLogger(__name__) class TapMirror(model_base.BASEV2, model_base.HasId, model_base.HasProjectNoIndex): """Represents a Tap Mirror A Tap Mirror can be a GRE or ERSPAN tunnel representation. """ __tablename__ = 'tap_mirrors' name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE)) port_id = sa.Column(sa.String(db_const.UUID_FIELD_SIZE), nullable=False) directions = sa.Column(sa.String(255), nullable=False) remote_ip = sa.Column(sa.String(db_const.IP_ADDR_FIELD_SIZE), nullable=False) mirror_type = sa.Column(sa.Enum('erspanv1', 'gre', name='tapmirrors_type'), nullable=False) api_collections = [mirror_extension.COLLECTION_NAME] collection_resource_map = { mirror_extension.COLLECTION_NAME: mirror_extension.RESOURCE_NAME} class Taas_mirror_db_mixin(tap_m_extension.TapMirrorBase): def _make_tap_mirror_dict(self, tap_mirror, fields=None): res = { 'id': tap_mirror.get('id'), 'project_id': tap_mirror.get('project_id'), 'name': tap_mirror.get('name'), 'description': tap_mirror.get('description'), 'port_id': tap_mirror.get('port_id'), 'directions': jsonutils.loads(tap_mirror.get('directions')), 'remote_ip': tap_mirror.get('remote_ip'), 'mirror_type': tap_mirror.get('mirror_type'), } return db_utils.resource_fields(res, fields) @db_api.retry_if_session_inactive() def get_port_details(self, context, port_id): with db_api.CONTEXT_READER.using(context): core_plugin = directory.get_plugin() return core_plugin.get_port(context, port_id) @db_api.retry_if_session_inactive() @log_helpers.log_method_call def create_tap_mirror(self, context, tap_mirror): fields = tap_mirror['tap_mirror'] project_id = fields.get('project_id') with db_api.CONTEXT_WRITER.using(context): tap_mirror_db = TapMirror( id=uuidutils.generate_uuid(), project_id=project_id, name=fields.get('name'), description=fields.get('description'), port_id=fields.get('port_id'), directions=jsonutils.dumps(fields.get('directions')), remote_ip=fields.get('remote_ip'), mirror_type=fields.get('mirror_type'), ) # TODO(lajoskatona): Check tunnel_id... context.session.add(tap_mirror_db) return self._make_tap_mirror_dict(tap_mirror_db) def _get_tap_mirror(self, context, id): with db_api.CONTEXT_READER.using(context): try: return model_query.get_by_id(context, TapMirror, id) except exc.NoResultFound: raise taas_exc.TapMirrorNotFound(mirror_id=id) @log_helpers.log_method_call def get_tap_mirror(self, context, id, fields=None): with db_api.CONTEXT_READER.using(context): t_m = self._get_tap_mirror(context, id) return self._make_tap_mirror_dict(t_m, fields) @db_api.retry_if_session_inactive() @log_helpers.log_method_call def get_tap_mirrors(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): with db_api.CONTEXT_READER.using(context): return model_query.get_collection(context, TapMirror, self._make_tap_mirror_dict, filters=filters, fields=fields) @log_helpers.log_method_call def delete_tap_mirror(self, context, id): with db_api.CONTEXT_WRITER.using(context): count = context.session.query(TapMirror).filter_by(id=id).delete() if not count: raise taas_exc.TapMirrorNotFound(mirror_id=id) @db_api.retry_if_session_inactive() @log_helpers.log_method_call def update_tap_mirror(self, context, id, tap_mirror): t_m = tap_mirror['tap_mirror'] with db_api.CONTEXT_WRITER.using(context): tap_mirror_db = self._get_tap_mirror(context, id) tap_mirror_db.update(t_m) return self._make_tap_mirror_dict(tap_mirror_db) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/extensions/0000775000175000017500000000000000000000000022010 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/extensions/__init__.py0000664000175000017500000000000000000000000024107 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/extensions/taas.py0000664000175000017500000000623000000000000023313 0ustar00zuulzuul00000000000000# Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc from neutron_lib.api.definitions import taas as taas_api_def from neutron_lib.api import extensions from neutron_lib.services import base as service_base from neutron.api.v2 import resource_helper from neutron_taas.common import config config.register() class Taas(extensions.APIExtensionDescriptor): api_definition = taas_api_def @classmethod def get_resources(cls): """Returns Ext Resources.""" plural_mappings = resource_helper.build_plural_mappings( {}, taas_api_def.RESOURCE_ATTRIBUTE_MAP) resources = resource_helper.build_resource_info( plural_mappings, taas_api_def.RESOURCE_ATTRIBUTE_MAP, taas_api_def.ALIAS, translate_name=False, allow_bulk=True) return resources @classmethod def get_plugin_interface(cls): return TaasPluginBase class TaasPluginBase(service_base.ServicePluginBase, metaclass=abc.ABCMeta): def get_plugin_description(self): return taas_api_def.DESCRIPTION @classmethod def get_plugin_type(cls): return taas_api_def.ALIAS @abc.abstractmethod def create_tap_service(self, context, tap_service): """Create a Tap Service.""" pass @abc.abstractmethod def delete_tap_service(self, context, id): """Delete a Tap Service.""" pass @abc.abstractmethod def get_tap_service(self, context, id, fields=None): """Get a Tap Service.""" pass @abc.abstractmethod def get_tap_services(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): """List all Tap Services.""" pass @abc.abstractmethod def update_tap_service(self, context, id, tap_service): """Update a Tap Service.""" pass @abc.abstractmethod def create_tap_flow(self, context, tap_flow): """Create a Tap Flow.""" pass @abc.abstractmethod def get_tap_flow(self, context, id, fields=None): """Get a Tap Flow.""" pass @abc.abstractmethod def delete_tap_flow(self, context, id): """Delete a Tap Flow.""" pass @abc.abstractmethod def get_tap_flows(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): """List all Tap Flows.""" pass @abc.abstractmethod def update_tap_flow(self, context, id, tap_flow): """Update a Tap Flow.""" pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/extensions/tap_mirror.py0000664000175000017500000000454700000000000024552 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 abc from neutron_lib.api.definitions import tap_mirror as tap_mirror_api_def from neutron_lib.api import extensions as api_extensions from neutron_lib.services import base as service_base from neutron.api.v2 import resource_helper class Tap_mirror(api_extensions.APIExtensionDescriptor): api_definition = tap_mirror_api_def @classmethod def get_resources(cls): plural_mappings = resource_helper.build_plural_mappings( {}, tap_mirror_api_def.RESOURCE_ATTRIBUTE_MAP) resources = resource_helper.build_resource_info( plural_mappings, tap_mirror_api_def.RESOURCE_ATTRIBUTE_MAP, tap_mirror_api_def.ALIAS, translate_name=False, allow_bulk=False) return resources @classmethod def get_plugin_interface(cls): return TapMirrorBase class TapMirrorBase(service_base.ServicePluginBase, metaclass=abc.ABCMeta): def get_plugin_description(self): return tap_mirror_api_def.DESCRIPTION @classmethod def get_plugin_type(cls): return tap_mirror_api_def.ALIAS @abc.abstractmethod def create_tap_mirror(self, context, tap_mirror): """Create a Tap Mirror.""" pass @abc.abstractmethod def get_tap_mirror(self, context, id, fields=None): """Get a Tap Mirror.""" pass @abc.abstractmethod def get_tap_mirrors(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): """List all Tap Mirrors.""" pass @abc.abstractmethod def delete_tap_mirror(self, context, id): """Delete a Tap Mirror.""" pass @abc.abstractmethod def update_tap_mirror(self, context, id, tap_mirror): """Update a Tap Mirror.""" pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/extensions/vlan_filter.py0000664000175000017500000000147700000000000024700 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 vlan_filter from neutron_lib.api import extensions as api_extensions class Vlan_filter(api_extensions.APIExtensionDescriptor): """Extension class supporting taas VLAN filtering.""" api_definition = vlan_filter ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/opts.py0000664000175000017500000000206100000000000021147 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import neutron.services.provider_configuration import neutron_taas.common.config import neutron_taas.services.taas.agents.extensions.taas def list_agent_opts(): return [ ('DEFAULT', neutron_taas.services.taas.agents.extensions.taas.OPTS) ] def list_opts(): return [ ('service_providers', neutron.services.provider_configuration.serviceprovider_opts), ('quotas', neutron_taas.common.config.taas_quota_opts), ('taas', neutron_taas.common.config.taas_opts) ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/policies/0000775000175000017500000000000000000000000021420 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/policies/__init__.py0000664000175000017500000000155500000000000023537 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools from neutron_taas.policies import tap_flow from neutron_taas.policies import tap_mirror from neutron_taas.policies import tap_service def list_rules(): return itertools.chain( tap_flow.list_rules(), tap_service.list_rules(), tap_mirror.list_rules(), ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/policies/tap_flow.py0000664000175000017500000000324600000000000023612 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_policy import policy from neutron_lib.policy import RULE_ADMIN_OR_OWNER rules = [ policy.DocumentedRuleDefault( 'create_tap_flow', RULE_ADMIN_OR_OWNER, 'Create a tap flow', [ { 'method': 'POST', 'path': '/taas/tap_flows', } ] ), policy.DocumentedRuleDefault( 'update_tap_flow', RULE_ADMIN_OR_OWNER, 'Update a tap flow', [ { 'method': 'PUT', 'path': '/taas/tap_flows/{id}', } ] ), policy.DocumentedRuleDefault( 'get_tap_flow', RULE_ADMIN_OR_OWNER, 'Show a tap flow', [ { 'method': 'GET', 'path': '/taas/tap_flows/{id}', } ] ), policy.DocumentedRuleDefault( 'delete_tap_flow', RULE_ADMIN_OR_OWNER, 'Delete a tap flow', [ { 'method': 'DELETE', 'path': '/taas/tap_flows/{id}', } ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/policies/tap_mirror.py0000664000175000017500000000420400000000000024150 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_policy import policy from neutron.conf.policies import base COLLECTION_PATH = '/taas/tap_mirrors' RESOURCE_PATH = '/taas/tap_mirrors/{id}' rules = [ policy.DocumentedRuleDefault( name='create_tap_mirror', check_str=base.ADMIN_OR_PROJECT_MEMBER, scope_types=['project'], description='Create a Tap Mirror', operations=[ { 'method': 'POST', 'path': COLLECTION_PATH } ], ), policy.DocumentedRuleDefault( name='update_tap_mirror', check_str=base.ADMIN_OR_PROJECT_MEMBER, scope_types=['project'], description='Update a Tap Mirror', operations=[ { 'method': 'PUT', 'path': RESOURCE_PATH } ], ), policy.DocumentedRuleDefault( name='get_tap_mirror', check_str=base.ADMIN_OR_PROJECT_READER, scope_types=['project'], description='Show a Tap Mirror', operations=[ { 'method': 'GET', 'path': COLLECTION_PATH }, { 'method': 'GET', 'path': RESOURCE_PATH }, ] ), policy.DocumentedRuleDefault( name='delete_tap_mirror', check_str=base.ADMIN_OR_PROJECT_MEMBER, scope_types=['project'], description='Delete a Tap Mirror', operations=[ { 'method': 'DELETE', 'path': RESOURCE_PATH, } ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/policies/tap_service.py0000664000175000017500000000331300000000000024276 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_policy import policy from neutron_lib.policy import RULE_ADMIN_OR_OWNER rules = [ policy.DocumentedRuleDefault( 'create_tap_service', RULE_ADMIN_OR_OWNER, 'Create a tap service', [ { 'method': 'POST', 'path': '/taas/tap_services', } ] ), policy.DocumentedRuleDefault( 'update_tap_service', RULE_ADMIN_OR_OWNER, 'Updates a tap service', [ { 'method': 'PUT', 'path': '/taas/tap_services/{id}', } ] ), policy.DocumentedRuleDefault( 'get_tap_service', RULE_ADMIN_OR_OWNER, 'Show a tap service', [ { 'method': 'GET', 'path': '/taas/tap_services/{id}', } ] ), policy.DocumentedRuleDefault( 'delete_tap_service', RULE_ADMIN_OR_OWNER, 'Delete a tap service', [ { 'method': 'DELETE', 'path': '/taas/tap_services/{id}', } ] ), ] def list_rules(): return rules ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/services/0000775000175000017500000000000000000000000021434 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/__init__.py0000664000175000017500000000000000000000000023533 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/services/taas/0000775000175000017500000000000000000000000022364 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/__init__.py0000664000175000017500000000000000000000000024463 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/0000775000175000017500000000000000000000000023645 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/__init__.py0000664000175000017500000000000000000000000025744 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/common/0000775000175000017500000000000000000000000025135 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/common/__init__.py0000664000175000017500000000000000000000000027234 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/common/taas_agent.py0000664000175000017500000003037300000000000027623 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import manager from neutron_taas.services.taas.drivers.linux \ import ovs_constants as taas_ovs_consts from neutron_taas.common import topics from neutron_taas.services.taas.agents import taas_agent_api as api from neutron_lib.api.definitions import portbindings from neutron_lib import constants from neutron_lib import context as neutron_context from neutron_lib import rpc as n_rpc from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging import oslo_messaging as messaging from oslo_service import service LOG = logging.getLogger(__name__) class TaasPluginApi(api.TaasPluginApiMixin): def __init__(self, topic, host): super().__init__(topic, host) target = messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) def sync_tap_resources(self, sync_tap_res, host): """Send Rpc to plugin to recreate pre-existing tap resources.""" LOG.debug("In RPC Call for Sync Tap Resources: Host=%s, MSG=%s", host, sync_tap_res) context = neutron_context.get_admin_context() cctxt = self.client.prepare(fanout=False) cctxt.cast(context, 'sync_tap_resources', sync_tap_res=sync_tap_res, host=host) def set_tap_service_status(self, msg, status, host): LOG.debug("In RPC Call for set tap service status: Host=%s, MSG=%s, " "Status=%s", host, msg, status) context = neutron_context.get_admin_context() cctxt = self.client.prepare(fanout=False) cctxt.cast(context, 'set_tap_service_status', msg=msg, status=status, host=host) def set_tap_flow_status(self, msg, status, host): LOG.debug("In RPC Call for set tap flow status: Host=%s, MSG=%s, " "Status=%s", host, msg, status) context = neutron_context.get_admin_context() cctxt = self.client.prepare(fanout=False) cctxt.cast(context, 'set_tap_flow_status', msg=msg, status=status, host=host) @log_helpers.log_method_call def set_tap_mirror_status(self, msg, status, host): LOG.debug("In RPC Call for set Tap Mirror status") # TODO(lajoskatona): We can have a status field to indicate that the # Neutron port is UP, is it useful? class TaasAgentRpcCallback(api.TaasAgentRpcCallbackMixin): def __init__(self, conf, driver_type): LOG.debug("TaaS Agent initialize called") self.conf = conf self.driver_type = driver_type super().__init__() def initialize(self): self.taas_driver = manager.NeutronManager.load_class_for_provider( 'neutron_taas.taas.agent_drivers', self.driver_type)() self.taas_driver.consume_api(self.agent_api) self.taas_driver.initialize() self.func_dict = { 'create_tap_service': { 'msg_name': 'tap_service', 'set_status_func_name': 'set_tap_service_status', 'fail_status': constants.ERROR, 'succ_status': constants.ACTIVE}, 'create_tap_flow': { 'msg_name': 'tap_flow', 'set_status_func_name': 'set_tap_flow_status', 'fail_status': constants.ERROR, 'succ_status': constants.ACTIVE}, 'delete_tap_service': { 'msg_name': 'tap_service', 'set_status_func_name': 'set_tap_service_status', 'fail_status': constants.PENDING_DELETE, 'succ_status': constants.INACTIVE}, 'delete_tap_flow': { 'msg_name': 'tap_flow', 'set_status_func_name': 'set_tap_flow_status', 'fail_status': constants.PENDING_DELETE, 'succ_status': constants.INACTIVE}, 'create_tap_mirror': { 'msg_name': 'tap_mirror', 'set_status_func_name': 'set_tap_mirror_status', 'fail_status': constants.ERROR, 'succ_status': constants.ACTIVE}, 'delete_tap_mirror': { 'msg_name': 'tap_mirror', 'set_status_func_name': 'set_tap_mirror_status', 'fail_status': constants.PENDING_DELETE, 'succ_status': constants.INACTIVE}, 'periodic_tasks': { 'msg_name': 'periodic_tasks', } } self.portbind_drivers_map = {portbindings.VNIC_DIRECT: 'sriov', portbindings.VNIC_NORMAL: 'ovs'} self._taas_rpc_setup() TaasAgentService(self).start(self.taas_plugin_rpc, self.conf.host) def consume_api(self, agent_api): self.agent_api = agent_api def _invoke_driver_for_plugin_api(self, context, args, func_name): LOG.debug("Invoking Driver for %(func_name)s from agent", {'func_name': func_name}) status_msg = {} if func_name != 'periodic_tasks': func_dict = self.func_dict[func_name] status_msg = {'id': args[func_dict['msg_name']]['id']} try: driver_func = getattr(self.taas_driver, func_name) driver_func(args) except Exception: LOG.error("Failed to invoke the driver") rpc_func = getattr( self.taas_plugin_rpc, self.func_dict[func_name]['set_status_func_name']) rpc_func(status_msg, self.func_dict[func_name]['fail_status'], self.conf.host) return if func_name != 'periodic_tasks': rpc_func = getattr( self.taas_plugin_rpc, self.func_dict[func_name]['set_status_func_name']) rpc_func(status_msg, self.func_dict[func_name]['succ_status'], self.conf.host) def create_tap_service(self, context, tap_service_msg, host): """Handle Rpc from plugin to create a tap_service.""" if not self._driver_and_host_verification( host, tap_service_msg['port']): LOG.debug("RPC Call for Create Tap Serv. Either Host value [%s]" "(received in RPC) doesn't match the host value " "stored in agent [%s], or incompatible driver type. " "Ignoring the message.", host, self.conf.host) return LOG.debug("In RPC Call for Create Tap Service: MSG=%s", tap_service_msg) return self._invoke_driver_for_plugin_api( context, tap_service_msg, 'create_tap_service') def create_tap_flow(self, context, tap_flow_msg, host): if not self._driver_and_host_verification(host, tap_flow_msg['port']): LOG.debug("RPC Call for Create Tap Flow. Either Host value [%s]" "(received in RPC) doesn't match the host value " "stored in agent [%s], or incompatible driver type. " "Ignoring the message.", host, self.conf.host) return LOG.debug("In RPC Call for Create Tap Flow: MSG=%s", tap_flow_msg) return self._invoke_driver_for_plugin_api( context, tap_flow_msg, 'create_tap_flow') def delete_tap_service(self, context, tap_service_msg, host): # # Cleanup operations must be performed by all hosts # where the source and/or destination ports associated # with this tap service were residing. # if not self._is_driver_port_type_compatible(tap_service_msg['port']): LOG.debug("RPC Call for Delete Tap Service. Incompatible driver " "type. Ignoring the message. Host=[%s]", host) return LOG.debug("In RPC Call for Delete Tap Service: MSG=%s", tap_service_msg) return self._invoke_driver_for_plugin_api( context, tap_service_msg, 'delete_tap_service') def delete_tap_flow(self, context, tap_flow_msg, host): if not self._driver_and_host_verification(host, tap_flow_msg['port']): LOG.debug("RPC Call for Delete Tap Flow. Either Host value [%s]" "(received in RPC) doesn't match the host value " "stored in agent [%s], or incompatible driver type. " "Ignoring the message.", host, self.conf.host) return LOG.debug("In RPC Call for Delete Tap Flow: MSG=%s", tap_flow_msg) return self._invoke_driver_for_plugin_api( context, tap_flow_msg, 'delete_tap_flow') @log_helpers.log_method_call def create_tap_mirror(self, context, tap_mirror_msg, host): """Handle Rpc from plugin to create a tap_mirror.""" if not self._driver_and_host_verification( host, tap_mirror_msg['port']): LOG.debug("RPC Call for Create Tap Mirror. Either Host value [%s]" "(received in RPC) doesn't match the host value " "stored in agent [%s], or incompatible driver type. " "Ignoring the message.", host, self.conf.host) return LOG.debug("In RPC Call for Creae Tap Mirror: MSG=%s", tap_mirror_msg) return self._invoke_driver_for_plugin_api( context, tap_mirror_msg, 'create_tap_mirror') @log_helpers.log_method_call def delete_tap_mirror(self, context, tap_mirror_msg, host): """....""" if not self._driver_and_host_verification(host, tap_mirror_msg['port']): LOG.debug("RPC Call for Delete Tap Mirror. Either Host value [%s]" "(received in RPC) doesn't match the host value " "stored in agent [%s], or incompatible driver type. " "Ignoring the message.", host, self.conf.host) return LOG.debug("In RPC Call for Delete Tap Mirror: MSG=%s", tap_mirror_msg) return self._invoke_driver_for_plugin_api( context, tap_mirror_msg, 'delete_tap_mirror') def _taas_rpc_setup(self): # setup RPC to msg taas plugin self.taas_plugin_rpc = TaasPluginApi( topics.TAAS_PLUGIN, self.conf.host) endpoints = [self] conn = n_rpc.Connection() conn.create_consumer(topics.TAAS_AGENT, endpoints, fanout=False) conn.consume_in_threads() def periodic_tasks(self): return self._invoke_driver_for_plugin_api( context=None, args=None, func_name='periodic_tasks') def get_driver_type(self): return self.driver_type def _is_driver_port_type_compatible(self, port): return ( port.get(portbindings.VNIC_TYPE) in self.portbind_drivers_map and self.portbind_drivers_map[port.get(portbindings.VNIC_TYPE)] == self.driver_type) def _driver_and_host_verification(self, host, port): return ((host == self.conf.host) and self._is_driver_port_type_compatible(port)) class TaasAgentService(service.Service): def __init__(self, driver): super().__init__() self.driver = driver def start(self, taas_plugin_rpc, host): super().start() if self.driver.get_driver_type() == \ taas_ovs_consts.EXTENSION_DRIVER_TYPE: self.tg.add_timer( int(cfg.CONF.taas_agent_periodic_interval), self.driver.periodic_tasks, None ) # Indicate the TaaS plugin to recreate the taas resources rpc_msg = {'host_id': host} taas_plugin_rpc.sync_tap_resources(rpc_msg, host) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/extensions/0000775000175000017500000000000000000000000026044 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/extensions/__init__.py0000664000175000017500000000000000000000000030143 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/extensions/taas.py0000664000175000017500000000515000000000000027347 0ustar00zuulzuul00000000000000# Copyright 2017 FUJITSU LABORATORIES LTD. # Copyright 2016 NEC Technologies India Pvt. Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import abc from neutron_lib.agent import l2_extension from neutron_taas.services.taas.agents.common import taas_agent from oslo_config import cfg from oslo_log import log as logging LOG = logging.getLogger(__name__) OPTS = [ cfg.IntOpt( 'taas_agent_periodic_interval', default=5, help=_('Seconds between periodic task runs') ) ] cfg.CONF.register_opts(OPTS) class TaasAgentDriver(metaclass=abc.ABCMeta): """Defines stable abstract interface for TaaS Agent Driver.""" @abc.abstractmethod def initialize(self): """Perform Taas agent driver initialization.""" def consume_api(self, agent_api): """Consume the AgentAPI instance from the TaasAgentExtension class :param agent_api: An instance of an agent specific API """ @abc.abstractmethod def create_tap_service(self, tap_service_msg): """Create a Tap Service request in driver.""" @abc.abstractmethod def create_tap_flow(self, tap_flow_msg): """Create a tap flow request in driver.""" @abc.abstractmethod def delete_tap_service(self, tap_service_msg): """delete a Tap Service request in driver.""" @abc.abstractmethod def delete_tap_flow(self, tap_flow_msg): """Delete a tap flow request in driver.""" class TaasAgentExtension(l2_extension.L2AgentExtension): def initialize(self, connection, driver_type): """Initialize agent extension.""" self.taas_agent = taas_agent.TaasAgentRpcCallback( cfg.CONF, driver_type) self.taas_agent.consume_api(self.agent_api) self.taas_agent.initialize() def consume_api(self, agent_api): """Receive neutron agent API object Allows an extension to gain access to resources internal to the neutron agent and otherwise unavailable to the extension. """ self.agent_api = agent_api def handle_port(self, context, data): pass def delete_port(self, context, data): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/agents/taas_agent_api.py0000664000175000017500000000427400000000000027165 0ustar00zuulzuul00000000000000# Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import rpc as n_rpc import oslo_messaging as messaging class TaasPluginApiMixin: # Currently there are no Calls the Agent makes towards the Plugin. def __init__(self, topic, host): self.host = host target = messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) super().__init__() class TaasAgentRpcCallbackMixin: """Mixin for Taas agent Implementations.""" def __init__(self): super().__init__() def consume_api(self, agent_api): """Receive neutron agent API object Allows an extension to gain access to resources internal to the neutron agent and otherwise unavailable to the extension. """ self.agent_api = agent_api def create_tap_service(self, context, tap_service_msg, host): """Handle RPC cast from plugin to create a tap service.""" pass def delete_tap_service(self, context, tap_service_msg, host): """Handle RPC cast from plugin to delete a tap service.""" pass def create_tap_flow(self, context, tap_flow_msg, host): """Handle RPC cast from plugin to create a tap flow""" pass def delete_tap_flow(self, context, tap_flow_msg, host): """Handle RPC cast from plugin to delete a tap flow""" pass def create_tap_mirror(self, context, tap_mirror_msg, host): """Handle RPC cast from plugin to create a Tap Mirror.""" pass def delete_tap_mirror(self, context, tap_mirror_msg, host): """Handle RPC cast from plugin to delete a Tap Mirror.""" pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8432288 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/0000775000175000017500000000000000000000000024042 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/__init__.py0000664000175000017500000000000000000000000026141 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/0000775000175000017500000000000000000000000025201 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/__init__.py0000664000175000017500000000000000000000000027300 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/ovs_constants.py0000664000175000017500000000163400000000000030462 0ustar00zuulzuul00000000000000# Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # OVS tables used by TaaS in br-tap TAAS_RECV_LOC = 1 TAAS_RECV_REM = 2 # OVS tables used by TaaS in br-tun TAAS_SEND_UCAST = 30 TAAS_SEND_FLOOD = 31 TAAS_CLASSIFY = 35 TAAS_DST_CHECK = 36 TAAS_SRC_CHECK = 37 TAAS_DST_RESPOND = 38 TAAS_SRC_RESPOND = 39 # OVS TaaS extension driver type EXTENSION_DRIVER_TYPE = 'ovs' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/ovs_taas.py0000664000175000017500000007064400000000000027405 0ustar00zuulzuul00000000000000# Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections from neutron.agent.common import ovs_lib from neutron.agent.linux import utils from neutron.conf.agent import common from neutron.conf.plugins.ml2.drivers import ovs_conf from neutron_lib.plugins.ml2 import ovs_constants as n_ovs_consts from neutron_taas.services.taas.agents.extensions import taas as taas_base import neutron_taas.services.taas.drivers.linux.ovs_constants \ as taas_ovs_consts import neutron_taas.services.taas.drivers.linux.ovs_utils as taas_ovs_utils from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging LOG = logging.getLogger(__name__) TaaS_DRIVER_NAME = 'Taas OVS driver' class OVSBridge_tap_extension(ovs_lib.OVSBridge): def __init__(self, br_name, root_helper, datapath_type=n_ovs_consts.OVS_DATAPATH_SYSTEM): super().__init__(br_name, datapath_type=datapath_type) class OvsTaasDriver(taas_base.TaasAgentDriver): def __init__(self): super().__init__() LOG.debug("Initializing Taas OVS Driver") self.agent_api = None self.root_helper = common.get_root_helper(cfg.CONF) ovs_conf.register_ovs_agent_opts(cfg.CONF) self.datapath_type = cfg.CONF.OVS.datapath_type self.tunnel_types = cfg.CONF.AGENT.tunnel_types def initialize(self): self.int_br = self.agent_api.request_int_br() self.tun_br = self.agent_api.request_tun_br() self.tap_br = OVSBridge_tap_extension('br-tap', self.root_helper, datapath_type=self.datapath_type) # Prepare OVS bridges for TaaS self.setup_ovs_bridges() # Setup key-value manager for ingress BCMC flows self.bcmc_kvm = taas_ovs_utils.key_value_mgr(4096) def periodic_tasks(self, args=None): # # Regenerate the flow in br-tun's TAAS_SEND_FLOOD table # to ensure all existing tunnel ports are included. # if self.tunnel_types: self.update_tunnel_flood_flow() def setup_ovs_bridges(self): # # br-int : Integration Bridge # br-tap : Tap Bridge # br-tun : Tunnel Bridge # # Create br-tap self.tap_br.create() # Connect br-tap to br-int and br-tun self.int_br.add_patch_port('patch-int-tap', 'patch-tap-int') self.tap_br.add_patch_port('patch-tap-int', 'patch-int-tap') if self.tunnel_types: self.tun_br.add_patch_port('patch-tun-tap', 'patch-tap-tun') self.tap_br.add_patch_port('patch-tap-tun', 'patch-tun-tap') # Get patch port IDs patch_tap_int_id = self.tap_br.get_port_ofport('patch-tap-int') if self.tunnel_types: patch_tap_tun_id = self.tap_br.get_port_ofport('patch-tap-tun') patch_tun_tap_id = self.tun_br.get_port_ofport('patch-tun-tap') # Purge all existing Taas flows from br-tap and br-tun self.tap_br.delete_flows(table=0) self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_LOC) self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_REM) if self.tunnel_types: self.tun_br.delete_flows(table=0, in_port=patch_tun_tap_id) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SEND_UCAST) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SEND_FLOOD) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_CLASSIFY) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_DST_CHECK) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SRC_CHECK) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_DST_RESPOND) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SRC_RESPOND) # # Configure standard TaaS flows in br-tun # self._setup_flows_for_tun_br(patch_tun_tap_id) # # Configure standard TaaS flows in br-tap # self.tap_br.add_flow(table=0, priority=1, in_port=patch_tap_int_id, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_RECV_LOC) if self.tunnel_types: self.tap_br.add_flow(table=0, priority=1, in_port=patch_tap_tun_id, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_RECV_REM) self.tap_br.add_flow(table=0, priority=0, actions="drop") if self.tunnel_types: self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_LOC, priority=0, actions="output:%s" % str(patch_tap_tun_id)) self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_REM, priority=0, actions="drop") def _setup_flows_for_tun_br(self, patch_tun_tap_id): # # Configure standard Taas flows in tun_br # self.tun_br.add_flow(table=0, priority=1, in_port=patch_tun_tap_id, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_SEND_UCAST) self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SEND_UCAST, priority=0, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_SEND_FLOOD) flow_action = self._create_tunnel_flood_flow_action() if flow_action != "": self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SEND_FLOOD, priority=0, actions=flow_action) self.tun_br.add_flow(table=taas_ovs_consts.TAAS_CLASSIFY, priority=2, reg0=0, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_DST_CHECK) self.tun_br.add_flow(table=taas_ovs_consts.TAAS_CLASSIFY, priority=1, reg0=1, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_DST_CHECK) self.tun_br.add_flow(table=taas_ovs_consts.TAAS_CLASSIFY, priority=1, reg0=2, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_SRC_CHECK) self.tun_br.add_flow(table=taas_ovs_consts.TAAS_DST_CHECK, priority=0, actions="drop") self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SRC_CHECK, priority=0, actions="drop") self.tun_br.add_flow(table=taas_ovs_consts.TAAS_DST_RESPOND, priority=2, reg0=0, actions="output:%s" % str(patch_tun_tap_id)) self.tun_br.add_flow( table=taas_ovs_consts.TAAS_DST_RESPOND, priority=1, reg0=1, actions=("output:%s," "move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID" "[0..11],mod_vlan_vid:2,output:in_port" % str(patch_tun_tap_id))) self.tun_br.add_flow( table=taas_ovs_consts.TAAS_SRC_RESPOND, priority=1, actions=("learn(table=%s,hard_timeout=60," "priority=1,NXM_OF_VLAN_TCI[0..11]," "load:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID" "[0..11],load:0->NXM_OF_VLAN_TCI[0..11]," "output:NXM_OF_IN_PORT[])" % taas_ovs_consts.TAAS_SEND_UCAST)) def consume_api(self, agent_api): self.agent_api = agent_api @log_helpers.log_method_call def create_tap_service(self, tap_service_msg): """Create a tap service :param tap_service_msg: a dict of the tap_service, taas_id which is the VLAN Id reserved for mirroring for this tap-service and the neutron port of the tap-service: {tap_service: {}, taas_id: VID, port: {}} """ taas_id = tap_service_msg['taas_id'] port = tap_service_msg['port'] # Get OVS port id for tap service port ovs_port = self.int_br.get_vif_port_by_id(port['id']) ovs_port_id = ovs_port.ofport # Get VLAN id for tap service port port_dict = self.int_br.get_port_tag_dict() port_vlan_id = port_dict[ovs_port.port_name] # Get patch port IDs patch_int_tap_id = self.int_br.get_port_ofport('patch-int-tap') patch_tap_int_id = self.tap_br.get_port_ofport('patch-tap-int') # Add flow(s) in br-int self.int_br.add_flow(table=0, priority=25, in_port=patch_int_tap_id, dl_vlan=taas_id, actions="mod_vlan_vid:%s,output:%s" % (str(port_vlan_id), str(ovs_port_id))) # Add flow(s) in br-tap self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_LOC, priority=1, dl_vlan=taas_id, actions="output:in_port") self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_REM, priority=1, dl_vlan=taas_id, actions="output:%s" % str(patch_tap_int_id)) # Add flow(s) in br-tun if self.tunnel_types: for tunnel_type in n_ovs_consts.TUNNEL_NETWORK_TYPES: self.tun_br.add_flow( table=n_ovs_consts.TUN_TABLE[tunnel_type], priority=1, tun_id=taas_id, actions=("move:NXM_OF_VLAN_TCI[0..11]->" "NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID" "[0..11]->NXM_OF_VLAN_TCI[0..11]," "resubmit(,%s)" % taas_ovs_consts.TAAS_CLASSIFY)) self.tun_br.add_flow(table=taas_ovs_consts.TAAS_DST_CHECK, priority=1, tun_id=taas_id, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_DST_RESPOND) # Get hybrid plug info vif_details = port.get('binding:vif_details') is_hybrid_plug = vif_details.get('ovs_hybrid_plug') if is_hybrid_plug: ovs_port_name = ovs_port.port_name linux_br_name = ovs_port_name.replace('qvo', 'qbr') utils.execute(['ip', 'link', 'set', linux_br_name, 'type', 'bridge', 'ageing_time', 0], run_as_root=True, privsep_exec=True) @log_helpers.log_method_call def delete_tap_service(self, tap_service_msg): """Delete a tap service :param tap_service_msg: a dict of the tap_service, taas_id which is the VLAN Id reserved for mirroring for this tap-service and the neutron port of the tap-service: {tap_service: {}, taas_id: VID, port: {}} """ taas_id = tap_service_msg['taas_id'] # Get patch port ID patch_int_tap_id = self.int_br.get_port_ofport('patch-int-tap') # Delete flow(s) from br-int self.int_br.delete_flows(table=0, in_port=patch_int_tap_id, dl_vlan=taas_id) # Delete flow(s) from br-tap self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_LOC, dl_vlan=taas_id) self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_REM, dl_vlan=taas_id) if self.tunnel_types: # Delete flow(s) from br-tun for tunnel_type in n_ovs_consts.TUNNEL_NETWORK_TYPES: self.tun_br.delete_flows( table=n_ovs_consts.TUN_TABLE[tunnel_type], tun_id=taas_id) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_DST_CHECK, tun_id=taas_id) self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SRC_CHECK, tun_id=taas_id) @log_helpers.log_method_call def create_tap_flow(self, tap_flow_msg): """Create a tap flow :param tap_flow_msg: a dict of the tap_flow, the mac of the port of the tap-flow, taas_id which is the VLAN Id reserved for mirroring for the tap-service associated with this tap-flow, the neutron port of the tap-flow, and the port of the tap-service: {tap_flow: {}, port_mac: '', taas_id: VID, port: {}, tap_service_port: {}} """ taas_id = tap_flow_msg['taas_id'] port = tap_flow_msg['port'] direction = tap_flow_msg['tap_flow']['direction'] physical_network = tap_flow_msg['tf_nw']['physical_network'] \ if 'tf_nw' in tap_flow_msg else None network_type = tap_flow_msg['tf_nw']['network_type'] \ if 'tf_nw' in tap_flow_msg else None # Get OVS port id for tap flow port ovs_port = self.int_br.get_vif_port_by_id(port['id']) ovs_port_id = ovs_port.ofport # Get patch port ID patch_int_tap_id = self.int_br.get_port_ofport('patch-int-tap') # Add flow(s) in br-int if direction in ('OUT', 'BOTH'): self.int_br.add_flow(table=0, priority=20, in_port=ovs_port_id, actions="normal,mod_vlan_vid:%s,output:%s" % (str(taas_id), str(patch_int_tap_id))) if direction in ('IN', 'BOTH'): port_mac = tap_flow_msg['port_mac'] # # Note: The ingress side flow (for unicast traffic) should # include a check for the 'VLAN id of the Neutron # network the port belongs to' + 'MAC address of the # port', to comply with the requirement that port MAC # addresses are unique only within a Neutron network. # Unfortunately, at the moment there is no clean way # to implement such a check, given OVS's handling of # VLAN tags and Neutron's use of the NORMAL action in # br-int. # # We are therefore temporarily disabling the VLAN id # check until a mechanism is available to implement # it correctly. The {broad,multi}cast flow, which is # also dependent on the VLAN id, has been disabled # for the same reason. # # Get VLAN id for tap flow port # port_dict = self.int_br.get_port_tag_dict() # port_vlan_id = port_dict[ovs_port.port_name] if not physical_network: self.int_br.add_flow( table=0, priority=20, # dl_vlan=port_vlan_id, dl_dst=port_mac, actions="normal,mod_vlan_vid:%s,output:%s" % (str(taas_id), str(patch_int_tap_id)) ) else: actions = "output:{},mod_vlan_vid:{},output:{}".format( str(ovs_port_id), str(taas_id), str(patch_int_tap_id) ) if network_type == 'vlan': actions = 'strip_vlan,' + actions self.int_br.add_flow( table=0, priority=20, # dl_vlan=port_vlan_id, dl_dst=port_mac, actions=actions ) # self._add_update_ingress_bcmc_flow(port_vlan_id, # taas_id, # patch_int_tap_id) # Add flow(s) in br-tun if self.tunnel_types: for tunnel_type in n_ovs_consts.TUNNEL_NETWORK_TYPES: self.tun_br.add_flow( table=n_ovs_consts.TUN_TABLE[tunnel_type], priority=1, tun_id=taas_id, actions=("move:NXM_OF_VLAN_TCI[0..11]->" "NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID" "[0..11]->NXM_OF_VLAN_TCI[0..11]," "resubmit(,%s)" % taas_ovs_consts.TAAS_CLASSIFY)) self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SRC_CHECK, priority=1, tun_id=taas_id, actions="resubmit(,%s)" % taas_ovs_consts.TAAS_SRC_RESPOND) @log_helpers.log_method_call def delete_tap_flow(self, tap_flow_msg): """Delete a tap flow :param tap_flow_msg: a dict of the tap_flow, the mac of the port of the tap-flow, taas_id which is the VLAN Id reserved for mirroring for the tap-service associated with this tap-flow, the neutron port of the tap-flow, the port of the tap-service, the list of source VLAN IDs, and VLAN filter list. {tap_flow: {}, port_mac: '', taas_id: VID, port: {}, tap_service_port: {}, source_vlans_list: [], vlan_filter_list: []} """ port = tap_flow_msg['port'] direction = tap_flow_msg['tap_flow']['direction'] # Get OVS port id for tap flow port ovs_port = self.int_br.get_vif_port_by_id(port['id']) ovs_port_id = ovs_port.ofport # Delete flow(s) from br-int if direction in ('OUT', 'BOTH'): self.int_br.delete_flows(table=0, in_port=ovs_port_id) if direction in ('IN', 'BOTH'): port_mac = tap_flow_msg['port_mac'] # # The VLAN id related checks have been temporarily disabled. # Please see comment in create_tap_flow() for details. # # taas_id = tap_flow['taas_id'] # Get VLAN id for tap flow port # port_dict = self.int_br.get_port_tag_dict() # port_vlan_id = port_dict[ovs_port.port_name] # Get patch port ID # patch_int_tap_id = self.int_br.get_port_ofport('patch-int-tap') self.int_br.delete_flows(table=0, # dl_vlan=port_vlan_id, dl_dst=port_mac) # self._del_update_ingress_bcmc_flow(port_vlan_id, # taas_id, # patch_int_tap_id) def update_tunnel_flood_flow(self): flow_action = self._create_tunnel_flood_flow_action() if flow_action != "": self.tun_br.mod_flow(table=taas_ovs_consts.TAAS_SEND_FLOOD, actions=flow_action) def _create_tunnel_flood_flow_action(self): port_name_list = self.tun_br.get_port_name_list() flow_action = ("move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID[0..11]," "mod_vlan_vid:1") tunnel_ports_exist = False for port_name in port_name_list: if port_name not in ('patch-int', 'patch-tun-tap'): flow_action += (",output:%d" % self.tun_br.get_port_ofport(port_name)) tunnel_ports_exist = True if tunnel_ports_exist: return flow_action else: return "" def _create_ingress_bcmc_flow_action(self, taas_id_list, out_port_id): flow_action = "normal" for taas_id in taas_id_list: flow_action += (",mod_vlan_vid:%d,output:%d" % (taas_id, out_port_id)) return flow_action # # Adds or updates a special flow in br-int to mirror (duplicate and # redirect to 'out_port_id') all ingress broadcast/multicast traffic, # associated with a VLAN, to possibly multiple tap service instances. # def _add_update_ingress_bcmc_flow(self, vlan_id, taas_id, out_port_id): # Add a tap service instance affiliation with VLAN self.bcmc_kvm.affiliate(vlan_id, taas_id) # Find all tap service instances affiliated with VLAN taas_id_list = self.bcmc_kvm.list_affiliations(vlan_id) # # Add/update flow to mirror ingress BCMC traffic, associated # with VLAN, to all affiliated tap-service instances. # flow_action = self._create_ingress_bcmc_flow_action(taas_id_list, out_port_id) self.int_br.add_flow(table=0, priority=20, dl_vlan=vlan_id, dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", actions=flow_action) # # Removes or updates a special flow in br-int to mirror (duplicate # and redirect to 'out_port_id') all ingress broadcast/multicast # traffic, associated with a VLAN, to possibly multiple tap-service # instances. # def _del_update_ingress_bcmc_flow(self, vlan_id, taas_id, out_port_id): # Remove a tap-service instance affiliation with VLAN self.bcmc_kvm.unaffiliate(vlan_id, taas_id) # Find all tap-service instances affiliated with VLAN taas_id_list = self.bcmc_kvm.list_affiliations(vlan_id) # # If there are tap service instances affiliated with VLAN, update # the flow to mirror ingress BCMC traffic, associated with VLAN, # to all of them. Otherwise, remove the flow. # if taas_id_list: flow_action = self._create_ingress_bcmc_flow_action(taas_id_list, out_port_id) self.int_br.add_flow(table=0, priority=20, dl_vlan=vlan_id, dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", actions=flow_action) else: self.int_br.delete_flows(table=0, dl_vlan=vlan_id, dl_dst=("01:00:00:00:00:00/" "01:00:00:00:00:00")) @log_helpers.log_method_call def create_tap_mirror(self, tap_mirror_msg): source_port = tap_mirror_msg['port'] tap_mirror = tap_mirror_msg['tap_mirror'] type = '' patch_int_tap_id = self.int_br.get_port_ofport('patch-int-tap') patch_tap_int_id = self.tap_br.get_port_ofport('patch-tap-int') # Get OVS port id for tap flow port ovs_port = self.int_br.get_vif_port_by_id(source_port['id']) ovs_port_id = ovs_port.ofport options = collections.OrderedDict() options['remote_ip'] = tap_mirror['remote_ip'] if 'erspan' in tap_mirror['mirror_type']: type = 'erspan' # For ERSPAN type 2 (version I) as that is supported # only from taas options['erspan_ver'] = "1" else: type = 'gre' directions = tap_mirror['directions'] for direction, tunnel_id in directions.items(): options['erspan_idx'] = str(tunnel_id) # Note(lajoskatona): this is treated as hexa, so # spanId will be d102, and Index will be d258 in the packet. # As OVN doesn't care about this let's have OVS driver the same # behaviour. options['key'] = str(tunnel_id) port_name = 'tm_%s_%s' % (direction.lower(), tap_mirror['id'][0:6]) attrs = [('type', type), ('options', options)] mirror_of_port = self.tap_br.add_port(port_name, *attrs) if direction == 'IN': self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_LOC, priority=20, dl_dst=source_port['mac_address'], actions="output:%s" % str(mirror_of_port)) self.int_br.add_flow( table=0, priority=20, dl_dst=source_port['mac_address'], actions="output:%s,resubmit(,%s)" % (str(patch_int_tap_id), str(n_ovs_consts.PACKET_RATE_LIMIT))) if direction == 'OUT': self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_LOC, priority=20, dl_src=source_port['mac_address'], actions="output:%s" % str(mirror_of_port)) self.int_br.add_flow( table=0, priority=20, in_port=ovs_port_id, actions="output:%s,resubmit(,%s)" % (str(patch_int_tap_id), str(n_ovs_consts.PACKET_RATE_LIMIT))) # Add flow(s) in br-tap self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_LOC, priority=1, dl_dst=source_port['mac_address'], actions="output:in_port") self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_REM, priority=1, dl_dst=source_port['mac_address'], actions="output:%s" % str(patch_tap_int_id)) @log_helpers.log_method_call def delete_tap_mirror(self, tap_mirror_msg): source_port = tap_mirror_msg['port'] tap_mirror = tap_mirror_msg['tap_mirror'] directions = tap_mirror['directions'] # Get OVS port id for tap flow port ovs_port = self.int_br.get_vif_port_by_id(source_port['id']) ovs_port_id = ovs_port.ofport self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_REM, dl_dst=source_port['mac_address']) self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_LOC, dl_dst=source_port['mac_address']) for direction, tunnel_id in directions.items(): if direction == 'IN': self.int_br.delete_flows(table=0, dl_dst=source_port['mac_address']) self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_LOC, dl_dst=source_port['mac_address']) if direction == 'OUT': self.int_br.delete_flows(table=0, in_port=ovs_port_id) self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_LOC, dl_src=source_port['mac_address']) port_name = 'tm_%s_%s' % (direction.lower(), tap_mirror['id'][0:6]) self.tap_br.delete_port(port_name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/ovs_utils.py0000664000175000017500000000517600000000000027613 0ustar00zuulzuul00000000000000# Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This class implements a simple key-value manager that can support # the following relationships. # # - Multiple values may be affiliated with a key. # - A value may be affiliated with multiple keys. # - A value may be affiliated with a key multiple times. # class key_value_mgr: # # Initializes internal state for specified # keys # def __init__(self, nr_keys): self.key_list = [] for i in range(nr_keys): self.key_list.append([]) # # Returns specified key-value affilation, if it exists. # def _find_affiliation(self, key, value): aff_list = self.key_list[key] for aff in aff_list: if aff['value'] == value: return aff return None # # Adds an affiliation of 'value' with 'key' # def affiliate(self, key, value): # Locate key-value affiliation aff = self._find_affiliation(key, value) if aff is None: # Create a (new) key-value affiliation aff = { 'value': value, 'refcnt': 0, } aff_list = self.key_list[key] aff_list.append(aff) # Increment affiliation reference count aff['refcnt'] += 1 # # Removes an affiliation of 'value' with 'key' # def unaffiliate(self, key, value): # Locate key-value affiliation aff = self._find_affiliation(key, value) if aff is None: return # Decrement affiliation reference count aff['refcnt'] -= 1 # Destroy affiliation iff no outstanding references if aff['refcnt'] <= 0: aff_list = self.key_list[key] aff_list.remove(aff) return # # Lists all values affiliated with 'key' # # Note: The returned list is a set (contains no duplicates) # def list_affiliations(self, key): aff_list = self.key_list[key] value_list = [] for aff in aff_list: value_list.append(aff['value']) return value_list ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/sriov_nic_exceptions.py0000664000175000017500000000237000000000000032011 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import exceptions as qexception # TaaS SR-IOV driver exception handling classes class SriovNicSwitchDriverInvocationError(qexception.Invalid): message = _("Failed to invoke SR-IOV TaaS driver command: " "%(tap_service_pf_device)s, %(tap_service_vf_index)s, " "%(source_vf_index)s, %(vlan_filter)s, " "%(vf_to_vf_all_vlans)s, %(direction)s") def __init__(self, **kwargs): super().__init__(**kwargs) class PciDeviceNotFoundById(qexception.NotFound): message = _("PCI device %(id)s not found") class PciSlotNotFound(qexception.NotFound): message = _("PCI slot (Port-id, MAC): %(port_id)s, %(mac)s not found") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/sriov_nic_taas.py0000664000175000017500000004523000000000000030562 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.agent import common from neutron_taas.common import constants as taas_consts from neutron_taas.common import utils as common_utils from neutron_taas.services.taas.agents.extensions import taas as taas_base from neutron_taas.services.taas.drivers.linux import sriov_nic_exceptions \ as taas_exc from neutron_taas.services.taas.drivers.linux import sriov_nic_utils \ as sriov_utils from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging from oslo_utils import excutils import threading LOG = logging.getLogger(__name__) TaaS_DRIVER_NAME = 'Taas SRIOV NIC Switch driver' class SriovNicTaasDriver(taas_base.TaasAgentDriver): def __init__(self): super().__init__() LOG.debug("Initializing Taas SRIOV NIC Switch Driver") self.agent_api = None self.root_helper = common.get_root_helper(cfg.CONF) self.lock = threading.Lock() def initialize(self): LOG.debug("Initialize routine called for Taas SRIOV NIC Switch Driver") self.sriov_utils = sriov_utils.SriovNicUtils() def consume_api(self, agent_api): self.agent_api = agent_api @log_helpers.log_method_call def create_tap_service(self, tap_service_msg): """Create a tap service :param tap_service_msg: a dict of the tap_service, taas_id which is the VLAN Id reserved for mirroring for this tap-service and the neutron port of the tap-service: {tap_service: {}, taas_id: VID, port: {}} """ ts_port = tap_service_msg['port'] LOG.debug("SRIOV Driver: Inside create_tap_service: " "Port-id: %(port_id)s", {'port_id': ts_port['id']}) port_params = self.sriov_utils.get_sriov_port_params(ts_port) LOG.info("TaaS SRIOV: create_tap_service RPC invoked for " "port %(id)s, MAC %(ts_port_mac)s, PCI %(ts_pci_slot)s, " "VF-Index %(vf_index)s, PF-Device %(pf_device)s, " "src_vlans %(src_vlans)s; ", {'id': ts_port['id'], 'ts_port_mac': port_params['mac'], 'ts_pci_slot': port_params['pci_slot'], 'vf_index': port_params['vf_index'], 'pf_device': port_params['pf_device'], 'src_vlans': port_params['src_vlans']}) @log_helpers.log_method_call def delete_tap_service(self, tap_service_msg): """Delete a tap service :param tap_service_msg: a dict of the tap_service, taas_id which is the VLAN Id reserved for mirroring for this tap-service and the neutron port of the tap-service: {tap_service: {}, taas_id: VID, port: {}} """ ts_port = tap_service_msg['port'] LOG.debug("SRIOV Driver: Inside delete_tap_service: " "Port-id: %(port_id)s", {'port_id': ts_port['id']}) port_params = self.sriov_utils.get_sriov_port_params(ts_port) LOG.info("TaaS SRIOV: delete_tap_service RPC invoked for " "port %(id)s, MAC %(ts_port_mac)s, PCI %(ts_pci_slot)s, " "VF-Index %(vf_index)s, PF-Device %(pf_device)s, " "src_vlans %(src_vlans)s; ", {'id': ts_port['id'], 'ts_port_mac': port_params['mac'], 'ts_pci_slot': port_params['pci_slot'], 'vf_index': port_params['vf_index'], 'pf_device': port_params['pf_device'], 'src_vlans': port_params['src_vlans']}) @log_helpers.log_method_call def create_tap_flow(self, tap_flow_msg): """Create a tap flow :param tap_flow_msg: a dict of the tap_flow, the mac of the port of the tap-flow, taas_id which is the VLAN Id reserved for mirroring for the tap-service associated with this tap-flow, the neutron port of the tap-flow, and the port of the tap-service: {tap_flow: {}, port_mac: '', taas_id: '', port: {}, tap_service_port: {}} """ source_port = tap_flow_msg['port'] ts_port = tap_flow_msg['tap_service_port'] direction = tap_flow_msg['tap_flow']['direction'] vlan_filter = tap_flow_msg['tap_flow']['vlan_filter'] vf_to_vf_all_vlans = False LOG.debug("SRIOV Driver: Inside create_tap_flow: " "SRC Port-id: %(src_port_id)s, " "DEST Port-id: %(dest_port_id)s " "Direction: %(direction)s", {'src_port_id': source_port['id'], 'dest_port_id': ts_port['id'], 'direction': direction}) src_port_params = self.sriov_utils.get_sriov_port_params(source_port) ts_port_params = self.sriov_utils.get_sriov_port_params(ts_port) LOG.info("TaaS src_port_params " "port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, " "VF-Index %(vf_index)s, PF-Device %(pf_device)s, " "src_vlans %(src_vlans)s; ", {'id': source_port['id'], 'port_mac': src_port_params['mac'], 'pci_slot': src_port_params['pci_slot'], 'vf_index': src_port_params['vf_index'], 'pf_device': src_port_params['pf_device'], 'src_vlans': src_port_params['src_vlans']}) LOG.info("TaaS ts_port_params " "port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, " "VF-Index %(vf_index)s, PF-Device %(pf_device)s, " "VLAN-Filter %(vlan_filter)s, src_vlans %(src_vlans)s; ", {'id': ts_port['id'], 'port_mac': ts_port_params['mac'], 'pci_slot': ts_port_params['pci_slot'], 'vf_index': ts_port_params['vf_index'], 'pf_device': ts_port_params['pf_device'], 'vlan_filter': vlan_filter, 'src_vlans': ts_port_params['src_vlans']}) # If no VLAN filter configured on source port, then include all vlans if not src_port_params['src_vlans'] or \ src_port_params['src_vlans'] == '0': src_port_params['src_vlans'] = taas_consts.VLAN_RANGE LOG.debug("TaaS no src_vlans in src_port") # If no VLAN filter configured on probe port, then include all vlans if not vlan_filter: vlan_filter = taas_consts.VLAN_RANGE vf_to_vf_all_vlans = True LOG.debug("VF to VF mirroring for all VLANs. " "Direction %(direction)s", {'direction': direction}) if not src_port_params['pci_slot']: LOG.error("No PCI Slot for source_port %(id)s with MAC %(mac)s; ", {'id': source_port['id'], 'mac': src_port_params['mac'], 'source_vlans': src_port_params['src_vlans']}) raise taas_exc.PciSlotNotFound(port_id=source_port['id'], mac=src_port_params['mac']) if not ts_port_params['pci_slot']: LOG.error("No PCI Slot for ts_port %(id)s with MAC %(mac)s; ", {'id': ts_port['id'], 'mac': ts_port_params['mac'], 'vlan_filter': vlan_filter}) raise taas_exc.PciSlotNotFound(port_id=ts_port['id'], mac=ts_port_params['mac']) if src_port_params['pf_device'] != ts_port_params['pf_device']: LOG.error("SRIOV NIC Driver only supports mirroring b/w " "VF_src %(VF_src)s -> VF_probe %(VF_probe)s on same PF. " "PF_src %(PF_src)s and PF_probe %(PF_probe)s " "are different; ", {'VF_src': src_port_params['vf_index'], 'VF_probe': ts_port_params['vf_index'], 'PF_src': src_port_params['pf_device'], 'PF_probe': ts_port_params['pf_device']}) return # Fetch common VLAN tags src_vlans_list = sorted(set(common_utils.get_list_from_ranges_str( src_port_params['src_vlans']))) vlan_filter_list = sorted(set( common_utils.get_list_from_ranges_str(vlan_filter))) common_vlans_list = list(set(src_vlans_list).intersection( vlan_filter_list)) common_vlans_rng_str = common_utils.get_ranges_str_from_list( common_vlans_list) LOG.info("TaaS src_vlans_list %(src_vlans_list)s, " "vlan_filter_list %(vlan_filter_list)s, " "common_vlans_list %(common_vlans_list)s, " "common_vlans_rng_str %(common_vlans_rng_str)s; ", {'src_vlans_list': src_vlans_list, 'vlan_filter_list': vlan_filter_list, 'common_vlans_list': common_vlans_list, 'common_vlans_rng_str': common_vlans_rng_str}) if ts_port_params['pf_device'] and \ ts_port_params['vf_index'] and \ src_port_params['vf_index']: with self.lock: try: LOG.info("TaaS invoking execute_sysfs_command") self.sriov_utils.execute_sysfs_command( 'add', ts_port_params, src_port_params, common_vlans_rng_str, vf_to_vf_all_vlans, direction) except Exception: LOG.error("TaaS error in invoking execute_sysfs_command") with excutils.save_and_reraise_exception(): raise taas_exc.SriovNicSwitchDriverInvocationError( tap_service_pf_device=ts_port_params['pf_device'], tap_service_vf_index=ts_port_params['vf_index'], source_vf_index=src_port_params['vf_index'], vlan_filter=common_vlans_rng_str, vf_to_vf_all_vlans=vf_to_vf_all_vlans, direction=direction) return @log_helpers.log_method_call def delete_tap_flow(self, tap_flow_msg): """Delete a tap flow :param tap_flow_msg: a dict of the tap_flow, the mac of the port of the tap-flow, taas_id which is the VLAN Id reserved for mirroring for the tap-service associated with this tap-flow, the neutron port of the tap-flow, the port of the tap-service, the list of source VLAN IDs, and VLAN filter list. {tap_flow: {}, port_mac: '', taas_id: '', port: {}, tap_service_port: {}, source_vlans_list: [], vlan_filter_list: []} """ source_port = tap_flow_msg['port'] ts_port = tap_flow_msg['tap_service_port'] vlan_filter = tap_flow_msg['tap_flow']['vlan_filter'] direction = tap_flow_msg['tap_flow']['direction'] vf_to_vf_all_vlans = False LOG.debug("SRIOV Driver: Inside delete_tap_flow: " "SRC Port-id: %(src_port_id)s, " "DEST Port-id: %(dest_port_id)s " "Direction: %(direction)s", {'src_port_id': source_port['id'], 'dest_port_id': ts_port['id'], 'direction': direction}) src_port_params = self.sriov_utils.get_sriov_port_params(source_port) ts_port_params = self.sriov_utils.get_sriov_port_params(ts_port) LOG.info("TaaS src_port_params " "port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, " "VF-Index %(vf_index)s, PF-Device %(pf_device)s, " "src_vlans %(src_vlans)s; ", {'id': source_port['id'], 'port_mac': src_port_params['mac'], 'pci_slot': src_port_params['pci_slot'], 'vf_index': src_port_params['vf_index'], 'pf_device': src_port_params['pf_device'], 'src_vlans': src_port_params['src_vlans']}) LOG.info("TaaS ts_port_params " "port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, " "VF-Index %(vf_index)s, PF-Device %(pf_device)s, " "VLAN-Filter %(vlan_filter)s, src_vlans %(src_vlans)s; ", {'id': ts_port['id'], 'port_mac': ts_port_params['mac'], 'pci_slot': ts_port_params['pci_slot'], 'vf_index': ts_port_params['vf_index'], 'pf_device': ts_port_params['pf_device'], 'vlan_filter': vlan_filter, 'src_vlans': ts_port_params['src_vlans']}) # If no VLAN filter configured on source port, then include all vlans if not src_port_params['src_vlans']: src_port_params['src_vlans'] = taas_consts.VLAN_RANGE LOG.debug("TaaS no src_vlans in src_port") # If no VLAN filter configured on probe port, then include all vlans if not vlan_filter: vf_to_vf_all_vlans = True LOG.debug("VF to VF mirroring for all VLANs. " "Direction %(direction)s", {'direction': direction}) if not src_port_params['pci_slot']: LOG.error("No PCI Slot for source_port %(id)s with MAC %(mac)s; ", {'id': source_port['id'], 'mac': src_port_params['mac'], 'source_vlans': src_port_params['src_vlans']}) raise taas_exc.PciSlotNotFound(port_id=source_port['id'], mac=src_port_params['mac']) if not ts_port_params['pci_slot']: LOG.error("No PCI Slot for ts_port %(id)s with MAC %(mac)s; ", {'id': ts_port['id'], 'mac': ts_port_params['mac']}) raise taas_exc.PciSlotNotFound(port_id=ts_port['id'], mac=ts_port_params['mac']) # Fetch common VLAN tags src_vlans_list = [] for src_vlans_str in tap_flow_msg['source_vlans_list']: src_vlans_list.extend(common_utils.get_list_from_ranges_str( src_vlans_str)) src_vlans_list = sorted(set(src_vlans_list)) vlan_filter_list = [] for vlan_filter_str in tap_flow_msg['vlan_filter_list']: vlan_filter_list.extend(common_utils.get_list_from_ranges_str( vlan_filter_str)) vlan_filter_list = sorted(set(vlan_filter_list)) common_vlans_list = \ list(set(src_vlans_list).intersection(vlan_filter_list)) common_vlans_rng_str = \ common_utils.get_ranges_str_from_list(common_vlans_list) LOG.info("TaaS src_vlans_list %(src_vlans_list)s, " "vlan_filter_list %(vlan_filter_list)s, " "common_vlans_list %(common_vlans_list)s, " "common_vlans_rng_str %(common_vlans_rng_str)s; ", {'src_vlans_list': src_vlans_list, 'vlan_filter_list': vlan_filter_list, 'common_vlans_list': common_vlans_list, 'common_vlans_rng_str': common_vlans_rng_str}) if ts_port_params['pf_device'] and \ ts_port_params['vf_index'] and \ src_port_params['vf_index']: with self.lock: try: LOG.info("TaaS invoking execute_sysfs_command") self.sriov_utils.execute_sysfs_command('rem', ts_port_params, src_port_params, taas_consts. VLAN_RANGE, False, 'BOTH') except Exception: LOG.error("TaaS error in invoking execute_sysfs_command") with excutils.save_and_reraise_exception(): raise taas_exc.SriovNicSwitchDriverInvocationError( ts_pf_dev=ts_port_params['pf_device'], ts_vf_index=ts_port_params['vf_index'], source_vf_index=src_port_params['vf_index'], common_vlans_rng_str=taas_consts.VLAN_RANGE, vf_to_vf_all_vlans=vf_to_vf_all_vlans, direction=direction) if common_vlans_rng_str: try: LOG.info("TaaS invoking execute_sysfs_command") self.sriov_utils.execute_sysfs_command( 'add', ts_port_params, src_port_params, common_vlans_rng_str, False, 'BOTH') except Exception: LOG.error("TaaS error in invoking " "execute_sysfs_command") with excutils.save_and_reraise_exception(): raise taas_exc.SriovNicSwitchDriverInvocationError( ts_pf_dev=ts_port_params['pf_device'], ts_vf_index=ts_port_params['vf_index'], source_vf_index=src_port_params['vf_index'], common_vlans_rng_str=common_vlans_rng_str, vf_to_vf_all_vlans=vf_to_vf_all_vlans, direction=direction) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/drivers/linux/sriov_nic_utils.py0000664000175000017500000002575100000000000031000 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This class implements a utility functions for SRIOV NIC Switch Driver # import os from oslo_log import log as logging from neutron.agent.linux import utils from neutron_lib.api.definitions import portbindings from neutron_taas.services.taas.drivers.linux import sriov_nic_exceptions \ as taas_exc import glob import re LOG = logging.getLogger(__name__) class SriovNicUtils: # # Initializes internal state for specified # keys # def __init__(self): LOG.debug("SriovNicUtils: init called") # # Returns specified key-value affilation, if it exists. # def execute_sysfs_command(self, command, ts_port_params, src_port_params, common_vlans_ranges_str, vf_to_vf_all_vlans, direction): """Execute the SRIOV NIC Switch Driver's SysFs command. # Mirror traffic from VF0 to VF3 on interface p2p1, ex. echo add 3 > /sys/class/net/p2p1/device/sriov/0/ingress_mirror echo add 3 > /sys/class/net/p2p1/device/sriov/0/egress_mirror # Remove traffic mirroring from VF0 to VF3 on interface p2p1, ex. echo rem 3 > /sys/class/net/p2p1/device/sriov/0/ingress_mirror echo rem 3 > /sys/class/net/p2p1/device/sriov/0/egress_mirror # Add VLANs 2,6,18-22 to Mirror traffic to VF3 (port p2p1), ex. echo add 2,6,18-22 > /sys/class/net/p2p1/device/sriov/3/vlan_mirror # Remove VLANs 2,6,18-22 to Mirror traffic to VF3 (port p2p1), ex. echo rem 2,6,18-22 > /sys/class/net/p2p1/device/sriov/3/vlan_mirror # Remove all VLANs from mirroring at VF3, ex. echo rem 0-4095 > /sys/class/net/p1p1/device/sriov/3/vlan_mirror """ LOG.debug("TaaS sysfs command params %(command)s, " "ts_port_params %(ts_port_params)s, " "src_port_params %(src_port_params)s, " "common_vlans_ranges_str %(common_vlans_ranges_str)s; " "vf_to_vf_all_vlans %(vf_to_vf_all_vlans)s; " "direction %(direction)s; ", {'command': command, 'ts_port_params': ts_port_params, 'src_port_params': src_port_params, 'common_vlans_ranges_str': common_vlans_ranges_str, 'vf_to_vf_all_vlans': vf_to_vf_all_vlans, 'direction': direction}) if vf_to_vf_all_vlans: if direction in ['OUT', 'BOTH']: commit_cmd = ['i40e_sysfs_command', ts_port_params['pf_device'], src_port_params['vf_index'], 'egress_mirror', command, ts_port_params['vf_index']] try: LOG.info("TaaS executing sysfs_command %(command)s", {'command': commit_cmd}) utils.execute(commit_cmd, run_as_root=True, privsep_exec=True) except (OSError, RuntimeError, IndexError, ValueError) as e: LOG.error("Exception while executing Sysfs command " "Exception: %s", e) return if direction in ['IN', 'BOTH']: commit_cmd = ['i40e_sysfs_command', ts_port_params['pf_device'], src_port_params['vf_index'], 'ingress_mirror', command, ts_port_params['vf_index']] try: LOG.info("TaaS executing sysfs_command %(command)s", {'command': commit_cmd}) utils.execute(commit_cmd, run_as_root=True, privsep_exec=True) except (OSError, RuntimeError, IndexError, ValueError) as e: LOG.error("Exception while executing Sysfs command " "Exception: %s", e) return else: if direction != 'BOTH': LOG.warning("SRIOV NIC Switch driver only supports" "direction=BOTH for specific VLANs' mirroring") commit_cmd = ['i40e_sysfs_command', ts_port_params['pf_device'], ts_port_params['vf_index'], 'vlan_mirror', command, common_vlans_ranges_str] try: LOG.info("TaaS executing sysfs_command %(command)s", {'command': commit_cmd}) utils.execute(commit_cmd, run_as_root=True, privsep_exec=True) except (OSError, RuntimeError, IndexError, ValueError) as e: LOG.error("Exception while executing Sysfs command " "Exception: %s", e) return def _get_sysfs_netdev_path(self, pci_addr, pf_interface): """Get the sysfs path based on the PCI address of the device. Assumes a networking device - will not check for the existence of the path. """ if pf_interface: return "/sys/bus/pci/devices/%s/physfn/net" % pci_addr return "/sys/bus/pci/devices/%s/net" % pci_addr def get_ifname_by_pci_address(self, pci_addr, pf_interface=False): """Get the interface name based on a VF's pci address. The returned interface name is either the parent PF's or that of the VF itself based on the argument of pf_interface. """ dev_path = self._get_sysfs_netdev_path(pci_addr, pf_interface) try: dev_info = os.listdir(dev_path) return dev_info.pop() except Exception: raise taas_exc.PciDeviceNotFoundById(id=pci_addr) def get_mac_by_pci_address(self, pci_addr, pf_interface=False): """Get the MAC address of the nic based on its PCI address. Raises PciDeviceNotFoundById in case the pci device is not a NIC """ dev_path = self._get_sysfs_netdev_path(pci_addr, pf_interface) if_name = self.get_ifname_by_pci_address(pci_addr, pf_interface) addr_file = os.path.join(dev_path, if_name, 'address') try: with open(addr_file, encoding="utf-8") as f: mac = next(f).strip() return mac except (OSError, StopIteration) as e: LOG.warning("Could not find the expected sysfs file for " "determining the MAC address of the PCI device " "%(addr)s. May not be a NIC. Error: %(e)s", {'addr': pci_addr, 'e': e}) raise taas_exc.PciDeviceNotFoundById(id=pci_addr) def get_vf_num_by_pci_address(self, pci_addr): """Get the VF number based on a VF's pci address A VF is associated with an VF number, which ip link command uses to configure it. This can be obtained from the PCI device filesystem. """ VIRTFN_RE = re.compile(r"virtfn(\d+)") virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr) vf_num = None LOG.debug("TaaS: pci_addr: %(pci_addr)s " "virtfns_path: %(virtfns_path)s", {'pci_addr': pci_addr, 'virtfns_path': virtfns_path}) try: for vf_path in glob.iglob(virtfns_path): if re.search(pci_addr, os.readlink(vf_path)): t = VIRTFN_RE.search(vf_path) vf_num = t.group(1) break except Exception: pass if vf_num is None: LOG.warning("TaaS: No net device was found for pci: %(pci_addr)s " "virtfns_path: %(virtfns_path)s", {'pci_addr': pci_addr, 'virtfns_path': virtfns_path}) raise taas_exc.PciDeviceNotFoundById(id=pci_addr) return vf_num def get_net_name_by_vf_pci_address(self, vfaddress, pf_interface=False): """Given the VF PCI address, returns the net device name. Every VF is associated to a PCI network device. This function returns the libvirt name given to this network device; e.g.: net_enp8s0f0_90_e2_ba_5e_a6_40 ... In the libvirt parser information tree, the network device stores the network capabilities associated to this device. """ LOG.debug("TaaS: vfaddr: %(vfaddr)s ", {'vfaddr': vfaddress}) try: mac = self.get_mac_by_pci_address(vfaddress, pf_interface).split(':') ifname = self.get_ifname_by_pci_address(vfaddress, pf_interface) LOG.debug("TaaS: mac: %(mac)s, ifname: %(ifname)s", {'mac': mac, 'ifname': ifname}) return ("net_%(ifname)s_%(mac)s" % {'ifname': ifname, 'mac': '_'.join(mac)}) except Exception: LOG.warning("No net device was found for VF %(vfaddress)s", {'vfaddress': vfaddress}) return def get_sriov_port_params(self, sriov_port): """Returns a dict of common SRIOV parameters for a given SRIOV port """ LOG.debug("TaaS: sriov_port %(id)s; ", {'id': sriov_port['id']}) port_mac = sriov_port['mac_address'] pci_slot = None src_vlans = None if sriov_port.get(portbindings.PROFILE): pci_slot = sriov_port[portbindings.PROFILE].get('pci_slot') if sriov_port.get(portbindings.VIF_DETAILS): src_vlans = sriov_port[portbindings.VIF_DETAILS].get('vlan') LOG.debug("TaaS: pci_slot %(pci_slot)s; " "src_vlans %(src_vlans)s; ", {'pci_slot': pci_slot, 'src_vlans': src_vlans}) if not pci_slot: LOG.error("No PCI Slot for sriov_port %(id)s with MAC %(mac)s; ", {'id': sriov_port['id'], 'mac': port_mac}) return vf_index = self.get_vf_num_by_pci_address(pci_slot) pf_device = self.get_ifname_by_pci_address(pci_slot, True) return {'mac': port_mac, 'pci_slot': pci_slot, 'vf_index': vf_index, 'pf_device': pf_device, 'src_vlans': src_vlans} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/0000775000175000017500000000000000000000000025562 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/__init__.py0000664000175000017500000000350500000000000027676 0ustar00zuulzuul00000000000000# Copyright (C) 2016 Midokura SARL. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc class TaasBaseDriver(metaclass=abc.ABCMeta): def __init__(self, service_plugin): self.service_plugin = service_plugin @property def service_type(self): pass @abc.abstractmethod def create_tap_service_precommit(self, context): pass @abc.abstractmethod def delete_tap_service_precommit(self, context): pass @abc.abstractmethod def create_tap_flow_precommit(self, context): pass @abc.abstractmethod def delete_tap_flow_precommit(self, context): pass @abc.abstractmethod def create_tap_service_postcommit(self, context): pass @abc.abstractmethod def delete_tap_service_postcommit(self, context): pass @abc.abstractmethod def create_tap_flow_postcommit(self, context): pass @abc.abstractmethod def delete_tap_flow_postcommit(self, context): pass @abc.abstractmethod def create_tap_mirror_precommit(self, context): pass @abc.abstractmethod def create_tap_mirror_postcommit(self, context): pass @abc.abstractmethod def delete_tap_mirror_precommit(self, context): pass @abc.abstractmethod def delete_tap_mirror_postcommit(self, context): pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/ovn/0000775000175000017500000000000000000000000026364 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/ovn/__init__.py0000664000175000017500000000000000000000000030463 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/ovn/helper.py0000664000175000017500000001037300000000000030221 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 queue import threading from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging from ovs.stream import Stream from neutron_taas.services.taas.service_drivers.ovn.ovsdb import impl_idl_taas LOG = logging.getLogger(__name__) CONF = cfg.CONF class TaasOvnProviderHelper(): def __init__(self): ovn_conf.register_opts() self._requests = queue.Queue() self._helper_thread = threading.Thread(target=self._request_handler) self._helper_thread.daemon = True self._check_and_set_ssl_files() self._taas_mirror_func_map = { 'mirror_del': self.mirror_del, 'mirror_add': self.mirror_add, } self._subscribe() self._helper_thread.start() def _subscribe(self): registry.subscribe(self._post_fork_initialize, resources.PROCESS, events.AFTER_INIT) def _post_fork_initialize(self, resource, event, trigger, payload=None): self.ovn_nbdb = impl_idl_taas.OvnNbIdlForTaas() self.ovn_nbdb_api = self.ovn_nbdb.start() def _check_and_set_ssl_files(self): priv_key_file = CONF.ovn.ovn_nb_private_key cert_file = CONF.ovn.ovn_nb_certificate ca_cert_file = CONF.ovn.ovn_nb_ca_cert if priv_key_file: Stream.ssl_set_private_key_file(priv_key_file) if cert_file: Stream.ssl_set_certificate_file(cert_file) if ca_cert_file: Stream.ssl_set_ca_cert_file(ca_cert_file) def _request_handler(self): while True: request = self._requests.get() request_type = request['type'] if request_type == 'exit': break request_handler = self._taas_mirror_func_map.get(request_type) try: if request_handler: request_handler(request['info']) self._requests.task_done() except Exception: # If any unexpected exception happens we don't want the # notify_loop to exit. LOG.exception('Unexpected exception in request_handler') def _execute_commands(self, commands): with self.ovn_nbdb_api.transaction(check_error=True) as txn: for command in commands: txn.add(command) def shutdown(self): self._requests.put({'type': 'exit'}) self._helper_thread.join() self.ovn_nbdb.stop() del self.ovn_nbdb_api def add_request(self, req): self._requests.put(req) @log_helpers.log_method_call def mirror_del(self, request): port_id = request.pop('port_id') ovn_port = self.ovn_nbdb_api.lookup('Logical_Switch_Port', port_id) mirror = self.ovn_nbdb_api.mirror_get( request['name']).execute(check_error=True) self.ovn_nbdb_api.lsp_detach_mirror( ovn_port.name, mirror.uuid, if_exist=True).execute(check_error=True) self.ovn_nbdb_api.mirror_del( mirror.uuid).execute(check_error=True) @log_helpers.log_method_call def mirror_add(self, request): port_id = request.pop('port_id') ovn_port = self.ovn_nbdb_api.lookup('Logical_Switch_Port', port_id) mirror = self.ovn_nbdb_api.mirror_add( **request).execute(check_error=True) self.ovn_nbdb_api.lsp_attach_mirror( ovn_port.name, mirror.uuid, may_exist=True).execute(check_error=True) return mirror ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/ovn/ovsdb/0000775000175000017500000000000000000000000027501 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/ovn/ovsdb/__init__.py0000664000175000017500000000000000000000000031600 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/ovn/ovsdb/impl_idl_taas.py0000664000175000017500000001313100000000000032653 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 atexit import contextlib import tenacity from neutron.common.ovn import exceptions as ovn_exceptions from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron_lib import exceptions as n_exc from oslo_config import cfg from oslo_log import log from ovsdbapp.backend import ovs_idl from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import transaction as idl_trans from ovsdbapp.schema.ovn_northbound import impl_idl as nb_impl_idl LOG = log.getLogger(__name__) CONF = cfg.CONF class Backend(ovs_idl.Backend): lookup_table = {} ovsdb_connection = None def __init__(self, connection): ovn_conf.register_opts() self.ovsdb_connection = connection super().__init__(connection) def start_connection(self, connection): try: self.ovsdb_connection.start() except Exception as e: connection_exception = OvsdbConnectionUnavailable( db_schema=self.schema, error=e) LOG.exception(connection_exception) raise connection_exception from e @property def idl(self): return self.ovsdb_connection.idl @property def tables(self): return self.idl.tables _tables = tables def is_table_present(self, table_name): return table_name in self._tables def is_col_present(self, table_name, col_name): return self.is_table_present(table_name) and ( col_name in self._tables[table_name].columns) def create_transaction(self, check_error=False, log_errors=True): return idl_trans.Transaction( self, self.ovsdb_connection, self.ovsdb_connection.timeout, check_error, log_errors) # Check for a column match in the table. If not found do a retry with # a stop delay of 10 secs. This function would be useful if the caller # wants to verify for the presence of a particular row in the table # with the column match before doing any transaction. # Eg. We can check if Logical_Switch row is present before adding a # logical switch port to it. @tenacity.retry(retry=tenacity.retry_if_exception_type(RuntimeError), wait=tenacity.wait_exponential(), stop=tenacity.stop_after_delay(10), reraise=True) def check_for_row_by_value_and_retry(self, table, column, match): try: idlutils.row_by_value(self.idl, table, column, match) except idlutils.RowNotFound as e: msg = (_("%(match)s does not exist in %(column)s of %(table)s") % {'match': match, 'column': column, 'table': table}) raise RuntimeError(msg) from e class OvsdbConnectionUnavailable(n_exc.ServiceUnavailable): message = _("OVS database connection to %(db_schema)s failed with error: " "'%(error)s'. Verify that the OVS and OVN services are " "available and that the 'ovn_nb_connection' and " "'ovn_sb_connection' configuration options are correct.") class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend): def __init__(self, connection): super().__init__(connection) self.idl._session.reconnect.set_probe_interval( ovn_conf.get_ovn_ovsdb_probe_interval()) @contextlib.contextmanager def transaction(self, check_error=False, log_errors=True, nested=True, **kwargs): """A wrapper on the ovsdbapp transaction to work with revisions. This method is just a wrapper around the ovsdbapp transaction to handle revision conflicts correctly. """ try: with super().transaction(check_error, log_errors, nested, **kwargs) as t: yield t except ovn_exceptions.RevisionConflict as e: LOG.info('Transaction aborted. Reason: %s', e) class OvnNbIdlForTaas(connection.OvsdbIdl): SCHEMA = "OVN_Northbound" TABLES = ('Logical_Switch_Port', 'Mirror') def __init__(self): ovn_conf.register_opts() self.conn_string = ovn_conf.get_ovn_nb_connection() helper = self._get_ovsdb_helper(self.conn_string) for table in OvnNbIdlForTaas.TABLES: helper.register_table(table) super().__init__(self.conn_string, helper) atexit.register(self.stop) @tenacity.retry( wait=tenacity.wait_exponential(18), reraise=True) def _get_ovsdb_helper(self, connection_string): return idlutils.get_schema_helper(connection_string, self.SCHEMA) def start(self): self.conn = connection.Connection(self, timeout=180) return OvsdbNbOvnIdl(self.conn) def stop(self): # Close the running connection if it has been initalized if hasattr(self, 'conn'): if not self.conn.stop(timeout=180): LOG.debug("Connection terminated to OvnNb " "but a thread is still alive") del self.conn # Close the idl session self.close() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/ovn/taas_ovn.py0000664000175000017500000001013500000000000030550 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.api.definitions import tap_mirror as tap_m_api_def from oslo_log import helpers as log_helpers from oslo_log import log as logging from neutron_taas.services.taas import service_drivers from neutron_taas.services.taas.service_drivers.ovn import helper LOG = logging.getLogger(__name__) class TaasOvnDriver(service_drivers.TaasBaseDriver): """Taas OVN Service Driver class""" more_supported_extension_aliases = [tap_m_api_def.ALIAS] def __init__(self, service_plugin): LOG.debug("Loading Taas OVN Driver.") super().__init__(service_plugin) self._ovn_helper = helper.TaasOvnProviderHelper() def __del__(self): self._ovn_helper.shutdown() @log_helpers.log_method_call def create_tap_service_precommit(self, context): LOG.warning("Not implemented") @log_helpers.log_method_call def create_tap_service_postcommit(self, context): LOG.warning("Not implemented") @log_helpers.log_method_call def delete_tap_service_precommit(self, context): LOG.warning("Not implemented") @log_helpers.log_method_call def delete_tap_service_postcommit(self, context): LOG.warning("Not implemented") @log_helpers.log_method_call def create_tap_flow_precommit(self, context): LOG.warning("Not implemented") @log_helpers.log_method_call def create_tap_flow_postcommit(self, context): LOG.warning("Not implemented") @log_helpers.log_method_call def delete_tap_flow_precommit(self, context): """Send tap flow deletion RPC message to agent.""" LOG.warning("Not implemented") @log_helpers.log_method_call def delete_tap_flow_postcommit(self, context): LOG.warning("Not implemented") @log_helpers.log_method_call def create_tap_mirror_precommit(self, context): pass @log_helpers.log_method_call def create_tap_mirror_postcommit(self, context): LOG.info('create_tap_mirror_postcommit %s', context.tap_mirror) t_m = context.tap_mirror type = 'erspan' if 'erspan' in t_m['mirror_type'] else 'gre' directions = t_m['directions'] for direction, tunnel_id in directions.items(): mirror_port_name = 'tm_%s_%s' % (direction.lower(), t_m['id'][0:6]) ovn_direction = ('from-lport' if direction == 'OUT' else 'to-lport') request = {'type': 'mirror_add', 'info': {'name': mirror_port_name, 'direction_filter': ovn_direction, 'dest': t_m['remote_ip'], 'mirror_type': type, 'index': int(tunnel_id), 'port_id': t_m['port_id']}} self._ovn_helper.add_request(request) @log_helpers.log_method_call def delete_tap_mirror_precommit(self, context): LOG.info('delete_tap_mirror_precommit %s', context.tap_mirror) t_m = context.tap_mirror directions = t_m['directions'] for direction, tunnel_id in directions.items(): mirror_port_name = 'tm_%s_%s' % (direction.lower(), t_m['id'][0:6]) request = { 'type': 'mirror_del', 'info': {'id': t_m['id'], 'name': mirror_port_name, 'sink': t_m['remote_ip'], 'port_id': t_m['port_id']} } self._ovn_helper.add_request(request) @log_helpers.log_method_call def delete_tap_mirror_postcommit(self, context): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/service_driver_context.py0000664000175000017500000000465100000000000032721 0ustar00zuulzuul00000000000000# Copyright (C) 2016 Midokura SARL. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import taas from oslo_log import log LOG = log.getLogger(__name__) class ServiceDriverContext: """ServiceDriverContext context base class""" def __init__(self, service_plugin, plugin_context): self._plugin = service_plugin self._plugin_context = plugin_context class TapServiceContext(ServiceDriverContext): def __init__(self, service_plugin, plugin_context, tap_service): super().__init__(service_plugin, plugin_context) self._tap_service = tap_service self._tap_id_association = None self._setup_tap_id_association(tap_service['id']) def _setup_tap_id_association(self, tap_service_id): try: self._tap_id_association = self._plugin.get_tap_id_association( self._plugin_context, tap_service_id) except taas.TapServiceNotFound: LOG.debug("Not found tap_ip_association for tap_service: %s", tap_service_id) @property def tap_service(self): return self._tap_service @property def tap_id_association(self): return self._tap_id_association @tap_id_association.setter def tap_id_association(self, tap_id_association): """Set tap_id_association in context""" self._tap_id_association = tap_id_association class TapFlowContext(ServiceDriverContext): def __init__(self, service_plugin, plugin_context, tap_flow): super().__init__(service_plugin, plugin_context) self._tap_flow = tap_flow @property def tap_flow(self): return self._tap_flow class TapMirrorContext(ServiceDriverContext): def __init__(self, service_plugin, plugin_context, tap_mirror): super().__init__(service_plugin, plugin_context) self._tap_mirror = tap_mirror @property def tap_mirror(self): return self._tap_mirror ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/taas_agent_api.py0000664000175000017500000000553000000000000031076 0ustar00zuulzuul00000000000000# Copyright (C) 2016 Midokura SARL. # Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron_lib import rpc as n_rpc from oslo_log import helpers as log_helpers from oslo_log import log as logging import oslo_messaging as messaging LOG = logging.getLogger(__name__) class TaasAgentApi: """RPC calls to agent APIs""" def __init__(self, topic, host): self.host = host target = messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) def create_tap_service(self, context, tap_service_msg, host): LOG.debug("In RPC Call for Create Tap Service: Host=%s, MSG=%s", host, tap_service_msg) cctxt = self.client.prepare(fanout=True) cctxt.cast(context, 'create_tap_service', tap_service_msg=tap_service_msg, host=host) def create_tap_flow(self, context, tap_flow_msg, host): LOG.debug("In RPC Call for Create Tap Flow: Host=%s, MSG=%s", host, tap_flow_msg) cctxt = self.client.prepare(fanout=True) cctxt.cast(context, 'create_tap_flow', tap_flow_msg=tap_flow_msg, host=host) def delete_tap_service(self, context, tap_service_msg, host): LOG.debug("In RPC Call for Delete Tap Service: Host=%s, MSG=%s", host, tap_service_msg) cctxt = self.client.prepare(fanout=True) cctxt.cast(context, 'delete_tap_service', tap_service_msg=tap_service_msg, host=host) def delete_tap_flow(self, context, tap_flow_msg, host): LOG.debug("In RPC Call for Delete Tap Flow: Host=%s, MSG=%s", host, tap_flow_msg) cctxt = self.client.prepare(fanout=True) cctxt.cast(context, 'delete_tap_flow', tap_flow_msg=tap_flow_msg, host=host) @log_helpers.log_method_call def create_tap_mirror(self, context, tap_mirror_msg, host): cctxt = self.client.prepare(fanout=True) cctxt.cast(context, 'create_tap_mirror', tap_mirror_msg=tap_mirror_msg, host=host) @log_helpers.log_method_call def delete_tap_mirror(self, context, tap_mirror_msg, host): cctxt = self.client.prepare(fanout=True) cctxt.cast(context, 'delete_tap_mirror', tap_mirror_msg=tap_mirror_msg, host=host) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/service_drivers/taas_rpc.py0000664000175000017500000003620700000000000027740 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # Copyright (C) 2016 Midokura SARL. # Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 portbindings from neutron_lib import constants from neutron_lib.db import api as db_api from neutron_lib import exceptions as n_exc from neutron_lib import rpc as n_rpc from neutron_taas.common import constants as taas_consts from neutron_taas.common import topics from neutron_taas.services.taas import service_drivers from neutron_taas.services.taas.service_drivers import (service_driver_context as sd_context) from neutron_taas.services.taas.service_drivers import taas_agent_api from neutron_taas.services.taas import taas_plugin from oslo_config import cfg from oslo_log import helpers as log_helpers from oslo_log import log as logging from oslo_utils import excutils LOG = logging.getLogger(__name__) class TaasCallbacks: def __init__(self, rpc_driver, plugin): super().__init__() self.rpc_driver = rpc_driver self.plugin = plugin def sync_tap_resources(self, context, sync_tap_res, host): """Handle Rpc from Agent to sync up Tap resources.""" LOG.debug("In RPC Call for Sync Tap Resources: MSG=%s", sync_tap_res) # Get list of configured tap-services active_tss = self.plugin.get_tap_services( context, filters={'status': [constants.ACTIVE]}) for ts in active_tss: # If tap-service port is bound to a different host than the one # which sent this RPC, then continue. ts_port = self.plugin.get_port_details( context, ts['port_id']) if ts_port['binding:host_id'] != host: continue driver_context = sd_context.TapServiceContext(self.plugin, context, ts) try: self.rpc_driver.create_tap_service_postcommit(driver_context) except Exception: with excutils.save_and_reraise_exception(): LOG.error("Failed to create tap service on driver," "deleting tap_service %s", ts['id']) # pylint: disable=E1003 super(taas_plugin.TaasPlugin, self.plugin).delete_tap_service(context, ts['id']) # Get all the active tap flows for current tap-service active_tfs = self.plugin.get_tap_flows( context, filters={'tap_service_id': [ts['id']], 'status': [constants.ACTIVE]}) # Filter out the tap flows associated with distinct tap services for tf in active_tfs: driver_context = sd_context.TapFlowContext(self.plugin, context, tf) try: self.rpc_driver.create_tap_flow_postcommit(driver_context) except Exception: with excutils.save_and_reraise_exception(): LOG.error("Failed to create tap flow on driver," "deleting tap_flow %s", tf['id']) # pylint: disable=E1003 super(taas_plugin.TaasPlugin, self.plugin).delete_tap_flow(context, tf['id']) @db_api.CONTEXT_WRITER def set_tap_service_status(self, context, msg, status, host=None): """Handle Rpc from Agent to set the status of Tap resources.""" LOG.info("In RPC Call to set tap service status: Host=%s, " "MSG=%s, STATUS=%s", host, msg, status) # Clear the resource from DB once agent indicates successful deletion # by mech driver. if status == constants.INACTIVE: ts = self.plugin.get_tap_service(context, msg['id']) driver_context = sd_context.TapServiceContext(self.plugin, context, ts) # pylint: disable=E1003 super(taas_plugin.TaasPlugin, self.plugin).delete_tap_service(context, msg['id']) self.plugin.driver.delete_tap_service_postcommit( driver_context) return ts = self.plugin.get_tap_service(context, msg['id']) ts['status'] = status # pylint: disable=E1003 super(taas_plugin.TaasPlugin, self.plugin).update_tap_service( context, msg['id'], {'tap_service': ts}) @db_api.CONTEXT_WRITER def set_tap_flow_status(self, context, msg, status, host=None): """Handle Rpc from Agent to set the status of Tap resources.""" LOG.info("In RPC Call to set tap flow status: Host=%s, " "MSG=%s, STATUS=%s", host, msg, status) # Clear the resource from DB once agent indicates successful deletion # by mech driver. if status == constants.INACTIVE: tf = self.plugin.get_tap_flow(context, msg['id']) driver_context = sd_context.TapFlowContext(self.plugin, context, tf) # pylint: disable=E1003 super(taas_plugin.TaasPlugin, self.plugin).delete_tap_flow( context, msg['id']) self.plugin.driver.delete_tap_flow_postcommit(driver_context) return tf = self.plugin.get_tap_flow(context, msg['id']) tf['status'] = status # pylint: disable=E1003 super(taas_plugin.TaasPlugin, self.plugin).update_tap_flow( context, msg['id'], {'tap_flow': tf}) class TaasRpcDriver(service_drivers.TaasBaseDriver): """Taas Rpc Service Driver class""" def __init__(self, service_plugin): LOG.debug("Loading TaasRpcDriver.") super().__init__(service_plugin) self.endpoints = [TaasCallbacks(self, service_plugin)] self.conn = n_rpc.Connection() self.conn.create_consumer(topics.TAAS_PLUGIN, self.endpoints, fanout=False) self.conn.consume_in_threads() self.agent_rpc = taas_agent_api.TaasAgentApi( topics.TAAS_AGENT, cfg.CONF.host ) def _get_taas_id(self, context, tf): ts = self.service_plugin.get_tap_service(context, tf['tap_service_id']) taas_id = (self.service_plugin.get_tap_id_association( context, tap_service_id=ts['id']))['taas_id'] return taas_id def create_tap_service_precommit(self, context): ts = context.tap_service tap_id_association = context._plugin.create_tap_id_association( context._plugin_context, ts['id']) context.tap_id_association = tap_id_association def create_tap_service_postcommit(self, context): """Send tap service creation RPC message to agent. This RPC message includes taas_id that is added vlan_range_start to so that taas-ovs-agent can use taas_id as VLANID. """ # Get taas id associated with the Tap Service ts = context.tap_service tap_id_association = context.tap_id_association taas_vlan_id = tap_id_association['taas_id'] port = self.service_plugin.get_port_details(context._plugin_context, ts['port_id']) host = port['binding:host_id'] rpc_msg = {'tap_service': ts, 'taas_id': taas_vlan_id, 'port': port} self.agent_rpc.create_tap_service(context._plugin_context, rpc_msg, host) def delete_tap_service_precommit(self, context): """Send tap service deletion RPC message to agent. This RPC message includes taas_id that is added vlan_range_start to so that taas-ovs-agent can use taas_id as VLANID. """ ts = context.tap_service tap_id_association = context.tap_id_association taas_vlan_id = tap_id_association['taas_id'] try: port = self.service_plugin.get_port_details( context._plugin_context, ts['port_id']) host = port['binding:host_id'] except n_exc.PortNotFound: # if not found, we just pass to None port = None host = None rpc_msg = {'tap_service': ts, 'taas_id': taas_vlan_id, 'port': port} self.agent_rpc.delete_tap_service(context._plugin_context, rpc_msg, host) def delete_tap_service_postcommit(self, context): pass def create_tap_flow_precommit(self, context): pass def create_tap_flow_postcommit(self, context): """Send tap flow creation RPC message to agent.""" tf = context.tap_flow taas_id = self._get_taas_id(context._plugin_context, tf) # Extract the host where the source port is located port = self.service_plugin.get_port_details(context._plugin_context, tf['source_port']) host = port['binding:host_id'] port_mac = port['mac_address'] # Extract the tap-service port ts = self.service_plugin.get_tap_service(context._plugin_context, tf['tap_service_id']) ts_port = self.service_plugin.get_port_details( context._plugin_context, ts['port_id']) # Get network data where the tap flow port is located tf_nw = self.service_plugin.get_port_network_data( context._plugin_context, port) # Send RPC message to both the source port host and # tap service(destination) port host rpc_msg = {'tap_flow': tf, 'port_mac': port_mac, 'taas_id': taas_id, 'port': port, 'tap_service_port': ts_port, 'tf_nw': tf_nw } self.agent_rpc.create_tap_flow(context._plugin_context, rpc_msg, host) def delete_tap_flow_precommit(self, context): """Send tap flow deletion RPC message to agent.""" tf = context.tap_flow taas_id = self._get_taas_id(context._plugin_context, tf) # Extract the host where the source port is located port = self.service_plugin.get_port_details(context._plugin_context, tf['source_port']) host = port['binding:host_id'] port_mac = port['mac_address'] # Extract the tap-service port ts = self.service_plugin.get_tap_service(context._plugin_context, tf['tap_service_id']) ts_port = self.service_plugin.get_port_details( context._plugin_context, ts['port_id']) src_vlans_list = [] vlan_filter_list = [] if port.get(portbindings.VNIC_TYPE) == portbindings.VNIC_DIRECT: # Get all the tap Flows that are associated with the Tap service active_tfs = self.service_plugin.get_tap_flows( context._plugin_context, filters={'tap_service_id': [tf['tap_service_id']], 'status': [constants.ACTIVE]}, fields=['source_port', 'vlan_filter']) for tap_flow in active_tfs: source_port = self.service_plugin.get_port_details( context._plugin_context, tap_flow['source_port']) LOG.debug("taas: active TF's source_port %(source_port)s", {'source_port': source_port}) src_vlans = "" if source_port.get(portbindings.VIF_DETAILS): src_vlans = source_port[portbindings.VIF_DETAILS].get( portbindings.VIF_DETAILS_VLAN) # If no VLAN filter configured on source port, # then include all vlans if not src_vlans or src_vlans == '0': src_vlans = taas_consts.VLAN_RANGE src_vlans_list.append(src_vlans) vlan_filter = tap_flow['vlan_filter'] # If no VLAN filter configured for tap-flow, # then include all vlans if not vlan_filter: vlan_filter = taas_consts.VLAN_RANGE vlan_filter_list.append(vlan_filter) # Send RPC message to both the source port host and # tap service(destination) port host rpc_msg = {'tap_flow': tf, 'port_mac': port_mac, 'taas_id': taas_id, 'port': port, 'tap_service_port': ts_port, 'source_vlans_list': src_vlans_list, 'vlan_filter_list': vlan_filter_list} self.agent_rpc.delete_tap_flow(context._plugin_context, rpc_msg, host) def delete_tap_flow_postcommit(self, context): pass @log_helpers.log_method_call def create_tap_mirror_precommit(self, context): pass @log_helpers.log_method_call def create_tap_mirror_postcommit(self, context): """Send Tap Mirror creation RPC message to agent. This RPC message includes .... """ # Get taas id associated with the Tap Service tm = context.tap_mirror port = self.service_plugin.get_port_details(context._plugin_context, tm['port_id']) host = port['binding:host_id'] rpc_msg = {'tap_mirror': tm, 'port': port} self.agent_rpc.create_tap_mirror(context._plugin_context, rpc_msg, host) @log_helpers.log_method_call def delete_tap_mirror_precommit(self, context): """Send Tap Mirror deletion RPC message to agent. This RPC message includes ..... """ tm = context.tap_mirror try: port = self.service_plugin.get_port_details( context._plugin_context, tm['port_id']) host = port['binding:host_id'] except n_exc.PortNotFound: # if not found, we just pass to None port = None host = None rpc_msg = {'tap_mirror': tm, 'port': port} self.agent_rpc.delete_tap_mirror(context._plugin_context, rpc_msg, host) @log_helpers.log_method_call def delete_tap_mirror_postcommit(self, context): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/taas_plugin.py0000664000175000017500000002070000000000000025243 0ustar00zuulzuul00000000000000# Copyright (C) 2016 Midokura SARL. # Copyright (C) 2015 Ericsson AB # Copyright (c) 2015 Gigamon # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.db import servicetype_db as st_db from neutron.services import provider_configuration as pconf from neutron.services import service_base from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib.db import api as db_api from neutron_lib import exceptions as n_exc from neutron_lib.exceptions import taas as taas_exc from neutron_taas.common import constants as taas_consts from neutron_taas.db import taas_db from neutron_taas.services.taas.service_drivers import (service_driver_context as sd_context) from oslo_log import log as logging from oslo_utils import excutils LOG = logging.getLogger(__name__) def add_provider_configuration(type_manager, service_type): type_manager.add_provider_configuration( service_type, pconf.ProviderConfiguration('neutron_taas')) @registry.has_registry_receivers class TaasPlugin(taas_db.Taas_db_Mixin): supported_extension_aliases = ["taas", "taas-vlan-filter"] path_prefix = "/taas" def __init__(self): LOG.debug("TAAS PLUGIN INITIALIZATION") self.service_type_manager = st_db.ServiceTypeManager.get_instance() add_provider_configuration(self.service_type_manager, taas_consts.TAAS) self._load_drivers() self.driver = self._get_driver_for_provider(self.default_provider) LOG.info(("TAAS Plugin initialized using service drivers: " "%(service_drivers)s, default: %(default_driver)s"), {'service_drivers': self.drivers.keys(), 'default_driver': self.default_provider}) def _load_drivers(self): """Loads plugin-drivers specified in configuration.""" self.drivers, self.default_provider = service_base.load_drivers( taas_consts.TAAS, self) def _get_driver_for_provider(self, provider): if provider in self.drivers: return self.drivers[provider] raise n_exc.Invalid("Error retrieving driver for provider %s" % provider) @db_api.CONTEXT_WRITER def create_tap_service(self, context, tap_service): LOG.debug("create_tap_service() called") t_s = tap_service['tap_service'] tenant_id = t_s['tenant_id'] port_id = t_s['port_id'] # Get port details port = self.get_port_details(context, port_id) # Check if the port is owned by the tenant. if port['tenant_id'] != tenant_id: raise taas_exc.PortDoesNotBelongToTenant() # Extract the host where the port is located host = port['binding:host_id'] if host is not None: LOG.debug("Host on which the port is created = %s", host) else: LOG.debug("Host could not be found, Port Binding disbaled!") # Create tap service in the db model ts = super().create_tap_service(context, tap_service) driver_context = sd_context.TapServiceContext(self, context, ts) self.driver.create_tap_service_precommit(driver_context) try: self.driver.create_tap_service_postcommit(driver_context) except Exception: with excutils.save_and_reraise_exception(): LOG.error("Failed to create tap service on driver," "deleting tap_service %s", ts['id']) super().delete_tap_service(context, ts['id']) return ts @db_api.CONTEXT_WRITER def delete_tap_service(self, context, id): LOG.debug("delete_tap_service() called") # Get all the tap Flows that are associated with the Tap service # and delete them as well t_f_collection = self.get_tap_flows( context, filters={'tap_service_id': [id]}, fields=['id']) for t_f in t_f_collection: self.delete_tap_flow(context, t_f['id']) ts = self.get_tap_service(context, id) driver_context = sd_context.TapServiceContext(self, context, ts) if ts['status'] == constants.ACTIVE: ts['status'] = constants.PENDING_DELETE super().update_tap_service( context, id, {'tap_service': ts}) method = self.driver.delete_tap_service_precommit else: super().delete_tap_service(context, id) method = self.driver.delete_tap_service_postcommit try: method(driver_context) except Exception: with excutils.save_and_reraise_exception(): LOG.error("Failed to delete tap service on driver. " "tap_sevice: %s", id) @db_api.CONTEXT_WRITER def create_tap_flow(self, context, tap_flow): LOG.debug("create_tap_flow() called") t_f = tap_flow['tap_flow'] tenant_id = t_f['tenant_id'] # Check if the tenant id of the source port is the same as the # tenant_id of the tap service we are attaching it to. ts = self.get_tap_service(context, t_f['tap_service_id']) ts_tenant_id = ts['tenant_id'] if tenant_id != ts_tenant_id: raise taas_exc.TapServiceNotBelongToTenant() # create tap flow in the db model tf = super().create_tap_flow(context, tap_flow) driver_context = sd_context.TapFlowContext(self, context, tf) self.driver.create_tap_flow_precommit(driver_context) try: self.driver.create_tap_flow_postcommit(driver_context) except Exception: with excutils.save_and_reraise_exception(): LOG.error("Failed to create tap flow on driver," "deleting tap_flow %s", tf['id']) super().delete_tap_flow(context, tf['id']) return tf @db_api.CONTEXT_WRITER def delete_tap_flow(self, context, id): LOG.debug("delete_tap_flow() called") tf = self.get_tap_flow(context, id) driver_context = sd_context.TapFlowContext(self, context, tf) if tf['status'] == constants.ACTIVE: tf['status'] = constants.PENDING_DELETE super().update_tap_flow(context, id, {'tap_flow': tf}) method = self.driver.delete_tap_flow_precommit else: super().delete_tap_flow(context, id) method = self.driver.delete_tap_flow_postcommit try: method(driver_context) except Exception: with excutils.save_and_reraise_exception(): LOG.error("Failed to delete tap flow on driver. " "tap_flow: %s", id) @registry.receives(resources.PORT, [events.PRECOMMIT_DELETE]) def handle_delete_port(self, resource, event, trigger, payload): context = payload.context deleted_port = payload.latest_state if not deleted_port: LOG.error("TaaS: Handle Delete Port: Invalid port object received") return deleted_port_id = deleted_port['id'] LOG.info("TaaS: Handle Delete Port: %s", deleted_port_id) # Get list of configured tap-services t_s_collection = self.get_tap_services( context, filters={'port_id': [deleted_port_id]}, fields=['id']) for t_s in t_s_collection: try: self.delete_tap_service(context, t_s['id']) except taas_exc.TapServiceNotFound: LOG.debug("Not found tap_service: %s", t_s['id']) t_f_collection = self.get_tap_flows( context, filters={'source_port': [deleted_port_id]}, fields=['id']) for t_f in t_f_collection: try: self.delete_tap_flow(context, t_f['id']) except taas_exc.TapFlowNotFound: LOG.debug("Not found tap_flow: %s", t_f['id']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/services/taas/tap_mirror_plugin.py0000664000175000017500000001441600000000000026500 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 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 portbindings from neutron_lib.api.definitions import tap_mirror as t_m_api_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.exceptions import taas as taas_exc from oslo_log import helpers as log_helpers from oslo_log import log as logging from oslo_utils import excutils from neutron_taas.common import constants as taas_consts from neutron_taas.db import tap_mirror_db from neutron_taas.services.taas.service_drivers import (service_driver_context as sd_context) LOG = logging.getLogger(__name__) @registry.has_registry_receivers class TapMirrorPlugin(tap_mirror_db.Taas_mirror_db_mixin): supported_extension_aliases = [t_m_api_def.ALIAS] path_prefix = "/taas" def __init__(self): super().__init__() self.service_type_manager = st_db.ServiceTypeManager.get_instance() self.service_type_manager.add_provider_configuration( taas_consts.TAAS, pconf.ProviderConfiguration('neutron_taas')) # Load the service driver from neutron.conf. self.drivers, self.default_provider = service_base.load_drivers( taas_consts.TAAS, self) # Associate driver names to driver objects for driver_name, driver in self.drivers.items(): driver.name = driver_name self.driver = self._get_driver_for_provider(self.default_provider) LOG.info(("Tap Mirror plugin using service drivers: " "%(service_drivers)s, default: %(default_driver)s"), {'service_drivers': self.drivers.keys(), 'default_driver': self.default_provider}) def _get_driver_for_provider(self, provider): if provider in self.drivers: return self.drivers[provider] raise n_exc.Invalid("Error retrieving driver for provider %s" % provider) @log_helpers.log_method_call def create_tap_mirror(self, context, tap_mirror): t_m = tap_mirror['tap_mirror'] port_id = t_m['port_id'] project_id = t_m['project_id'] with db_api.CONTEXT_READER.using(context): # Get port details port = self.get_port_details(context, port_id) if port['tenant_id'] != project_id: raise taas_exc.PortDoesNotBelongToTenant() host = port[portbindings.HOST_ID] if host is not None: LOG.debug("Host on which the port is created = %s", host) else: LOG.debug("Host could not be found, Port Binding disbaled!") # Fail here? Is it a valid usecase to create a mirror for a # port that is not bound? with db_api.CONTEXT_WRITER.using(context): self._validate_tap_tunnel_id(context, t_m['directions']) tm = super().create_tap_mirror(context, tap_mirror) # Precommit phase, is it necessary? tunnel id check should be in # it.... driver_context = sd_context.TapMirrorContext(self, context, tm) self.driver.create_tap_mirror_precommit(driver_context) # Postcommit phase, do the backend stuff, or fail. try: self.driver.create_tap_mirror_postcommit(driver_context) except Exception: pass return tm def _validate_tap_tunnel_id(self, context, mirror_directions): mirrors = self.get_tap_mirrors(context) for mirror in mirrors: for direction, tunnel_id in mirror['directions'].items(): if tunnel_id in mirror_directions.values(): raise taas_exc.TapMirrorTunnelConflict( tunnel_id=tunnel_id) @log_helpers.log_method_call def delete_tap_mirror(self, context, id): with db_api.CONTEXT_WRITER.using(context): tm = self.get_tap_mirror(context, id) if tm: # Precommit phase driver_context = sd_context.TapMirrorContext( self, context, tm) self.driver.delete_tap_mirror_precommit(driver_context) # check if tunnel id was really deleted super().delete_tap_mirror(context, id) # Postcommit phase, do the backend stuff, or fail. try: self.driver.delete_tap_mirror_postcommit(driver_context) except Exception: with excutils.save_and_reraise_exception(): LOG.error("Failed to delete Tap Mirror on driver. " "tap_mirror: %s", id) @registry.receives(resources.PORT, [events.PRECOMMIT_DELETE]) @log_helpers.log_method_call def handle_delete_port(self, resource, event, trigger, payload): context = payload.context deleted_port = payload.latest_state if not deleted_port: LOG.error("Tap Mirror: Handle Delete Port: " "Invalid port object received") return deleted_port_id = deleted_port['id'] LOG.info("Tap Mirror: Handle Delete Port: %s", deleted_port_id) tap_mirrors = self.get_tap_mirrors( context, filters={'port_id': [deleted_port_id]}, fields=['id']) for t_m in tap_mirrors: try: self.delete_tap_mirror(context, t_m['id']) except taas_exc.TapMirrorNotFound: LOG.debug("Tap Mirror not found: %s", t_m['id']) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/taas_client/0000775000175000017500000000000000000000000022077 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/taas_client/__init__.py0000664000175000017500000000000000000000000024176 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/taas_client/osc/0000775000175000017500000000000000000000000022663 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/taas_client/osc/__init__.py0000664000175000017500000000000000000000000024762 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/taas_client/osc/tap_flow.py0000664000175000017500000002021100000000000025044 0ustar00zuulzuul00000000000000# All Rights Reserved 2020 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from osc_lib.cli import format_columns from osc_lib.cli import identity as identity_utils from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from neutron_taas._i18n import _ from neutron_taas.taas_client.osc import tap_service LOG = logging.getLogger(__name__) TAP_FLOW = 'tap_flow' TAP_FLOWS = '%ss' % TAP_FLOW _attr_map = ( ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('name', 'Name', column_util.LIST_BOTH), ('status', 'Status', column_util.LIST_BOTH), ('source_port', 'source_port', column_util.LIST_BOTH), ('tap_service_id', 'tap_service_id', column_util.LIST_BOTH), ('direction', 'Direction', column_util.LIST_BOTH), ) _formatters = { 'vlan_filter': format_columns.ListColumn, } def _add_updatable_args(parser): parser.add_argument( '--name', help=_('Name of this Tap service.')) parser.add_argument( '--description', help=_('Description for this Tap service.')) class CreateTapFlow(command.ShowOne): _description = _("Create a tap flow") def get_parser(self, prog_name): parser = super().get_parser(prog_name) identity_utils.add_project_owner_option_to_parser(parser) _add_updatable_args(parser) parser.add_argument( '--port', required=True, metavar="SOURCE_PORT", help=_('Source port to which the Tap Flow is connected.')) parser.add_argument( '--tap-service', required=True, metavar="TAP_SERVICE", help=_('Tap Service to which the Tap Flow belongs.')) parser.add_argument( '--direction', required=True, metavar="DIRECTION", choices=['IN', 'OUT', 'BOTH'], type=lambda s: s.upper(), help=_('Direction of the Tap flow. Possible options are: ' 'IN, OUT, BOTH')) parser.add_argument( '--vlan-filter', required=False, metavar="VLAN_FILTER", help=_('VLAN Ids to be mirrored in the form of range string.')) return parser def take_action(self, parsed_args): client = self.app.client_manager.network attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) if parsed_args.port is not None: source_port = client.find_port(parsed_args.port)['id'] attrs['source_port'] = source_port if parsed_args.tap_service is not None: tap_service_id = client.find_tap_service( parsed_args.tap_service)['id'] attrs['tap_service_id'] = tap_service_id if parsed_args.direction is not None: attrs['direction'] = parsed_args.direction if parsed_args.vlan_filter is not None: attrs['vlan_filter'] = parsed_args.vlan_filter if 'project' in parsed_args and parsed_args.project is not None: project_id = identity_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id attrs['tenant_id'] = project_id obj = client.create_tap_flow(**attrs) display_columns, columns = tap_service._get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class ListTapFlow(command.Lister): _description = _("List tap flows that belong to a given tenant") def get_parser(self, prog_name): parser = super().get_parser(prog_name) identity_utils.add_project_owner_option_to_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.network params = {} if parsed_args.project is not None: project_id = identity_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id params['tenant_id'] = project_id objs = client.tap_flows(retrieve_all=True, params=params) headers, columns = column_util.get_column_definitions( _attr_map, long_listing=True) return (headers, (osc_utils.get_dict_properties( s, columns, formatters=_formatters) for s in objs)) class ShowTapFlow(command.ShowOne): _description = _("Show information of a given tap flow") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_FLOW, metavar="<%s>" % TAP_FLOW, help=_("ID or name of tap flow to look up."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network id = client.find_tap_flow(parsed_args.tap_flow, ignore_missing=False).id obj = client.get_tap_flow(id) display_columns, columns = tap_service._get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class DeleteTapFlow(command.Command): _description = _("Delete a tap flow") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_FLOW, metavar="<%s>" % TAP_FLOW, nargs="+", help=_("ID(s) or name(s) of tap flow to delete."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network fails = 0 for id_or_name in parsed_args.tap_flow: try: id = client.find_tap_flow(id_or_name, ignore_missing=False).id client.delete_tap_flow(id) LOG.warning("Tap flow %(id)s deleted", {'id': id}) except Exception as e: fails += 1 LOG.error("Failed to delete tap flow with name or ID " "'%(id_or_name)s': %(e)s", {'id_or_name': id_or_name, 'e': e}) if fails > 0: msg = (_("Failed to delete %(fails)s of %(total)s tap flow.") % {'fails': fails, 'total': len(parsed_args.tap_flow)}) raise exceptions.CommandError(msg) class UpdateTapFlow(command.ShowOne): _description = _("Update a tap flow.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_FLOW, metavar="<%s>" % TAP_FLOW, help=_("ID or name of tap flow to update."), ) _add_updatable_args(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.network original_t_f = client.find_tap_flow(parsed_args.tap_flow, ignore_missing=False).id attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) obj = client.update_tap_flow(original_t_f, **attrs) columns, display_columns = column_util.get_columns(obj, _attr_map) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/taas_client/osc/tap_mirror.py0000664000175000017500000002011700000000000025414 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 logging from osc_lib.cli import identity as identity_utils from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from openstackclient.network.v2 import port as osc_port from neutron_taas._i18n import _ from neutron_taas.taas_client.osc import tap_service LOG = logging.getLogger(__name__) TAP_MIRROR = 'tap_mirror' TAP_MIRRORS = '%ss' % TAP_MIRROR _attr_map = ( ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('name', 'Name', column_util.LIST_BOTH), ('port_id', 'Port', column_util.LIST_BOTH), ('directions', 'Directions', column_util.LIST_LONG_ONLY), ('remote_ip', 'Remote IP', column_util.LIST_BOTH), ('mirror_type', 'Mirror Type', column_util.LIST_LONG_ONLY), ) def _get_columns(item): column_map = {} hidden_columns = ['location', 'tenant_id'] return osc_utils.get_osc_show_columns_for_sdk_resource( item, column_map, hidden_columns ) class CreateTapMirror(command.ShowOne): _description = _("Create a Tap Mirror") def get_parser(self, prog_name): parser = super().get_parser(prog_name) identity_utils.add_project_owner_option_to_parser(parser) tap_service._add_updatable_args(parser) parser.add_argument( '--port', dest='port_id', required=True, metavar="PORT", help=_('Port to which the Tap Mirror is connected.')) parser.add_argument( '--directions', dest='directions', action=osc_port.JSONKeyValueAction, required=True, help=_('A dictionary of direction and tunnel_id. Direction can ' 'be IN and OUT.')) parser.add_argument( '--remote-ip', dest='remote_ip', required=True, help=_('The remote IP of the Tap Mirror, this will be the ' 'remote end of the GRE or ERSPAN v1 tunnel')) parser.add_argument( '--mirror-type', dest='mirror_type', required=True, help=_('The type of the mirroring, it can be gre or erspanv1')) return parser def take_action(self, parsed_args): client = self.app.client_manager.network attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) if parsed_args.port_id is not None: port_id = client.find_port(parsed_args.port_id)['id'] attrs['port_id'] = port_id if parsed_args.directions is not None: attrs['directions'] = parsed_args.directions if parsed_args.remote_ip is not None: attrs['remote_ip'] = parsed_args.remote_ip if parsed_args.mirror_type is not None: attrs['mirror_type'] = parsed_args.mirror_type if 'project' in parsed_args and parsed_args.project is not None: project_id = identity_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id attrs['tenant_id'] = project_id obj = client.create_tap_mirror(**attrs) display_columns, columns = tap_service._get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class ListTapMirror(command.Lister): _description = _("List Tap Mirrors that belong to a given tenant") def get_parser(self, prog_name): parser = super().get_parser(prog_name) identity_utils.add_project_owner_option_to_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.network params = {} if parsed_args.project is not None: project_id = identity_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id params['tenant_id'] = project_id objs = client.tap_mirrors(retrieve_all=True, params=params) headers, columns = column_util.get_column_definitions( _attr_map, long_listing=True) return (headers, (osc_utils.get_dict_properties( s, columns) for s in objs)) class ShowTapMirror(command.ShowOne): _description = _("Show information of a given Tap Mirror") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_MIRROR, metavar="<%s>" % TAP_MIRROR, help=_("ID or name of Tap Mirror to look up."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network id = client.find_tap_mirror(parsed_args.tap_mirror, ignore_missing=False).id obj = client.get_tap_mirror(id) display_columns, columns = tap_service._get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class DeleteTapMirror(command.Command): _description = _("Delete a Tap Mirror") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_MIRROR, metavar="<%s>" % TAP_MIRROR, nargs="+", help=_("ID(s) or name(s) of the Tap Mirror to delete."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network fails = 0 for id_or_name in parsed_args.tap_mirror: try: id = client.find_tap_mirror(id_or_name, ignore_missing=False).id client.delete_tap_mirror(id) LOG.warning("Tap Mirror %(id)s deleted", {'id': id}) except Exception as e: fails += 1 LOG.error("Failed to delete Tap Mirror with name or ID " "'%(id_or_name)s': %(e)s", {'id_or_name': id_or_name, 'e': e}) if fails > 0: msg = (_("Failed to delete %(fails)s of %(total)s Tap Mirror.") % {'fails': fails, 'total': len(parsed_args.tap_mirror)}) raise exceptions.CommandError(msg) class UpdateTapMirror(command.ShowOne): _description = _("Update a Tap Mirror.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_MIRROR, metavar="<%s>" % TAP_MIRROR, help=_("ID or name of the Tap Mirror to update."), ) tap_service._add_updatable_args(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.network original_t_s = client.find_tap_mirror(parsed_args.tap_mirror, ignore_missing=False).id attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) obj = client.update_tap_mirror(original_t_s, **attrs) display_columns, columns = tap_service._get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/taas_client/osc/tap_service.py0000664000175000017500000001650200000000000025545 0ustar00zuulzuul00000000000000# All Rights Reserved 2020 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from osc_lib.cli import identity as identity_utils from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from neutron_taas._i18n import _ LOG = logging.getLogger(__name__) TAP_SERVICE = 'tap_service' TAP_SERVICES = '%ss' % TAP_SERVICE _attr_map = ( ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('name', 'Name', column_util.LIST_BOTH), ('port_id', 'Port', column_util.LIST_BOTH), ('status', 'Status', column_util.LIST_BOTH), ) def _add_updatable_args(parser): parser.add_argument( '--name', help=_('Name of this Tap service.')) parser.add_argument( '--description', help=_('Description for this Tap service.')) def _updatable_args2body(parsed_args, body): for attribute in ['name', 'description']: if (hasattr(parsed_args, attribute) and getattr(parsed_args, attribute) is not None): body[attribute] = getattr(parsed_args, attribute) def _get_columns(item): column_map = {} hidden_columns = ['location', 'tenant_id'] return osc_utils.get_osc_show_columns_for_sdk_resource( item, column_map, hidden_columns ) class CreateTapService(command.ShowOne): _description = _("Create a tap service") def get_parser(self, prog_name): parser = super().get_parser(prog_name) identity_utils.add_project_owner_option_to_parser(parser) _add_updatable_args(parser) parser.add_argument( '--port', dest='port_id', required=True, metavar="PORT", help=_('Port to which the Tap service is connected.')) return parser def take_action(self, parsed_args): client = self.app.client_manager.network attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) if parsed_args.port_id is not None: port_id = client.find_port(parsed_args.port_id)['id'] attrs['port_id'] = port_id if 'project' in parsed_args and parsed_args.project is not None: project_id = identity_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id attrs['tenant_id'] = project_id obj = client.create_tap_service(**attrs) display_columns, columns = _get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class ListTapService(command.Lister): _description = _("List tap services that belong to a given tenant") def get_parser(self, prog_name): parser = super().get_parser(prog_name) identity_utils.add_project_owner_option_to_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.network params = {} if parsed_args.project is not None: project_id = identity_utils.find_project( self.app.client_manager.identity, parsed_args.project, parsed_args.project_domain, ).id params['tenant_id'] = project_id objs = client.tap_services(retrieve_all=True, params=params) headers, columns = column_util.get_column_definitions( _attr_map, long_listing=True) return (headers, (osc_utils.get_dict_properties( s, columns) for s in objs)) class ShowTapService(command.ShowOne): _description = _("Show information of a given tap service") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_SERVICE, metavar="<%s>" % TAP_SERVICE, help=_("ID or name of tap service to look up."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network id = client.find_tap_service(parsed_args.tap_service, ignore_missing=False).id obj = client.get_tap_service(id) display_columns, columns = _get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data class DeleteTapService(command.Command): _description = _("Delete a tap service") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_SERVICE, metavar="<%s>" % TAP_SERVICE, nargs="+", help=_("ID(s) or name(s) of tap service to delete."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network fails = 0 for id_or_name in parsed_args.tap_service: try: id = client.find_tap_service(id_or_name, ignore_missing=False).id client.delete_tap_service(id) LOG.warning("Tap service %(id)s deleted", {'id': id}) except Exception as e: fails += 1 LOG.error("Failed to delete tap service with name or ID " "'%(id_or_name)s': %(e)s", {'id_or_name': id_or_name, 'e': e}) if fails > 0: msg = (_("Failed to delete %(fails)s of %(total)s tap service.") % {'fails': fails, 'total': len(parsed_args.tap_service)}) raise exceptions.CommandError(msg) class UpdateTapService(command.ShowOne): _description = _("Update a tap service.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_SERVICE, metavar="<%s>" % TAP_SERVICE, help=_("ID or name of tap service to update."), ) _add_updatable_args(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.network original_t_s = client.find_tap_service(parsed_args.tap_service, ignore_missing=False).id attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) obj = client.update_tap_service(original_t_s, **attrs) display_columns, columns = _get_columns(obj) data = osc_utils.get_dict_properties(obj, columns) return display_columns, data ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/tests/0000775000175000017500000000000000000000000020753 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/__init__.py0000664000175000017500000000000000000000000023052 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/base.py0000664000175000017500000000452400000000000022244 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslotest import base FAKE_PORT_PARAMS = { 'mac': '52:54:00:12:35:02', 'pci_slot': 3, 'vf_index': '89', 'pf_device': 'net_enp0s3_52_54_00_12_35_02', 'src_vlans': '20'} FAKE_TAP_SERVICE = { 'taas_id': '1234', 'port': { 'id': 'fake_1', 'mac_address': "52:54:00:12:35:02", 'binding:profile': {'pci_slot': 3}, 'binding:vif_details': {'vlan': '20'} } } FAKE_OF_PORT = { 'port_name': 'tap4321', 'ofport': 12, } FAKE_PORT_DICT = { FAKE_OF_PORT['port_name']: 4 } FAKE_TAP_SERVICE_OVS = { 'taas_id': 4321, 'port': { 'id': 'fake_2', 'mac_address': "fa:16:3e:33:0e:d4", 'binding:profile': {}, 'binding:vif_details': { 'connectivity': 'l2', 'port_filter': True, 'ovs_hybrid_plug': False, 'datapath_type': 'system', 'bridge_name': 'br-int' } } } FAKE_TAP_FLOW = { 'taas_id': FAKE_TAP_SERVICE_OVS['taas_id'], 'port': FAKE_TAP_SERVICE['port'], 'port_mac': 'fa:16:3e:5c:67:6a', 'ts_port': FAKE_TAP_SERVICE['port'], 'source_vlans_list': ['4-6', '8-10', '15-18,20'], 'vlan_filter_list': '1-5,9,18,20,27-30,4000-4095', 'tap_flow': { 'direction': 'IN', 'vlan_filter': '20' } } FAKE_TAP_MIRROR_OUT = { 'tap_mirror': { 'id': 'mirror_uuid', 'port_id': 'port_uuid', 'directions': {'OUT': '102'}, 'remote_ip': '100.109.0.48', 'mirror_type': 'gre', }, 'port': { 'id': 'port_uuid', 'mac_address': 'fa:16:3e:69:0e:f3', } } FAKE_TAP_MIRROR_IN = copy.deepcopy(FAKE_TAP_MIRROR_OUT) FAKE_TAP_MIRROR_IN['tap_mirror']['directions'] = {'IN': '101'} class TaasTestCase(base.BaseTestCase): """Test case base class for all unit tests.""" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8472288 tap_as_a_service-15.0.0/neutron_taas/tests/unit/0000775000175000017500000000000000000000000021732 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/__init__.py0000664000175000017500000000000000000000000024031 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/db/0000775000175000017500000000000000000000000022317 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/db/__init__.py0000664000175000017500000000000000000000000024416 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/db/test_migrations.py0000664000175000017500000000410000000000000026077 0ustar00zuulzuul00000000000000# Copyright 2016 VMware, Inc # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg from neutron.db.migration.alembic_migrations import external from neutron.db.migration import cli as migration from neutron.tests.functional.db import test_migrations from neutron.tests.unit import testlib_api from neutron_taas.db import head # EXTERNAL_TABLES should contain all names of tables that are not related to # current repo. EXTERNAL_TABLES = set(external.TABLES) VERSION_TABLE = 'taas_alembic_version' class TestModelsMigrationsTAAS(test_migrations.TestModelsMigrations, testlib_api.MySQLTestCaseMixin, testlib_api.SqlTestCaseLight): 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): return False if type_ == 'index' and reflected and name.startswith("idx_autoinc_"): return False return True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/db/test_taas_db.py0000664000175000017500000002311700000000000025331 0ustar00zuulzuul00000000000000# Copyright 2016 VMware, 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.unit import testlib_api from neutron_lib import context from neutron_lib.exceptions import taas as taas_exc from oslo_utils import importutils from oslo_utils import uuidutils from neutron_taas.db import taas_db DB_PLUGIN_KLAAS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' _uuid = uuidutils.generate_uuid class TaaSDbTestCase(testlib_api.SqlTestCase): """Unit test for TaaS DB support.""" def setUp(self): super().setUp() self.ctx = context.get_admin_context() self.mixin = taas_db.Taas_db_Mixin() self.plugin = importutils.import_object(DB_PLUGIN_KLAAS) self.tenant_id = 'fake-tenant-id' def _get_tap_service_data(self, name='ts-1', port_id=None): port_id = port_id or _uuid() return {"tap_service": {"name": name, "tenant_id": self.tenant_id, "description": "test tap service", "port_id": port_id}} def _get_tap_flow_data(self, tap_service_id, name='tf-1', direction='BOTH', source_port=None, vlan_filter=None): source_port = source_port or _uuid() return {"tap_flow": {"name": name, "tenant_id": self.tenant_id, "description": "test tap flow", "tap_service_id": tap_service_id, "source_port": source_port, "direction": direction, "vlan_filter": vlan_filter}} def _get_tap_service(self, tap_service_id): """Helper method to retrieve tap service.""" with self.ctx.session.begin(): return self.mixin.get_tap_service(self.ctx, tap_service_id) def _get_tap_services(self): """Helper method to retrieve all tap services.""" with self.ctx.session.begin(): return self.mixin.get_tap_services(self.ctx) def _create_tap_service(self, tap_service): """Helper method to create tap service.""" with self.ctx.session.begin(): return self.mixin.create_tap_service(self.ctx, tap_service) def _update_tap_service(self, tap_service_id, tap_service): """Helper method to update tap service.""" with self.ctx.session.begin(): return self.mixin.update_tap_service(self.ctx, tap_service_id, tap_service) def _delete_tap_service(self, tap_service_id): """Helper method to delete tap service.""" with self.ctx.session.begin(): return self.mixin.delete_tap_service(self.ctx, tap_service_id) def _get_tap_flow(self, tap_flow_id): """Helper method to retrieve tap flow.""" with self.ctx.session.begin(): return self.mixin.get_tap_flow(self.ctx, tap_flow_id) def _get_tap_flows(self): """Helper method to retrieve all tap flows.""" with self.ctx.session.begin(): return self.mixin.get_tap_flows(self.ctx) def _create_tap_flow(self, tap_flow): """Helper method to create tap flow.""" with self.ctx.session.begin(): return self.mixin.create_tap_flow(self.ctx, tap_flow) def _update_tap_flow(self, tap_flow_id, tap_flow): """Helper method to update tap flow.""" with self.ctx.session.begin(): return self.mixin.update_tap_flow(self.ctx, tap_flow_id, tap_flow) def _delete_tap_flow(self, tap_flow_id): """Helper method to delete tap flow.""" with self.ctx.session.begin(): return self.mixin.delete_tap_flow(self.ctx, tap_flow_id) def test_tap_service_get(self): """Test to retrieve a tap service from the database.""" name = 'test-tap-service' data = self._get_tap_service_data(name=name) result = self._create_tap_service(data) get_result = self._get_tap_service(result['id']) self.assertEqual(name, get_result['name']) def test_tap_service_create(self): """Test to create a tap service in the database.""" name = 'test-tap-service' port_id = _uuid() data = self._get_tap_service_data(name=name, port_id=port_id) result = self._create_tap_service(data) self.assertEqual(name, result['name']) self.assertEqual(port_id, result['port_id']) def test_tap_service_list(self): """Test to retrieve all tap services from the database.""" name_1 = "ts-1" data_1 = self._get_tap_service_data(name=name_1) name_2 = "ts-2" data_2 = self._get_tap_service_data(name=name_2) self._create_tap_service(data_1) self._create_tap_service(data_2) tap_services = self._get_tap_services() self.assertEqual(2, len(tap_services)) def test_tap_service_update(self): """Test to update a tap service in the database.""" original_name = "ts-1" updated_name = "ts-1-got-updated" data = self._get_tap_service_data(name=original_name) ts = self._create_tap_service(data) updated_data = self._get_tap_service_data(name=updated_name) ts_updated = self._update_tap_service(ts['id'], updated_data) self.assertEqual(updated_name, ts_updated['name']) def test_tap_service_delete(self): """Test to delete a tap service from the database.""" data = self._get_tap_service_data() result = self._create_tap_service(data) self._delete_tap_service(result['id']) self.assertRaises(taas_exc.TapServiceNotFound, self._get_tap_service, result['id']) def test_tap_flow_get(self): """Test to retrieve a tap flow from the database.""" ts_data = self._get_tap_service_data() ts = self._create_tap_service(ts_data) tf_name = 'test-tap-flow' tf_data = self._get_tap_flow_data(tap_service_id=ts['id'], name=tf_name) tf = self._create_tap_flow(tf_data) get_tf = self._get_tap_flow(tf['id']) self.assertEqual(tf_name, get_tf['name']) def test_tap_flow_create(self): """Test to create a tap flow in the database.""" ts_data = self._get_tap_service_data() ts = self._create_tap_service(ts_data) tf_name = 'test-tap-flow' tf_direction = 'IN' tf_source_port = _uuid() tf_data = self._get_tap_flow_data(tap_service_id=ts['id'], name=tf_name, source_port=tf_source_port, direction=tf_direction) tf = self._create_tap_flow(tf_data) self.assertEqual(tf_name, tf['name']) self.assertEqual(tf_direction, tf['direction']) self.assertEqual(tf_source_port, tf['source_port']) def test_tap_flow_create_with_vlan_filter(self): """Test to create a tap flow (with vlan_filter) in the database.""" ts_data = self._get_tap_service_data() ts = self._create_tap_service(ts_data) tf_name = 'test-tap-flow' tf_direction = 'IN' tf_source_port = _uuid() tf_vlan_filter = '9-18,27,36-45' tf_data = self._get_tap_flow_data(tap_service_id=ts['id'], name=tf_name, source_port=tf_source_port, direction=tf_direction, vlan_filter=tf_vlan_filter) tf = self._create_tap_flow(tf_data) self.assertEqual(tf_name, tf['name']) self.assertEqual(tf_direction, tf['direction']) self.assertEqual(tf_source_port, tf['source_port']) self.assertEqual(tf_vlan_filter, tf['vlan_filter']) def test_tap_flow_list(self): """Test to retrieve all tap flows from the database.""" ts_data = self._get_tap_service_data() ts = self._create_tap_service(ts_data) tf_1_name = "tf-1" tf_1_data = self._get_tap_flow_data(tap_service_id=ts['id'], name=tf_1_name) tf_2_name = "tf-2" tf_2_data = self._get_tap_flow_data(tap_service_id=ts['id'], name=tf_2_name) self._create_tap_flow(tf_1_data) self._create_tap_flow(tf_2_data) tap_flows = self._get_tap_flows() self.assertEqual(2, len(tap_flows)) def test_tap_flow_delete(self): """Test to delete a tap flow from the database.""" ts_data = self._get_tap_service_data() ts = self._create_tap_service(ts_data) tf_name = "test-tap-flow" tf_data = self._get_tap_flow_data(tap_service_id=ts['id'], name=tf_name) tf = self._create_tap_flow(tf_data) self._delete_tap_flow(tf['id']) self.assertRaises(taas_exc.TapFlowNotFound, self._get_tap_flow, tf['id']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/db/test_tap_mirror_db.py0000664000175000017500000001125400000000000026556 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.tests.unit import testlib_api from neutron_lib import context from neutron_lib.exceptions import taas as taas_exc from oslo_utils import importutils from oslo_utils import uuidutils from neutron_taas.db import tap_mirror_db DB_PLUGIN_KLAAS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' _uuid = uuidutils.generate_uuid class TapMirrorDbTestCase(testlib_api.SqlTestCase): """Unit test for Tap Mirror DB support.""" def setUp(self): super().setUp() self.ctx = context.get_admin_context() self.db_mixin = tap_mirror_db.Taas_mirror_db_mixin() self.plugin = importutils.import_object(DB_PLUGIN_KLAAS) self.project_id = 'fake-project-id' def _get_tap_mirror_data(self, name='tm-1', port_id=None, directions='{"IN": "99"}', remote_ip='10.99.8.3', mirror_type='erspanv1'): port_id = port_id or _uuid() return {"tap_mirror": {"name": name, "project_id": self.project_id, "description": "test tap mirror", "port_id": port_id, 'directions': directions, 'remote_ip': remote_ip, 'mirror_type': mirror_type } } def _get_tap_mirror(self, tap_mirror_id): """Helper method to retrieve tap Mirror.""" with self.ctx.session.begin(): return self.db_mixin.get_tap_mirror(self.ctx, tap_mirror_id) def _get_tap_mirrors(self): """Helper method to retrieve all tap Mirror.""" with self.ctx.session.begin(): return self.db_mixin.get_tap_mirrors(self.ctx) def _create_tap_mirror(self, tap_mirror): """Helper method to create tap Mirror.""" with self.ctx.session.begin(): return self.db_mixin.create_tap_mirror(self.ctx, tap_mirror) def _update_tap_mirror(self, tap_mirror_id, tap_mirror): """Helper method to update tap Mirror.""" with self.ctx.session.begin(): return self.db_mixin.update_tap_mirror(self.ctx, tap_mirror_id, tap_mirror) def _delete_tap_mirror(self, tap_mirror_id): """Helper method to delete tap Mirror.""" with self.ctx.session.begin(): return self.db_mixin.delete_tap_mirror(self.ctx, tap_mirror_id) def test_tap_mirror_get(self): name = 'test-tap-mirror' data = self._get_tap_mirror_data(name=name) result = self._create_tap_mirror(data) get_result = self._get_tap_mirror(result['id']) self.assertEqual(name, get_result['name']) def test_tap_mirror_create(self): name = 'test-tap-mirror' port_id = _uuid() data = self._get_tap_mirror_data(name=name, port_id=port_id) result = self._create_tap_mirror(data) self.assertEqual(name, result['name']) self.assertEqual(port_id, result['port_id']) def test_tap_mirror_list(self): name_1 = "tm-1" data_1 = self._get_tap_mirror_data(name=name_1) name_2 = "tm-2" data_2 = self._get_tap_mirror_data(name=name_2) self._create_tap_mirror(data_1) self._create_tap_mirror(data_2) tap_mirrors = self._get_tap_mirrors() self.assertEqual(2, len(tap_mirrors)) def test_tap_mirror_update(self): original_name = "tm-1" updated_name = "tm-1-got-updated" data = self._get_tap_mirror_data(name=original_name) tm = self._create_tap_mirror(data) updated_data = self._get_tap_mirror_data(name=updated_name) tm_updated = self._update_tap_mirror(tm['id'], updated_data) self.assertEqual(updated_name, tm_updated['name']) def test_tap_mirror_delete(self): data = self._get_tap_mirror_data() result = self._create_tap_mirror(data) self._delete_tap_mirror(result['id']) self.assertRaises(taas_exc.TapMirrorNotFound, self._get_tap_mirror, result['id']) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/extensions/0000775000175000017500000000000000000000000024131 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/extensions/__init__.py0000664000175000017500000000000000000000000026230 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/extensions/test_taas.py0000664000175000017500000001051400000000000026473 0ustar00zuulzuul00000000000000# Copyright 2017 FUJITSU LABORATORIES LTD. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 webob import exc from oslo_utils import uuidutils from neutron.api import extensions from neutron.conf import common as conf_common from neutron.tests.unit.api.v2 import test_base as test_api_v2 from neutron.tests.unit.extensions import base as test_extensions_base from neutron_lib.api.definitions import taas as taas_api from neutron_taas import extensions as taas_extensions _uuid = uuidutils.generate_uuid _get_path = test_api_v2._get_path TAP_SERVICE_PATH = 'taas/tap_services' TAP_FLOW_PATH = 'taas/tap_flows' class TaasExtensionTestCase(test_extensions_base.ExtensionTestCase): def setUp(self): conf_common.register_core_common_config_opts() extensions.append_api_extensions_path(taas_extensions.__path__) super().setUp() plural_mappings = {'tap_service': 'tap_services', 'tap_flow': 'tap_flows'} self.setup_extension( '{}.{}'.format(taas_extensions.taas.TaasPluginBase.__module__, taas_extensions.taas.TaasPluginBase.__name__), taas_api.ALIAS, taas_extensions.taas.Taas, 'taas', plural_mappings=plural_mappings, translate_resource_name=False) self.instance = self.plugin.return_value def test_create_tap_service(self): tenant_id = _uuid() tap_service_data = { 'tenant_id': tenant_id, 'name': 'MyTap', 'description': 'This is my tap service', 'port_id': _uuid(), 'project_id': tenant_id, } data = {'tap_service': tap_service_data} expected_ret_val = copy.copy(data['tap_service']) expected_ret_val.update({'id': _uuid()}) instance = self.plugin.return_value self.instance.create_tap_service.return_value = expected_ret_val res = self.api.post(_get_path(TAP_SERVICE_PATH, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) instance.create_tap_service.assert_called_with( mock.ANY, tap_service=data) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('tap_service', res) self.assertEqual(expected_ret_val, res['tap_service']) def test_delete_tap_service(self): self._test_entity_delete('tap_service') def _get_expected_tap_flow(self, data): return data def test_create_tap_flow(self): tenant_id = _uuid() tap_flow_data = { 'tenant_id': tenant_id, 'name': 'MyTapFlow', 'description': 'This is my tap flow', 'direction': 'BOTH', 'tap_service_id': _uuid(), 'source_port': _uuid(), 'project_id': tenant_id, } data = {'tap_flow': tap_flow_data} expected_data = self._get_expected_tap_flow(data) expected_ret_val = copy.copy(expected_data['tap_flow']) expected_ret_val.update({'id': _uuid()}) instance = self.plugin.return_value instance.create_tap_flow.return_value = expected_ret_val res = self.api.post(_get_path(TAP_FLOW_PATH, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) instance.create_tap_flow.assert_called_with( mock.ANY, tap_flow=expected_data) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('tap_flow', res) self.assertEqual(expected_ret_val, res['tap_flow']) def test_delete_tap_flow(self): self._test_entity_delete('tap_flow') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/extensions/test_tap_mirror.py0000664000175000017500000000605100000000000027722 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 unittest import mock from webob import exc from oslo_utils import uuidutils from neutron.api import extensions from neutron.conf import common as conf_common from neutron.tests.unit.api.v2 import test_base as test_api_v2 from neutron.tests.unit.extensions import base as test_extensions_base from neutron_lib.api.definitions import tap_mirror as tap_mirror_api from neutron_taas import extensions as taas_extensions TAP_MIRROR_PATH = 'taas/tap_mirrors' class TapMirrorExtensionTestCase(test_extensions_base.ExtensionTestCase): def setUp(self): conf_common.register_core_common_config_opts() extensions.append_api_extensions_path(taas_extensions.__path__) super().setUp() plural_mappings = {'tap_mirror': 'tap_mirrors'} self.setup_extension( '%s.%s' % (taas_extensions.tap_mirror.TapMirrorBase.__module__, taas_extensions.tap_mirror.TapMirrorBase.__name__), tap_mirror_api.ALIAS, taas_extensions.tap_mirror.Tap_mirror, 'taas', plural_mappings=plural_mappings, translate_resource_name=False) self.instance = self.plugin.return_value def test_create_tap_mirror(self): project_id = uuidutils.generate_uuid() tap_mirror_data = { 'project_id': project_id, 'tenant_id': project_id, 'name': 'MyMirror', 'description': 'This is my Tap Mirror', 'port_id': uuidutils.generate_uuid(), 'directions': {"IN": 101}, 'remote_ip': '10.99.8.3', 'mirror_type': 'gre', } data = {'tap_mirror': tap_mirror_data} expected_ret_val = copy.copy(tap_mirror_data) expected_ret_val.update({'id': uuidutils.generate_uuid()}) self.instance.create_tap_mirror.return_value = expected_ret_val res = self.api.post(test_api_v2._get_path(TAP_MIRROR_PATH, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) self.instance.create_tap_mirror.assert_called_with( mock.ANY, tap_mirror=data) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('tap_mirror', res) self.assertEqual(expected_ret_val, res['tap_mirror']) def test_delete_tap_mirror(self): self._test_entity_delete('tap_mirror') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/extensions/test_vlan_filter.py0000664000175000017500000000677600000000000030067 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from neutron_taas.common import constants as taas_consts from neutron_taas.tests.unit.extensions import test_taas as test_taas_ext from oslo_utils import uuidutils from webob import exc from neutron.tests.unit.api.v2 import test_base as test_api_v2 from neutron_lib.api.definitions import taas as taas_api_def from neutron_lib.api.definitions import vlan_filter as vlan_filter_ext import webtest _uuid = uuidutils.generate_uuid _get_path = test_api_v2._get_path class VlanFilterExtensionTestCase(test_taas_ext.TaasExtensionTestCase): def setUp(self): super().setUp() attr_map = taas_api_def.RESOURCE_ATTRIBUTE_MAP attr_map['tap_flows'].update( vlan_filter_ext.RESOURCE_ATTRIBUTE_MAP['tap_flows']) def _get_expected_tap_flow(self, data): ret = super()._get_expected_tap_flow(data) ret['tap_flow'].update( vlan_filter=data['tap_flow'].get('vlan_filter', None)) return ret def test_create_tap_flow_with_vlan_filter(self): tenant_id = _uuid() tap_flow_data = { 'tenant_id': tenant_id, 'name': 'MyTapFlow', 'description': 'This is my tap flow', 'direction': 'BOTH', 'tap_service_id': _uuid(), 'source_port': _uuid(), 'project_id': tenant_id, 'vlan_filter': taas_consts.VLAN_RANGE, } data = {'tap_flow': tap_flow_data} expected_data = self._get_expected_tap_flow(data) expected_ret_val = copy.copy(expected_data['tap_flow']) expected_ret_val.update({'id': _uuid()}) instance = self.plugin.return_value instance.create_tap_flow.return_value = expected_ret_val res = self.api.post( _get_path(test_taas_ext.TAP_FLOW_PATH, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) instance.create_tap_flow.assert_called_with( mock.ANY, tap_flow=expected_data) self.assertEqual(exc.HTTPCreated.code, res.status_int) res = self.deserialize(res) self.assertIn('tap_flow', res) self.assertEqual(expected_ret_val, res['tap_flow']) def test_create_tap_flow_invalid_vlan_filter_value(self): tenant_id = _uuid() tap_flow_data = { 'tenant_id': tenant_id, 'name': 'MyTapFlow', 'description': 'This is my tap flow', 'direction': 'BOTH', 'tap_service_id': _uuid(), 'source_port': _uuid(), 'project_id': tenant_id, 'vlan_filter': '10-25,', } data = {'tap_flow': tap_flow_data} self.assertRaises( webtest.app.AppError, self.api.post, _get_path(test_taas_ext.TAP_FLOW_PATH, fmt=self.fmt), self.serialize(data), content_type='application/%s' % self.fmt) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/0000775000175000017500000000000000000000000023555 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/__init__.py0000664000175000017500000000000000000000000025654 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/drivers/0000775000175000017500000000000000000000000025233 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/drivers/__init__.py0000664000175000017500000000000000000000000027332 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/drivers/test_linux_ovs_taas.py0000664000175000017500000004033000000000000031702 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 unittest import mock from neutron_lib.plugins.ml2 import ovs_constants as n_ovs_consts import neutron_taas.services.taas.drivers.linux.ovs_constants \ as taas_ovs_consts from neutron_taas.services.taas.drivers.linux import ovs_taas from neutron_taas.tests import base class FakeVifPort(object): def __init__(self, port_name, ofport, vif_id, vif_mac, switch): self.port_name = port_name self.ofport = ofport self.vif_id = vif_id self.vif_mac = vif_mac self.switch = switch class FakeBridge(object): def __init__(self, br_name): self.br_name = br_name def add_patch_port(self, local_name, remote_name): pass def get_port_ofport(self, port_name): return base.FAKE_OF_PORT def add_flow(self, **kwargs): pass def delete_flows(self, **kwargs): pass def get_vif_port_by_id(self, port_id): port = base.FAKE_TAP_SERVICE_OVS['port'] return FakeVifPort( port_name=base.FAKE_OF_PORT['port_name'], ofport=base.FAKE_OF_PORT['ofport'], vif_id=port_id, vif_mac=port['mac_address'], switch=port['binding:vif_details']['bridge_name'] ) def get_port_tag_dict(self): return base.FAKE_PORT_DICT class TestOvsDriverTaas(base.TaasTestCase): def setUp(self): super(TestOvsDriverTaas, self).setUp() def _create_tun_flood_flow(self): return '' def _init_taas_driver(self, mock_ovs_ext_api, mock_tap_ext): obj = ovs_taas.OvsTaasDriver() obj.agent_api = mock_ovs_ext_api obj.tunnel_types = 'vxlan' obj._create_tunnel_flood_flow_action = self._create_tun_flood_flow mock_br_int = mock_ovs_ext_api.request_int_br.return_value mock_br_int.add_flow = mock.Mock() mock_br_int.delete_flows = mock.Mock() mock_br_int.add_patch_port = mock.Mock() mock_br_tun = mock_ovs_ext_api.request_tun_br.return_value mock_br_tun.add_flow = mock.Mock() mock_br_tun.delete_flows = mock.Mock() mock_br_tun.add_patch_port = mock.Mock() mock_tap_bridge = mock_tap_ext.return_value mock_tap_bridge.create.return_value = None mock_tap_bridge.add_flow = mock.Mock() mock_tap_bridge.delete_flows = mock.Mock() obj.initialize() return obj, mock_tap_bridge, mock_br_int, mock_br_tun def _vlidate_bridge_initialization(self, mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun): mock_ovs_ext_api.request_int_br.assert_called_once() mock_ovs_ext_api.request_tun_br.assert_called_once() mock_tap_bridge.create.assert_called_once() mock_br_int.add_patch_port.assert_called_once() mock_br_tun.add_patch_port.assert_called_once() mock_tap_bridge.add_patch_port.assert_has_calls([ mock.call('patch-tap-int', 'patch-int-tap'), mock.call('patch-tap-tun', 'patch-tun-tap') ]) mock_tap_bridge.add_flow.assert_has_calls([ mock.call(table=0, priority=1, in_port=mock.ANY, actions='resubmit(,1)'), mock.call(table=0, priority=1, in_port=mock.ANY, actions='resubmit(,2)'), mock.call(table=0, priority=0, actions='drop'), mock.call(table=1, priority=0, actions=mock.ANY), mock.call(table=2, priority=0, actions='drop'), ]) mock_br_tun.add_flow.assert_has_calls([ mock.call(table=0, priority=1, in_port=mock.ANY, actions='resubmit(,30)'), mock.call(table=30, priority=0, actions='resubmit(,31)'), mock.call(table=35, priority=2, reg0=0, actions='resubmit(,36)'), mock.call(table=35, priority=1, reg0=1, actions='resubmit(,36)'), mock.call(table=35, priority=1, reg0=2, actions='resubmit(,37)'), mock.call(table=36, priority=0, actions='drop'), mock.call(table=37, priority=0, actions='drop'), mock.call(table=38, priority=2, reg0=0, actions=mock.ANY), mock.call(table=38, priority=1, reg0=1, actions=mock.ANY), mock.call(table=39, priority=1, actions=mock.ANY) ]) @mock.patch('neutron.plugins.ml2.drivers.openvswitch.agent.' 'ovs_agent_extension_api.OVSAgentExtensionAPI') @mock.patch('neutron_taas.services.taas.drivers.linux.ovs_taas.' 'OVSBridge_tap_extension') def test_create_tap_service(self, mock_tap_ext, mock_api): tap_service = base.FAKE_TAP_SERVICE_OVS mock_ovs_ext_api = mock_api.return_value mock_ovs_ext_api.request_int_br.return_value = FakeBridge('br_int') mock_ovs_ext_api.request_tun_br.return_value = FakeBridge('br_tun') obj, mock_tap_bridge, mock_br_int, mock_br_tun = \ self._init_taas_driver(mock_ovs_ext_api, mock_tap_ext) self._vlidate_bridge_initialization(mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun) mock_tap_bridge.reset_mock() mock_br_int.add_flow.reset_mock() mock_br_tun.add_flow.reset_mock() with mock.patch('neutron.agent.linux.utils.execute'): obj.create_tap_service(tap_service) mock_tap_bridge.add_flow.assert_has_calls([ mock.call(table=1, priority=1, dl_vlan=mock.ANY, actions='output:in_port'), mock.call(table=2, priority=1, dl_vlan=mock.ANY, actions=mock.ANY) ]) mock_br_int.add_flow.assert_called_once_with( table=0, priority=25, in_port=mock.ANY, dl_vlan=mock.ANY, actions=mock.ANY ) mock_br_tun.add_flow.assert_has_calls([ mock.call(table=n_ovs_consts.GRE_TUN_TO_LV, priority=1, tun_id=mock.ANY, actions=mock.ANY), mock.call(table=n_ovs_consts.VXLAN_TUN_TO_LV, priority=1, tun_id=mock.ANY, actions=mock.ANY), mock.call(table=n_ovs_consts.GENEVE_TUN_TO_LV, priority=1, tun_id=mock.ANY, actions=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_DST_CHECK, priority=1, tun_id=mock.ANY, actions='resubmit(,38)') ]) @mock.patch('neutron.plugins.ml2.drivers.openvswitch.agent.' 'ovs_agent_extension_api.OVSAgentExtensionAPI') @mock.patch('neutron_taas.services.taas.drivers.linux.ovs_taas.' 'OVSBridge_tap_extension') def test_delete_tap_service(self, mock_tap_ext, mock_api): tap_service = base.FAKE_TAP_SERVICE_OVS mock_ovs_ext_api = mock_api.return_value mock_ovs_ext_api.request_int_br.return_value = FakeBridge('br_int') mock_ovs_ext_api.request_tun_br.return_value = FakeBridge('br_tun') obj, mock_tap_bridge, mock_br_int, mock_br_tun = \ self._init_taas_driver(mock_ovs_ext_api, mock_tap_ext) self._vlidate_bridge_initialization(mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun) mock_tap_bridge.delete_flows.reset_mock() mock_br_int.delete_flows.reset_mock() mock_br_tun.delete_flows.reset_mock() obj.delete_tap_service(tap_service) mock_tap_bridge.delete_flows.assert_has_calls([ mock.call(table=1, dl_vlan=mock.ANY), mock.call(table=2, dl_vlan=mock.ANY), ]) mock_br_int.delete_flows.assert_called_once_with( table=0, in_port=mock.ANY, dl_vlan=mock.ANY, ) mock_br_tun.delete_flows.assert_has_calls([ mock.call(table=n_ovs_consts.GRE_TUN_TO_LV, tun_id=mock.ANY), mock.call(table=n_ovs_consts.VXLAN_TUN_TO_LV, tun_id=mock.ANY), mock.call(table=n_ovs_consts.GENEVE_TUN_TO_LV, tun_id=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_DST_CHECK, tun_id=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_SRC_CHECK, tun_id=mock.ANY) ]) @mock.patch('neutron.plugins.ml2.drivers.openvswitch.agent.' 'ovs_agent_extension_api.OVSAgentExtensionAPI') @mock.patch('neutron_taas.services.taas.drivers.linux.ovs_taas.' 'OVSBridge_tap_extension') def test_create_tap_flow(self, mock_tap_ext, mock_api): tap_flow = base.FAKE_TAP_FLOW mock_ovs_ext_api = mock_api.return_value mock_ovs_ext_api.request_int_br.return_value = FakeBridge('br_int') mock_ovs_ext_api.request_tun_br.return_value = FakeBridge('br_tun') obj, mock_tap_bridge, mock_br_int, mock_br_tun = \ self._init_taas_driver(mock_ovs_ext_api, mock_tap_ext) self._vlidate_bridge_initialization(mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun) mock_tap_bridge.reset_mock() mock_br_int.add_flow.reset_mock() mock_br_tun.add_flow.reset_mock() obj.create_tap_flow(tap_flow) mock_tap_bridge.add_flow.assert_not_called() mock_br_tun.add_flow.assert_has_calls([ mock.call(table=n_ovs_consts.GRE_TUN_TO_LV, priority=1, tun_id=mock.ANY, actions=mock.ANY), mock.call(table=n_ovs_consts.VXLAN_TUN_TO_LV, priority=1, tun_id=mock.ANY, actions=mock.ANY), mock.call(table=n_ovs_consts.GENEVE_TUN_TO_LV, priority=1, tun_id=mock.ANY, actions=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_SRC_CHECK, priority=1, tun_id=mock.ANY, actions=mock.ANY) ]) mock_br_int.add_flow.assert_called_once_with( table=0, priority=20, dl_dst=tap_flow['port_mac'], actions=mock.ANY ) @mock.patch('neutron.plugins.ml2.drivers.openvswitch.agent.' 'ovs_agent_extension_api.OVSAgentExtensionAPI') @mock.patch('neutron_taas.services.taas.drivers.linux.ovs_taas.' 'OVSBridge_tap_extension') def test_delete_tap_flow(self, mock_tap_ext, mock_api): tap_flow = base.FAKE_TAP_FLOW mock_ovs_ext_api = mock_api.return_value mock_ovs_ext_api.request_int_br.return_value = FakeBridge('br_int') mock_ovs_ext_api.request_tun_br.return_value = FakeBridge('br_tun') obj, mock_tap_bridge, mock_br_int, mock_br_tun = \ self._init_taas_driver(mock_ovs_ext_api, mock_tap_ext) self._vlidate_bridge_initialization(mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun) mock_tap_bridge.reset_mock() mock_br_int.delete_flows.reset_mock() mock_br_tun.delete_flows.reset_mock() obj.delete_tap_flow(tap_flow) mock_tap_bridge.delete_flows.assert_not_called() mock_br_tun.delete_flows.assert_not_called() mock_br_int.delete_flows.assert_called_once_with( table=0, dl_dst=tap_flow['port_mac'] ) @mock.patch('neutron.plugins.ml2.drivers.openvswitch.agent.' 'ovs_agent_extension_api.OVSAgentExtensionAPI') @mock.patch('neutron_taas.services.taas.drivers.linux.ovs_taas.' 'OVSBridge_tap_extension') def test_create_tap_mirror_out_direction(self, mock_tap_ext, mock_api): tap_mirror = base.FAKE_TAP_MIRROR_OUT mock_ovs_ext_api = mock_api.return_value mock_ovs_ext_api.request_int_br.return_value = FakeBridge('br_int') mock_ovs_ext_api.request_tun_br.return_value = FakeBridge('br_tun') obj, mock_tap_bridge, mock_br_int, mock_br_tun = \ self._init_taas_driver(mock_ovs_ext_api, mock_tap_ext) self._vlidate_bridge_initialization(mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun) mock_tap_bridge.reset_mock() mock_br_int.add_flow.reset_mock() obj.create_tap_mirror(tap_mirror) mock_tap_bridge.add_flow.assert_has_calls([ mock.call(table=taas_ovs_consts.TAAS_RECV_LOC, priority=20, dl_src=tap_mirror['port']['mac_address'], actions=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_RECV_LOC, priority=1, dl_dst=tap_mirror['port']['mac_address'], actions=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_RECV_REM, priority=1, dl_dst=tap_mirror['port']['mac_address'], actions=mock.ANY), ]) mock_br_int.add_flow.assert_called_once_with( table=0, priority=20, in_port=mock.ANY, actions=mock.ANY ) @mock.patch('neutron.plugins.ml2.drivers.openvswitch.agent.' 'ovs_agent_extension_api.OVSAgentExtensionAPI') @mock.patch('neutron_taas.services.taas.drivers.linux.ovs_taas.' 'OVSBridge_tap_extension') def test_create_tap_mirror_in_direction(self, mock_tap_ext, mock_api): tap_mirror = base.FAKE_TAP_MIRROR_IN mock_ovs_ext_api = mock_api.return_value mock_ovs_ext_api.request_int_br.return_value = FakeBridge('br_int') mock_ovs_ext_api.request_tun_br.return_value = FakeBridge('br_tun') obj, mock_tap_bridge, mock_br_int, mock_br_tun = \ self._init_taas_driver(mock_ovs_ext_api, mock_tap_ext) self._vlidate_bridge_initialization(mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun) mock_tap_bridge.reset_mock() mock_br_int.add_flow.reset_mock() obj.create_tap_mirror(tap_mirror) mock_tap_bridge.add_flow.assert_has_calls([ mock.call(table=taas_ovs_consts.TAAS_RECV_LOC, priority=20, dl_dst=tap_mirror['port']['mac_address'], actions=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_RECV_LOC, priority=1, dl_dst=tap_mirror['port']['mac_address'], actions=mock.ANY), mock.call(table=taas_ovs_consts.TAAS_RECV_REM, priority=1, dl_dst=tap_mirror['port']['mac_address'], actions=mock.ANY), ]) mock_br_int.add_flow.assert_called_once_with( table=0, priority=20, dl_dst=base.FAKE_TAP_MIRROR_IN['port']['mac_address'], actions=mock.ANY ) @mock.patch('neutron.plugins.ml2.drivers.openvswitch.agent.' 'ovs_agent_extension_api.OVSAgentExtensionAPI') @mock.patch('neutron_taas.services.taas.drivers.linux.ovs_taas.' 'OVSBridge_tap_extension') def test_delete_tap_mirror(self, mock_tap_ext, mock_api): tap_mirror = base.FAKE_TAP_MIRROR_OUT mock_ovs_ext_api = mock_api.return_value mock_ovs_ext_api.request_int_br.return_value = FakeBridge('br_int') mock_ovs_ext_api.request_tun_br.return_value = FakeBridge('br_tun') obj, mock_tap_bridge, mock_br_int, mock_br_tun = \ self._init_taas_driver(mock_ovs_ext_api, mock_tap_ext) self._vlidate_bridge_initialization(mock_ovs_ext_api, mock_tap_bridge, mock_br_int, mock_br_tun) mock_tap_bridge.reset_mock() mock_br_int.delete_flows.reset_mock() obj.delete_tap_mirror(tap_mirror) mock_tap_bridge.delete_flows.assert_has_calls([ mock.call(table=taas_ovs_consts.TAAS_RECV_REM, dl_dst=tap_mirror['port']['mac_address']), mock.call(table=taas_ovs_consts.TAAS_RECV_LOC, dl_dst=tap_mirror['port']['mac_address']), mock.call(table=taas_ovs_consts.TAAS_RECV_LOC, dl_src=tap_mirror['port']['mac_address']), ]) mock_br_int.delete_flows.assert_called_once() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/drivers/test_linux_sriov_nic_driver.py0000664000175000017500000002405700000000000033441 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from neutron_taas.services.taas.drivers.linux import sriov_nic_exceptions \ as taas_exc from neutron_taas.services.taas.drivers.linux import sriov_nic_taas from neutron_taas.tests import base class TestSriovNicTaas(base.TaasTestCase): def setUp(self): super().setUp() @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_create_tap_service(self, mock_sriov_utils): tap_service = base.FAKE_TAP_SERVICE mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ return_value = base.FAKE_PORT_PARAMS obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() obj.create_tap_service(tap_service) mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ assert_called_once_with(tap_service['port']) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_create_tap_service_no_pf_device_and_vf_index( self, mock_sriov_utils): tap_service = base.FAKE_TAP_SERVICE temp_fake_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) temp_fake_port_params['pf_device'] = None temp_fake_port_params['vf_index'] = None mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ return_value = base.FAKE_PORT_PARAMS obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertIsNone(obj.create_tap_service(tap_service)) mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ assert_called_once_with(tap_service['port']) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_delete_tap_service(self, mock_sriov_utils): tap_service = base.FAKE_TAP_SERVICE mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ return_value = base.FAKE_PORT_PARAMS obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() obj.create_tap_service(tap_service) mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ assert_called_once_with(tap_service['port']) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_delete_tap_service_no_pf_device_and_vf_index( self, mock_sriov_utils): tap_service = base.FAKE_TAP_SERVICE temp_fake_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) temp_fake_port_params['pf_device'] = None temp_fake_port_params['vf_index'] = None mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ return_value = base.FAKE_PORT_PARAMS obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertIsNone(obj.create_tap_service(tap_service)) mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ assert_called_once_with(tap_service['port']) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_create_tap_flow(self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'vlan_filter_list': '1-5,9,18,20,27-30,4000-4095', 'tap_flow': {'direction': 'IN', 'vlan_filter': '20'}} src_port_params = ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() obj.create_tap_flow(tap_flow) mock_sriov_utils.SriovNicUtils().execute_sysfs_command.\ assert_called_once_with('add', ts_port_params, src_port_params, '20', False, 'IN') @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_create_tap_flow_no_vlan_filter_on_source_and_probe( self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'tap_flow': {'direction': 'IN', 'vlan_filter': '20'}} src_port_params = ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params['vlan_filter'] = None src_port_params['src_vlans'] = None mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() obj.create_tap_flow(tap_flow) mock_sriov_utils.SriovNicUtils().execute_sysfs_command.\ assert_called_once_with('add', ts_port_params, src_port_params, '20', False, 'IN') @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_create_tap_flow_no_source_pci_slot( self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'tap_flow': {'direction': 'IN', 'vlan_filter': 20}} src_port_params = ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) src_port_params['pci_slot'] = None mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertRaises( taas_exc.PciSlotNotFound, obj.create_tap_flow, tap_flow) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_create_tap_flow_no_ts_pci_slot( self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'tap_flow': {'direction': 'IN', 'vlan_filter': 20}} src_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params['pci_slot'] = None mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertRaises( taas_exc.PciSlotNotFound, obj.create_tap_flow, tap_flow) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_create_tap_flow_different_pf_devices( self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'tap_flow': {'direction': 'IN', 'vlan_filter': 20}} src_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params['pf_device'] = 'net_enp0s3_52_54_00_12_35_02' src_port_params['pf_device'] = 'net_enp0s8_52_54_00_12_35_01' mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertIsNotNone(obj.create_tap_flow, tap_flow) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_delete_tap_flow(self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'source_vlans_list': ['4-6', '8-10', '15-18,20'], 'vlan_filter_list': ['1-5,9,18,20,27-30,4000-4095'], 'tap_flow': {'direction': 'IN', 'vlan_filter': '20'}} src_port_params = ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertIsNone(obj.delete_tap_flow(tap_flow)) self.assertEqual(2, mock_sriov_utils.SriovNicUtils(). execute_sysfs_command.call_count) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_delete_tap_flow_no_source_pci_slot( self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'source_vlans_list': [4, 5, 9], 'tap_flow': {'direction': 'IN', 'vlan_filter': 20}} src_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) src_port_params['pci_slot'] = None mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertRaises(taas_exc.PciSlotNotFound, obj.delete_tap_flow, tap_flow) @mock.patch.object(sriov_nic_taas, 'sriov_utils') def test_delete_tap_flow_no_ts_pci_slot( self, mock_sriov_utils): tap_flow = {'port': base.FAKE_TAP_SERVICE['port'], 'tap_service_port': base.FAKE_TAP_SERVICE['port'], 'tap_flow': {'direction': 'IN', 'vlan_filter': 20}} src_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params = copy.deepcopy(base.FAKE_PORT_PARAMS) ts_port_params['pci_slot'] = None mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\ side_effect = [src_port_params, ts_port_params] obj = sriov_nic_taas.SriovNicTaasDriver() obj.initialize() self.assertRaises(taas_exc.PciSlotNotFound, obj.delete_tap_flow, tap_flow) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/drivers/test_linux_sriov_utils.py0000664000175000017500000003030000000000000032441 0ustar00zuulzuul00000000000000# Copyright (C) 2018 AT&T # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This class implements a utility functions for SRIOV NIC Switch Driver # import copy import re from unittest import mock from neutron_taas.common import utils as common_utils from neutron_taas.services.taas.drivers.linux import sriov_nic_exceptions \ as taas_exc from neutron_taas.services.taas.drivers.linux import sriov_nic_utils from neutron_taas.tests import base FAKE_SRIOV_PORT = { 'id': 'fake_1', 'mac_address': "52:54:00:12:35:02", 'binding:profile': { 'pci_slot': None}, 'binding:vif_details': {'vlan': 20} } class TestSriovNicUtils(base.TaasTestCase): def setUp(self): super().setUp() def test_get_sysfs_netdev_path_with_pf_interface(self): self.assertEqual( "/sys/bus/pci/devices/12/physfn/net", sriov_nic_utils.SriovNicUtils(). _get_sysfs_netdev_path(12, True)) def test_get_sysfs_netdev_path_without_pf_interface(self): self.assertEqual( "/sys/bus/pci/devices/12/net", sriov_nic_utils.SriovNicUtils(). _get_sysfs_netdev_path(12, False)) @mock.patch.object(sriov_nic_utils, 'os') def test_get_ifname_by_pci_address(self, mock_os): mock_os.listdir.return_value = ['random1', 'random2'] self.assertEqual(sriov_nic_utils.SriovNicUtils(). get_ifname_by_pci_address(12, False), 'random2') @mock.patch.object(sriov_nic_utils, 'os') def test_get_ifname_by_pci_address_no_dev_info(self, mock_os): mock_os.listdir.return_value = list() self.assertRaises( taas_exc.PciDeviceNotFoundById, sriov_nic_utils.SriovNicUtils().get_ifname_by_pci_address, 12, 9) @mock.patch.object(sriov_nic_utils, 'os') @mock.patch.object(sriov_nic_utils, 'open', create=True) def test_get_mac_by_pci_address(self, mock_open, mock_os): mock_os.listdir.return_value = ['random1', 'random2'] mock_os.path.join.return_value = 'random' fake_file_handle = ["52:54:00:12:35:02"] fake_file_iter = fake_file_handle.__iter__() mock_open.return_value.__enter__.return_value = fake_file_iter self.assertEqual( "52:54:00:12:35:02", sriov_nic_utils.SriovNicUtils(). get_mac_by_pci_address(12, False)) @mock.patch.object(sriov_nic_utils, 'os') @mock.patch.object(sriov_nic_utils, 'open', create=True) def test_get_mac_by_pci_address_no_content(self, mock_open, mock_os): mock_os.listdir.return_value = ['random1', 'random2'] mock_os.path.join.return_value = 'random' fake_file_handle = [] fake_file_iter = fake_file_handle.__iter__() mock_open.return_value.__enter__.return_value = fake_file_iter self.assertRaises( taas_exc.PciDeviceNotFoundById, sriov_nic_utils.SriovNicUtils().get_mac_by_pci_address, 12, False) @mock.patch.object(sriov_nic_utils, 'os') def test_get_mac_by_pci_address_wrong_dev_path(self, mock_os): mock_os.listdir.return_value = ['random1', 'random2'] mock_os.path.join.return_value = 'random' self.assertRaises( taas_exc.PciDeviceNotFoundById, sriov_nic_utils.SriovNicUtils().get_mac_by_pci_address, 12, False) @mock.patch.object(sriov_nic_utils, 'os') @mock.patch.object(sriov_nic_utils, 'open', create=True) def test_get_net_name_by_vf_pci_address(self, mock_open, mock_os): mock_os.listdir.return_value = ['enp0s3', 'enp0s2'] mock_os.path.join.return_value = 'random' fake_file_handle = ["52:54:00:12:35:02"] fake_file_iter = fake_file_handle.__iter__() mock_open.return_value.__enter__.return_value = fake_file_iter self.assertEqual( 'net_enp0s3_52_54_00_12_35_02', sriov_nic_utils.SriovNicUtils(). get_net_name_by_vf_pci_address(12)) def _common_merge_utility(self, value): output_list = list() for v in value: output_list.append(v) return output_list def test_get_ranges_str_from_list(self): input_list = [4, 11, 12, 13, 25, 26, 27] self.assertEqual("4,11-13,25-27", common_utils. get_ranges_str_from_list(input_list)) def test_get_list_from_ranges_str(self): input_str = "4,6,10-13,25-27" expected_output = [4, 6, 10, 11, 12, 13, 25, 26, 27] self.assertEqual(expected_output, common_utils. get_list_from_ranges_str(input_str)) def test_get_vf_num_by_pci_address_neg(self): self.assertRaises( taas_exc.PciDeviceNotFoundById, sriov_nic_utils.SriovNicUtils().get_vf_num_by_pci_address, 12) @mock.patch.object(sriov_nic_utils, 'glob') @mock.patch.object(sriov_nic_utils, 're') @mock.patch.object(sriov_nic_utils, 'os') def test_get_vf_num_by_pci_address(self, mock_os, mock_re, mock_glob): mock_glob.iglob.return_value = ['file1'] mock_os.readlink.return_value = 12 mock_re.compile().search.return_value = re.match(r"(\d+)", "89") self.assertEqual( '89', sriov_nic_utils.SriovNicUtils(). get_vf_num_by_pci_address(12)) @mock.patch.object(sriov_nic_utils, 'glob') @mock.patch.object(sriov_nic_utils, 're') @mock.patch.object(sriov_nic_utils, 'os') @mock.patch.object(sriov_nic_utils, 'open', create=True) @mock.patch.object(sriov_nic_utils, 'portbindings') def test_get_sriov_port_params(self, mock_port_bindings, mock_open, mock_os, mock_re, mock_glob): sriov_port = copy.deepcopy(FAKE_SRIOV_PORT) fake_profile = mock_port_bindings.PROFILE = 'binding:profile' mock_port_bindings.VIF_DETAILS = 'binding:vif_details' sriov_port[fake_profile]['pci_slot'] = 3 mock_glob.iglob.return_value = ['file1'] mock_os.readlink.return_value = 12 mock_re.compile().search.return_value = re.match(r"(\d+)", "89") mock_os.listdir.return_value = ['net_enp0s2_52_54_00_12_35_02', 'net_enp0s3_52_54_00_12_35_02'] mock_os.path.join.return_value = 'random' fake_file_handle = ["52:54:00:12:35:02"] fake_file_iter = fake_file_handle.__iter__() mock_open.return_value.__enter__.return_value = fake_file_iter expected_output = { 'mac': '52:54:00:12:35:02', 'pci_slot': 3, 'vf_index': '89', 'pf_device': 'net_enp0s3_52_54_00_12_35_02', 'src_vlans': 20} self.assertEqual( expected_output, sriov_nic_utils.SriovNicUtils(). get_sriov_port_params(sriov_port)) @mock.patch.object(sriov_nic_utils, 'glob') @mock.patch.object(sriov_nic_utils, 're') @mock.patch.object(sriov_nic_utils, 'os') @mock.patch.object(sriov_nic_utils, 'open', create=True) @mock.patch.object(sriov_nic_utils, 'portbindings') def test_get_sriov_port_params_no_pci_slot(self, mock_port_bindings, mock_open, mock_os, mock_re, mock_glob): sriov_port = copy.deepcopy(FAKE_SRIOV_PORT) mock_port_bindings.PROFILE = 'binding:profile' mock_port_bindings.VIF_DETAILS = 'binding:vif_details' mock_glob.iglob.return_value = ['file1'] mock_os.readlink.return_value = 12 mock_re.compile().search.return_value = re.match(r"(\d+)", "89") mock_os.listdir.return_value = ['enp0s3', 'enp0s2'] mock_os.path.join.return_value = 'random' fake_file_handle = ["52:54:00:12:35:02"] fake_file_iter = fake_file_handle.__iter__() mock_open.return_value.__enter__.return_value = fake_file_iter self.assertIsNone(sriov_nic_utils.SriovNicUtils(). get_sriov_port_params(sriov_port)) @mock.patch.object(sriov_nic_utils, 'utils') @mock.patch.object(sriov_nic_utils, 'os') def test_execute_sysfs_command_egress_add(self, mock_os, mock_neutron_utils): sriov_nic_utils.SriovNicUtils().execute_sysfs_command( 'add', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'}, "4,11-13", True, "OUT") egress_cmd = ['i40e_sysfs_command', 'p2p1', '18', 'egress_mirror', 'add', '9'] mock_neutron_utils.execute.assert_called_once_with( egress_cmd, run_as_root=True, privsep_exec=True) @mock.patch.object(sriov_nic_utils, 'utils') @mock.patch.object(sriov_nic_utils, 'os') def test_execute_sysfs_command_ingress_add(self, mock_os, mock_neutron_utils): sriov_nic_utils.SriovNicUtils().execute_sysfs_command( 'add', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'}, "4,11-13", True, "IN") ingress_cmd = ['i40e_sysfs_command', 'p2p1', '18', 'ingress_mirror', 'add', '9'] mock_neutron_utils.execute.assert_called_once_with( ingress_cmd, run_as_root=True, privsep_exec=True) @mock.patch.object(sriov_nic_utils, 'utils') @mock.patch.object(sriov_nic_utils, 'os') def test_execute_sysfs_command_both_add( self, mock_os, mock_neutron_utils): sriov_nic_utils.SriovNicUtils().execute_sysfs_command( 'add', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'}, "4,11-13", True, "BOTH") self.assertEqual(2, mock_neutron_utils.execute.call_count) @mock.patch.object(sriov_nic_utils, 'utils') @mock.patch.object(sriov_nic_utils, 'os') def test_execute_sysfs_command_egress_rem(self, mock_os, mock_neutron_utils): sriov_nic_utils.SriovNicUtils().execute_sysfs_command( 'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'}, "4,11-13", True, "OUT") egress_cmd = ['i40e_sysfs_command', 'p2p1', '18', 'egress_mirror', 'rem', '9'] mock_neutron_utils.execute.assert_called_once_with( egress_cmd, run_as_root=True, privsep_exec=True) @mock.patch.object(sriov_nic_utils, 'utils') @mock.patch.object(sriov_nic_utils, 'os') def test_execute_sysfs_command_ingress_rem(self, mock_os, mock_neutron_utils): sriov_nic_utils.SriovNicUtils().execute_sysfs_command( 'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'}, "4,11-13", True, "IN") ingress_cmd = ['i40e_sysfs_command', 'p2p1', '18', 'ingress_mirror', 'rem', '9'] mock_neutron_utils.execute.assert_called_once_with( ingress_cmd, run_as_root=True, privsep_exec=True) @mock.patch.object(sriov_nic_utils, 'utils') @mock.patch.object(sriov_nic_utils, 'os') def test_execute_sysfs_command_both_rem( self, mock_os, mock_neutron_utils): sriov_nic_utils.SriovNicUtils().execute_sysfs_command( 'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'}, "4,11-13", True, "BOTH") self.assertEqual(2, mock_neutron_utils.execute.call_count) @mock.patch.object(sriov_nic_utils, 'utils') @mock.patch.object(sriov_nic_utils, 'os') def test_execute_sysfs_command_not_both_vf_to_vf_all_vlans_False( self, mock_os, mock_neutron_utils): cmd = ['i40e_sysfs_command', 'p2p1', '9', 'vlan_mirror', 'rem', '4,11-13'] sriov_nic_utils.SriovNicUtils().execute_sysfs_command( 'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'}, "4,11-13", False, "FAKE") mock_neutron_utils.execute.assert_called_once_with( cmd, run_as_root=True, privsep_exec=True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/0000775000175000017500000000000000000000000024357 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/__init__.py0000664000175000017500000000000000000000000026456 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/ovsdb/0000775000175000017500000000000000000000000025474 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/ovsdb/__init__.py0000664000175000017500000000000000000000000027573 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/ovsdb/schema_files/0000775000175000017500000000000000000000000030116 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/ovsdb/schema_files/ovn-nb.ovsschema0000664000175000017500000010227500000000000033236 0ustar00zuulzuul00000000000000{ "name": "OVN_Northbound", "version": "7.1.0", "cksum": "217362582 33949", "tables": { "NB_Global": { "columns": { "name": {"type": "string"}, "nb_cfg": {"type": {"key": "integer"}}, "nb_cfg_timestamp": {"type": {"key": "integer"}}, "sb_cfg": {"type": {"key": "integer"}}, "sb_cfg_timestamp": {"type": {"key": "integer"}}, "hv_cfg": {"type": {"key": "integer"}}, "hv_cfg_timestamp": {"type": {"key": "integer"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "connections": { "type": {"key": {"type": "uuid", "refTable": "Connection"}, "min": 0, "max": "unlimited"}}, "ssl": { "type": {"key": {"type": "uuid", "refTable": "SSL"}, "min": 0, "max": 1}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "ipsec": {"type": "boolean"}}, "maxRows": 1, "isRoot": true}, "Copp": { "columns": { "name": {"type": "string"}, "meters": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, "Logical_Switch": { "columns": { "name": {"type": "string"}, "ports": {"type": {"key": {"type": "uuid", "refTable": "Logical_Switch_Port", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "acls": {"type": {"key": {"type": "uuid", "refTable": "ACL", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "qos_rules": {"type": {"key": {"type": "uuid", "refTable": "QoS", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "load_balancer": {"type": {"key": {"type": "uuid", "refTable": "Load_Balancer", "refType": "weak"}, "min": 0, "max": "unlimited"}}, "load_balancer_group": { "type": {"key": {"type": "uuid", "refTable": "Load_Balancer_Group"}, "min": 0, "max": "unlimited"}}, "dns_records": {"type": {"key": {"type": "uuid", "refTable": "DNS", "refType": "weak"}, "min": 0, "max": "unlimited"}}, "copp": {"type": {"key": {"type": "uuid", "refTable": "Copp", "refType": "weak"}, "min": 0, "max": 1}}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "forwarding_groups": { "type": {"key": {"type": "uuid", "refTable": "Forwarding_Group", "refType": "strong"}, "min": 0, "max": "unlimited"}}}, "isRoot": true}, "Logical_Switch_Port": { "columns": { "name": {"type": "string"}, "type": {"type": "string"}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "parent_name": {"type": {"key": "string", "min": 0, "max": 1}}, "tag_request": { "type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 4095}, "min": 0, "max": 1}}, "tag": { "type": {"key": {"type": "integer", "minInteger": 1, "maxInteger": 4095}, "min": 0, "max": 1}}, "addresses": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, "dynamic_addresses": {"type": {"key": "string", "min": 0, "max": 1}}, "port_security": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, "up": {"type": {"key": "boolean", "min": 0, "max": 1}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, "dhcpv4_options": {"type": {"key": {"type": "uuid", "refTable": "DHCP_Options", "refType": "weak"}, "min": 0, "max": 1}}, "dhcpv6_options": {"type": {"key": {"type": "uuid", "refTable": "DHCP_Options", "refType": "weak"}, "min": 0, "max": 1}}, "mirror_rules": {"type": {"key": {"type": "uuid", "refTable": "Mirror", "refType": "weak"}, "min": 0, "max": "unlimited"}}, "ha_chassis_group": { "type": {"key": {"type": "uuid", "refTable": "HA_Chassis_Group", "refType": "strong"}, "min": 0, "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": false}, "Forwarding_Group": { "columns": { "name": {"type": "string"}, "vip": {"type": "string"}, "vmac": {"type": "string"}, "liveness": {"type": "boolean"}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "child_port": {"type": {"key": "string", "min": 1, "max": "unlimited"}}}, "isRoot": false}, "Address_Set": { "columns": { "name": {"type": "string"}, "addresses": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, "Port_Group": { "columns": { "name": {"type": "string"}, "ports": {"type": {"key": {"type": "uuid", "refTable": "Logical_Switch_Port", "refType": "weak"}, "min": 0, "max": "unlimited"}}, "acls": {"type": {"key": {"type": "uuid", "refTable": "ACL", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, "Load_Balancer": { "columns": { "name": {"type": "string"}, "vips": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "protocol": { "type": {"key": {"type": "string", "enum": ["set", ["tcp", "udp", "sctp"]]}, "min": 0, "max": 1}}, "health_check": {"type": { "key": {"type": "uuid", "refTable": "Load_Balancer_Health_Check", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "ip_port_mappings": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "selection_fields": { "type": {"key": {"type": "string", "enum": ["set", ["eth_src", "eth_dst", "ip_src", "ip_dst", "tp_src", "tp_dst"]]}, "min": 0, "max": "unlimited"}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true}, "Load_Balancer_Group": { "columns": { "name": {"type": "string"}, "load_balancer": {"type": {"key": {"type": "uuid", "refTable": "Load_Balancer", "refType": "weak"}, "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, "Load_Balancer_Health_Check": { "columns": { "vip": {"type": "string"}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "ACL": { "columns": { "name": {"type": {"key": {"type": "string", "maxLength": 63}, "min": 0, "max": 1}}, "priority": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 32767}}}, "direction": {"type": {"key": {"type": "string", "enum": ["set", ["from-lport", "to-lport"]]}}}, "match": {"type": "string"}, "action": {"type": {"key": {"type": "string", "enum": ["set", ["allow", "allow-related", "allow-stateless", "drop", "reject", "pass"]]}}}, "log": {"type": "boolean"}, "severity": {"type": {"key": {"type": "string", "enum": ["set", ["alert", "warning", "notice", "info", "debug"]]}, "min": 0, "max": 1}}, "meter": {"type": {"key": "string", "min": 0, "max": 1}}, "label": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 4294967295}}}, "tier": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 3}}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "QoS": { "columns": { "priority": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 32767}}}, "direction": {"type": {"key": {"type": "string", "enum": ["set", ["from-lport", "to-lport"]]}}}, "match": {"type": "string"}, "action": {"type": {"key": {"type": "string", "enum": ["set", ["dscp"]]}, "value": {"type": "integer", "minInteger": 0, "maxInteger": 63}, "min": 0, "max": "unlimited"}}, "bandwidth": {"type": {"key": {"type": "string", "enum": ["set", ["rate", "burst"]]}, "value": {"type": "integer", "minInteger": 1, "maxInteger": 4294967295}, "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "Mirror": { "columns": { "name": {"type": "string"}, "filter": {"type": {"key": {"type": "string", "enum": ["set", ["from-lport", "to-lport", "both"]]}}}, "sink":{"type": "string"}, "type": {"type": {"key": {"type": "string", "enum": ["set", ["gre", "erspan", "local"]]}}}, "index": {"type": "integer"}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, "Meter": { "columns": { "name": {"type": "string"}, "unit": {"type": {"key": {"type": "string", "enum": ["set", ["kbps", "pktps"]]}}}, "bands": {"type": {"key": {"type": "uuid", "refTable": "Meter_Band", "refType": "strong"}, "min": 1, "max": "unlimited"}}, "fair": {"type": {"key": "boolean", "min": 0, "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, "Meter_Band": { "columns": { "action": {"type": {"key": {"type": "string", "enum": ["set", ["drop"]]}}}, "rate": {"type": {"key": {"type": "integer", "minInteger": 1, "maxInteger": 4294967295}}}, "burst_size": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 4294967295}}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "Logical_Router": { "columns": { "name": {"type": "string"}, "ports": {"type": {"key": {"type": "uuid", "refTable": "Logical_Router_Port", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "static_routes": {"type": {"key": {"type": "uuid", "refTable": "Logical_Router_Static_Route", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "policies": { "type": {"key": {"type": "uuid", "refTable": "Logical_Router_Policy", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, "nat": {"type": {"key": {"type": "uuid", "refTable": "NAT", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "load_balancer": {"type": {"key": {"type": "uuid", "refTable": "Load_Balancer", "refType": "weak"}, "min": 0, "max": "unlimited"}}, "load_balancer_group": { "type": {"key": {"type": "uuid", "refTable": "Load_Balancer_Group"}, "min": 0, "max": "unlimited"}}, "copp": {"type": {"key": {"type": "uuid", "refTable": "Copp", "refType": "weak"}, "min": 0, "max": 1}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true}, "Logical_Router_Port": { "columns": { "name": {"type": "string"}, "gateway_chassis": { "type": {"key": {"type": "uuid", "refTable": "Gateway_Chassis", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "ha_chassis_group": { "type": {"key": {"type": "uuid", "refTable": "HA_Chassis_Group", "refType": "strong"}, "min": 0, "max": 1}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "networks": {"type": {"key": "string", "min": 1, "max": "unlimited"}}, "mac": {"type": "string"}, "peer": {"type": {"key": "string", "min": 0, "max": 1}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, "ipv6_ra_configs": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "ipv6_prefix": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "status": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": false}, "Logical_Router_Static_Route": { "columns": { "route_table": {"type": "string"}, "ip_prefix": {"type": "string"}, "policy": {"type": {"key": {"type": "string", "enum": ["set", ["src-ip", "dst-ip"]]}, "min": 0, "max": 1}}, "nexthop": {"type": "string"}, "output_port": {"type": {"key": "string", "min": 0, "max": 1}}, "bfd": {"type": {"key": {"type": "uuid", "refTable": "BFD", "refType": "weak"}, "min": 0, "max": 1}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "Logical_Router_Policy": { "columns": { "priority": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 32767}}}, "match": {"type": "string"}, "action": {"type": { "key": {"type": "string", "enum": ["set", ["allow", "drop", "reroute"]]}}}, "nexthop": {"type": {"key": "string", "min": 0, "max": 1}}, "nexthops": {"type": { "key": "string", "min": 0, "max": "unlimited"}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "NAT": { "columns": { "external_ip": {"type": "string"}, "external_mac": {"type": {"key": "string", "min": 0, "max": 1}}, "external_port_range": {"type": "string"}, "logical_ip": {"type": "string"}, "logical_port": {"type": {"key": "string", "min": 0, "max": 1}}, "type": {"type": {"key": {"type": "string", "enum": ["set", ["dnat", "snat", "dnat_and_snat" ]]}}}, "allowed_ext_ips": {"type": { "key": {"type": "uuid", "refTable": "Address_Set", "refType": "strong"}, "min": 0, "max": 1}}, "exempted_ext_ips": {"type": { "key": {"type": "uuid", "refTable": "Address_Set", "refType": "strong"}, "min": 0, "max": 1}}, "gateway_port": { "type": {"key": {"type": "uuid", "refTable": "Logical_Router_Port", "refType": "weak"}, "min": 0, "max": 1}}, "options": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "DHCP_Options": { "columns": { "cidr": {"type": "string"}, "options": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true}, "Connection": { "columns": { "target": {"type": "string"}, "max_backoff": {"type": {"key": {"type": "integer", "minInteger": 1000}, "min": 0, "max": 1}}, "inactivity_probe": {"type": {"key": "integer", "min": 0, "max": 1}}, "other_config": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "is_connected": {"type": "boolean", "ephemeral": true}, "status": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}}, "indexes": [["target"]]}, "DNS": { "columns": { "records": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true}, "SSL": { "columns": { "private_key": {"type": "string"}, "certificate": {"type": "string"}, "ca_cert": {"type": "string"}, "bootstrap_ca_cert": {"type": "boolean"}, "ssl_protocols": {"type": "string"}, "ssl_ciphers": {"type": "string"}, "external_ids": {"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "maxRows": 1}, "Gateway_Chassis": { "columns": { "name": {"type": "string"}, "chassis_name": {"type": "string"}, "priority": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 32767}}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": false}, "HA_Chassis": { "columns": { "chassis_name": {"type": "string"}, "priority": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 32767}}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, "HA_Chassis_Group": { "columns": { "name": {"type": "string"}, "ha_chassis": { "type": {"key": {"type": "uuid", "refTable": "HA_Chassis", "refType": "strong"}, "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, "BFD": { "columns": { "logical_port": {"type": "string"}, "dst_ip": {"type": "string"}, "min_tx": {"type": {"key": {"type": "integer", "minInteger": 1}, "min": 0, "max": 1}}, "min_rx": {"type": {"key": {"type": "integer"}, "min": 0, "max": 1}}, "detect_mult": {"type": {"key": {"type": "integer", "minInteger": 1}, "min": 0, "max": 1}}, "status": { "type": {"key": {"type": "string", "enum": ["set", ["down", "init", "up", "admin_down"]]}, "min": 0, "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "options": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["logical_port", "dst_ip"]], "isRoot": true}, "Static_MAC_Binding": { "columns": { "logical_port": {"type": "string"}, "ip": {"type": "string"}, "mac": {"type": "string"}, "override_dynamic_mac": {"type": "boolean"}}, "indexes": [["logical_port", "ip"]], "isRoot": true}, "Chassis_Template_Var": { "columns": { "chassis": {"type": "string"}, "variables": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["chassis"]], "isRoot": true} } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/ovsdb/test_impl_idl_taas.py0000664000175000017500000000561400000000000031714 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 from unittest import mock from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.tests import base from ovs.db import idl as ovs_idl from ovsdbapp.backend import ovs_idl as real_ovs_idl from ovsdbapp.backend.ovs_idl import idlutils from neutron_taas.services.taas.service_drivers.ovn.ovsdb import impl_idl_taas basedir = os.path.dirname(os.path.abspath(__file__)) schema_files = { 'OVN_Northbound': os.path.join(basedir, 'schema_files', 'ovn-nb.ovsschema'), } class TestOvnNbIdlForTaas(base.BaseTestCase): def setUp(self): super().setUp() ovn_conf.register_opts() self.mock_gsh = mock.patch.object( idlutils, 'get_schema_helper', side_effect=lambda x, y: ovs_idl.SchemaHelper( location=schema_files['OVN_Northbound'])).start() self.idl_taas = impl_idl_taas.OvnNbIdlForTaas() def test__get_ovsdb_helper(self): self.mock_gsh.reset_mock() self.idl_taas._get_ovsdb_helper('foo') self.mock_gsh.assert_called_once_with('foo', 'OVN_Northbound') @mock.patch.object(real_ovs_idl.Backend, 'autocreate_indices', mock.Mock(), create=True) def test_start(self): with mock.patch('ovsdbapp.backend.ovs_idl.connection.Connection', side_effect=lambda x, timeout: mock.Mock()): idl_taas_1 = impl_idl_taas.OvnNbIdlForTaas() ret_taas_1 = idl_taas_1.start() id1 = id(ret_taas_1.ovsdb_connection) idl_taas_2 = impl_idl_taas.OvnNbIdlForTaas() ret_taas_2 = idl_taas_2.start() id2 = id(ret_taas_2.ovsdb_connection) self.assertNotEqual(id1, id2) @mock.patch('ovsdbapp.backend.ovs_idl.connection.Connection') def test_stop(self, mock_conn): mock_conn.stop.return_value = False with mock.patch.object(self.idl_taas, 'close') as mock_close: self.idl_taas.start() self.idl_taas.stop() mock_close.assert_called_once_with() @mock.patch('ovsdbapp.backend.ovs_idl.connection.Connection') def test_stop_no_connection(self, mock_conn): mock_conn.stop.return_value = False with mock.patch.object(self.idl_taas, 'close') as mock_close: self.idl_taas.stop() mock_close.assert_called_once_with() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/test_helper.py0000664000175000017500000000641100000000000027251 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 unittest import mock from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.tests import base from neutron_lib.callbacks import events from neutron_lib.callbacks import resources from neutron_taas.services.taas.service_drivers.ovn import helper class TestTaasOvnProviderHelper(base.BaseTestCase): def setUp(self): super().setUp() ovn_conf.register_opts() ovn_nb_idl = mock.patch( 'neutron_taas.services.taas.service_drivers.ovn.ovsdb.' 'impl_idl_taas.OvnNbIdlForTaas') self.mock_ovn_nb_idl = ovn_nb_idl.start() mock.patch( 'ovsdbapp.backend.ovs_idl.idlutils.get_schema_helper').start() self.helper = helper.TaasOvnProviderHelper() self.helper._post_fork_initialize( resources.PROCESS, events.AFTER_INIT, None) self.ovn_nbdb_api = mock.patch.object(self.helper, 'ovn_nbdb_api') self.ovn_nbdb_api.start() add_req_thread = mock.patch.object(helper.TaasOvnProviderHelper, 'add_request') self.mock_add_request = add_req_thread.start() def test_mirror_add(self): port_id = '1234' name = 'foo_mirror' dest_ip = '10.92.10.5' type = 'gre' tunnel_id = 101 direction = 'to-lport' self.helper.mirror_add({ 'name': name, 'direction_filter': direction, 'dest': dest_ip, 'mirror_type': type, 'index': tunnel_id, 'port_id': port_id }) self.helper.ovn_nbdb_api.lookup.assert_called_once_with( 'Logical_Switch_Port', port_id) self.helper.ovn_nbdb_api.mirror_add.assert_called_once_with( name=name, direction_filter=direction, dest=dest_ip, mirror_type=type, index=tunnel_id ) self.helper.ovn_nbdb_api.lsp_attach_mirror.assert_called_once() def test_mirror_del(self): port_id = '1234' name = 'foo_mirror' dest_ip = '10.92.10.5' type = 'gre' tunnel_id = 101 direction = 'to-lport' self.helper.mirror_del({ 'port_id': port_id, 'name': name, 'direction_filter': direction, 'dest': dest_ip, 'mirror_type': type, 'index': tunnel_id, 'port_id': port_id }) self.helper.ovn_nbdb_api.lookup.assert_called_once_with( 'Logical_Switch_Port', port_id) self.helper.ovn_nbdb_api.mirror_get.assert_called_once_with(name) self.helper.ovn_nbdb_api.lsp_detach_mirror.assert_called_once() self.helper.ovn_nbdb_api.mirror_del.assert_called_once() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/ovn/test_taas_ovn.py0000664000175000017500000001124300000000000027603 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 unittest import mock from neutron.tests import base from oslo_utils import uuidutils from neutron_taas.services.taas.service_drivers.ovn import helper from neutron_taas.services.taas.service_drivers.ovn import taas_ovn class FakeMirrorContext(): def __init__(self, tap_mirror): self._tap_mirror = tap_mirror @property def tap_mirror(self): return self._tap_mirror class TestTaasOvnDriver(base.BaseTestCase): def setUp(self): super().setUp() self.driver = taas_ovn.TaasOvnDriver('tapmirror') add_req_thread = mock.patch.object(helper.TaasOvnProviderHelper, 'add_request') self.mock_add_request = add_req_thread.start() helper_mock = mock.patch.object(helper.TaasOvnProviderHelper, 'shutdown') helper_mock.start() self.tap_mirror_dict = { 'mirror_type': 'gre', 'directions': {'IN': 101}, 'id': uuidutils.generate_uuid(), 'remote_ip': '10.92.10.5', 'port_id': uuidutils.generate_uuid() } self.multi_dir_t_mirror = copy.deepcopy(self.tap_mirror_dict) self.multi_dir_t_mirror['directions'] = {'IN': 101, 'OUT': 102} def test_create_tap_mirror_postcommit(self): ctx = FakeMirrorContext(self.tap_mirror_dict) self.driver.create_tap_mirror_postcommit(ctx) expected_dict = { 'type': 'mirror_add', 'info': { 'name': mock.ANY, 'direction_filter': 'to-lport', 'dest': self.tap_mirror_dict['remote_ip'], 'mirror_type': self.tap_mirror_dict['mirror_type'], 'index': self.tap_mirror_dict['directions']['IN'], 'port_id': self.tap_mirror_dict['port_id'], } } self.mock_add_request.assert_called_once_with(expected_dict) def test_create_tap_mirror_postcommit_multi_dir(self): ctx = FakeMirrorContext(self.multi_dir_t_mirror) self.driver.create_tap_mirror_postcommit(ctx) expected_in_call = { 'type': 'mirror_add', 'info': { 'name': mock.ANY, 'direction_filter': 'to-lport', 'dest': self.tap_mirror_dict['remote_ip'], 'mirror_type': self.tap_mirror_dict['mirror_type'], 'index': self.tap_mirror_dict['directions']['IN'], 'port_id': self.tap_mirror_dict['port_id'], } } expected_out_call = copy.deepcopy(expected_in_call) expected_out_call['info']['direction_filter'] = 'from-lport' out_dir_tun_id = self.multi_dir_t_mirror['directions']['OUT'] expected_out_call['info']['index'] = out_dir_tun_id expected_calls = [ mock.call(expected_in_call), mock.call(expected_out_call) ] self.mock_add_request.assert_has_calls(expected_calls) def test_delete_tap_mirror_precommit(self): ctx = FakeMirrorContext(self.tap_mirror_dict) self.driver.delete_tap_mirror_precommit(ctx) expected_dict = { 'type': 'mirror_del', 'info': { 'id': self.tap_mirror_dict['id'], 'name': mock.ANY, 'sink': self.tap_mirror_dict['remote_ip'], 'port_id': self.tap_mirror_dict['port_id']} } self.mock_add_request.assert_called_once_with(expected_dict) def test_delete_tap_mirror_precommit_multi_dir(self): ctx = FakeMirrorContext(self.multi_dir_t_mirror) self.driver.delete_tap_mirror_precommit(ctx) expected_call = { 'type': 'mirror_del', 'info': { 'id': self.tap_mirror_dict['id'], 'name': mock.ANY, 'sink': self.tap_mirror_dict['remote_ip'], 'port_id': self.tap_mirror_dict['port_id'], } } expected_calls = [ mock.call(expected_call), mock.call(expected_call) ] self.mock_add_request.assert_has_calls(expected_calls) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/taas/0000775000175000017500000000000000000000000024505 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/taas/__init__.py0000664000175000017500000000000000000000000026604 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/taas/test_taas_plugin.py0000664000175000017500000003165700000000000030440 0ustar00zuulzuul00000000000000# Copyright (C) 2015 Midokura SARL. # 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 testtools from unittest import mock from neutron_lib import constants from neutron_lib import context from neutron_lib.exceptions import taas as taas_exc from neutron_lib import rpc as n_rpc from neutron_lib.utils import net as n_utils from oslo_config import cfg from oslo_utils import uuidutils from neutron.tests.unit import testlib_api import neutron_taas.db.taas_db # noqa from neutron_taas.services.taas.service_drivers import taas_agent_api from neutron_taas.services.taas.service_drivers import taas_rpc from neutron_taas.services.taas import taas_plugin class DummyError(Exception): pass class TestTaasPlugin(testlib_api.SqlTestCase): def setUp(self): super().setUp() mock.patch.object(n_rpc, 'Connection', spec=object).start() mock.patch.object(taas_agent_api, 'TaasAgentApi', spec=object).start() self.driver = mock.MagicMock() mock.patch('neutron.services.service_base.load_drivers', return_value=({'dummy_provider': self.driver}, 'dummy_provider')).start() mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=mock.MagicMock()).start() self._plugin = taas_plugin.TaasPlugin() self._context = context.get_admin_context() self.taas_cbs = taas_rpc.TaasCallbacks(self.driver, self._plugin) self._project_id = self._tenant_id = 'tenant-X' self._network_id = uuidutils.generate_uuid() self._host_id = 'host-A' self._port_id = uuidutils.generate_uuid() self._port_details = { 'tenant_id': self._tenant_id, 'binding:host_id': self._host_id, 'mac_address': n_utils.get_random_mac( 'fa:16:3e:00:00:00'.split(':')), } self._tap_service = { 'tenant_id': self._tenant_id, 'name': 'MyTap', 'description': 'This is my tap service', 'port_id': self._port_id, 'project_id': self._project_id, } self.vlan_filter = "1-5,9,18,27-30,99-108,4000-4095" self._tap_flow = { 'description': 'This is my tap flow', 'direction': 'BOTH', 'name': 'MyTapFlow', 'source_port': self._port_id, 'tenant_id': self._tenant_id, 'project_id': self._project_id, 'vlan_filter': self.vlan_filter, } @contextlib.contextmanager def tap_service(self): req = { 'tap_service': self._tap_service, } with mock.patch.object(self._plugin, 'get_port_details', return_value=self._port_details): self._plugin.create_tap_service(self._context, req) self._tap_service['id'] = mock.ANY self._tap_service['status'] = constants.DOWN self.driver.assert_has_calls([ mock.call.create_tap_service_precommit(mock.ANY), mock.call.create_tap_service_postcommit(mock.ANY), ]) pre_args = self.driver.create_tap_service_precommit.call_args[0][0] self.assertEqual(self._context, pre_args._plugin_context) self.assertEqual(self._tap_service, pre_args.tap_service) post_args = self.driver.create_tap_service_postcommit.call_args[0][0] self.assertEqual(self._context, post_args._plugin_context) self.assertEqual(self._tap_service, post_args.tap_service) self.taas_cbs.set_tap_service_status( self._context, {'id': pre_args.tap_service['id']}, constants.ACTIVE, "dummyHost") self._tap_service['status'] = constants.ACTIVE yield self._plugin.get_tap_service(self._context, pre_args.tap_service['id']) @contextlib.contextmanager def tap_flow(self, tap_service, tenant_id=None): self._tap_flow['tap_service_id'] = tap_service if tenant_id is not None: self._tap_flow['tenant_id'] = tenant_id req = { 'tap_flow': self._tap_flow, } with mock.patch.object(self._plugin, 'get_port_details', return_value=self._port_details): self._plugin.create_tap_flow(self._context, req) self._tap_flow['id'] = mock.ANY self._tap_flow['status'] = constants.DOWN self._tap_service['id'] = mock.ANY self._tap_flow['vlan_filter'] = mock.ANY self.driver.assert_has_calls([ mock.call.create_tap_flow_precommit(mock.ANY), mock.call.create_tap_flow_postcommit(mock.ANY), ]) pre_args = self.driver.create_tap_flow_precommit.call_args[0][0] self.assertEqual(self._context, pre_args._plugin_context) self.assertEqual(self._tap_flow, pre_args.tap_flow) post_args = self.driver.create_tap_flow_postcommit.call_args[0][0] self.assertEqual(self._context, post_args._plugin_context) self.assertEqual(self._tap_flow, post_args.tap_flow) self.taas_cbs.set_tap_flow_status( self._context, {'id': pre_args.tap_flow['id']}, constants.ACTIVE, "dummyHost") self._tap_flow['status'] = constants.ACTIVE yield self._plugin.get_tap_flow(self._context, pre_args.tap_flow['id']) def test_create_tap_service(self): with self.tap_service(): pass def test_verify_taas_id_reused(self): # make small range id cfg.CONF.set_override("vlan_range_start", 1, group="taas") cfg.CONF.set_override("vlan_range_end", 3, group="taas") with self.tap_service() as ts_1, self.tap_service() as ts_2, \ self.tap_service() as ts_3, self.tap_service() as ts_4: ts_id_1 = ts_1['id'] ts_id_2 = ts_2['id'] ts_id_3 = ts_3['id'] tap_id_assoc_1 = self._plugin.create_tap_id_association( self._context, ts_id_1) tap_id_assoc_2 = self._plugin.create_tap_id_association( self._context, ts_id_2) self.assertEqual({1, 2}, {tap_id_assoc_1['taas_id'], tap_id_assoc_2['taas_id']}) with testtools.ExpectedException(taas_exc.TapServiceLimitReached): self._plugin.create_tap_id_association( self._context, ts_4['id'] ) # free an tap_id and verify could reallocate same taas id self._plugin.delete_tap_service(self._context, ts_id_1) self.taas_cbs.set_tap_service_status(self._context, {'id': ts_id_1}, constants.INACTIVE, "dummyHost") tap_id_assoc_3 = self._plugin.create_tap_id_association( self._context, ts_id_3) self.assertEqual({1, 2}, {tap_id_assoc_3['taas_id'], tap_id_assoc_2['taas_id']}) def test_create_tap_service_wrong_tenant_id(self): self._port_details['tenant_id'] = 'other-tenant' with testtools.ExpectedException(taas_exc.PortDoesNotBelongToTenant), \ self.tap_service(): pass self.assertEqual([], self.driver.mock_calls) def test_create_tap_service_reach_limit(self): # TODO(Yoichiro):Need to move this test to taas_rpc test pass def test_create_tap_service_failed_on_service_driver(self): attr = {'create_tap_service_postcommit.side_effect': DummyError} self.driver.configure_mock(**attr) with testtools.ExpectedException(DummyError): req = { 'tap_service': self._tap_service, } with mock.patch.object(self._plugin, 'get_port_details', return_value=self._port_details): self._plugin.create_tap_service(self._context, req) def test_delete_tap_service(self): with self.tap_service() as ts: self._plugin.delete_tap_service(self._context, ts['id']) self._tap_service['id'] = ts['id'] self.driver.assert_has_calls([ mock.call.delete_tap_service_precommit(mock.ANY), ]) self._tap_service['status'] = constants.PENDING_DELETE pre_args = self.driver.delete_tap_service_precommit.call_args[0][0] self.assertEqual(self._context, pre_args._plugin_context) self.assertEqual(self._tap_service, pre_args.tap_service) self.taas_cbs.set_tap_service_status(self._context, {'id': self._tap_service['id']}, constants.INACTIVE, "dummyHost") def test_delete_tap_service_with_flow(self): with self.tap_service() as ts, \ self.tap_flow(tap_service=ts['id']) as tf: self._plugin.delete_tap_service(self._context, ts['id']) self._tap_service['id'] = ts['id'] self._tap_flow['id'] = tf['id'] self.driver.assert_has_calls([ mock.call.delete_tap_flow_precommit(mock.ANY), mock.call.delete_tap_service_precommit(mock.ANY), ]) self._tap_service['status'] = constants.PENDING_DELETE self._tap_flow['status'] = constants.PENDING_DELETE pre_args = self.driver.delete_tap_flow_precommit.call_args[0][0] self.assertEqual(self._context, pre_args._plugin_context) self.assertEqual(self._tap_flow, pre_args.tap_flow) pre_args = self.driver.delete_tap_service_precommit.call_args[0][0] self.assertEqual(self._context, pre_args._plugin_context) self.assertEqual(self._tap_service, pre_args.tap_service) self.taas_cbs.set_tap_flow_status(self._context, {'id': self._tap_flow['id']}, constants.INACTIVE, "dummyHost") self.taas_cbs.set_tap_service_status(self._context, {'id': self._tap_service['id']}, constants.INACTIVE, "dummyHost") def test_delete_tap_service_non_existent(self): with testtools.ExpectedException(taas_exc.TapServiceNotFound): self._plugin.delete_tap_service(self._context, 'non-existent') def test_create_tap_flow(self): with self.tap_service() as ts, self.tap_flow(tap_service=ts['id']): pass def test_create_tap_flow_wrong_tenant_id(self): with self.tap_service() as ts, \ testtools.ExpectedException( taas_exc.TapServiceNotBelongToTenant), \ self.tap_flow(tap_service=ts['id'], tenant_id='other-tenant'): pass def test_create_tap_flow_failed_on_service_driver(self): with self.tap_service() as ts: attr = {'create_tap_flow_postcommit.side_effect': DummyError} self.driver.configure_mock(**attr) with testtools.ExpectedException(DummyError): self._tap_flow['tap_service_id'] = ts['id'] req = { 'tap_flow': self._tap_flow, } with mock.patch.object(self._plugin, 'get_port_details', return_value=self._port_details): self._plugin.create_tap_flow(self._context, req) def test_delete_tap_flow(self): with self.tap_service() as ts, \ self.tap_flow(tap_service=ts['id']) as tf: self._plugin.delete_tap_flow(self._context, tf['id']) self._tap_flow['id'] = tf['id'] self.driver.assert_has_calls([ mock.call.delete_tap_flow_precommit(mock.ANY), ]) self._tap_flow['status'] = constants.PENDING_DELETE pre_args = self.driver.delete_tap_flow_precommit.call_args[0][0] self.assertEqual(self._context, pre_args._plugin_context) self.assertEqual(self._tap_flow, pre_args.tap_flow) self.taas_cbs.set_tap_flow_status(self._context, {'id': self._tap_flow['id']}, constants.INACTIVE, "dummyHost") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/services/taas/test_tap_mirror_plugin.py0000664000175000017500000001353200000000000031656 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 contextlib import testtools from unittest import mock from neutron.tests.unit import testlib_api from neutron_lib import context from neutron_lib.exceptions import taas as taas_exc from neutron_lib import rpc as n_rpc from neutron_lib.utils import net as n_utils from oslo_utils import uuidutils from neutron_taas.services.taas import tap_mirror_plugin class TestTapMirrorPlugin(testlib_api.SqlTestCase): def setUp(self): super().setUp() mock.patch.object(n_rpc, 'Connection', spec=object).start() self.driver = mock.MagicMock() mock.patch('neutron.services.service_base.load_drivers', return_value=({'dummy_provider': self.driver}, 'dummy_provider')).start() mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance', return_value=mock.MagicMock()).start() self._plugin = tap_mirror_plugin.TapMirrorPlugin() self._context = context.get_admin_context() self._project_id = self._tenant_id = uuidutils.generate_uuid() self._network_id = uuidutils.generate_uuid() self._host_id = 'host-A' self._port_id = uuidutils.generate_uuid() self._port_details = { 'tenant_id': self._tenant_id, 'binding:host_id': self._host_id, 'mac_address': n_utils.get_random_mac( 'fa:16:3e:00:00:00'.split(':')), } self._tap_mirror = { 'project_id': self._project_id, 'tenant_id': self._tenant_id, 'name': 'MyMirror', 'description': 'This is my Tap Mirror', 'port_id': self._port_id, 'directions': {"IN": 101}, 'remote_ip': '10.99.8.3', 'mirror_type': 'gre', } @contextlib.contextmanager def tap_mirror(self, **kwargs): self._tap_mirror.update(kwargs) req = { 'tap_mirror': self._tap_mirror, } with mock.patch.object(self._plugin, 'get_port_details', return_value=self._port_details): mirror = self._plugin.create_tap_mirror(self._context, req) self._tap_mirror['id'] = mock.ANY self.driver.assert_has_calls([ mock.call.create_tap_mirror_precommit(mock.ANY), mock.call.create_tap_mirror_postcommit(mock.ANY), ]) pre_call_args = self.driver.create_tap_mirror_precommit.call_args[0][0] self.assertEqual(self._context, pre_call_args._plugin_context) self.assertEqual(self._tap_mirror, pre_call_args.tap_mirror) post_call_args = self.driver.create_tap_mirror_postcommit.call_args post_call_args = post_call_args[0][0] self.assertEqual(self._context, post_call_args._plugin_context) self.assertEqual(self._tap_mirror, post_call_args.tap_mirror) yield self._plugin.get_tap_mirror(self._context, mirror['id']) def test_create_tap_mirror(self): with self.tap_mirror(): pass def test_create_tap_mirror_wrong_project_id(self): self._port_details['project_id'] = 'other-tenant' self._port_details['tenant_id'] = 'other-tenant' with testtools.ExpectedException(taas_exc.PortDoesNotBelongToTenant), \ self.tap_mirror(): pass self.assertEqual([], self.driver.mock_calls) def test_create_duplicate_tunnel_id(self): with self.tap_mirror() as tm1: with mock.patch.object(self._plugin, 'get_tap_mirrors', return_value=[tm1]): with testtools.ExpectedException( taas_exc.TapMirrorTunnelConflict), \ self.tap_mirror(directions={"IN": 101}): pass def test_create_different_tunnel_id(self): with self.tap_mirror() as tm1: with mock.patch.object(self._plugin, 'get_tap_mirrors', return_value=[tm1]): with self.tap_mirror(directions={"IN": 102}): pass def test_same_tunnel_id_different_direction(self): with self.tap_mirror() as tm1: with mock.patch.object(self._plugin, 'get_tap_mirrors', return_value=[tm1]): with testtools.ExpectedException( taas_exc.TapMirrorTunnelConflict), \ self.tap_mirror(directions={"OUT": 101}): pass def test_two_direction_tunnel_id(self): with self.tap_mirror(directions={'IN': 101, 'OUT': 102}) as tm1: with mock.patch.object(self._plugin, 'get_tap_mirrors', return_value=[tm1]): with testtools.ExpectedException( taas_exc.TapMirrorTunnelConflict), \ self.tap_mirror(directions={"OUT": 101}): pass def test_delete_tap_mrror(self): with self.tap_mirror() as tm: self._plugin.delete_tap_mirror(self._context, tm['id']) self._tap_mirror['id'] = tm['id'] def test_delete_tap_mirror_non_existent(self): with testtools.ExpectedException(taas_exc.TapMirrorNotFound): self._plugin.delete_tap_mirror(self._context, 'non-existent') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8512287 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/0000775000175000017500000000000000000000000024220 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/__init__.py0000664000175000017500000000000000000000000026317 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1743591513.855229 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/osc/0000775000175000017500000000000000000000000025004 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/osc/__init__.py0000664000175000017500000000000000000000000027103 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/osc/fakes.py0000664000175000017500000000770100000000000026454 0ustar00zuulzuul00000000000000# All Rights Reserved 2020 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from oslo_utils import uuidutils class FakeTapService: @staticmethod def create_tap_service(attrs=None): """Create a fake tap service.""" attrs = attrs or {} tap_service_attrs = { 'id': uuidutils.generate_uuid(), 'tenant_id': uuidutils.generate_uuid(), 'name': 'test_tap_service' + uuidutils.generate_uuid(), 'status': 'ACTIVE', } tap_service_attrs.update(attrs) return copy.deepcopy(tap_service_attrs) @staticmethod def create_tap_services(attrs=None, count=1): """Create multiple fake tap services.""" tap_services = [] for i in range(0, count): if attrs is None: attrs = {'id': 'fake_id%d' % i} elif getattr(attrs, 'id', None) is None: attrs['id'] = 'fake_id%d' % i tap_services.append(FakeTapService.create_tap_service( attrs=attrs)) return tap_services class FakeTapFlow: @staticmethod def create_tap_flow(attrs=None): """Create a fake tap service.""" attrs = attrs or {} tap_flow_attrs = { 'id': uuidutils.generate_uuid(), 'tenant_id': uuidutils.generate_uuid(), 'name': 'test_tap_flow' + uuidutils.generate_uuid(), 'status': 'ACTIVE', 'direction': 'BOTH', } tap_flow_attrs.update(attrs) return copy.deepcopy(tap_flow_attrs) @staticmethod def create_tap_flows(attrs=None, count=1): """Create multiple fake tap flows.""" tap_flows = [] for i in range(0, count): if attrs is None: attrs = { 'id': 'fake_id%d' % i, 'source_port': uuidutils.generate_uuid(), 'tap_service_id': uuidutils.generate_uuid() } elif getattr(attrs, 'id', None) is None: attrs['id'] = 'fake_id%d' % i tap_flows.append(FakeTapFlow.create_tap_flow(attrs=attrs)) return tap_flows class FakeTapMirror(object): @staticmethod def create_tap_mirror(attrs=None): """Create a fake tap mirror.""" attrs = attrs or {} tap_mirror_attrs = { 'id': uuidutils.generate_uuid(), 'tenant_id': uuidutils.generate_uuid(), 'name': 'test_tap_mirror' + uuidutils.generate_uuid(), 'port_id': uuidutils.generate_uuid(), 'directions': 'IN=99', 'remote_ip': '192.10.10.2', 'mirror_type': 'gre', } tap_mirror_attrs.update(attrs) return copy.deepcopy(tap_mirror_attrs) @staticmethod def create_tap_mirrors(attrs=None, count=1): """Create multiple fake tap mirrors.""" tap_mirrors = [] for i in range(0, count): if attrs is None: attrs = { 'id': 'fake_id%d' % i, 'port_id': uuidutils.generate_uuid(), 'name': 'test_tap_mirror_%d' % i, 'directions': 'IN=%d' % 99 + i, 'remote_ip': '192.10.10.%d' % (i + 3), } elif getattr(attrs, 'id', None) is None: attrs['id'] = 'fake_id%d' % i tap_mirrors.append(FakeTapMirror.create_tap_mirror(attrs=attrs)) return tap_mirrors ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/osc/test_osc_tap_flow.py0000664000175000017500000002346600000000000031107 0ustar00zuulzuul00000000000000# All Rights Reserved 2020 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import operator from unittest import mock from neutronclient.tests.unit.osc.v2 import fakes as test_fakes from openstack.network.v2 import tap_flow as _tap_flow from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from oslo_utils import uuidutils from neutron_taas.taas_client.osc import tap_flow as osc_tap_flow from neutron_taas.taas_client.osc import tap_service as osc_tap_service from neutron_taas.tests.unit.taas_client.osc import fakes columns_long = tuple(col for col, _, listing_mode in osc_tap_flow._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) headers_long = tuple(head for _, head, listing_mode in osc_tap_flow._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) sorted_attr_map = sorted(osc_tap_flow._attr_map, key=operator.itemgetter(1)) sorted_columns = tuple(col for col, _, _ in sorted_attr_map) sorted_headers = tuple(head for _, head, _ in sorted_attr_map) def _get_data(attrs, columns=sorted_columns): return osc_utils.get_dict_properties(attrs, columns) class TestCreateTapFlow(test_fakes.TestNeutronClientOSCV2): columns = ( 'direction', 'id', 'name', 'source_port', 'status', 'tap_service_id', ) def setUp(self): super().setUp() self.cmd = osc_tap_flow.CreateTapFlow(self.app, self.namespace) def test_create_tap_flow(self): """Test Create Tap Flow.""" port_id = uuidutils.generate_uuid() fake_tap_service = fakes.FakeTapService.create_tap_service( attrs={'port_id': port_id} ) port_id = uuidutils.generate_uuid() fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( attrs={ 'source_port': port_id, 'tap_service_id': fake_tap_service['id'] } ) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.create_tap_flow = mock.Mock( return_value=fake_tap_flow) self.app.client_manager.network.find_port = mock.Mock( return_value={'id': port_id}) self.app.client_manager.network.find_tap_service = mock.Mock( return_value=fake_tap_service) arg_list = [ '--name', fake_tap_flow['name'], '--port', fake_tap_flow['source_port'], '--tap-service', fake_tap_flow['tap_service_id'], '--direction', fake_tap_flow['direction'], ] verify_list = [ ('name', fake_tap_flow['name']), ('port', fake_tap_flow['source_port']), ('tap_service', fake_tap_flow['tap_service_id']), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) with mock.patch.object( self.app.client_manager.network, '_find') as nc_find: nc_find.side_effect = [ {'id': fake_tap_flow['tap_service_id']} ] columns, data = self.cmd.take_action(parsed_args) mock_create_t_f = self.app.client_manager.network.create_tap_flow mock_create_t_f.assert_called_once_with( **{ 'name': fake_tap_flow['name'], 'source_port': fake_tap_flow['source_port'], 'tap_service_id': fake_tap_flow['tap_service_id'], 'direction': fake_tap_flow['direction'] } ) self.assertEqual(self.columns, columns) fake_data = _get_data( fake_tap_flow, osc_tap_service._get_columns(fake_tap_flow)[1]) self.assertItemEqual(fake_data, data) class TestListTapFlow(test_fakes.TestNeutronClientOSCV2): def setUp(self): super().setUp() self.cmd = osc_tap_flow.ListTapFlow(self.app, self.namespace) def test_list_tap_flows(self): """Test List Tap Flow.""" fake_tap_flows = fakes.FakeTapFlow.create_tap_flows( attrs={ 'source_port': uuidutils.generate_uuid(), 'tap_service_id': uuidutils.generate_uuid(), }, count=2) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.tap_flows = mock.Mock( return_value=fake_tap_flows) arg_list = [] verify_list = [] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.app.client_manager.network.tap_flows.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertListItemEqual( list(data), [_get_data(fake_tap_flow, columns_long) for fake_tap_flow in fake_tap_flows] ) class TestDeleteTapFlow(test_fakes.TestNeutronClientOSCV2): def setUp(self): super().setUp() self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_flow = mock.Mock( side_effect=lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id)) self.cmd = osc_tap_flow.DeleteTapFlow(self.app, self.namespace) def test_delete_tap_flow(self): """Test Delete tap flow.""" fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( attrs={ 'source_port': uuidutils.generate_uuid(), 'tap_service_id': uuidutils.generate_uuid(), } ) self.app.client_manager.network.delete_tap_flow = mock.Mock() arg_list = [ fake_tap_flow['id'], ] verify_list = [ (osc_tap_flow.TAP_FLOW, [fake_tap_flow['id']]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) result = self.cmd.take_action(parsed_args) mock_delete_tap_flow = self.app.client_manager.network.delete_tap_flow mock_delete_tap_flow.assert_called_once_with(fake_tap_flow['id']) self.assertIsNone(result) class TestShowTapFlow(test_fakes.TestNeutronClientOSCV2): columns = ( 'direction', 'id', 'name', 'source_port', 'status', 'tap_service_id' ) def setUp(self): super().setUp() self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_flow = mock.Mock( side_effect=lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id)) self.cmd = osc_tap_flow.ShowTapFlow(self.app, self.namespace) def test_show_tap_flow(self): """Test Show tap flow.""" fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( attrs={ 'source_port': uuidutils.generate_uuid(), 'tap_service_id': uuidutils.generate_uuid(), } ) self.app.client_manager.network.get_tap_flow = mock.Mock( return_value=fake_tap_flow) arg_list = [ fake_tap_flow['id'], ] verify_list = [ (osc_tap_flow.TAP_FLOW, fake_tap_flow['id']), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.app.client_manager.network.get_tap_flow.assert_called_once_with( fake_tap_flow['id']) self.assertEqual(self.columns, headers) fake_data = _get_data( fake_tap_flow, osc_tap_service._get_columns(fake_tap_flow)[1]) self.assertItemEqual(fake_data, data) class TestUpdateTapFlow(test_fakes.TestNeutronClientOSCV2): _new_name = 'new_name' columns = ( 'Direction', 'ID', 'Name', 'Status', 'Tenant', 'source_port', 'tap_service_id', ) def setUp(self): super().setUp() self.cmd = osc_tap_flow.UpdateTapFlow(self.app, self.namespace) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_flow = mock.Mock( side_effect=lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id)) def test_update_tap_flow(self): """Test update tap service""" fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( attrs={ 'source_port': uuidutils.generate_uuid(), 'tap_service_id': uuidutils.generate_uuid(), } ) new_tap_flow = copy.deepcopy(fake_tap_flow) new_tap_flow['name'] = self._new_name self.app.client_manager.network.update_tap_flow = mock.Mock( return_value=new_tap_flow) arg_list = [ fake_tap_flow['id'], '--name', self._new_name, ] verify_list = [('name', self._new_name)] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = {'name': self._new_name} mock_update_t_f = self.app.client_manager.network.update_tap_flow mock_update_t_f.assert_called_once_with(new_tap_flow['id'], **attrs) self.assertEqual(self.columns, columns) self.assertItemEqual(_get_data(new_tap_flow), data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/osc/test_osc_tap_mirror.py0000664000175000017500000002300100000000000031433 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 import operator from unittest import mock from neutronclient.tests.unit.osc.v2 import fakes as test_fakes from openstack.network.v2 import tap_mirror from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from oslo_utils import uuidutils from neutron_taas.taas_client.osc import tap_mirror as osc_tap_mirror from neutron_taas.tests.unit.taas_client.osc import fakes columns_long = tuple(col for col, _, listing_mode in osc_tap_mirror._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) headers_long = tuple(head for _, head, listing_mode in osc_tap_mirror._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) sorted_attr_map = sorted(osc_tap_mirror._attr_map, key=operator.itemgetter(1)) sorted_columns = tuple(col for col, _, _ in sorted_attr_map) sorted_headers = tuple(head for _, head, _ in sorted_attr_map) def _get_data(attrs, columns=sorted_columns): return osc_utils.get_dict_properties(attrs, columns) class TestCreateTapMirror(test_fakes.TestNeutronClientOSCV2): columns = ( 'directions', 'id', 'mirror_type', 'name', 'port_id', 'remote_ip', ) def setUp(self): super().setUp() self.cmd = osc_tap_mirror.CreateTapMirror(self.app, self.namespace) def test_create_tap_mirror(self): port_id = uuidutils.generate_uuid() fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( attrs={'port_id': port_id} ) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.create_tap_mirror = mock.Mock( return_value=fake_tap_mirror) self.app.client_manager.network.find_port = mock.Mock( return_value={'id': port_id}) self.app.client_manager.network.find_tap_mirror = mock.Mock( side_effect=lambda _, name_or_id: {'id': name_or_id}) arg_list = [ '--name', fake_tap_mirror['name'], '--port', fake_tap_mirror['port_id'], '--directions', fake_tap_mirror['directions'], '--remote-ip', fake_tap_mirror['remote_ip'], '--mirror-type', fake_tap_mirror['mirror_type'], ] verify_directions = fake_tap_mirror['directions'].split('=') verify_directions_dict = {verify_directions[0]: verify_directions[1]} verify_list = [ ('name', fake_tap_mirror['name']), ('port_id', fake_tap_mirror['port_id']), ('directions', verify_directions_dict), ('remote_ip', fake_tap_mirror['remote_ip']), ('mirror_type', fake_tap_mirror['mirror_type']), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) self.app.client_manager.network.find_tap_mirror = mock.Mock( return_value=fake_tap_mirror) columns, data = self.cmd.take_action(parsed_args) create_tap_m_mock = self.app.client_manager.network.create_tap_mirror create_tap_m_mock.assert_called_once_with( **{'name': fake_tap_mirror['name'], 'port_id': fake_tap_mirror['port_id'], 'directions': verify_directions_dict, 'remote_ip': fake_tap_mirror['remote_ip'], 'mirror_type': fake_tap_mirror['mirror_type']}) self.assertEqual(self.columns, columns) fake_data = _get_data( fake_tap_mirror, osc_tap_mirror._get_columns(fake_tap_mirror)[1]) self.assertEqual(fake_data, data) class TestListTapMirror(test_fakes.TestNeutronClientOSCV2): def setUp(self): super().setUp() self.cmd = osc_tap_mirror.ListTapMirror(self.app, self.namespace) def test_list_tap_mirror(self): """Test List Tap Mirror.""" fake_tap_mirrors = fakes.FakeTapMirror.create_tap_mirrors( attrs={'port_id': uuidutils.generate_uuid()}, count=4) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.tap_mirrors = mock.Mock( return_value=fake_tap_mirrors) arg_list = [] verify_list = [] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.app.client_manager.network.tap_mirrors.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertListItemEqual( list(data), [_get_data(fake_tap_mirror, columns_long) for fake_tap_mirror in fake_tap_mirrors] ) class TestDeleteTapMirror(test_fakes.TestNeutronClientOSCV2): def setUp(self): super().setUp() self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_mirror = mock.Mock( side_effect=lambda name_or_id, ignore_missing: tap_mirror.TapMirror(id=name_or_id)) self.cmd = osc_tap_mirror.DeleteTapMirror(self.app, self.namespace) def test_delete_tap_mirror(self): """Test Delete Tap Mirror.""" fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( attrs={'port_id': uuidutils.generate_uuid()} ) self.app.client_manager.network.delete_tap_mirror = mock.Mock() arg_list = [ fake_tap_mirror['id'], ] verify_list = [ (osc_tap_mirror.TAP_MIRROR, [fake_tap_mirror['id']]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) result = self.cmd.take_action(parsed_args) mock_delete_tap_m = self.app.client_manager.network.delete_tap_mirror mock_delete_tap_m.assert_called_once_with(fake_tap_mirror['id']) self.assertIsNone(result) class TestShowTapMirror(test_fakes.TestNeutronClientOSCV2): columns = ( 'directions', 'id', 'mirror_type', 'name', 'port_id', 'remote_ip', ) def setUp(self): super().setUp() self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_mirror = mock.Mock( side_effect=lambda name_or_id, ignore_missing: tap_mirror.TapMirror(id=name_or_id)) self.cmd = osc_tap_mirror.ShowTapMirror(self.app, self.namespace) def test_show_tap_mirror(self): """Test Show Tap Mirror.""" fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( attrs={'port_id': uuidutils.generate_uuid()} ) self.app.client_manager.network.get_tap_mirror = mock.Mock( return_value=fake_tap_mirror) arg_list = [ fake_tap_mirror['id'], ] verify_list = [ (osc_tap_mirror.TAP_MIRROR, fake_tap_mirror['id']), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) mock_get_tap_m = self.app.client_manager.network.get_tap_mirror mock_get_tap_m.assert_called_once_with( fake_tap_mirror['id']) self.assertEqual(self.columns, headers) fake_data = _get_data( fake_tap_mirror, osc_tap_mirror._get_columns(fake_tap_mirror)[1]) self.assertItemEqual(fake_data, data) class TestUpdateTapMirror(test_fakes.TestNeutronClientOSCV2): _new_name = 'new_name' columns = ( 'directions', 'id', 'mirror_type', 'name', 'port_id', 'remote_ip', ) def setUp(self): super().setUp() self.cmd = osc_tap_mirror.UpdateTapMirror(self.app, self.namespace) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_mirror = mock.Mock( side_effect=lambda name_or_id, ignore_missing: tap_mirror.TapMirror(id=name_or_id)) def test_update_tap_mirror(self): """Test update Tap Mirror""" fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( attrs={'port_id': uuidutils.generate_uuid()} ) new_tap_mirror = copy.deepcopy(fake_tap_mirror) new_tap_mirror['name'] = self._new_name self.app.client_manager.network.update_tap_mirror = mock.Mock( return_value=new_tap_mirror) arg_list = [ fake_tap_mirror['id'], '--name', self._new_name, ] verify_list = [('name', self._new_name)] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = {'name': self._new_name} mock_update_tap_m = self.app.client_manager.network.update_tap_mirror mock_update_tap_m.assert_called_once_with( fake_tap_mirror['id'], **attrs) self.assertEqual(self.columns, columns) fake_data = _get_data( new_tap_mirror, osc_tap_mirror._get_columns(new_tap_mirror)[1]) self.assertItemEqual(fake_data, data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/neutron_taas/tests/unit/taas_client/osc/test_osc_tap_service.py0000664000175000017500000002157700000000000031601 0ustar00zuulzuul00000000000000# All Rights Reserved 2020 # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import operator from unittest import mock from neutronclient.tests.unit.osc.v2 import fakes as test_fakes from openstack.network.v2 import tap_service from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from oslo_utils import uuidutils from neutron_taas.taas_client.osc import tap_service as osc_tap_service from neutron_taas.tests.unit.taas_client.osc import fakes columns_long = tuple(col for col, _, listing_mode in osc_tap_service._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) headers_long = tuple(head for _, head, listing_mode in osc_tap_service._attr_map if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY)) sorted_attr_map = sorted(osc_tap_service._attr_map, key=operator.itemgetter(1)) sorted_columns = tuple(col for col, _, _ in sorted_attr_map) sorted_headers = tuple(head for _, head, _ in sorted_attr_map) def _get_data(attrs, columns=sorted_columns): return osc_utils.get_dict_properties(attrs, columns) class TestCreateTapService(test_fakes.TestNeutronClientOSCV2): columns = ( 'id', 'name', 'port_id', 'status', ) def setUp(self): super().setUp() self.cmd = osc_tap_service.CreateTapService(self.app, self.namespace) def test_create_tap_service(self): """Test Create Tap Service.""" port_id = uuidutils.generate_uuid() fake_tap_service = fakes.FakeTapService.create_tap_service( attrs={'port_id': port_id} ) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.create_tap_service = mock.Mock( return_value=fake_tap_service) self.app.client_manager.network.find_port = mock.Mock( return_value={'id': port_id}) self.app.client_manager.network.find_tap_service = mock.Mock( side_effect=lambda _, name_or_id: {'id': name_or_id}) arg_list = [ '--name', fake_tap_service['name'], '--port', fake_tap_service['port_id'], ] verify_list = [ ('name', fake_tap_service['name']), ('port_id', fake_tap_service['port_id']), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) self.app.client_manager.network.find_tap_service = mock.Mock( return_value=fake_tap_service) columns, data = self.cmd.take_action(parsed_args) create_tap_s_mock = self.app.client_manager.network.create_tap_service create_tap_s_mock.assert_called_once_with( **{'name': fake_tap_service['name'], 'port_id': fake_tap_service['port_id']}) self.assertEqual(self.columns, columns) fake_data = _get_data( fake_tap_service, osc_tap_service._get_columns(fake_tap_service)[1]) self.assertEqual(fake_data, data) class TestListTapService(test_fakes.TestNeutronClientOSCV2): def setUp(self): super().setUp() self.cmd = osc_tap_service.ListTapService(self.app, self.namespace) def test_list_tap_service(self): """Test List Tap Service.""" fake_tap_services = fakes.FakeTapService.create_tap_services( attrs={'port_id': uuidutils.generate_uuid()}, count=4) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.tap_services = mock.Mock( return_value=fake_tap_services) arg_list = [] verify_list = [] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) self.app.client_manager.network.tap_services.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertListItemEqual( list(data), [_get_data(fake_tap_service, columns_long) for fake_tap_service in fake_tap_services] ) class TestDeleteTapService(test_fakes.TestNeutronClientOSCV2): def setUp(self): super().setUp() self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_service = mock.Mock( side_effect=lambda name_or_id, ignore_missing: tap_service.TapService(id=name_or_id)) self.cmd = osc_tap_service.DeleteTapService(self.app, self.namespace) def test_delete_tap_service(self): """Test Delete tap service.""" fake_tap_service = fakes.FakeTapService.create_tap_service( attrs={'port_id': uuidutils.generate_uuid()} ) self.app.client_manager.network.delete_tap_service = mock.Mock() arg_list = [ fake_tap_service['id'], ] verify_list = [ (osc_tap_service.TAP_SERVICE, [fake_tap_service['id']]), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) result = self.cmd.take_action(parsed_args) mock_delete_tap_s = self.app.client_manager.network.delete_tap_service mock_delete_tap_s.assert_called_once_with(fake_tap_service['id']) self.assertIsNone(result) class TestShowTapService(test_fakes.TestNeutronClientOSCV2): columns = ( 'id', 'name', 'port_id', 'status', ) def setUp(self): super().setUp() self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_service = mock.Mock( side_effect=lambda name_or_id, ignore_missing: tap_service.TapService(id=name_or_id)) self.cmd = osc_tap_service.ShowTapService(self.app, self.namespace) def test_show_tap_service(self): """Test Show tap service.""" fake_tap_service = fakes.FakeTapService.create_tap_service( attrs={'port_id': uuidutils.generate_uuid()} ) self.app.client_manager.network.get_tap_service = mock.Mock( return_value=fake_tap_service) arg_list = [ fake_tap_service['id'], ] verify_list = [ (osc_tap_service.TAP_SERVICE, fake_tap_service['id']), ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) headers, data = self.cmd.take_action(parsed_args) mock_get_tap_s = self.app.client_manager.network.get_tap_service mock_get_tap_s.assert_called_once_with( fake_tap_service['id']) self.assertEqual(self.columns, headers) fake_data = _get_data( fake_tap_service, osc_tap_service._get_columns(fake_tap_service)[1]) self.assertItemEqual(fake_data, data) class TestUpdateTapService(test_fakes.TestNeutronClientOSCV2): _new_name = 'new_name' columns = ( 'id', 'name', 'port_id', 'status', ) def setUp(self): super().setUp() self.cmd = osc_tap_service.UpdateTapService(self.app, self.namespace) self.app.client_manager.network = mock.Mock() self.app.client_manager.network.find_tap_service = mock.Mock( side_effect=lambda name_or_id, ignore_missing: tap_service.TapService(id=name_or_id)) def test_update_tap_service(self): """Test update tap service""" fake_tap_service = fakes.FakeTapService.create_tap_service( attrs={'port_id': uuidutils.generate_uuid()} ) new_tap_service = copy.deepcopy(fake_tap_service) new_tap_service['name'] = self._new_name self.app.client_manager.network.update_tap_service = mock.Mock( return_value=new_tap_service) arg_list = [ fake_tap_service['id'], '--name', self._new_name, ] verify_list = [('name', self._new_name)] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) attrs = {'name': self._new_name} mock_update_tap_s = self.app.client_manager.network.update_tap_service mock_update_tap_s.assert_called_once_with( fake_tap_service['id'], **attrs) self.assertEqual(self.columns, columns) fake_data = _get_data( new_tap_service, osc_tap_service._get_columns(new_tap_service)[1]) self.assertItemEqual(fake_data, data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/pyproject.toml0000664000175000017500000000014500000000000020023 0ustar00zuulzuul00000000000000[build-system] requires = ["pbr>=5.7.0", "setuptools>=64.0.0", "wheel"] build-backend = "pbr.build" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8232286 tap_as_a_service-15.0.0/releasenotes/0000775000175000017500000000000000000000000017600 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1743591513.855229 tap_as_a_service-15.0.0/releasenotes/notes/0000775000175000017500000000000000000000000020730 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023201 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/notes/bp-port-mirroring-sriov-vf-879bc2aa53c2c8d4.yaml0000664000175000017500000000120400000000000031251 0ustar00zuulzuul00000000000000--- prelude: > Neutron TaaS is integrated with new TaaS Agent driver (i.e. SRIOV) for Intel i40e driver backend. features: - | [`blueprint port-mirroring-sriov-vf `_] * Adds a new TaaS Agent driver (i.e. SRIOV) for Intel i40e driver backend. * Neutron TaaS Agent is refactored to decouple its tight binding with ovs driver. This prepares the taas agent code-base for addition of a new TaaS Agent driver (SRIOV). * Adds a new API extension, i.e. taas-vlan-filter for adding a new attribute (vlan_filter) in tap-flow data model. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/notes/clear-tap-resources-on-port-delete-9583cdd7cd6098ea.yaml0000664000175000017500000000064300000000000032647 0ustar00zuulzuul00000000000000--- prelude: > Deleting a port associated with some tap resources (tap service or tap flow) shall now cascade delete those associated tap resources as well. It should be noted that this change alters the API behaviour in the sense that earlier deleting a port did not clear the associated tap resources. issues: - | Fixes bug `1814937 `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/notes/drop-python-3-6-and-3-7-c54e20d68667644a.yaml0000664000175000017500000000020100000000000027523 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=1743591468.0 tap_as_a_service-15.0.0/releasenotes/notes/tap-mirror-with-ovn-driver-ff26bc79d3d411be.yaml0000664000175000017500000000054400000000000031333 0ustar00zuulzuul00000000000000--- features: - | Add possibility to create (CRUD) ``tap_mirrors`` with ``OVN`` backend. At least ``OVN`` ``v22.12.0`` is necessary to create mirrors. Other tap-as-a-service APIs (tap-service and tap-flow) are not part of this effort (https://specs.openstack.org/openstack/neutron-specs/specs/2023.2/erspan-for-tap-as-a-service.html) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/notes/tap-mirror-with-ovs-driver-7f09654e1e960a0d.yaml0000664000175000017500000000013700000000000031121 0ustar00zuulzuul00000000000000--- features: - | Add possibility to create (CRUD) ``tap_mirrors`` with ``OVS`` backend. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/releasenotes/source/0000775000175000017500000000000000000000000021100 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/2023.1.rst0000664000175000017500000000021000000000000022350 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022352 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000022352 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/2024.2.rst0000664000175000017500000000020200000000000022353 0ustar00zuulzuul00000000000000=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/releasenotes/source/_static/0000775000175000017500000000000000000000000022526 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000024777 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/conf.py0000664000175000017500000002441600000000000022406 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 # tap-as-a-service documentation build configuration file, created by # sphinx-quickstart on Sun May 8 12:21:42 2016. # # 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 = [ 'reno.sphinxext', 'openstackdocstheme', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] 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 = 'tap-as-a-service' copyright = '2017, Tap-as-a-service developers' author = 'Tap-as-a-service developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version_info = pbr.version.VersionInfo('tap-as-a-service') version = version_info.version_string_with_vcs() # The full version, including alpha/beta/rc tags. release = version_info.canonical_version_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. # 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. # This patterns also effect to html_static_path and html_extra_path 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 = 'sphinx' # 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 # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = 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 = 'default' # html_theme = 'openstackdocs' # openstackdocstheme options # repository_name = 'openstack/%s' % project # bug_project = project # bug_tag = 'doc' # html_last_updated_fmt = '%Y-%m-%d %H:%M' # 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. # " v documentation" by default. # html_title = u'tap-as-a-service v1.0.1.0' # 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 (relative to this directory) to use as a 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 not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # html_last_updated_fmt = None # 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 # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'tap-as-a-servicedoc' # -- 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': '', # Latex figure (float) alignment # 'figure_align': 'htbp', } # 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 = [ (master_doc, 'tap-as-a-service.tex', 'tap-as-a-service Documentation', 'OpenStack', '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 = [ (master_doc, 'tap-as-a-service', 'tap-as-a-service Documentation', [author], 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 = [ (master_doc, 'tap-as-a-service', 'tap-as-a-service Documentation', author, 'tap-as-a-service', '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=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/index.rst0000664000175000017500000000032400000000000022740 0ustar00zuulzuul00000000000000============================== Tap-as-a-service Release Notes ============================== .. toctree:: :maxdepth: 1 unreleased 2024.2 2024.1 2023.2 2023.1 zed yoga stein queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000023127 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000022747 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/unreleased.rst0000664000175000017500000000015300000000000023760 0ustar00zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000022561 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000022416 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/requirements.txt0000664000175000017500000000053200000000000020373 0ustar00zuulzuul00000000000000pbr>=5.5.0 # Apache-2.0 neutron>=16.0.0.0b1 # Apache-2.0 neutron-lib>=2.11.0 # Apache-2.0 openstacksdk>=0.102.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 # Opt-in for neutron-lib consumption patches # http://lists.openstack.org/pipermail/openstack-dev/2018-September/135063.html # neutron-lib-current ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/setup.cfg0000664000175000017500000000575300000000000016742 0ustar00zuulzuul00000000000000[metadata] name = tap-as-a-service summary = Tap-as-a-Service (TaaS) is an extension to the OpenStack network service (Neutron), it provides remote port mirroring capability for tenant virtual networks. description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = http://www.openstack.org/ python_requires = >=3.9 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 :: 3 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 [files] packages = neutron_taas data_files = etc/neutron/rootwrap.d = etc/neutron/rootwrap.d/taas-i40e-sysfs.filters scripts = bin/i40e_sysfs_command [entry_points] neutron.agent.l2.extensions = taas = neutron_taas.services.taas.agents.extensions.taas:TaasAgentExtension neutron_taas.taas.agent_drivers = ovs = neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver sriov = neutron_taas.services.taas.drivers.linux.sriov_nic_taas:SriovNicTaasDriver neutron.service_plugins = taas = neutron_taas.services.taas.taas_plugin:TaasPlugin tapmirror = neutron_taas.services.taas.tap_mirror_plugin:TapMirrorPlugin neutron.db.alembic_migrations = tap-as-a-service = neutron_taas.db.migration:alembic_migration oslo.config.opts = neutron.taas = neutron_taas.opts:list_opts neutron.taas.agent = neutron_taas.opts:list_agent_opts oslo.policy.policies = tap-as-a-service = neutron_taas.policies:list_rules neutron.policies = tap-as-a-service = neutron_taas.policies:list_rules openstack.neutronclient.v2 = tap_service_create = neutron_taas.taas_client.osc.tap_service:CreateTapService tap_service_list = neutron_taas.taas_client.osc.tap_service:ListTapService tap_service_show = neutron_taas.taas_client.osc.tap_service:ShowTapService tap_service_delete = neutron_taas.taas_client.osc.tap_service:DeleteTapService tap_service_update = neutron_taas.taas_client.osc.tap_service:UpdateTapService tap_flow_create = neutron_taas.taas_client.osc.tap_flow:CreateTapFlow tap_flow_list = neutron_taas.taas_client.osc.tap_flow:ListTapFlow tap_flow_show = neutron_taas.taas_client.osc.tap_flow:ShowTapFlow tap_flow_delete = neutron_taas.taas_client.osc.tap_flow:DeleteTapFlow tap_flow_update = neutron_taas.taas_client.osc.tap_flow:UpdateTapFlow tap_mirror_create = neutron_taas.taas_client.osc.tap_mirror:CreateTapMirror tap_mirror_list = neutron_taas.taas_client.osc.tap_mirror:ListTapMirror tap_mirror_show = neutron_taas.taas_client.osc.tap_mirror:ShowTapMirror tap_mirror_delete = neutron_taas.taas_client.osc.tap_mirror:DeleteTapMirror tap_mirror_update = neutron_taas.taas_client.osc.tap_mirror:UpdateTapMirror [pbr] autodoc_index_modules = True warnerrors = True [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/setup.py0000664000175000017500000000200600000000000016617 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/specs/0000775000175000017500000000000000000000000016224 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/specs/index.rst0000664000175000017500000000025400000000000020066 0ustar00zuulzuul00000000000000.. tap-as-a-service specs documentation index ============== Specifications ============== Mitaka specs ============ .. toctree:: :glob: :maxdepth: 1 mitaka/* ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/specs/mitaka/0000775000175000017500000000000000000000000017472 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/specs/mitaka/tap-as-a-service.rst0000664000175000017500000005072100000000000023272 0ustar00zuulzuul00000000000000.. This work is licensed under a Creative Commons Attribution 3.0 Unported License. http://creativecommons.org/licenses/by/3.0/legalcode ============================ Tap-as-a-Service for Neutron ============================ Launchpad blueprint: https://blueprints.launchpad.net/neutron/+spec/tap-as-a-service This spec explains an extension for the port mirroring functionality. Port mirroring involves sending a copy of packets ingressing and/or egressing one port (where ingress means entering a VM and egress means leaving a VM) to another port, (usually different from the packet's original destination). A port could be attached to a VM or a networking resource like router. While the blueprint describes the functionality of mirroring Neutron ports as an extension to the port object, the spec proposes to offer port mirroring as a service, which will enable more advanced use-cases (e.g. intrusion detection) to be deployed. The proposed port mirroring capability shall be introduced in Neutron as a service called "Tap-as-a-Service". Problem description =================== Neutron currently does not support the functionality of port mirroring for tenant networks. This feature will greatly benefit tenants and admins, who want to debug their virtual networks and gain visibility into their VMs by monitoring and analyzing the network traffic associated with them (e.g. IDS). This spec focuses on mirroring traffic from one Neutron port to another; future versions may address mirroring from a Neutron port to an arbitrary interface (not managed by Neutron) on a compute host or the network controller. Different usage scenarios for the service are listed below: 1. Tapping/mirroring network traffic ingressing and/or egressing a particular Neutron port. 2. Tapping/mirroring all network traffic on a tenant network. 3. Tenant or admin will be able to do tap/traffic mirroring based on a policy rule and set destination as a Neutron port, which can be linked to a virtual machine as normal Nova operations or to a physical machine via l2-gateway functionality. 4. Admin will be able to do packet level network debugging for the virtual network. 5. Provide a way for real time analytics based on different criteria, like tenants, ports, traffic types (policy) etc. Note that some of the above use-cases are not covered by this proposal, at least for the first step. Proposed change =============== The proposal is to introduce a new Neutron service plugin, called "Tap-as-a-Service", which provides tapping (port-mirroring) capability for Neutron networks; tenant or provider networks. This service will be modeled similar to other Neutron services such as the firewall, load-balancer, L3-router etc. The proposed service will allow the tenants to create a tap service instance to which they can add Neutron ports that need to be mirrored by creating tap flows. The tap service itself will be a Neutron port, which will be the destination port for the mirrored traffic. The destination Tap-as-a-Service Neutron port should be created beforehand on a network owned by the tenant who is requesting the service. The ports to be mirrored that are added to the service must be owned by the same tenant who created the tap service instance. Even on a shared network, a tenant will only be allowed to mirror the traffic from ports that they own on the shared network and not traffic from ports that they do not own on the shared network. The ports owned by the tenant that are mirrored can be on networks other than the network on which tap service port is created. This allows the tenant to mirror traffic from any port it owns on a network on to the same Tap-as-a-Service Neutron port. The tenant can launch a VM specifying the tap destination port for the VM interface (--nic port-id=tap_port_uuid), thus receiving mirrored traffic for further processing (dependent on use case) on that VM. The following would be the work flow for using this service from a tenant's point of view 0. Create a Neutron port which will be used as the destination port. This can be a part of ordinary VM launch. 1. Create a tap service instance, specifying the Neutron port. 2. If you haven't yet, launch a monitoring or traffic analysis VM and connect it to the destination port for the tap service instance. 3. Associate Neutron ports with a tap service instance if/when they need to be monitored. 4. Disassociate Neutron ports from a tap service instance if/when they no longer need to be monitored. 5. Destroy a tap-service instance when it is no longer needed. 6. Delete the destination port when it is no longer neeeded. Please note that the normal work flow of launching a VM is not affected while using TaaS. Alternatives ------------ As an alternative to introducing port mirroring functionality under Neutron services, it could be added as an extension to the existing Neutron v2 APIs. Data model impact ----------------- Tap-as-a-Service introduces the following data models into Neutron as database schemas. 1. tap_service +-------------+--------+----------+-----------+---------------+-------------------------+ | Attribute | Type | Access | Default | Validation/ | Description | | Name | | (CRUD) | Value | Conversion | | +=============+========+==========+===========+===============+=========================+ | id | UUID | R, all | generated | N/A | UUID of the tap | | | | | | | service instance. | +-------------+--------+----------+-----------+---------------+-------------------------+ | project_id | String | CR, all | Requester | N/A | ID of the | | | | | | | project creating | | | | | | | the service | +-------------+--------+----------+-----------+---------------+-------------------------+ | name | String | CRU, all | Empty | N/A | Name for the service | | | | | | | instance. | +-------------+--------+----------+-----------+---------------+-------------------------+ | description | String | CRU, all | Empty | N/A | Description of the | | | | | | | service instance. | +-------------+--------+----------+-----------+---------------+-------------------------+ | port_id | UUID | CR, all | N/A | UUID of a | An existing Neutron port| | | | | | valid Neutron | to which traffic will | | | | | | port | be mirrored | +-------------+--------+----------+-----------+---------------+-------------------------+ | status | String | R, all | N/A | N/A | The operation status of | | | | | | | the resource | | | | | | | (ACTIVE, PENDING_foo, | | | | | | | ERROR, ...) | +-------------+--------+----------+-----------+---------------+-------------------------+ 2. tap_flow +----------------+--------+----------+-----------+---------------+-------------------------+ | Attribute | Type | Access | Default | Validation/ | Description | | Name | | (CRUD) | Value | Conversion | | +================+========+==========+===========+===============+=========================+ | id | UUID | R, all | generated | N/A | UUID of the | | | | | | | tap flow instance. | +----------------+--------+----------+-----------+---------------+-------------------------+ | name | String | CRU, all | Empty | N/A | Name for the tap flow | | | | | | | instance. | +----------------+--------+----------+-----------+---------------+-------------------------+ | description | String | CRU, all | Empty | N/A | Description of the | | | | | | | tap flow instance. | +----------------+--------+----------+-----------+---------------+-------------------------+ | tap_service_id | UUID | CR, all | N/A | Valid tap | UUID of the tap | | | | | | service UUID | service instance. | +----------------+--------+----------+-----------+---------------+-------------------------+ | source_port | UUID | CR, all | N/A | UUID of a | UUID of the Neutron | | | | | | valid Neutron | port that needed to be | | | | | | port | mirrored | +----------------+--------+----------+-----------+---------------+-------------------------+ | direction | ENUM | CR, all | BOTH | | Whether to mirror the | | | (IN, | | | | traffic leaving or | | | OUT, | | | | arriving at the | | | BOTH) | | | | source port | | | | | | | IN: Network -> VM | | | | | | | OUT: VM -> Network | +----------------+--------+----------+-----------+---------------+-------------------------+ | status | String | R, all | N/A | N/A | The operation status of | | | | | | | the resource | | | | | | | (ACTIVE, PENDING_foo, | | | | | | | ERROR, ...) | +----------------+--------+----------+-----------+---------------+-------------------------+ REST API impact --------------- Tap-as-a-Service shall be offered over the RESTFull API interface under the following namespace: http://wiki.openstack.org/Neutron/TaaS/API_1.0 The resource attribute map for TaaS is provided below: .. code-block:: python direction_enum = ['IN', 'OUT', 'BOTH'] RESOURCE_ATTRIBUTE_MAP = { 'tap_service': { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, 'primary_key': True}, 'project_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'required_by_policy': True, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'port_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True}, 'status': {'allow_post': False, 'allow_put': False, 'is_visible': True}, }, 'tap_flow': { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, 'primary_key': True}, 'name': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'description': {'allow_post': True, 'allow_put': True, 'validate': {'type:string': None}, 'is_visible': True, 'default': ''}, 'tap_service_id': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid': None}, 'required_by_policy': True, 'is_visible': True}, 'source_port': {'allow_post': True, 'allow_put': False, 'validate': {'type:uuid': None}, 'required_by_policy': True, 'is_visible': True}, 'direction': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': direction_enum}, 'is_visible': True}, 'status': {'allow_post': False, 'allow_put': False, 'is_visible': True}, } } Security impact --------------- A TaaS instance comprises a collection of source Neutron ports (whose ingress and/or egress traffic are being mirrored) and a destination Neutron port (where the mirrored traffic is received). Security Groups will be handled differently for these two classes of ports, as described below: Destination Side: Ingress Security Group filters, including the filter that prevents MAC-address spoofing, will be disabled for the destination Neutron port. This will ensure that all of the mirrored packets received at this port are able to reach the monitoring VM attached to it. Source Side: Ideally it would be nice to mirror all packets entering and/or leaving the virtual NICs associated with the VMs that are being monitored. This means capturing ingress traffic after it passes the inbound Security Group filters and capturing egress traffic before it passes the outbound Security Group filters. However, due to the manner in which Security Groups are currently implemented in OpenStack (i.e. north of the Open vSwitch ports, using Linux IP Tables) this is not possible because port mirroring support resides inside Open vSwitch. Therefore, in the first version of TaaS, Security Groups will be ignored for the source Neutron ports; this effectively translates into capturing ingress traffic before it passes the inbound Security Group filters and capturing egress traffic after it passes the outbound Security Group filters. In other words, port mirroring will be implemented for all packets entering and/or leaving the Open vSwitch ports associated with the respective virtual NICs of the VMs that are being monitored. There is a separate effort that has been initiated to implement Security Groups within OpenvSwitch. A later version of TaaS may make use of this feature, if and when it is available, so that we can realize the ideal behavior described above. It should be noted that such an enhancement should not require a change to the TaaS data model. Keeping data privacy aspects in mind and preventing the data center admin from snooping on tenant's network traffic without their knowledge, the admin shall not be allowed to mirror traffic from any ports that belong to tenants. Hence creation of 'Tap_Flow' is only permitted on ports that are owned by the creating tenant. If an admin wants to monitor tenant's traffic, the admin will have to join that tenant as a member. This will ensure that the tenant is aware that the admin might be monitoring their traffic. Notifications impact -------------------- A set of new RPC calls for communication between the TaaS server and agents are required and will be put in place as part of the reference implementation. IPv6 impact -------------------- None Other end user impact --------------------- Users will be able to invoke and access the TaaS APIs through python-neutronclient. Performance Impact ------------------ The performance impact of mirroring traffic needs to be examined and quantified. The impact of a tenant potentially mirroring all traffic from all ports could be large and needs more examination. Some alternatives to reduce the amount of mirrored traffic are listed below. 1. Rate limiting on the ports being mirrored. 2. Filters to select certain flows ingressing/egressing a port to be mirrored. 3. Having a quota on the number of TaaS Flows that can be defined by the tenant. Other deployer impact --------------------- Configurations for the service plugin will be added later. A new bridge (br-tap) mentioned in Implementation section. Developer impact ---------------- This will be a new extension API, and will not affect the existing API. Community impact ---------------- None Follow up work -------------- Going forward, TaaS would be incorporated with Service Insertion [2]_ similar to other existing services like FWaaS, LBaaS, and VPNaaS. While integrating Tap-as-a-Service with Service Insertion the key changes to the data model needed would be the removal of 'network_id' and 'port_id' from the 'Tap_Service' data model. Some policy based filtering rules would help alleviate the potential performance issues. We might want to ensure exclusive use of the destination port. We might want to create the destination port automatically on tap-service creation, rather than specifying an existing port. In that case, network_id should be taken as a parameter for tap-service creation, instead of port_id. We might want to allow the destination port be used for purposes other than just launching a VM on it, for example the port could be used as an 'external-port' [1]_ to get the mirrored data out from the tenant virtual network on a device or network not managed by openstack. We might want to introduce a way to tap a whole traffic for the specified network. We need a mechanism to coordinate usage of various resources with other agent extensions. E.g. OVS flows, tunnel IDs, VLAN IDs. Implementation ============== The reference implementation for TaaS will be based on Open vSwitch. In addition to the existing integration (br-int) and tunnel (br-tun) bridges, a separate tap bridge (br-tap) will be used. The tap bridge provides nice isolation for supporting more complex TaaS features (e.g. filtering mirrored packets) in the future. The tapping operation will be realized by adding higher priority flows in br-int, which duplicate the ingress and/or egress packets associated with specific ports (belonging to the VMs being monitored) and send the copies to br-tap. Packets sent to br-tap will also be tagged with an appropriate VLAN id corresponding to the associated TaaS instance (in the initial release these VLAN ids may be reserved from highest to lowest; in later releases it should be coordinated with the Neutron service). The original packets will continue to be processed normally, so as not to affect the traffic patterns of the VMs being monitored. Flows will be placed in br-tap to determine if the mirrored traffic should be sent to br-tun or not. If the destination port of a Tap-aaS instance happens to reside on the same host as a source port, packets from that source port will be returned to br-int; otherwise they will be forwarded to br-tun for delivery to a remote node. Packets arriving at br-tun from br-tap will get routed to the destination ports of appropriate TaaS instances using the same GRE or VXLAN tunnel network that is used to pass regular traffic between hosts. Separate tunnel IDs will be used to isolate different TaaS instances from one another and from the normal (non-mirrored) traffic passing through the bridge. This will ensure that proper action can be taken on the receiving end of a tunnel so that mirrored traffic is sent to br-tap instead of br-int. Special flows will be used in br-tun to automatically learn about the location of the destination ports of TaaS instances. Packets entering br-tap from br-tun will be forwarded to br-int only if the destination port of the corresponding TaaS instance resides on the same host. Finally, packets entering br-int from br-tap will be delivered to the appropriate destination port after the TaaS instance VLAN id is replaced with the VLAN id for the port. Assignee(s) ----------- * Vinay Yadhav Work Items ---------- * TaaS API and data model implementation. * TaaS OVS driver. * OVS agent changes for port mirroring. Dependencies ============ None Testing ======= * Unit Tests to be added. * Functional tests in tempest to be added. * API Tests in Tempest to be added. Documentation Impact ==================== * User Documentation needs to be updated * Developer Documentation needs to be updated References ========== .. [1] External port https://review.openstack.org/#/c/87825 .. [2] Service base and insertion https://review.openstack.org/#/c/93128 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/0000775000175000017500000000000000000000000022070 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/PKG-INFO0000644000175000017500000000447000000000000023170 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: tap-as-a-service Version: 15.0.0 Summary: Tap-as-a-Service (TaaS) is an extension to the OpenStack network service (Neutron), it provides remote port mirroring capability for tenant virtual networks. Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr>=5.5.0 Requires-Dist: neutron>=16.0.0.0b1 Requires-Dist: neutron-lib>=2.11.0 Requires-Dist: openstacksdk>=0.102.0 Requires-Dist: python-openstackclient>=3.12.0 Requires-Dist: osc-lib>=2.3.0 ================ Tap as a Service ================ Tap-as-a-Service (TaaS) is an extension to the OpenStack network service (Neutron). It provides remote port mirroring capability for tenant virtual networks. Port mirroring involves sending a copy of packets entering and/or leaving one port to another port, which is usually different from the original destinations of the packets being mirrored. This service has been primarily designed to help tenants (or the cloud administrator) debug complex virtual networks and gain visibility into their VMs, by monitoring the network traffic associated with them. TaaS honors tenant boundaries and its mirror sessions are capable of spanning across multiple compute and network nodes. It serves as an essential infrastructure component that can be utilized for supplying data to a variety of network analytics and security applications (e.g. IDS). * Free software: Apache license * API Reference: https://opendev.org/openstack/tap-as-a-service/src/branch/master/API_REFERENCE.rst * Source: https://opendev.org/openstack/tap-as-a-service/ * Bugs: https://bugs.launchpad.net/tap-as-a-service For installing Tap-as-a-Service with Devstack please read the INSTALL.rst file ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/SOURCES.txt0000664000175000017500000001704700000000000023765 0ustar00zuulzuul00000000000000.coveragerc .mailmap .pre-commit-config.yaml .pylintrc .stestr.conf .zuul.yaml API_REFERENCE.rst AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst INSTALL.rst LICENSE README.rst bindep.txt pyproject.toml requirements.txt setup.cfg setup.py test-requirements.txt tox.ini bin/i40e_sysfs_command deliverables/queens/tap-as-a-service.yaml deliverables/rocky/tap-as-a-service.yaml deliverables/stein/tap-as-a-service.yaml deliverables/train/tap-as-a-service.yaml devstack/README.rst devstack/devstackgaterc devstack/plugin.sh devstack/settings doc/requirements.txt doc/source/api_reference.rst doc/source/conf.py doc/source/contributing.rst doc/source/index.rst doc/source/installation.rst doc/source/mirroring_sriov_ports.rst doc/source/mirroring_with_ovs_driver.rst doc/source/presentations.rst doc/source/readme.rst doc/source/specs doc/source/tap_mirrors_under_the_hood.rst doc/source/usage.rst etc/policy-generator.conf etc/neutron/policy.yaml.sample etc/neutron/rootwrap.d/taas-i40e-sysfs.filters etc/oslo-config-generator/taas_agent.ini etc/oslo-config-generator/taas_plugin.ini neutron_taas/__init__.py neutron_taas/_i18n.py neutron_taas/opts.py neutron_taas/common/__init__.py neutron_taas/common/config.py neutron_taas/common/constants.py neutron_taas/common/topics.py neutron_taas/common/utils.py neutron_taas/db/__init__.py neutron_taas/db/head.py neutron_taas/db/taas_db.py neutron_taas/db/tap_mirror_db.py neutron_taas/db/migration/__init__.py neutron_taas/db/migration/taas_init_ops.py neutron_taas/db/migration/alembic_migration/README neutron_taas/db/migration/alembic_migration/__init__.py neutron_taas/db/migration/alembic_migration/env.py neutron_taas/db/migration/alembic_migration/script.py.mako neutron_taas/db/migration/alembic_migration/versions/CONTRACT_HEAD neutron_taas/db/migration/alembic_migration/versions/EXPAND_HEAD neutron_taas/db/migration/alembic_migration/versions/start_neutron_taas.py neutron_taas/db/migration/alembic_migration/versions/2025.1/expand/f8f1f10ebaf9_mirroring_for_taas.py neutron_taas/db/migration/alembic_migration/versions/newton/contract/1817af933379_remove_network_id_from_tap_service.py neutron_taas/db/migration/alembic_migration/versions/newton/contract/2ecce0368a62_add_foreign_key_constraint_on_tap_id_association.py neutron_taas/db/migration/alembic_migration/versions/newton/contract/4086b3cffc01_rename_tenant_to_project.py neutron_taas/db/migration/alembic_migration/versions/newton/contract/80c85b675b6e_initial_newton_no_op_contract_script.py neutron_taas/db/migration/alembic_migration/versions/newton/expand/04625466c6fa_initial_newton_no_op_expand_script.py neutron_taas/db/migration/alembic_migration/versions/newton/expand/fddbdec8711a_add_status.py neutron_taas/db/migration/alembic_migration/versions/pike/contract/bac61f603e39_alter_tap_id_associations_to_support_tap_id_reuse.py neutron_taas/db/migration/alembic_migration/versions/stein/expand/ccbcc559d175_add_vlan_filter_to_tap_flow.py neutron_taas/extensions/__init__.py neutron_taas/extensions/taas.py neutron_taas/extensions/tap_mirror.py neutron_taas/extensions/vlan_filter.py neutron_taas/policies/__init__.py neutron_taas/policies/tap_flow.py neutron_taas/policies/tap_mirror.py neutron_taas/policies/tap_service.py neutron_taas/services/__init__.py neutron_taas/services/taas/__init__.py neutron_taas/services/taas/taas_plugin.py neutron_taas/services/taas/tap_mirror_plugin.py neutron_taas/services/taas/agents/__init__.py neutron_taas/services/taas/agents/taas_agent_api.py neutron_taas/services/taas/agents/common/__init__.py neutron_taas/services/taas/agents/common/taas_agent.py neutron_taas/services/taas/agents/extensions/__init__.py neutron_taas/services/taas/agents/extensions/taas.py neutron_taas/services/taas/drivers/__init__.py neutron_taas/services/taas/drivers/linux/__init__.py neutron_taas/services/taas/drivers/linux/ovs_constants.py neutron_taas/services/taas/drivers/linux/ovs_taas.py neutron_taas/services/taas/drivers/linux/ovs_utils.py neutron_taas/services/taas/drivers/linux/sriov_nic_exceptions.py neutron_taas/services/taas/drivers/linux/sriov_nic_taas.py neutron_taas/services/taas/drivers/linux/sriov_nic_utils.py neutron_taas/services/taas/service_drivers/__init__.py neutron_taas/services/taas/service_drivers/service_driver_context.py neutron_taas/services/taas/service_drivers/taas_agent_api.py neutron_taas/services/taas/service_drivers/taas_rpc.py neutron_taas/services/taas/service_drivers/ovn/__init__.py neutron_taas/services/taas/service_drivers/ovn/helper.py neutron_taas/services/taas/service_drivers/ovn/taas_ovn.py neutron_taas/services/taas/service_drivers/ovn/ovsdb/__init__.py neutron_taas/services/taas/service_drivers/ovn/ovsdb/impl_idl_taas.py neutron_taas/taas_client/__init__.py neutron_taas/taas_client/osc/__init__.py neutron_taas/taas_client/osc/tap_flow.py neutron_taas/taas_client/osc/tap_mirror.py neutron_taas/taas_client/osc/tap_service.py neutron_taas/tests/__init__.py neutron_taas/tests/base.py neutron_taas/tests/unit/__init__.py neutron_taas/tests/unit/db/__init__.py neutron_taas/tests/unit/db/test_migrations.py neutron_taas/tests/unit/db/test_taas_db.py neutron_taas/tests/unit/db/test_tap_mirror_db.py neutron_taas/tests/unit/extensions/__init__.py neutron_taas/tests/unit/extensions/test_taas.py neutron_taas/tests/unit/extensions/test_tap_mirror.py neutron_taas/tests/unit/extensions/test_vlan_filter.py neutron_taas/tests/unit/services/__init__.py neutron_taas/tests/unit/services/drivers/__init__.py neutron_taas/tests/unit/services/drivers/test_linux_ovs_taas.py neutron_taas/tests/unit/services/drivers/test_linux_sriov_nic_driver.py neutron_taas/tests/unit/services/drivers/test_linux_sriov_utils.py neutron_taas/tests/unit/services/ovn/__init__.py neutron_taas/tests/unit/services/ovn/test_helper.py neutron_taas/tests/unit/services/ovn/test_taas_ovn.py neutron_taas/tests/unit/services/ovn/ovsdb/__init__.py neutron_taas/tests/unit/services/ovn/ovsdb/test_impl_idl_taas.py neutron_taas/tests/unit/services/ovn/ovsdb/schema_files/ovn-nb.ovsschema neutron_taas/tests/unit/services/taas/__init__.py neutron_taas/tests/unit/services/taas/test_taas_plugin.py neutron_taas/tests/unit/services/taas/test_tap_mirror_plugin.py neutron_taas/tests/unit/taas_client/__init__.py neutron_taas/tests/unit/taas_client/osc/__init__.py neutron_taas/tests/unit/taas_client/osc/fakes.py neutron_taas/tests/unit/taas_client/osc/test_osc_tap_flow.py neutron_taas/tests/unit/taas_client/osc/test_osc_tap_mirror.py neutron_taas/tests/unit/taas_client/osc/test_osc_tap_service.py releasenotes/notes/.placeholder releasenotes/notes/bp-port-mirroring-sriov-vf-879bc2aa53c2c8d4.yaml releasenotes/notes/clear-tap-resources-on-port-delete-9583cdd7cd6098ea.yaml releasenotes/notes/drop-python-3-6-and-3-7-c54e20d68667644a.yaml releasenotes/notes/tap-mirror-with-ovn-driver-ff26bc79d3d411be.yaml releasenotes/notes/tap-mirror-with-ovs-driver-7f09654e1e960a0d.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/queens.rst releasenotes/source/stein.rst releasenotes/source/unreleased.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder specs/index.rst specs/mitaka/tap-as-a-service.rst tap_as_a_service.egg-info/PKG-INFO tap_as_a_service.egg-info/SOURCES.txt tap_as_a_service.egg-info/dependency_links.txt tap_as_a_service.egg-info/entry_points.txt tap_as_a_service.egg-info/not-zip-safe tap_as_a_service.egg-info/pbr.json tap_as_a_service.egg-info/requires.txt tap_as_a_service.egg-info/top_level.txt tools/generate_config_file_samples.sh././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/dependency_links.txt0000664000175000017500000000000100000000000026136 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/entry_points.txt0000664000175000017500000000363000000000000025370 0ustar00zuulzuul00000000000000[neutron.agent.l2.extensions] taas = neutron_taas.services.taas.agents.extensions.taas:TaasAgentExtension [neutron.db.alembic_migrations] tap-as-a-service = neutron_taas.db.migration:alembic_migration [neutron.policies] tap-as-a-service = neutron_taas.policies:list_rules [neutron.service_plugins] taas = neutron_taas.services.taas.taas_plugin:TaasPlugin tapmirror = neutron_taas.services.taas.tap_mirror_plugin:TapMirrorPlugin [neutron_taas.taas.agent_drivers] ovs = neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver sriov = neutron_taas.services.taas.drivers.linux.sriov_nic_taas:SriovNicTaasDriver [openstack.neutronclient.v2] tap_flow_create = neutron_taas.taas_client.osc.tap_flow:CreateTapFlow tap_flow_delete = neutron_taas.taas_client.osc.tap_flow:DeleteTapFlow tap_flow_list = neutron_taas.taas_client.osc.tap_flow:ListTapFlow tap_flow_show = neutron_taas.taas_client.osc.tap_flow:ShowTapFlow tap_flow_update = neutron_taas.taas_client.osc.tap_flow:UpdateTapFlow tap_mirror_create = neutron_taas.taas_client.osc.tap_mirror:CreateTapMirror tap_mirror_delete = neutron_taas.taas_client.osc.tap_mirror:DeleteTapMirror tap_mirror_list = neutron_taas.taas_client.osc.tap_mirror:ListTapMirror tap_mirror_show = neutron_taas.taas_client.osc.tap_mirror:ShowTapMirror tap_mirror_update = neutron_taas.taas_client.osc.tap_mirror:UpdateTapMirror tap_service_create = neutron_taas.taas_client.osc.tap_service:CreateTapService tap_service_delete = neutron_taas.taas_client.osc.tap_service:DeleteTapService tap_service_list = neutron_taas.taas_client.osc.tap_service:ListTapService tap_service_show = neutron_taas.taas_client.osc.tap_service:ShowTapService tap_service_update = neutron_taas.taas_client.osc.tap_service:UpdateTapService [oslo.config.opts] neutron.taas = neutron_taas.opts:list_opts neutron.taas.agent = neutron_taas.opts:list_agent_opts [oslo.policy.policies] tap-as-a-service = neutron_taas.policies:list_rules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/not-zip-safe0000664000175000017500000000000100000000000024316 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/pbr.json0000664000175000017500000000005600000000000023547 0ustar00zuulzuul00000000000000{"git_version": "22dbac7", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/requires.txt0000664000175000017500000000016700000000000024474 0ustar00zuulzuul00000000000000pbr>=5.5.0 neutron>=16.0.0.0b1 neutron-lib>=2.11.0 openstacksdk>=0.102.0 python-openstackclient>=3.12.0 osc-lib>=2.3.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591513.0 tap_as_a_service-15.0.0/tap_as_a_service.egg-info/top_level.txt0000664000175000017500000000001500000000000024616 0ustar00zuulzuul00000000000000neutron_taas ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/test-requirements.txt0000664000175000017500000000061100000000000021346 0ustar00zuulzuul00000000000000hacking>=6.1.0,<6.2.0 # Apache-2.0 coverage>=5.2.1 # Apache-2.0 python-subunit>=1.4.0 # Apache-2.0/BSD psycopg2>=2.8.5 # LGPL/ZPL PyMySQL>=0.10.0 # MIT License oslotest>=4.4.1 # Apache-2.0 requests_mock>=1.5.0 # Apache-2.0 stestr>=3.0.1 # Apache-2.0 testresources>=2.0.1 # Apache-2.0/BSD testscenarios>=0.5.0 # Apache-2.0/BSD testtools>=2.4.0 # MIT isort==5.10.1 # MIT WebTest>=2.0.27 # MIT ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591513.8592288 tap_as_a_service-15.0.0/tools/0000775000175000017500000000000000000000000016247 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591468.0 tap_as_a_service-15.0.0/tools/generate_config_file_samples.sh0000775000175000017500000000144000000000000024447 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=1743591468.0 tap_as_a_service-15.0.0/tox.ini0000664000175000017500000001005600000000000016424 0ustar00zuulzuul00000000000000[tox] envlist = docs,py38,pep8 minversion = 3.18.0 ignore_basepython_conflict = True [testenv] basepython = python3 setenv = VIRTUAL_ENV={envdir} LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C EDITOR=vi PYTHONWARNINGS=default::DeprecationWarning usedevelop = True deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.py[c|o]" -delete find . -type l -name "*.py[c|o]" -delete find . -type d -name "__pycache__" -delete stestr run '{posargs}' allowlist_externals = bash find [tox:jenkins] sitepackages = True [testenv:pep8] deps = {[testenv]deps} bashate>=2.1.1 # Apache-2.0 bandit>=1.7.5 # Apache-2.0 pylint==3.2.0 # GPLv2 commands = flake8 pylint --version pylint --rcfile=.pylintrc --output-format=colorized {posargs:neutron_taas} neutron-db-manage --subproject tap-as-a-service --database-connection sqlite:// check_migration {[testenv:genconfig]commands} {[testenv:genpolicy]commands} allowlist_externals = bash [testenv:venv] commands = {posargs} [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source neutron_taas --parallel-mode commands = stestr run --no-subunit-trace {posargs} coverage combine coverage report --fail-under=50 --skip-covered 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 -d doc/build/doctrees -b html doc/source doc/build/html [testenv:releasenotes] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt -r{toxinidir}/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:genpolicy] commands = oslopolicy-sample-generator --config-file etc/policy-generator.conf [testenv:debug] commands = oslo_debug_helper {posargs} [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,*openstack/common*,*lib/python*,*egg,build # 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 [hacking] import_exceptions = neutron_taas._i18n [testenv:genconfig] commands = bash {toxinidir}/tools/generate_config_file_samples.sh [testenv:dev] # run locally (not in the gate) using editable mode # https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs commands = pip install -q -e "git+https://opendev.org/openstack/neutron#egg=neutron" [testenv:py3-dev] commands = {[testenv:dev]commands} {[testenv]commands} [testenv:pep8-dev] commands = {[testenv:dev]commands} {[testenv:pep8]commands}