././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0535874 os_vif-4.3.0/0000775000175000017500000000000015140132020011611 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/.coveragerc0000664000175000017500000000007215140131732013742 0ustar00zuulzuul[run] branch = True source = os_vif omit = os_vif/tests/* ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/.mailmap0000664000175000017500000000013115140131732013236 0ustar00zuulzuul# Format is: # # ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/.pre-commit-config.yaml0000664000175000017500000000115115140131732016101 0ustar00zuulzuul--- repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.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 files: .*\.(yaml|yml)$ exclude: '^zuul.d/.*$' - repo: https://opendev.org/openstack/hacking rev: 7.0.0 hooks: - id: hacking additional_dependencies: [] exclude: '^(doc|releasenotes)/.*$' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/.stestr.conf0000664000175000017500000000006215140131732014071 0ustar00zuulzuul[DEFAULT] test_path=${OS_TEST_PATH:-.} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/.zuul.yaml0000664000175000017500000000763215140131732013573 0ustar00zuulzuul- job: name: openstack-tox-functional-ovs-with-sudo parent: openstack-tox-functional-with-sudo required-projects: - opendev.org/openstack/devstack pre-run: playbooks/openstack-tox-functional-ovs-with-sudo/pre.yaml timeout: 600 - job: name: os-vif-tempest-base parent: devstack-tempest timeout: 7800 description: | Base integration test with Neutron networking and py3. This is derived from tempest-full-py3 and adapted for use in os-vif required-projects: - openstack/nova - openstack/os-vif - openstack/neutron - openstack/tempest vars: tempest_concurrency: 4 configure_swap_size: 8192 tox_envlist: full devstack_plugins: neutron: https://opendev.org/openstack/neutron.git devstack_localrc: FORCE_CONFIG_DRIVE: true ENABLE_VOLUME_MULTIATTACH: true # NOTE(sean-k-mooney) we do not have to set # DEVSTACK_PROJECT_FROM_GIT: "os-vif" # in the local.conf because os-vif is listed as a required # project and will be added to the LIB_FROM_GIT automatically. devstack_services: s-account: false s-container: false s-object: false s-proxy: false # without Swift, c-bak cannot run (in the Gate at least) c-bak: false - job: name: os-vif-ovs-base parent: os-vif-tempest-base description: | os-vif ovs base job, this should not be used directly. vars: devstack_services: # Disable OVN services br-ex-tcpdump: false br-int-flows: false ovn-controller: false ovn-northd: false ovs-vswitchd: false ovsdb-server: false q-ovn-metadata-agent: false # Neutron services q-agt: true q-dhcp: true q-l3: true q-meta: true q-metering: true devstack_localrc: Q_AGENT: openvswitch Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch Q_DVR_MODE: dvr_snat Q_ML2_TENANT_NETWORK_TYPE: vxlan devstack_local_conf: post-config: $NEUTRON_CONF: DEFAULT: enable_dvr: yes l3_ha: yes $NEUTRON_L3_CONF: agent: availability_zone: nova $NEUTRON_DHCP_CONF: agent: availability_zone: nova "/$NEUTRON_CORE_PLUGIN_CONF": ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: tunnel_types: vxlan - job: name: os-vif-ovs-iptables parent: os-vif-ovs-base description: | os-vif ovs iptables job (tests hybrid-plug=true) vars: devstack_local_conf: post-config: $NOVA_CONF: os_vif_ovs: isolate_vif: true # NOTE(sean-k-mooney): i do not believe that the devstack role # will merge the base /$NEUTRON_CORE_PLUGIN_CONF with the parent # job so we redefine the entire section "/$NEUTRON_CORE_PLUGIN_CONF": ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: tunnel_types: vxlan securitygroup: firewall_driver: iptables_hybrid enable_ipset: false - job: name: os-vif-ovn parent: os-vif-tempest-base description: | os-vif ovn job (tests hybrid-plug=false) vars: devstack_local_conf: post-config: $NOVA_CONF: os_vif_ovs: per_port_bridge: true - project: templates: - check-requirements - openstack-python3-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 - openstack-cover-jobs check: jobs: - openstack-tox-functional-ovs-with-sudo - os-vif-ovn - os-vif-ovs-iptables gate: jobs: - openstack-tox-functional-ovs-with-sudo - os-vif-ovn - os-vif-ovs-iptables ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/AUTHORS0000664000175000017500000000655215140132017012677 0ustar00zuulzuulAdrian Chiris Adrien Cunin Alin Balutoiu Andreas Jaeger Balazs Gibizer Bence Romsics Bodo Petermann Brian Haley Cao Xuan Hoang Carlos Goncalves ChangBo Guo(gcb) Charles Short Claudiu Belu Corey Bryant Cyril Roelandt Daniel P. Berrange Davanum Srinivas David Vallee Delisle Doug Hellmann Eric Fried Eric Fried Flavio Percoco Francesco Santoro Ghanshyam Mann Hamdy Khader Hangdong Zhang Ian Wienand Ihar Hrachyshka Jakub Libosvar Jan Gutter Janonymous Jay Pipes Kevin Benton Lucian Petrut Mamduh Mamduh Alassi Maria Malyarova Masayuki Igawa Matt Riedemann Michał Dulko Miguel Lavalle Moshe Levi OpenStack Release Bot Przemyslaw Lal Rajesh Tailor Rawlin Peters Rodolfo Alonso Hernandez Rodolfo Alonso Hernandez Rodolfo Alonso Hernandez <“ralonso@redhat.com”> Sahid Orentino Ferdjaoui Sean Dague Sean M. Collins Sean Mooney Sean Mooney Sean Mooney Sergey Belous Spencer Yu Sriharsha Basavapatna Stephen Finucane Swapnil Kulkarni (coolsvap) Takashi Kajinami Takashi NATSUME Takashi Natsume Thomas Bechtold Tony Breeds Tony Xu Vasyl Saienko Vieri <15050873171@163.com> Vu Cong Tuan XinxinShen YAMAMOTO Takashi ZhijunWei blue55 caoyuan elajkat ericxiett gecong1973 jacky06 kavithahr lingyongxu loooosy melanie witt melissaml pengyuesheng pranabjb qingszhao shaleijie sunjia vagrant wangjiaqi07 wu.shiming ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/CONTRIBUTING.rst0000664000175000017500000000112715140131732014264 0ustar00zuulzuulThe source repository for this project can be found at: https://opendev.org/openstack/os-vif Pull requests submitted through GitHub are not monitored. To start contributing to OpenStack, follow the steps in the contribution guide to set up and use Gerrit: https://docs.openstack.org/contributors/code-and-documentation/quick-start.html Bugs should be filed on Launchpad: https://bugs.launchpad.net/os-vif For more specific information about contributing to this repository, see the os-vif contributor guide: https://docs.openstack.org/os-vif/latest/contributor/contributing.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/ChangeLog0000664000175000017500000003502415140132017013375 0ustar00zuulzuulCHANGES ======= 4.3.0 ----- * Add TAP device pre-creation support for OVS/OVN * Fixed bridge name when per\_port\_bridge is used * Stabilize functional test * reno: Update master for unmaintained/2024.1 * Update master for stable/2025.2 * Remove remaining job with Ubuntu Jammy (22.04) 4.2.1 ----- * Zuul: do not use USE\_PYTHON3 4.2.0 ----- * OVS Trunk: Add bridge\_name to external\_ids * add pyproject.toml to support pip 23.1 * Adapt unit tests to pyroute2 0.9.1 * Update master for stable/2025.1 4.1.0 ----- * Update gate jobs as per the 2025.1 cycle testing runtime * Deprecate linux bridge plugin * Drop kuryr-kubernetes-tempest * Remove os-vif-linuxbridge * Do not add taps in trunk bridges to the dead vlan 4.0.0 ----- * Clean up Windows support * address test stablity under load * reno: Update master for unmaintained/2023.1 * Integrate pre-commit * Remove Python 3.8 support * Update master for stable/2024.2 3.7.0 ----- * Remove old excludes 3.6.0 ----- * reno: Update master for unmaintained/zed * Update master for stable/2024.1 * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria 3.5.0 ----- * Drop wrong stacklevel * Fix missing or unnecessary dependencies * reno: Update master for unmaintained/yoga * tox: Drop envdir * Update python classifier in setup.cfg 3.4.0 ----- * coveragerc: Remove non-existent path * Deprecate Windows support 3.3.0 ----- * Update master for stable/2023.2 * Don't break traffic if port already exists 3.2.0 ----- * remove focal based jobs * set default qos policy * Update master for stable/2023.1 3.1.1 ----- * Increase the swap size to 8GB in tempest jobs * Implement "BaseCommand" result property * Update gate jobs as per the 2023.1 cycle testing runtime * Make tox.ini tox 4.0.0 compatible 3.1.0 ----- * adapt to stestr 4.0 release * Move mtu update request into ovsdb transaction * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed * remove unicode from code 3.0.0 ----- * update ci since linuxbridge is now experimental * Drop lower-constraints.txt and its testing * Delete trunk bridges to avoid race with Neutron * update job template to zed * Check for hybrid plugging in OVS 2.8.0 ----- * Change minversion of tox to 3.18.0 * Update master for stable/yoga * Fix race with DPDK and vhostuserclient mode 2.7.1 ----- * Updating python testing classifier as per Yoga testing runtime 2.7.0 ----- * Fix typos * Add Python3 yoga unit tests * Update master for stable/xena * only register tables used by os-vif * Use TCP keepalives for ovsdb connections 2.6.0 ----- * add configurable per port bridges * update os-vif ci to account for devstack default changes 2.5.0 ----- * Creating oslo.config.opts entry\_points for plugins * setup.cfg: Replace dashes with underscores * Add Python3 xena unit tests * Update master for stable/wallaby 2.4.0 ----- * Resolve dependency issues * Drop use of deprecated collections classes 2.3.0 ----- * Fix hacking min version to 3.0.1 * Fix - os-vif fails to get the correct UpLink Representor * tox: Rename 'UPPER\_CONSTRAINTS\_FILE' -> 'TOX\_CONSTRAINTS\_FILE' * Add Python3 wallaby unit tests * Update master for stable/victoria * Refactor code of linux\_net to more cleaner and increase performace 2.2.0 ----- * deprecate ovs-vsctl driver and make native the default * windows: Add missing return * update tox envs and support pdf docs * [goal] migrate testing to ubuntu focal * support pyroute2 0.5.13 2.1.0 ----- * Use unittest.mock instead of third party mock * Switch to newer openstackdocstheme and reno versions * Remove .testr.conf * Remove egg\_info in setup.cfg * Remove translation sections from setup.cfg * Remove six * [Community goal] Update contributor documentation * Add Python3 victoria unit tests * Update master for stable/ussuri * Fix doc build job for wanring turn into error * trivial: Remove some rules from flake8 ignore list * Update hacking for Python3 2.0.0 ----- * [OVS] VLAN tag should be set in the Port register * Revert "[Follow Up] OVS DPDK port representors support" * move os-vif-ovs to be a non legacy job * [Follow Up] OVS DPDK port representors support * Drop python2 support and testing * Switch to Ussuri jobs * Update the constraints url * Update master for stable/train 1.17.0 ------ * Fix code bug in document * only disable mac ageing for ovs hybrid plug * Bump the openstackdocstheme extension to 1.20 * Blacklist sphinx 2.1.0 (autodoc bug) * Sync Sphinx requirement * Add Python 3 Train unit tests * set ignore\_basepython\_conflict = True in tox.ini * OVS DPDK port representors support * Fix mock of built in "open" function in unit tests 1.16.0 ------ * Remove unused vif\_plug\_ovs.i18n module * Fix Kuryr-Kubernetes job name * Replace git.openstack.org URLs with opendev.org URLs * Prevent "qbr" Linux Bridge from replying to ARP messages * Remove IP proxy methods * OpenDev Migration Patch * Refactor functional base test classes * Drop testtools from test-requirements.txt * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 1.15.1 ------ * add additional check and gate jobs for os-vif * Add "master" parameter to ip.set() API function 1.15.0 ------ * Add native implementation OVSDB API * docs: Use sphinx.ext.autodoc for profile, datapath offload types * docs: Use sphinx.ext.autodoc for VIF types * make functional tests run on python 3 * Fix nits in brctl removal (vif\_plug\_linux\_bridge) * docs: Add API docs for profile, datapath offload types * docs: Add API docs for VIF types * remove use of brctl from vif\_plug\_linux\_bridge * remove brctl from vif\_plug\_ovs * Add function "has\_table\_columns" to OVSDB implementation API * Clean up versioned object backlevelling code * Change python3.5 job to python3.7 job on Stein+ * Add create\_port field in VIFPortProfileOpenVSwitch profile * Convert hardcoded regexes to raw strings for py36 1.14.0 ------ * make kuryr-kubernetes-tempest-daemon-octavia non voting * Add test to check os\_vif.internal.command.ip.exists * Import IP implementation modules outside privsep context * Cleanup device at the end of 'test\_iproute\_object\_closes\_correctly' test * do not always plug ovs ports * Apply workaround to host\_info serialization test * Extend port profiles with datapath offload type * Update hacking version 1.13.1 ------ * Create iproute.IPRoute() inside a context 1.13.0 ------ * add isolate\_vif config option * Change openstack-dev to openstack-discuss * always create ovs port during plug * Update min tox version to 2.0 * Do not import pyroute2 on Windows 1.12.0 ------ * Do not call linux\_net.delete\_net\_dev on Windows * Fix random test\_unplug\_ovs failures * Reflow docs to 79 columns * clean up ip\_command interface * Remove IPTools deprecated implementation * Add abstract OVSDB API * Add support for Windows network commands * add nested DPDK VIF classes for kuryr-kubernetes * Fix upper-constraints link in tox file * Cleanup zuul config file * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Support for OVS DB TCP socket communication * Update reno for stable/rocky * Add vif\_plug\_noop to setup.cfg packages 1.11.0 ------ * add upper\_constraints support * convert os-vif docs to follow PTI * doc: Fix arg specs and object types in docs * Remove [tox:jenkins] section from tox.ini * Remove unnecessary pyNN testenv sections * move legacy-tempest-dsvm-nova-os-vif to repo * doc: Fix formatting issues * add noop plugin * Add release note link in README * Fix docstrings to work with Sphinx 1.7.4 * fix tox python3 overrides * fix tox py27 job * Trivial: Update pypi url to new url * Add lower-constraints job 1.10.0 ------ * Add kuryr-kubernetes Tempest job * Update links in README * ovs: do not delete port if already exists * zuul: Enable functional tests in gate * Update reno for stable/queens * Configure privsep binary * Fix VF-rep lookup routine to use parent PF number 1.9.0 ----- * adds iptools driver for ip commands * Revert "Move 'ips' field from Subnet object to VIF object" * Git ignore .stestr 1.8.0 ----- * Migrate from 'ip' commands to 'pyroute2' * Check if interface belongs to a Linux Bridge before removing * Updated from global requirements * Remove setting of version/release from releasenotes * Updated from global requirements * Move 'ips' field from Subnet object to VIF object * Add VersionedObjectPrintable mixin * Add Port Profile info to VIF objects Linux Bridge plugin * Updated from global requirements * ovs-hybrid: should permanently keep MAC entries * Add Port Profile info to VIF objects OVS plugin * Rehome OVO unit tests to tests.unit.test\_object.py * Add \`\`HostPortProfileInfo\`\` class * Add plugin names as constants * Updated from global requirements * Using assertIsNone() instead of assertEqual(None) * Read datapath\_type from VIF object * Update reno for stable/pike * Update the documentation link for doc migration * doc: Remove cruft from releasenotes conf.py 1.7.0 ----- * Improve OVS Representor VF Lookup * Improve OVS Representor Lookup * Add support for VIFPortProfileOVSRepresentor * unplug\_vf\_passthrough: don't try to delete representor netdev * Enable some off-by-default checks * set mtu on all code paths * fix read the representor phys\_port\_name * doc: Switch from oslosphinx to openstackdocstheme * doc: Create directory structure for docs migration * Use \`\`assert\_has\_calls\`\` to check function calls * Updated from global requirements * Rehome unit tests to \`\`tests\unit\`\` folder 1.6.0 ----- * hardware offload support for openvswitch * Use versionedobjects PCIAddress field * Fix typo VIFVIFHostDeviceDevType to VIFHostDeviceDevType 1.5.0 ----- * Updated from global requirements * Standardize README * Revert "hardware offload support for openvswitch" * hardware offload support for openvswitch * Fix typos in vif\_types.rst * Add documentation for Linux Bridge plugin * Add documentation for OVS plugin * docs: Stop building anything but html output * doc: Add glossary * doc: Rewrap 'vif\_types' document * Argument should have 2 params * Explain why we bring up the lb in hybird mode * Remove log translations * Use Sphinx 1.5 warning-is-error * Updated from global requirements * Updated from global requirements * vif\_plug\_ovs: Skip setting MTU on Windows when plugging devices * The Python 3.5 is added * Don't install iptables rules if neutron is filtering * Correct object path in comments * Delete H803 from ignore list * Update reno for stable/ocata * Removing Deprecated hacking Check * Fix broken Link * [py35] Switch filter to list comprehensions * Remove support for py33 1.4.0 ----- * introduces MTU support for vhost-user * vif\_plug\_ovs: Always set MTU when plugging devices * os-vif: add new port profiles to enable fast path vhostuser * add support for vhost-user reconnect * os-vif: add vif\_name to VIFVHostUser class * Changed the home-page link * Drop MANIFEST.in - it's not needed by pbr * Show team and repo badges on README * remove use of contextlib and with nested * host\_info: add ability to filter list of supported vifs * host\_info: fix get\_common\_version method on HostVIFInfo * host\_info: fix has\_vif/get\_vif methods on HostPluginInfo * Updated from global requirements * host\_info: fix has\_plugin/get\_plugin methods on HostInfo 1.3.0 ----- * Enable release notes translation * os-vif: add initial documentation about object model * Add oslo.concurrency to requirements * Make plugin loading more consistent with logging guidelines * Updated from global requirements * vif: stop VIFOpenVSwitch inheriting VIFBridge * Updated from global requirements * Add MTU to Network model and use it in plugging * Update reno for stable/newton * Adds Windows support for OvsPlugin * Check for concurrent bridge creation in bridge add 1.2.0 ----- * Add a reminder to remove Route.interface field * Updated from global requirements * Disable IPv6 on bridge devices in linux bridge code * Trivial: clean up oslo-incubator related stuff * Fix logging calls * Remove discover from test-requirements 1.1.0 ----- * Simplified if statement * Updated from global requirements * revert removal of create\_ovs\_vif\_port timeout * Ensure the OVS bridge exists when plugging * Don't create extraneous linux bridge/veth pair for VIFOpenVSwitch * Updated from global requirements * mtu: don't attempt to set link mtu if it's invalid * ovs: Avoids setting MTU if MTU is None or 0 * os\_vif: fix logging of exceptions during plug/unplug * vif\_plug\_ovs: clarify that the plugin was not in fact renamed * os\_vif: add logging for each plugin that is loaded * os\_vif: register objects before loading plugins * Add support for vhost-user * This change renames the ovs plugin * Updated from global requirements * remove unused entrypoints 1.0.0 ----- * Start using reno for release notes * vif\_plug\_ovs: merge both plugins into one * ovs: convert over to use privsep module * ovs: move code from plugin into linux\_net helper * linux\_bridge: convert over to use privsep module * test: use real UUID in all UUID fields * test: add workaround for non-deterministic ovo object comparison * os-vif: introduce a ComputeInfo object to represent compute info * linux\_bridge: actually apply the iptables rules * Fix calls to create\_ovs\_vif\_port * Remove vlan from hostdev and direct vif * Change network vlan to integer * VIFDirect: replace dev\_name with dev\_address * Use names() method of ExtensionManager insted of keys() * Remove obsolete obj\_relationships attribute * os-vif: add test for versioned object fingerprints * os\_vif: ensure objects are in an 'os\_vif' namespace * vif\_plug\_ovs: Disable IPv6 on bridge devices * import openvswitch plugin implementation * import linux bridge plugin implementation * Provide plugins an oslo\_config group for their setup * Adding dev\_type field to VIFHostDevice * Fix PciAddress regex * Update the test\_os\_vif.test\_initialize documentation * tox: ignore E126, E127, E128 indentation checks * Fix logic getting access to stevedore loaded plugin instance * plugin: fix typo in method annotation * Pass InstanceInfo to the plug/unplug methods * Fix definition of subnet object to not be untyped strings * Add formal classes for each of the types of VIF backend config * don't catch ProcessExecutionError exception as special case * remove dependancy on nova object model * actually register the various objects we define * remove obsolete requirements * Remove raise NotImplementedError from abstractmethods * remove python 2.6 trove classifier * reorder tox envlist to run python 3.4 before 2.7 * Import of code from https://github.com/jaypipes/os\_vif * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/HACKING.rst0000664000175000017500000000020615140131732013416 0ustar00zuulzuulos_vif Style Commandments ========================= Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/LICENSE0000664000175000017500000002363715140131732012642 0ustar00zuulzuul 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0535874 os_vif-4.3.0/PKG-INFO0000644000175000017500000000463615140132020012715 0ustar00zuulzuulMetadata-Version: 2.4 Name: os_vif Version: 4.3.0 Summary: A library for plugging and unplugging virtual interfaces in OpenStack. Home-page: https://docs.openstack.org/os-vif/latest/ 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 :: Only Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr>=3.0.0 Requires-Dist: oslo.concurrency>=3.20.0 Requires-Dist: oslo.config>=5.1.0 Requires-Dist: oslo.log>=3.30.0 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.privsep>=1.23.0 Requires-Dist: oslo.serialization>=2.20.0 Requires-Dist: oslo.utils>=2.0.0 Requires-Dist: oslo.versionedobjects>=1.28.0 Requires-Dist: ovsdbapp>=0.12.1 Requires-Dist: pyroute2>=0.5.2 Requires-Dist: stevedore>=1.20.0 Requires-Dist: debtcollector>=1.19.0 Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license-file Dynamic: requires-dist Dynamic: requires-python Dynamic: summary ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/os-vif.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ====== os-vif ====== .. image:: https://img.shields.io/pypi/v/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Downloads A library for plugging and unplugging virtual interfaces in OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/os-vif/latest/ * Source: https://opendev.org/openstack/os-vif * Bugs: https://bugs.launchpad.net/os-vif * Release Notes: https://docs.openstack.org/releasenotes/os-vif ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/README.rst0000664000175000017500000000151315140131732013311 0ustar00zuulzuul======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/os-vif.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ====== os-vif ====== .. image:: https://img.shields.io/pypi/v/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Downloads A library for plugging and unplugging virtual interfaces in OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/os-vif/latest/ * Source: https://opendev.org/openstack/os-vif * Bugs: https://bugs.launchpad.net/os-vif * Release Notes: https://docs.openstack.org/releasenotes/os-vif ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/bindep.txt0000664000175000017500000000053615140131732013630 0ustar00zuulzuul# This is a cross-platform list tracking distribution packages needed for install and tests; # see https://docs.openstack.org/infra/bindep/ for additional information. libffi-dev [platform:dpkg test] libffi-devel [platform:rpm test] make [pdf-docs] texlive [pdf-docs] texlive-xetex [pdf-docs] texlive-latex-recommended [pdf-docs] latexmk [pdf-docs] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0125875 os_vif-4.3.0/doc/0000775000175000017500000000000015140132020012356 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/requirements.txt0000664000175000017500000000012415140131732015650 0ustar00zuulzuulsphinx>=2.1.1 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0135875 os_vif-4.3.0/doc/source/0000775000175000017500000000000015140132020013656 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/conf.py0000664000175000017500000000334015140131732015166 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # -- 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.todo', 'sphinx.ext.autodoc', 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/os-vif' openstackdocs_bug_project = 'os-vif' openstackdocs_bug_tag = '' # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = '2016, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0135875 os_vif-4.3.0/doc/source/contributor/0000775000175000017500000000000015140132020016230 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/contributor/contributing.rst0000664000175000017500000000406415140131732021506 0ustar00zuulzuul============================ So You Want to Contribute... ============================ For general information on contributing to OpenStack, please check out the `contributor guide `_ to get started. It covers all the basics that are common to all OpenStack projects: the accounts you need, the basics of interacting with our Gerrit review system, how we communicate as a community, etc. Below will cover the more project specific information you need to get started with os-vif. Communication ~~~~~~~~~~~~~ Please refer `how-to-get-involved `_. Contacting the Core Team ~~~~~~~~~~~~~~~~~~~~~~~~ The overall structure of the os-vif team is documented on `the wiki `_. New Feature Planning ~~~~~~~~~~~~~~~~~~~~ You can file an RFE `bug `_ if it has no interaction with other projects like nova or neutron. If changes are part of the nova or neutron feature then it can be tracked as part of the nova or neutron feature. In that case, you should use the same topic to track the os-vif changes. Task Tracking ~~~~~~~~~~~~~ We track our tasks in `Launchpad `__. If you're looking for some smaller, easier work item to pick up and get started on, search for the 'low-hanging-fruit' tag. Reporting a Bug ~~~~~~~~~~~~~~~ You found an issue and want to make sure we are aware of it? You can do so on `Launchpad `__. More info about Launchpad usage can be found on `OpenStack docs page `_. Getting Your Patch Merged ~~~~~~~~~~~~~~~~~~~~~~~~~ All changes proposed to the os-vif requires two ``Code-Review +2`` votes from os-vif core reviewers before one of the core reviewers can approve patch by giving ``Workflow +1`` vote. One exception is for trivial changes for example typo fixes etc which can be approved by a single core. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/index.rst0000664000175000017500000000177615140131732015543 0ustar00zuulzuul====== os-vif ====== `os-vif` is a library for plugging and unplugging virtual interfaces (VIFs) in OpenStack. It provides: - Versioned objects that represent various types of virtual interfaces and their components - Base VIF plugin class that supplies a ``plug()`` and ``unplug()`` interface - Plugins for two networking backends - Open vSwitch and Linux Bridge `os-vif` is intended to define a common model for representing VIF types in OpenStack. With the exception of the two included plugins, all plugins for other networking backends are maintained in separate code repositories. Usage Guide ----------- .. toctree:: :maxdepth: 2 user/usage user/vif-types user/host-info user/plugins/linux-bridge user/plugins/noop user/plugins/ovs For Contributors ---------------- * If you are a new contributor to os-vif please refer: :doc:`contributor/contributing` .. toctree:: :hidden: contributor/contributing Reference --------- .. toctree:: :maxdepth: 2 reference/glossary ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0135875 os_vif-4.3.0/doc/source/reference/0000775000175000017500000000000015140132020015614 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/reference/glossary.rst0000664000175000017500000001564615140131732020236 0ustar00zuulzuul======== Glossary ======== .. glossary:: Calico A virtual networking solution that uses IP routing (layer 3) to provide connectivity in the form of a flat IP network instead of bridging and tunneling. Refer to the `Calico documentation`__ for more information. __ http://docs.projectcalico.org Fast Path When used, 6Wind's proprietary fast path technology behaves as a transparent acceleration layer for traditional switches (:term:`Open vSwitch`, :term:`Linux Bridge`) and for alternative networking mechanisms (:term:`Calico`, Midonet). Linux Bridge The native networking "backend" found in Linux. Refer to the `Linux Foundation wiki`__ for more information. __ https://wiki.linuxfoundation.org/networking/bridge Open vSwitch A software implementation of a :term:`virtual multilayer network switch ` Refer to the `OVS documentation`__ for more information. __ http://docs.openvswitch.org VEB Virtual Ethernet Bridge A virtual Ethernet switch that implmented in a virtualized server environment. It is anything that mimics a traditional external layer 2 (L2) switch or bridge for connecting VMs. Generally implemented as a :term:`vSwitch`, though hardware-based VEBs using SR-IOV are possible. Refer to this `Virtual networking technologies brief`__ for more information. __ http://cs.nyu.edu/courses/fall14/CSCI-GA.3033-010/Network/SDN.pdf vSwitch Virtual Switch A software-based virtual switch that connects virtual NICs to other virtual NICs and the broader physical network. Refer to this `presentation`__ for more information. __ http://cs.nyu.edu/courses/fall14/CSCI-GA.3033-010/Network/SDN.pdf VEPA Virtual Ethernet Port Aggregator An approach to virtual networking where VM traffic is handled on the physical network rather than by a virtual switch. Unlike :term:`VNTag`, frames are not tagged and the switch will use a single port to handle all :term:`VIFs ` for a host. The basis of the :term:`802.1Qbg` spec. Refer to this `presentation`__ for more information. __ http://www.ieee802.org/1/files/public/docs2009/new-hudson-vepa_summary-0509.pdf VN-Tag VNTag An approach to virtual networking where an interface virtualizer (IV) is used in place of a :term:`VEB` to connect multiple :term:`VIFs ` to a single, external, IV-capable hardware bridge. Each VIF is tagged with a unique ID (`vif_id`) which is used to route traffic through IVs, and VIFs are then treated like any other interface. The basis of the :term:`802.1Qbh` and :term:`802.1Qbr` specs. Refer to this `Cisco presentation`__ for more information. __ https://learningnetwork.cisco.com/docs/DOC-27114 vhost An alternative to :term:`virtio` that allows userspace guest processes to share *virtqueues* directly with the kernel (or, more specifically, a kernel module) preventing the QEMU process from becoming a bottleneck. vhost-user A variation of :term:`vhost` that operates entirely in userspace. This allows userspace guest processes to share *virtqueues* with other processes operating in userspace, such as virtual switches, avoiding the kernel entirely and maximize performance. When used, a guest exposes a UNIX socket for its control plane, allowing the external userspace service to provide the backend data plane via a mapped memory region. This process must implement the corresponding virtio vhost protocol, such as :term:`virtio-net` for networking, on this socket. Refer to the `QEMU documentation`__ for more information. __ https://github.com/qemu/qemu/blob/master/docs/specs/vhost-user.txt virtio A class of virtual device emulated by QEMU. Virtio devices have *virtqueues* which can be used to share data from host to guest. Refer to the `libvirt Wiki`__ for more information. __ https://wiki.libvirt.org/page/Virtio virtio-net A network driver implementation based on virtio. Guests share *virtqueues* with the QEMU process, which in turn receives this traffic and forwards it to the host. Refer to the `KVM documentation`__ for more information. __ http://www.linux-kvm.org/page/Virtio VIF A virtual network interface. IEEE 802.1Q 802.1Q A networking standard that supports virtual LANs (VLANs) on an Ethernet network. Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1Q.html IEEE 802.1Qbg 802.1Qbg An amendment to the :term:`802.1Q` spec known as "Edge Virtual Bridging", 802.1Qbg is an approach to networking where VM traffic is handled on the physical network rather than by a virtual switch. Originally based on :term:`VEPA`. Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1bg.html IEEE 802.1Qbh 802.1Qbh A withdrawn amendment to the :term:`802.1Q` spec known as "Bridge Port Extensions", replaced by :term:`802.1Qbr` spec. Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1bh.html IEEE 802.1Qbr 802.1Qbr An amendment to the :term:`802.1Q` spec known as "Bridge Port Extensions", Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1br.html tc A framework for interacting with traffic control settings (QoS, essentially) in the Linux kernel. Refer to the `tc(8) man page`__ for more information. __ https://linux.die.net/man/8/tc SR-IOV Single Root I/O Virtualization An extension to the PCI Express (PCIe) specification that allows a device, typically a network adapter, to split access to its resources among various PCIe hardware functions, :term:`physical ` or :term:`virtual `. Refer to this `article by Scott Lowe`__ or the original `PCI-SIG spec`__ (paywall) for more information. __ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/ __ https://members.pcisig.com/wg/PCI-SIG/document/download/8272 PF Physical Function In SR-IOV, a PCIe function that has full configuration resources. An SR-IOV device can have *up to* 8 PFs, though this varies between devices. A PF would typically correspond to a single interface on a NIC. Refer to this `article by Scott Lowe`__ for more information. __ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/ VF Virtual Function In SR-IOV, a PCIe function that lacks configuration resources. An SR-IOV device can have *up to* 256 VFs, though this varies between devices. A VF must be of the same type as the parent device's :term:`PF`. Refer to this `article by Scott Lowe`__ for more information. __ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0145874 os_vif-4.3.0/doc/source/user/0000775000175000017500000000000015140132020014634 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/user/host-info.rst0000664000175000017500000000536315140131732017314 0ustar00zuulzuul================ Host Information ================ To enable negotiation of features between a service host (typically a compute node) and the network provider host, os-vif exposes some objects that describe the host running the plugins. Host Information Objects ======================== The following objects encode the information about the service host. HostInfo -------- This class provides information about the host as a whole. This currently means a list of plugins installed on the host. In the future this may include further information about the host OS state. HostPluginInfo -------------- This class provides information about the capabilities of a single os-vif plugin implementation that is installed on the host. This currently means a list of VIF objects that the plugin is capable of consuming. In the future this may include further information about resources on the host that the plugin can/will utilize. While many plugins will only ever support a single VIF object, it is permitted to support multiple different VIF objects. An example would be openvswitch which can use the same underlying host network functionality to configure a VM in several different ways. HostVIFInfo ----------- This class provides information on a single VIF object that is supported by a plugin. This will include the versioned object name and the minimum and maximum versions of the object that can be consumed. It is the responsibility of the network provider to ensure that it only sends back a serialized VIF object that satisfies the minimum and maximum version constraints indicated by the plugin. Objects outside of this version range will be rejected with a fatal error. Negotiating networking ====================== When a service host wants to create a network port, it will first populate an instance of the HostInfo class, to describe all the plugins installed on the host. It will then serialize this class to JSON and send it to the network manager host. The network manager host will deserialize it back into a HostInfo object. This can then be passed down into the network driver which can use it to decide how to configure the network port. If the os-vif version installed on the network host is older than that on the service host, it may not be able to deserialize the HostInfo class. In this case it should reply with an error to the service host. The error message should report the maximum version of the HostInfo class that is supported. the service host should then backlevel its HostInfo object to that version before serializing it and re-trying the port creation request. The mechanism or transport for passing the plugin information between the network and service hosts is left undefined. It is upto the user of os-vif to decide upon the appropriate approach. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0155876 os_vif-4.3.0/doc/source/user/plugins/0000775000175000017500000000000015140132020016315 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/user/plugins/linux-bridge.rst0000664000175000017500000000126215140131732021452 0ustar00zuulzuul============ Linux Bridge ============ The Linux Bridge plugin, ``vif_plug_linux_bridge``, is an *os-vif* VIF plugin for the Linux Bridge network backend. It is one of three plugins provided as part of *os-vif* itself, the others being :doc:`ovs` and :doc:`noop`. Supported VIF Types ------------------- The Linux Bridge plugin provides support for the following VIF types: :mod:`~os_vif.objects.VIFBridge` Configuration where a guest is connected to a Linux bridge via a TAP device. This is the only supported configuration for this plugin. For information on the VIF type objects, refer to :doc:`/user/vif-types`. Note that only the above VIF types are supported by this plugin. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/user/plugins/noop.rst0000664000175000017500000000132415140131732020033 0ustar00zuulzuul===== no-op ===== The no-op plugin, ``vif_plug_noop``, is an *os-vif* VIF plugin for use with network backends that do not require plugging of network interfaces. It is one of three plugins provided as part of *os-vif* itself, the others being :doc:`ovs` and :doc:`linux-bridge`. Supported VIF Types ------------------- The no-op plugin provides support for the following VIF types: :mod:`~os_vif.objects.VIFVHostUser` Configuration where a guest exposes a UNIX socket for its control plane. This configuration is used with a userspace dataplane such as VPP or Snabb switch. For information on the VIF type objects, refer to :doc:`/user/vif-types`. Note that only the above VIF types are supported by this plugin. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/user/plugins/ovs.rst0000664000175000017500000000356315140131732017676 0ustar00zuulzuul============ Open vSwitch ============ The Open vSwitch plugin, ``vif_plug_ovs``, is an *os-vif* VIF plugin for the Open vSwitch network backend. It is one of three plugins provided as part of *os-vif* itself, the others being :doc:`linux-bridge` and :doc:`noop`. Supported VIF Types ------------------- The Open vSwitch plugin provides support for the following VIF types: :mod:`~os_vif.objects.VIFOpenVSwitch` Configuration where a guest is directly connected an Open vSwitch bridge. :mod:`~os_vif.objects.VIFBridge` Configuration where a guest is connected to a Linux bridge via a TAP device, and that bridge is connected to the Open vSwitch bridge. This allows for the use of ``iptables`` rules for filtering traffic. :mod:`~os_vif.objects.VIFVHostUser` Configuration where a guest exposes a UNIX socket for its control plane. This configuration is used with the `DPDK datapath of Open vSwitch`__. __ http://docs.openvswitch.org/en/latest/howto/dpdk/ :mod:`~os_vif.objects.VIFHostDevice` Configuration where an :term:`SR-IOV` PCI device :term:`VF` is passed through to a guest. The ``hw-tc-offload`` feature should be enabled on the SR-IOV :term:`PF` using :command:`ethtool`: .. code-block:: shell ethtool -K hw-tc-offload This will create a *VF representor* per VF. The VF representor plays the same role as TAP devices in Para-Virtual (PV) setup. In this case the ``plug()`` method connects the VF representor to the OpenVSwitch bridge. .. important:: Support for this feature requires Linux Kernel >= 4.8 and Open vSwitch 2.8. These add support for :term:`tc`-based hardware offloads for SR-IOV VFs and offloading of OVS datapath rules using tc, respectively. .. versionadded:: 1.5.0 For information on the VIF type objects, refer to :doc:`/user/vif-types`. Note that only the above VIF types are supported by this plugin. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/user/usage.rst0000664000175000017500000000446015140131732016507 0ustar00zuulzuul===== Usage ===== The interface to the ``os_vif`` library is very simple. To begin using the library, first call the ``os_vif.initialize()`` function. This will load all installed plugins and register the object model: .. code-block:: python import os_vif os_vif.initialize() Once the ``os_vif`` library is initialized, there are only two other library functions: ``os_vif.plug()`` and ``os_vif.unplug()``. Both methods accept an argument of (a subclass of) type ``os_vif.objects.vif.VIFBase`` and an argument of type ``os_vif.objects.instance_info.InstanceInfo``: .. code-block:: python import uuid from nova import objects as nova_objects from os_vif import exception as vif_exc from os_vif.objects import fields from os_vif.objects import instance_info from os_vif.objects import network from os_vif.objects import subnet as os_subnet from os_vif.objects import vif as vif_obj instance_uuid = 'd7a730ca-3c28-49c3-8f26-4662b909fe8a' instance = nova_objects.Instance.get_by_uuid(instance_uuid) instance_info = instance_info.InstanceInfo( uuid=instance.uuid, name=instance.name, project_id=instance.project_id) subnet = os_subnet.Subnet(cidr='192.168.1.0/24') subnets = os_subnet.SubnetList([subnet]) network = network.Network(label='tenantnet', subnets=subnets, multi_host=False, should_provide_vlan=False, should_provide_bridge=False) vif_uuid = uuid.uuid4() vif = vif_obj.VIFVHostUser(id=vif_uuid, address=None, network=network, plugin='vhostuser', path='/path/to/socket', mode=fields.VIFVHostUserMode.SERVER) # Now do the actual plug operations to connect the VIF to # the backing network interface. try: os_vif.plug(vif, instance_info) except vif_exc.PlugException as err: # Handle the failure... # If you are removing a virtual machine and its interfaces, # you would use the unplug() operation: try: os_vif.unplug(vif, instance_info) except vif_exc.UnplugException as err: # Handle the failure... ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/doc/source/user/vif-types.rst0000664000175000017500000000416415140131732017332 0ustar00zuulzuul========= VIF Types ========= In *os-vif*, a VIF type refers to a particular approach for configuring the backend of a guest virtual network interface. There is a small, finite set of ways that a VIF backend can be configured for any given hypervisor and a limited amount of metadata is associated with each approach. .. py:module:: os_vif.objects.vif VIF objects =========== Each distinct type of VIF configuration is represented by a versioned object, subclassing :class:`VIFBase`. .. autoclass:: VIFBase .. autoclass:: VIFGeneric .. autoclass:: VIFBridge .. autoclass:: VIFOpenVSwitch .. autoclass:: VIFDirect .. autoclass:: VIFVHostUser .. autoclass:: VIFNestedDPDK VIF port profile objects ======================== Each VIF instance can optionally be associated with a port profile object. This provides a set of metadata attributes that serve to identify the guest virtual interface to the host. Different types of host connectivity will require different port profile object metadata. Each port profile type is associated with a versioned object, subclassing :class:`VIFPortProfileBase`. .. autoclass:: VIFPortProfileBase .. autoclass:: VIFPortProfileOpenVSwitch .. autoclass:: VIFPortProfileFPOpenVSwitch .. autoclass:: VIFPortProfileOVSRepresentor .. autoclass:: VIFPortProfileFPBridge .. autoclass:: VIFPortProfileFPTap .. autoclass:: VIFPortProfile8021Qbg .. autoclass:: VIFPortProfile8021Qbh .. autoclass:: VIFPortProfileK8sDPDK Datapath Offload type object ============================ Port profiles can be associated with a ``datapath_offload`` object. This provides a set of metadata attributes that serve to identify the datapath offload parameters of a VIF. Each different type of datapath offload is associated with a versioned object, subclassing :class:`DatapathOffloadBase`. .. autoclass:: DatapathOffloadBase .. autoclass:: DatapathOffloadRepresentor VIF network objects =================== Each VIF instance is associated with a set of objects which describe the logical network that the guest will be plugged into. This information is again represented by a set of versioned objects .. todo:: Populate this! ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0165875 os_vif-4.3.0/os_vif/0000775000175000017500000000000015140132020013076 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/__init__.py0000664000175000017500000001255415140131732015227 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_log import log as logging from stevedore import extension import os_vif.exception import os_vif.i18n import os_vif.objects _EXT_MANAGER = None LOG = logging.getLogger(__name__) def initialize(reset=False): """ Loads all os_vif plugins and initializes them with a dictionary of configuration options. These configuration options are passed as-is to the individual VIF plugins that are loaded via stevedore. :param reset: Recreate and load the VIF plugin extensions. """ global _EXT_MANAGER if _EXT_MANAGER is None: os_vif.objects.register_all() if reset or (_EXT_MANAGER is None): _EXT_MANAGER = extension.ExtensionManager(namespace='os_vif', invoke_on_load=False) loaded_plugins = [] for plugin_name in _EXT_MANAGER.names(): cls = _EXT_MANAGER[plugin_name].plugin obj = cls.load(plugin_name) LOG.debug(("Loaded VIF plugin class '%(cls)s' " "with name '%(plugin_name)s'"), {'cls': cls, 'plugin_name': plugin_name}) loaded_plugins.append(plugin_name) _EXT_MANAGER[plugin_name].obj = obj LOG.info("Loaded VIF plugins: %s", ", ".join(loaded_plugins)) def plug(vif, instance_info): """ Given a model of a VIF, perform operations to plug the VIF properly. :param vif: Instance of a subclass of ``os_vif.objects.vif.VIFBase``. :param instance_info: ``os_vif.objects.instance_info.InstanceInfo`` object. :raises ``exception.LibraryNotInitialized`` if the user of the library did not call ``os_vif.initialize(**config)`` before trying to plug a VIF. :raises ``exception.NoMatchingPlugin`` if there is no plugin for the type of VIF supplied. :raises ``exception.PlugException`` if anything fails during unplug operations. """ if _EXT_MANAGER is None: raise os_vif.exception.LibraryNotInitialized() plugin_name = vif.plugin try: plugin = _EXT_MANAGER[plugin_name].obj except KeyError: raise os_vif.exception.NoMatchingPlugin(plugin_name=plugin_name) try: LOG.debug("Plugging vif %s", vif) plugin.plug(vif, instance_info) LOG.info("Successfully plugged vif %s", vif) except Exception as err: LOG.error("Failed to plug vif %(vif)s", {"vif": vif}, exc_info=True) raise os_vif.exception.PlugException(vif=vif, err=err) def unplug(vif, instance_info): """ Given a model of a VIF, perform operations to unplug the VIF properly. :param vif: Instance of a subclass of `os_vif.objects.vif.VIFBase`. :param instance_info: `os_vif.objects.instance_info.InstanceInfo` object. :raises `exception.LibraryNotInitialized` if the user of the library did not call os_vif.initialize(**config) before trying to plug a VIF. :raises `exception.NoMatchingPlugin` if there is no plugin for the type of VIF supplied. :raises `exception.UnplugException` if anything fails during unplug operations. """ if _EXT_MANAGER is None: raise os_vif.exception.LibraryNotInitialized() plugin_name = vif.plugin try: plugin = _EXT_MANAGER[plugin_name].obj except KeyError: raise os_vif.exception.NoMatchingPlugin(plugin_name=plugin_name) try: LOG.debug("Unplugging vif %s", vif) plugin.unplug(vif, instance_info) LOG.info("Successfully unplugged vif %s", vif) except Exception as err: LOG.error("Failed to unplug vif %(vif)s", {"vif": vif}, exc_info=True) raise os_vif.exception.UnplugException(vif=vif, err=err) def host_info(permitted_vif_type_names=None): """ :param permitted_vif_type_names: list of VIF object names Get information about the host platform configuration to be provided to the network manager. This will include information about what plugins are installed in the host If permitted_vif_type_names is not None, the returned HostInfo will be filtered such that it only includes plugins which support one of the listed VIF types. This allows the caller to filter out impls which are not compatible with the current usage configuration. For example, to remove VIFVHostUser if the guest does not support shared memory. :returns: a os_vif.host_info.HostInfo class instance """ if _EXT_MANAGER is None: raise os_vif.exception.LibraryNotInitialized() plugins = [ _EXT_MANAGER[name].obj.describe() for name in sorted(_EXT_MANAGER.names()) ] info = os_vif.objects.host_info.HostInfo(plugin_info=plugins) if permitted_vif_type_names is not None: info.filter_vif_types(permitted_vif_type_names) return info ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/exception.py0000664000175000017500000000641415140131732015464 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 os_vif.i18n import _ class ExceptionBase(Exception): """Base Exception To correctly use this class, inherit from it and define a 'msg_fmt' property. That msg_fmt will get printf'd with the keyword arguments provided to the constructor. """ msg_fmt = _("An unknown exception occurred.") def __init__(self, message=None, **kwargs): self.kwargs = kwargs if not message: try: message = self.msg_fmt % kwargs except Exception: # at least get the core message out if something happened message = self.msg_fmt self.message = message super(ExceptionBase, self).__init__(message) def format_message(self): # NOTE(mrodden): use the first argument to the python Exception object # which should be our full NovaException message, (see __init__) return self.args[0] class LibraryNotInitialized(ExceptionBase): msg_fmt = _("Before using the os_vif library, you need to call " "os_vif.initialize()") class NoMatchingPlugin(ExceptionBase): msg_fmt = _("No VIF plugin was found with the name %(plugin_name)s") class NoMatchingPortProfileClass(ExceptionBase): msg_fmt = _("No PortProfile class was found with the name %(name)s") class NoSupportedPortProfileVersion(ExceptionBase): msg_fmt = _("PortProfile class %(name)s " "versions %(got_versions)s do not satisfy " "min=%(min_version)s max=%(max_version)s") class NoMatchingVIFClass(ExceptionBase): msg_fmt = _("No VIF class was found with the name %(name)s") class NoSupportedVIFVersion(ExceptionBase): msg_fmt = _("VIF class %(name)s versions %(got_versions)s " "do not satisfy min=%(min_version)s max=%(max_version)s") class PlugException(ExceptionBase): msg_fmt = _("Failed to plug VIF %(vif)s. Got error: %(err)s") class UnplugException(ExceptionBase): msg_fmt = _("Failed to unplug VIF %(vif)s. Got error: %(err)s") class NetworkMissingPhysicalNetwork(ExceptionBase): msg_fmt = _("Physical network is missing for network %(network_uuid)s") class NetworkInterfaceNotFound(ExceptionBase): msg_fmt = _("Network interface %(interface)s not found") class NetworkInterfaceTypeNotDefined(ExceptionBase): msg_fmt = _("Network interface type %(type)s not defined") class ExternalImport(ExceptionBase): msg_fmt = _("Use of this module outside of os_vif is not allowed. It must " "not be imported in os-vif plugins that are out of tree as it " "is not a public interface of os-vif.") class NotImplementedForOS(ExceptionBase): msg_fmt = _("Function %(function)s for %(os)s operating system") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/i18n.py0000664000175000017500000000202015140131732014232 0ustar00zuulzuul# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html. """ import oslo_i18n DOMAIN = 'os_vif' _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary def translate(value, user_locale): return oslo_i18n.translate(value, user_locale) def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0185874 os_vif-4.3.0/os_vif/internal/0000775000175000017500000000000015140132020014712 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/internal/__init__.py0000664000175000017500000000170715140131732017041 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 inspect from os import path from os_vif import exception os_vif_root = path.dirname(path.dirname(path.dirname(__file__))) frames_info = inspect.getouterframes(inspect.currentframe()) for frame_info in frames_info[1:]: importer_filename = inspect.getframeinfo(frame_info[0]).filename if os_vif_root in importer_filename: break else: raise exception.ExternalImport() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0195875 os_vif-4.3.0/os_vif/internal/ip/0000775000175000017500000000000015140132020015322 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/internal/ip/__init__.py0000664000175000017500000000000015140131732017432 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/internal/ip/api.py0000664000175000017500000000132215140131732016454 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_log import log as logging from os_vif.internal.ip.linux.impl_pyroute2 import PyRoute2 LOG = logging.getLogger(__name__) ip = PyRoute2() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/internal/ip/ip_command.py0000664000175000017500000000574415140131732020025 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 IpCommand(metaclass=abc.ABCMeta): TYPE_VETH = 'veth' TYPE_VLAN = 'vlan' TYPE_BRIDGE = 'bridge' TYPE_TUNTAP = 'tuntap' @abc.abstractmethod def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None, master=None): """Method to set a parameter in an interface. :param device: A network device (string) :param check_exit_code: List of integers of allowed execution exit codes :param state: String network device state :param mtu: Integer MTU value :param address: String MAC address :param promisc: Boolean promiscuous mode :param master: String the master device that this device belongs to :return: status of the command execution """ @abc.abstractmethod def add(self, device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None, ageing=None, mode=None, multiqueue=False): """Method to add an interface. :param device: A network device (string) :param dev_type: String network device type (TYPE_VETH, TYPE_VLAN, TYPE_BRIDGE, TYPE_TUNTAP) :param check_exit_code: List of integers of allowed execution exit codes :param peer: String peer name, for veth interfaces :param link: String root network interface name, 'device' will be a VLAN tagged virtual interface :param vlan_id: Integer VLAN ID for VLAN devices :param ageing: integer value in seconds before learned mac addresses are forgotten. :param mode: String mode for tuntap devices ('tap' or 'tun') :param multiqueue: Boolean to enable multiqueue for tuntap devices :return: status of the command execution """ @abc.abstractmethod def delete(self, device, check_exit_code=None): """Method to delete an interface. :param device: A network device (string) :param dev_type: String network device type (TYPE_VETH, TYPE_VLAN) :return: status of the command execution """ @abc.abstractmethod def exists(self, device): """Method to dectect if a device exists. :param device: A network device (string) :return: True if device exists else False """ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0205874 os_vif-4.3.0/os_vif/internal/ip/linux/0000775000175000017500000000000015140132020016461 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/internal/ip/linux/__init__.py0000664000175000017500000000000015140131732020571 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/internal/ip/linux/impl_pyroute2.py0000664000175000017500000001356215140131732021665 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_log import log as logging from oslo_utils import excutils from pyroute2 import iproute from pyroute2.netlink import exceptions as ipexc from pyroute2.netlink.rtnl import ifinfmsg from os_vif import exception from os_vif.internal.ip import ip_command from os_vif import utils LOG = logging.getLogger(__name__) class PyRoute2(ip_command.IpCommand): def _ip_link(self, ip, command, check_exit_code, **kwargs): try: LOG.debug('pyroute2 command %(command)s, arguments %(args)s' % {'command': command, 'args': kwargs}) return ip.link(command, **kwargs) except ipexc.NetlinkError as e: with excutils.save_and_reraise_exception() as ctx: if e.code in check_exit_code: LOG.error('NetlinkError was raised, code %s, message: %s' % (e.code, str(e))) ctx.reraise = False def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None, master=None): check_exit_code = check_exit_code or [] with iproute.IPRoute() as ip: idx = self.lookup_interface(ip, device) args = {'index': idx} if state: args['state'] = state if mtu: args['mtu'] = mtu if address: args['address'] = address if promisc is not None: flags = ip.link('get', index=idx)[0]['flags'] args['flags'] = (utils.set_mask(flags, ifinfmsg.IFF_PROMISC) if promisc is True else utils.unset_mask(flags, ifinfmsg.IFF_PROMISC)) if master: args['master'] = self.lookup_interface(ip, master) if isinstance(check_exit_code, int): check_exit_code = [check_exit_code] return self._ip_link(ip, 'set', check_exit_code, **args) def lookup_interface(self, ip, link): # TODO(sean-k-mooney): remove try block after we raise # the min pyroute2 version above 0.5.12 try: idx = ip.link_lookup(ifname=link) except ipexc.NetlinkError: raise exception.NetworkInterfaceNotFound(interface=link) if not len(idx): raise exception.NetworkInterfaceNotFound(interface=link) return idx[0] def add(self, device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None, ageing=None, mode=None, multiqueue=False): check_exit_code = check_exit_code or [] with iproute.IPRoute() as ip: args = {'ifname': device, 'kind': dev_type} if self.TYPE_VLAN == dev_type: args['vlan_id'] = vlan_id args['link'] = self.lookup_interface(ip, link) elif self.TYPE_VETH == dev_type: args['peer'] = peer elif self.TYPE_BRIDGE == dev_type: # NOTE(sean-k-mooney): the keys are defined in the pyroute2 # codebase but are not documented. see the nla_map field # in the bridge_data class located in the # pyroute2.netlink.rtnl.ifinfmsg module for mode details # https://github.com/svinota/pyroute2/blob/3ba9cdde34b2346ef8c2f8ba17cef5dbeb4c6d52/pyroute2/netlink/rtnl/ifinfmsg/__init__.py#L776-L820 args['IFLA_BR_FORWARD_DELAY'] = 0 # set no delay args['IFLA_BR_STP_STATE'] = 0 # disable spanning tree args['IFLA_BR_MCAST_SNOOPING'] = 0 # disable snooping # NOTE(sean-k-mooney): we conditionally enable mac ageing as # this code is shared between the ovs and linux bridge # plugins. For linux bridge we want to allow the default # ageing of 300 seconds, whereas for ovs with the ip-tables # firewall we want to disable ageing. None was chosen as # the default value of ageing to allow the caller to determine # what policy to use and keep this code generic. if ageing is not None: args['IFLA_BR_AGEING_TIME'] = ageing elif self.TYPE_TUNTAP == dev_type: # Set mode (default to 'tap' if not specified) # This matches the 'ip tuntap add' command behavior tap_mode = mode if mode else 'tap' args['mode'] = tap_mode # Enable multiqueue if requested # This sets the IFF_MULTI_QUEUE flag in IFTUN_IFR if multiqueue: args['multi_queue'] = True else: raise exception.NetworkInterfaceTypeNotDefined(type=dev_type) return self._ip_link(ip, 'add', check_exit_code, **args) def delete(self, device, check_exit_code=None): check_exit_code = check_exit_code or [] with iproute.IPRoute() as ip: idx = self.lookup_interface(ip, device) return self._ip_link(ip, 'del', check_exit_code, **{'index': idx}) def exists(self, device): """Return True if the device exists.""" with iproute.IPRoute() as ip: try: self.lookup_interface(ip, device) return True except Exception: return False ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0225875 os_vif-4.3.0/os_vif/objects/0000775000175000017500000000000015140132020014527 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/__init__.py0000664000175000017500000000156315140131732016656 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 register_all(): __import__('os_vif.objects.fixed_ip') __import__('os_vif.objects.host_info') __import__('os_vif.objects.instance_info') __import__('os_vif.objects.network') __import__('os_vif.objects.route') __import__('os_vif.objects.subnet') __import__('os_vif.objects.vif') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/base.py0000664000175000017500000000216315140131732016026 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_versionedobjects import base as ovo_base class VersionedObject(ovo_base.VersionedObject): OBJ_PROJECT_NAMESPACE = 'os_vif' class VersionedObjectPrintableMixin(object): """Mix-in to implement __str__ method for a versioned object If a versioned object needs to be printable in a easy-reading format, inherit from this class. """ def __str__(self): if callable(getattr(self, 'obj_to_primitive', None)): return str(self.obj_to_primitive()) return super(VersionedObjectPrintableMixin, self).__str__() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/fields.py0000664000175000017500000000334115140131732016361 0ustar00zuulzuul# Copyright 2016 Red Hat, 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_versionedobjects import fields class VIFDirectMode(fields.Enum): VEPA = 'vepa' PASSTHROUGH = 'passthrough' BRIDGE = 'bridge' ALL = (VEPA, PASSTHROUGH, BRIDGE) def __init__(self): super(VIFDirectMode, self).__init__( valid_values=VIFDirectMode.ALL) class VIFDirectModeField(fields.BaseEnumField): AUTO_TYPE = VIFDirectMode() class VIFVHostUserMode(fields.Enum): CLIENT = "client" SERVER = "server" ALL = (CLIENT, SERVER) def __init__(self): super(VIFVHostUserMode, self).__init__( valid_values=VIFVHostUserMode.ALL) class VIFVHostUserModeField(fields.BaseEnumField): AUTO_TYPE = VIFVHostUserMode() class ListOfIPAddressField(fields.AutoTypedField): AUTO_TYPE = fields.List(fields.IPAddress()) class VIFHostDeviceDevType(fields.Enum): ETHERNET = 'ethernet' GENERIC = 'generic' ALL = (ETHERNET, GENERIC) def __init__(self): super(VIFHostDeviceDevType, self).__init__( valid_values=VIFHostDeviceDevType.ALL) class VIFHostDeviceDevTypeField(fields.BaseEnumField): AUTO_TYPE = VIFHostDeviceDevType() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/fixed_ip.py0000664000175000017500000000240215140131732016677 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_versionedobjects import base from oslo_versionedobjects import fields from os_vif.objects import base as osv_base from os_vif.objects import fields as osv_fields @base.VersionedObjectRegistry.register class FixedIP(osv_base.VersionedObject): """Represents a fixed IP.""" # Version 1.0: Initial version VERSION = '1.0' fields = { 'address': fields.IPAddressField(), 'floating_ips': osv_fields.ListOfIPAddressField(), } @base.VersionedObjectRegistry.register class FixedIPList(osv_base.VersionedObject, base.ObjectListBase): # Version 1.0: Initial version VERSION = '1.0' fields = { 'objects': fields.ListOfObjectsField('FixedIP'), } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/host_info.py0000664000175000017500000001505215140131732017105 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_utils import versionutils from oslo_versionedobjects import base from oslo_versionedobjects import fields from os_vif import exception from os_vif.objects import base as osv_base def _get_common_version(object_name, max_version, min_version, exc_notmatch, exc_notsupported): """Returns the accepted version from the loaded OVO registry""" reg = base.VersionedObjectRegistry.obj_classes() if object_name not in reg: raise exc_notmatch(name=object_name) gotvers = [] for regobj in reg[object_name]: gotvers.append(regobj.VERSION) got = versionutils.convert_version_to_tuple(regobj.VERSION) minwant = versionutils.convert_version_to_tuple(min_version) maxwant = versionutils.convert_version_to_tuple(max_version) if minwant <= got <= maxwant: return regobj.VERSION raise exc_notsupported(name=object_name, got_versions=",".join(gotvers), min_version=min_version, max_version=max_version) @base.VersionedObjectRegistry.register class HostPortProfileInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a PortProfile class and its supported versions """ # Version 1.0: Initial version VERSION = "1.0" fields = { # object name of the subclass of os_vif.objects.vif.VIFPortProfileBase "profile_object_name": fields.StringField(), # String representing the earliest version of @name # that the plugin understands "min_version": fields.StringField(), # String representing the latest version of @name # that the plugin understands "max_version": fields.StringField(), } def get_common_version(self): return _get_common_version(self.profile_object_name, self.max_version, self.min_version, exception.NoMatchingPortProfileClass, exception.NoSupportedPortProfileVersion) @base.VersionedObjectRegistry.register class HostVIFInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a VIF class and its supported versions """ # Version 1.0: Initial version # Version 1.1: Adds 'supported_port_profiles' field VERSION = "1.1" fields = { # object name of the subclass of os_vif.objects.vif.VIFBase "vif_object_name": fields.StringField(), # String representing the earliest version of @name # that the plugin understands "min_version": fields.StringField(), # String representing the latest version of @name # that the plugin understands "max_version": fields.StringField(), # list of supported PortProfile objects and versions. "supported_port_profiles": fields.ListOfObjectsField( "HostPortProfileInfo") } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'supported_port_profiles' in primitive: del primitive['supported_port_profiles'] super(HostVIFInfo, self).obj_make_compatible(primitive, '1.0') def get_common_version(self): return _get_common_version(self.vif_object_name, self.max_version, self.min_version, exception.NoMatchingVIFClass, exception.NoSupportedVIFVersion) @base.VersionedObjectRegistry.register class HostPluginInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a plugin and its supported VIF classes """ # Version 1.0: Initial version VERSION = "1.0" fields = { # name of the plugin "plugin_name": fields.StringField(), # list of HostVIFInfo instances supported by the plugin "vif_info": fields.ListOfObjectsField("HostVIFInfo"), } def has_vif(self, name): for vif in self.vif_info: if vif.vif_object_name == name: return True return False def get_vif(self, name): for vif in self.vif_info: if vif.vif_object_name == name: return vif raise exception.NoMatchingVIFClass(vif_name=name) def filter_vif_types(self, permitted_vif_type_names): new_vif_info = [] for vif in self.vif_info: if vif.vif_object_name in permitted_vif_type_names: new_vif_info.append(vif) self.vif_info = new_vif_info @base.VersionedObjectRegistry.register class HostInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a host host and its supported plugin classes """ fields = { # list of HostPluginInfo instances supported by the host host "plugin_info": fields.ListOfObjectsField("HostPluginInfo"), } def has_plugin(self, name): for plugin in self.plugin_info: if name == plugin.plugin_name: return True return False def get_plugin(self, name): for plugin in self.plugin_info: if name == plugin.plugin_name: return plugin raise exception.NoMatchingPlugin(plugin_name=name) def filter_vif_types(self, permitted_vif_type_names): new_plugins = [] for plugin in self.plugin_info: plugin.filter_vif_types(permitted_vif_type_names) if len(plugin.vif_info) == 0: continue new_plugins.append(plugin) self.plugin_info = new_plugins ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/instance_info.py0000664000175000017500000000231215140131732017727 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_versionedobjects import base from oslo_versionedobjects import fields from os_vif.objects import base as osv_base @base.VersionedObjectRegistry.register class InstanceInfo(osv_base.VersionedObject): """Represents important information about a Nova instance.""" # Version 1.0: Initial version VERSION = '1.0' fields = { # UUID of the instance 'uuid': fields.UUIDField(), # The instance name, directly from the Nova instance field of the # same name 'name': fields.StringField(), # The project/tenant ID that owns the instance 'project_id': fields.StringField(), } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/network.py0000664000175000017500000000412615140131732016606 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_utils import versionutils from oslo_versionedobjects import base from oslo_versionedobjects import fields from os_vif import objects from os_vif.objects import base as osv_base @base.VersionedObjectRegistry.register class Network(osv_base.VersionedObject): """Represents a network.""" # Version 1.0: Initial version # Version 1.1: Added MTU field VERSION = '1.1' fields = { 'id': fields.UUIDField(), 'bridge': fields.StringField(), 'label': fields.StringField(), 'subnets': fields.ObjectField('SubnetList'), 'multi_host': fields.BooleanField(), 'should_provide_bridge': fields.BooleanField(), 'should_provide_vlan': fields.BooleanField(), 'bridge_interface': fields.StringField(nullable=True), 'vlan': fields.IntegerField(nullable=True), 'mtu': fields.IntegerField(nullable=True), } def __init__(self, **kwargs): kwargs.setdefault('subnets', objects.subnet.SubnetList(objects=[])) kwargs.setdefault('multi_host', False) kwargs.setdefault('should_provide_bridge', False) kwargs.setdefault('should_provide_vlan', False) kwargs.setdefault('mtu', None) super(Network, self).__init__(**kwargs) def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'mtu' in primitive: del primitive['mtu'] super(Network, self).obj_make_compatible(primitive, '1.0') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/route.py0000664000175000017500000000251015140131732016246 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_versionedobjects import base from oslo_versionedobjects import fields from os_vif.objects import base as osv_base @base.VersionedObjectRegistry.register class Route(osv_base.VersionedObject): """Represents a route.""" # Version 1.0: Initial version VERSION = '1.0' fields = { 'cidr': fields.IPNetworkField(), 'gateway': fields.IPAddressField(), # TODO(mriedem): This field is never set by Nova, remove it in v2.0 # of this object. 'interface': fields.StringField(), } @base.VersionedObjectRegistry.register class RouteList(osv_base.VersionedObject, base.ObjectListBase): # Version 1.0: Initial version VERSION = '1.0' fields = { 'objects': fields.ListOfObjectsField('Route'), } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/subnet.py0000664000175000017500000000266215140131732016420 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_versionedobjects import base from oslo_versionedobjects import fields from os_vif.objects import base as osv_base from os_vif.objects import fields as osv_fields @base.VersionedObjectRegistry.register class Subnet(osv_base.VersionedObject): """Represents a subnet.""" # Version 1.0: Initial version VERSION = '1.0' fields = { 'cidr': fields.IPNetworkField(), 'dns': osv_fields.ListOfIPAddressField(), 'gateway': fields.IPAddressField(), 'ips': fields.ObjectField("FixedIPList"), 'routes': fields.ObjectField("RouteList"), 'dhcp_server': fields.IPAddressField(), } @base.VersionedObjectRegistry.register class SubnetList(osv_base.VersionedObject, base.ObjectListBase): # Version 1.0: Initial version VERSION = '1.0' fields = { 'objects': fields.ListOfObjectsField('Subnet'), } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/objects/vif.py0000664000175000017500000005411615140131732015705 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 debtcollector import removals from oslo_utils import versionutils from oslo_versionedobjects import base from oslo_versionedobjects import fields from os_vif.objects import base as osv_base from os_vif.objects import fields as osv_fields @base.VersionedObjectRegistry.register class VIFBase(osv_base.VersionedObject, base.ComparableVersionedObject): """Represents a virtual network interface. The base VIF defines fields that are common to all types of VIF and provides an association to the network the VIF is plugged into. It should not be instantiated itself - use a subclass instead. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Unique identifier of the VIF port. 'id': fields.UUIDField(), #: The guest MAC address. 'address': fields.MACAddressField(nullable=True), #: The network to which the VIF is connected. 'network': fields.ObjectField('Network', nullable=True), #: Name of the registered os_vif plugin. 'plugin': fields.StringField(), #: Whether the VIF is initially online. 'active': fields.BooleanField(default=True), #: Whether the host VIF should be preserved on unplug. 'preserve_on_delete': fields.BooleanField(default=False), #: Whether the network service has provided traffic filtering. 'has_traffic_filtering': fields.BooleanField(default=False), #: The virtual port profile metadata. 'port_profile': fields.ObjectField('VIFPortProfileBase', subclasses=True) } @base.VersionedObjectRegistry.register class VIFGeneric(VIFBase): """A generic-style VIF. Generic-style VIFs are unbound, floating TUN/TAP devices that should be setup by the plugin, not the hypervisor. The way the TAP device is connected to the host network stack is explicitly left undefined. For libvirt drivers, this maps to type="ethernet" which just implies a bare TAP device with all setup delegated to the plugin. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the device to create. 'vif_name': fields.StringField() } @base.VersionedObjectRegistry.register class VIFBridge(VIFBase): """A bridge-style VIF. Bridge-style VIFs are bound to a Linux host bridge by the hypervisor. This provides Ethernet layer bridging, typically to the LAN. Other devices may be bound to the same L2 virtual bridge. For libvirt drivers, this maps to type='bridge'. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the virtual device to create. 'vif_name': fields.StringField(), #: Name of the physical device to connect to (e.g. ``br0``). 'bridge_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFOpenVSwitch(VIFBase): """A bridge-style VIF specifically for use with OVS. Open vSwitch VIFs are bound directly (or indirectly) to an Open vSwitch bridge by the hypervisor. Other devices may be bound to the same virtual bridge. For libvirt drivers, this also maps to type='bridge'. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the virtual device to create. 'vif_name': fields.StringField(), #: Name of the physical device to connect to (e.g. ``br0``). 'bridge_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFDirect(VIFBase): """A direct-style VIF. Despite the confusing name, direct-style VIFs utilize macvtap which is a device driver that inserts a software layer between a guest and an SR-IOV Virtual Function (VF). Contrast this with :class:`~os_vif.objects.vif.VIFHostDevice`, which allows the guest to directly connect to the VF. The connection to the device may operate in one of a number of different modes, :term:`VEPA` (either :term:`802.1Qbg` or :term:`802.1Qbh`), passthrough (exclusive assignment of the host NIC) or bridge (ethernet layer bridging of traffic). The passthrough mode would be used when there is a network device which needs to have a MAC address or VLAN configuration. For passthrough of network devices without MAC/VLAN configuration, :class:`~os_vif.objects.vif.VIFHostDevice` should be used instead. For libvirt drivers, this maps to type='direct' """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the device to create. 'vif_name': fields.StringField(), #: The PCI address of the host device. 'dev_address': fields.PCIAddressField(), #: Port connection mode. 'mode': osv_fields.VIFDirectModeField(), #: The VLAN device name to use. 'vlan_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFVHostUser(VIFBase): """A vhostuser-style VIF. vhostuser-style VIFs utilize a :term:`userspace vhost ` backend, which allows traffic to traverse between the guest and a host userspace application (commonly a virtual switch), bypassing the kernel network stack. Contrast this with :class:`~os_vif.objects.vif.VIFBridge`, where all packets must be handled by the hypervisor. For libvirt drivers, this maps to type='vhostuser' """ # Version 1.0: Initial release # Version 1.1: Added 'vif_name' VERSION = '1.1' fields = { #: Name of the vhostuser port to create. 'vif_name': fields.StringField(), #: UNIX socket path. 'path': fields.StringField(), #: UNIX socket access permissions. 'mode': osv_fields.VIFVHostUserModeField(), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'vif_name' in primitive: del primitive['vif_name'] super(VIFVHostUser, self).obj_make_compatible(primitive, '1.0') @base.VersionedObjectRegistry.register class VIFHostDevice(VIFBase): """A hostdev-style VIF. Hostdev-style VIFs provide a guest with direct access to an :term:`SR-IOV` :term:`Virtual Function` (VF) or an entire :term:`Physical Function` (PF). Contrast this with :class:`~ovs_vif.objects.vif.VIFDirect`, which includes a software layer between the interface and the guest. For libvirt drivers, this maps to type='hostdev' """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: The type of the host device. #: #: Valid values are ``ethernet`` and ``generic``. #: #: - ``ethernet`` is ```` #: - ``generic`` is ```` 'dev_type': osv_fields.VIFHostDeviceDevTypeField(), #: The PCI address of the host device. 'dev_address': fields.PCIAddressField(), } @base.VersionedObjectRegistry.register class VIFNestedDPDK(VIFBase): """A nested DPDK-style VIF. Nested DPDK-style VIFs are used by Kuryr-Kubernetes to provide accelerated DPDK datapath for nested Kubernetes pods running inside the VM. The port is first attached to the virtual machine, bound to the userspace driver (e.g. ``uio_pci_generic``, ``igb_uio`` or ``vfio-pci``) and then consumed by Kubernetes pod via the kuryr-kubernetes CNI plugin. This does not apply to libvirt drivers. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: PCI address of the device. 'pci_address': fields.StringField(), #: Name of the driver the device was previously bound to; it makes #: the controller driver agnostic (virtio, SR-IOV, etc.). 'dev_driver': fields.StringField(), } @base.VersionedObjectRegistry.register class DatapathOffloadBase(osv_base.VersionedObject, base.ComparableVersionedObject): """Base class for all types of datapath offload.""" # Version 1.0: Initial release VERSION = '1.0' @base.VersionedObjectRegistry.register class DatapathOffloadRepresentor(DatapathOffloadBase): """Offload type for VF Representors conforming to the switchdev model. This datapath offloads provides the metadata required to associate a VIF with a :term:`VF` representor conforming to the `switchdev`_ kernel model. If ``representor_name`` is specified, it indicates a desire to rename the representor to the given name on plugging. .. _switchdev: https://netdevconf.org/1.2/session.html?or-gerlitz """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name to set on the representor (if set). 'representor_name': fields.StringField(nullable=True), #: The PCI address of the Virtual Function. 'representor_address': fields.StringField(nullable=True), } @base.VersionedObjectRegistry.register class VIFPortProfileBase(osv_base.VersionedObject, base.ComparableVersionedObject): """Base class for all types of port profile. The base profile defines fields that are common to all types of profile. It should not be instantiated itself - use a subclass instead. """ # Version 1.0: Initial release # Version 1.1: Added 'datapath_offload' VERSION = '1.1' fields = { #: Datapath offload type of the port. 'datapath_offload': fields.ObjectField('DatapathOffloadBase', nullable=True, subclasses=True), } obj_relationships = { 'datapath_offload': (('1.1', '1.0'),), } @base.VersionedObjectRegistry.register class VIFPortProfileOpenVSwitch(VIFPortProfileBase): """Port profile info for Open vSwitch networks. This profile provides the metadata required to associate a VIF with an Open vSwitch interface. """ # Version 1.0: Initial release # Version 1.1: Added 'datapath_type' # Version 1.2: VIFPortProfileBase updated to 1.1 from 1.0 # Version 1.3: Added 'create_port' # Version 1.4: Added 'create_tap' and 'multiqueue' VERSION = '1.4' fields = { #: A UUID to uniquely identify the interface. If omitted one will be #: generated automatically. 'interface_id': fields.UUIDField(), #: The OpenVSwitch port profile for the interface. 'profile_id': fields.StringField(), #: Datapath type of the bridge. 'datapath_type': fields.StringField(nullable=True), #: Whether the os-vif plugin should add the port to the bridge. 'create_port': fields.BooleanField(default=False), #: Whether the os-vif plugin should create the TAP device. 'create_tap': fields.BooleanField(default=False), #: Whether to enable multiqueue on the TAP device. 'multiqueue': fields.BooleanField(default=False), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 4): if 'create_tap' in primitive: del primitive['create_tap'] if 'multiqueue' in primitive: del primitive['multiqueue'] if target_version < (1, 3) and 'create_port' in primitive: del primitive['create_port'] if target_version < (1, 1) and 'datapath_type' in primitive: del primitive['datapath_type'] if target_version < (1, 2): super(VIFPortProfileOpenVSwitch, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileOpenVSwitch, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfileFPOpenVSwitch(VIFPortProfileOpenVSwitch): """Port profile info for Open vSwitch networks using fast path. This profile provides the metadata required to associate a :term:`fast path ` VIF with an :term:`Open vSwitch` port. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 from 1.0 # Version 1.2: VIFPortProfileOpenVSwitch updated to 1.2 from 1.1 # Version 1.3: VIFPortProfileOpenVSwitch updated to 1.3 from 1.2 # Version 1.4: VIFPortProfileOpenVSwitch updated to 1.4 from 1.3 VERSION = '1.4' fields = { #: Name of the bridge (managed by fast path) to connect to. 'bridge_name': fields.StringField(), #: Whether the OpenVSwitch network is using hybrid plug. 'hybrid_plug': fields.BooleanField(default=False), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.0') elif target_version < (1, 2): super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.1') elif target_version < (1, 3): super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.2') elif target_version < (1, 4): super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.3') else: super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.4') @removals.removed_class("VIFPortProfileOVSRepresentor", category=PendingDeprecationWarning) @base.VersionedObjectRegistry.register class VIFPortProfileOVSRepresentor(VIFPortProfileOpenVSwitch): """Port profile info for OpenVSwitch networks using a representor. This profile provides the metadata required to associate a VIF with a :term:`VF` representor and :term:`Open vSwitch` port. If `representor_name` is specified, it indicates a desire to rename the representor to the given name on plugging. .. note:: This port profile is provided for backwards compatibility only. This interface has been superceded by the one provided by the :class:`DatapathOffloadRepresentor` class, which is now a field element of the :class:`VIFPortProfileBase` class. The ``datapath_offload`` field in port profiles should be used instead. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 from 1.0 # Version 1.2: VIFPortProfileOpenVSwitch updated to 1.2 from 1.1 # Version 1.3: VIFPortProfileOpenVSwitch updated to 1.3 from 1.2 # Version 1.4: VIFPortProfileOpenVSwitch updated to 1.4 from 1.3 VERSION = '1.4' fields = { #: Name to set on the representor (if set). 'representor_name': fields.StringField(nullable=True), #: The PCI address of the Virtual Function. 'representor_address': fields.PCIAddressField(nullable=True), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.0') elif target_version < (1, 2): super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.1') elif target_version < (1, 3): super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.2') elif target_version < (1, 4): super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.3') else: super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.4') @base.VersionedObjectRegistry.register class VIFPortProfileFPBridge(VIFPortProfileBase): """Port profile info for Linux Bridge networks using fast path. This profile provides the metadata required to associate a :term:`fast path ` VIF with a :term:`Linux Bridge` port. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: Name of the bridge (managed by fast path) to connect to. 'bridge_name': fields.StringField(), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileFPBridge, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileFPBridge, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfileFPTap(VIFPortProfileBase): """Port profile info for Calico networks using fast path. This profile provides the metadata required to associate a :term:`fast path ` VIF with a :term:`Calico` port. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: The MAC address of the host vhostuser port. 'mac_address': fields.MACAddressField(nullable=True), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileFPTap, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileFPTap, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfile8021Qbg(VIFPortProfileBase): """Port profile info for VEPA 802.1qbg networks. This profile provides the metadata required to associate a VIF with a VEPA host device supporting the :term:`802.1Qbg` spec. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { # TODO(stephenfin): Apparently the value 0 is reserved for manager_id, # so should we set 'minimum=1'? # https://libvirt.org/formatdomain.html#elementsNICS #: The VSI Manager ID identifies the database containing the VSI type #: and instance definitions. 'manager_id': fields.IntegerField(), #: The VSI Type ID identifies a VSI type characterizing the network #: access. VSI types are typically managed by network administrator. 'type_id': fields.IntegerField(), #: The VSI Type Version allows multiple versions of a VSI Type. 'type_id_version': fields.IntegerField(), #: The VSI Instance ID Identifier is generated when a VSI instance #: (i.e. a virtual interface of a virtual machine) is created. This is #: a globally unique identifier. 'instance_id': fields.UUIDField(), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfile8021Qbg, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfile8021Qbg, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfile8021Qbh(VIFPortProfileBase): """Port profile info for VEPA 802.1qbh networks. This profile provides the metadata required to associate a VIF with a VEPA host device supporting the :term:`802.1Qbh` spec. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: The name of the port profile that is to be applied to this #: interface. This name is resolved by the port profile database into #: the network parameters from the port profile, and those network #: parameters will be applied to this interface. 'profile_id': fields.StringField() } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfile8021Qbh, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfile8021Qbh, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfileK8sDPDK(VIFPortProfileBase): """Port profile info for Kuryr-Kubernetes DPDK ports. This profile provides the metadata required to associate nested DPDK VIF with a Kubernetes pod. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: Specify whether this vif requires L3 setup. 'l3_setup': fields.BooleanField(), #: String containing URL representing object in Kubernetes v1 API. 'selflink': fields.StringField(), #: String used in Kubernetes v1 API to identify the server's internal #: version of this object. 'resourceversion': fields.StringField() } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileK8sDPDK, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileK8sDPDK, self).obj_make_compatible( primitive, '1.1') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/opts.py0000664000175000017500000000164715140131732014456 0ustar00zuulzuul# Copyright (c) 2021 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. __all__ = [ 'list_plugins_opts', ] from copy import deepcopy import os_vif os_vif.initialize() _EXT_MANAGER = os_vif._EXT_MANAGER plugins_list = [ (name, _EXT_MANAGER[name].obj) for name in sorted(_EXT_MANAGER.names()) ] def list_plugins_opts(): return [('os_vif_' + g, deepcopy(o.CONFIG_OPTS)) for g, o in plugins_list] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/plugin.py0000664000175000017500000000573015140131732014764 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_config import cfg CONF = cfg.CONF class PluginBase(metaclass=abc.ABCMeta): """Base class for all VIF plugins.""" # Override to provide a tuple of oslo_config.Opt instances for # the plugin config parameters CONFIG_OPTS = () def __init__(self, config): """ Initialize the plugin object with the provided config :param config: ``oslo_config.ConfigOpts.GroupAttr`` instance: """ self.config = config @abc.abstractmethod def describe(self): """ Return an object that describes the plugin's supported vif types and the earliest/latest known VIF object versions. :returns: A ``os_vif.objects.host_info.HostPluginInfo`` instance """ @abc.abstractmethod def plug(self, vif, instance_info): """ Given a model of a VIF, perform operations to plug the VIF properly. :param vif: ``os_vif.objects.vif.VIFBase`` object. :param instance_info: ``os_vif.objects.instance_info.InstanceInfo`` object. :raises ``processutils.ProcessExecutionError``. Plugins implementing this method should let `processutils.ProcessExecutionError` bubble up. """ @abc.abstractmethod def unplug(self, vif, instance_info): """ Given a model of a VIF, perform operations to unplug the VIF properly. :param vif: ``os_vif.objects.vif.VIFBase`` object. :param instance_info: ``os_vif.objects.instance_info.InstanceInfo`` object. :raises ``processutils.ProcessExecutionError``. Plugins implementing this method should let ``processutils.ProcessExecutionError`` bubble up. """ @classmethod def load(cls, plugin_name): """ Load a plugin, registering its configuration options :param plugin_name: the name of the plugin extension :returns: an initialized instance of the class """ cfg_group_name = "os_vif_" + plugin_name cfg_opts = getattr(cls, "CONFIG_OPTS") cfg_vals = None if cfg_opts and len(cfg_opts) > 0: cfg_group = cfg.OptGroup( cfg_group_name, "os-vif plugin %s options" % plugin_name) CONF.register_opts(cfg_opts, group=cfg_group) cfg_vals = getattr(CONF, cfg_group_name) return cls(cfg_vals) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0225875 os_vif-4.3.0/os_vif/tests/0000775000175000017500000000000015140132020014240 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/__init__.py0000664000175000017500000000000015140131732016350 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0235875 os_vif-4.3.0/os_vif/tests/functional/0000775000175000017500000000000015140132020016402 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/functional/__init__.py0000664000175000017500000000000015140131732020512 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/functional/base.py0000664000175000017500000001161515140131732017703 0ustar00zuulzuul# Derived from: neutron/tests/functional/base.py # neutron/tests/base.py # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc import functools import inspect import os import sys import eventlet.timeout from os_vif import version as osvif_version from oslo_config import cfg from oslo_log import log as logging from oslo_utils import fileutils from oslotest import base CONF = cfg.CONF LOG = logging.getLogger(__name__) def _get_test_log_path(): return os.environ.get('OS_LOG_PATH', '/tmp') # This is the directory from which infra fetches log files for functional tests DEFAULT_LOG_DIR = os.path.join(_get_test_log_path(), 'osvif-functional-logs') def wait_until_true(predicate, timeout=15, sleep=1): """Wait until callable predicate is evaluated as True :param predicate: Callable deciding whether waiting should continue. Best practice is to instantiate predicate with ``functools.partial()``. :param timeout: Timeout in seconds how long should function wait. :param sleep: Polling interval for results in seconds. :return: True if the predicate is evaluated as True within the timeout, False in case of timeout evaluating the predicate. """ try: with eventlet.Timeout(timeout): while not predicate(): eventlet.sleep(sleep) except eventlet.Timeout: return False return True class _CatchTimeoutMetaclass(abc.ABCMeta): def __init__(cls, name, bases, dct): super(_CatchTimeoutMetaclass, cls).__init__(name, bases, dct) for name, method in inspect.getmembers( # NOTE(ihrachys): we should use isroutine because it will catch # both unbound methods (python2) and functions (python3) cls, predicate=inspect.isroutine): if name.startswith('test_'): setattr(cls, name, cls._catch_timeout(method)) @staticmethod def _catch_timeout(f): @functools.wraps(f) def func(self, *args, **kwargs): try: return f(self, *args, **kwargs) except eventlet.Timeout as e: self.fail('Execution of this test timed out: %s' % e) return func def setup_logging(component_name): """Sets up the logging options for a log with supplied name.""" logging.setup(cfg.CONF, component_name) LOG.info("Logging enabled!") LOG.info("%(prog)s version %(version)s", {'prog': sys.argv[0], 'version': osvif_version.__version__}) LOG.debug("command line: %s", " ".join(sys.argv)) def sanitize_log_path(path): """Sanitize the string so that its log path is shell friendly""" return path.replace(' ', '-').replace('(', '_').replace(')', '_') # Test worker cannot survive eventlet's Timeout exception, which effectively # kills the whole worker, with all test cases scheduled to it. This metaclass # makes all test cases convert Timeout exceptions into unittest friendly # failure mode (self.fail). class BaseFunctionalTestCase(base.BaseTestCase, metaclass=_CatchTimeoutMetaclass): """Base class for functional tests.""" COMPONENT_NAME = 'os_vif' PRIVILEGED_GROUP = 'os_vif_privileged' def setUp(self): super(BaseFunctionalTestCase, self).setUp() logging.register_options(CONF) setup_logging(self.COMPONENT_NAME) fileutils.ensure_tree(DEFAULT_LOG_DIR, mode=0o755) log_file = sanitize_log_path( os.path.join(DEFAULT_LOG_DIR, "%s.txt" % self.id())) self.flags(log_file=log_file) privsep_helper = os.path.join( os.getenv('VIRTUAL_ENV', os.path.dirname(sys.executable)[:-4]), 'bin', 'privsep-helper') self.flags( helper_command=' '.join(['sudo', '-E', privsep_helper]), group=self.PRIVILEGED_GROUP) def flags(self, **kw): """Override some configuration values. The keyword arguments are the names of configuration options to override and their values. If a group argument is supplied, the overrides are applied to the specified configuration option group. All overrides are automatically cleared at the end of the current test by the fixtures cleanup process. """ group = kw.pop('group', None) for k, v in kw.items(): CONF.set_override(k, v, group) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0235875 os_vif-4.3.0/os_vif/tests/functional/internal/0000775000175000017500000000000015140132020020216 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/functional/internal/__init__.py0000664000175000017500000000000015140131732022326 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0235875 os_vif-4.3.0/os_vif/tests/functional/internal/command/0000775000175000017500000000000015140132020021634 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/functional/internal/command/__init__.py0000664000175000017500000000000015140131732023744 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0245874 os_vif-4.3.0/os_vif/tests/functional/internal/command/ip/0000775000175000017500000000000015140132020022244 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/functional/internal/command/ip/__init__.py0000664000175000017500000000000015140131732024354 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py0000664000175000017500000003467215140131732026514 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 re import time from oslo_concurrency import processutils from oslo_utils import excutils from os_vif.internal.ip.api import ip as ip_lib from os_vif.tests.functional import base from os_vif.tests.functional import privsep @privsep.os_vif_pctxt.entrypoint def _execute_command(*args): return processutils.execute(*args) class ShellIpCommands(object): def add_device(self, device, dev_type, peer=None, link=None, vlan_id=None): if 'vlan' == dev_type: _execute_command('ip', 'link', 'add', 'link', link, 'name', device, 'type', dev_type, 'vlan', 'id', vlan_id) elif 'veth' == dev_type: _execute_command('ip', 'link', 'add', device, 'type', dev_type, 'peer', 'name', peer) elif 'dummy' == dev_type: _execute_command('ip', 'link', 'add', device, 'type', dev_type) # ensure that the device exists to prevent racing # with other ip commands for _ in range(10): if self.exist_device(device): return time.sleep(0.1) def del_device(self, device): if self.exist_device(device): _execute_command('ip', 'link', 'del', device) def set_status_up(self, device): _execute_command('ip', 'link', 'set', device, 'up') def set_status_down(self, device): _execute_command('ip', 'link', 'set', device, 'down') def set_device_mtu(self, device, mtu): _execute_command('ip', 'link', 'set', device, 'mtu', mtu) def show_device(self, device): val, err = _execute_command('ip', 'link', 'show', device) return val.splitlines() def exist_device(self, device): try: _execute_command('ip', 'link', 'show', device) return True except processutils.ProcessExecutionError as e: with excutils.save_and_reraise_exception() as saved_exception: if e.exit_code == 1: saved_exception.reraise = False return False def show_state(self, device): regex = re.compile(r".*state (?P\w+)") match = regex.match(self.show_device(device)[0]) if match is None: return return match.group('state') def is_admin_up(self, device): """Check if device is administratively up (UP flag set). This differs from show_state() which returns the operational state. TAP devices without a carrier will have state=DOWN but can still be administratively UP (UP in flags like ). """ # Flags are in angle brackets on the first line, e.g. first_line = self.show_device(device)[0] regex = re.compile(r".*<(?P[^>]+)>") match = regex.match(first_line) if match is None: return False flags = match.group('flags').split(',') return 'UP' in flags def show_promisc(self, device): regex = re.compile(r".*(PROMISC)") match = regex.match(self.show_device(device)[0]) return True if match else False def show_mac(self, device): exp = r".*link/ether (?P([0-9A-Fa-f]{2}[:]){5}[0-9A-Fa-f]{2})" regex = re.compile(exp) match = regex.match(self.show_device(device)[1]) if match is None: return return match.group('mac') def show_mtu(self, device): regex = re.compile(r".*mtu (?P\d+)") match = regex.match(self.show_device(device)[0]) if match is None: return return int(match.group('mtu')) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_set(*args, **kwargs): ip_lib.set(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_add(*args, **kwargs): ip_lib.add(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_delete(*args, **kwargs): ip_lib.delete(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_exists(*args, **kwargs): return ip_lib.exists(*args, **kwargs) class TestIpCommand(ShellIpCommands, base.BaseFunctionalTestCase): def setUp(self): super(TestIpCommand, self).setUp() def test_set_state(self): device1 = "test_dev_1" device2 = "test_dev_2" self.addCleanup(self.del_device, device1) self.add_device(device1, 'veth', peer=device2) _ip_cmd_set(device1, state='up') _ip_cmd_set(device2, state='up') self.assertEqual('UP', self.show_state(device1)) self.assertEqual('UP', self.show_state(device2)) _ip_cmd_set(device1, state='down') _ip_cmd_set(device2, state='down') self.assertEqual('DOWN', self.show_state(device1)) self.assertEqual('DOWN', self.show_state(device2)) def test_set_mtu(self): device = "test_dev_3" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') _ip_cmd_set(device, mtu=1200) self.assertEqual(1200, self.show_mtu(device)) _ip_cmd_set(device, mtu=900) self.assertEqual(900, self.show_mtu(device)) def test_set_address(self): device = "test_dev_4" address1 = "36:a7:e4:f9:01:01" address2 = "36:a7:e4:f9:01:01" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') _ip_cmd_set(device, address=address1) self.assertEqual(address1, self.show_mac(device)) _ip_cmd_set(device, address=address2) self.assertEqual(address2, self.show_mac(device)) def test_set_promisc(self): device = "test_dev_5" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') _ip_cmd_set(device, promisc=True) self.assertTrue(self.show_promisc(device)) _ip_cmd_set(device, promisc=False) self.assertFalse(self.show_promisc(device)) def test_add_vlan(self): device = "test_dev_6" link = "test_devlink" self.addCleanup(self.del_device, device) self.addCleanup(self.del_device, link) self.add_device(link, 'dummy') _ip_cmd_add(device, 'vlan', link=link, vlan_id=100) self.assertTrue(self.exist_device(device)) def test_add_veth(self): device = "test_dev_7" peer = "test_devpeer" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'veth', peer=peer) self.assertTrue(self.exist_device(device)) self.assertTrue(self.exist_device(peer)) def test_delete(self): device = "test_dev_8" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') self.assertTrue(self.exist_device(device)) _ip_cmd_delete(device) self.assertFalse(self.exist_device(device)) def test_iproute_object_closes_correctly(self): # NOTE(ralonsoh): check https://bugs.launchpad.net/os-vif/+bug/1807949 device = "test_dev_9" link = "test_devlink_2" self.add_device(link, 'dummy') self.addCleanup(self.del_device, device) self.addCleanup(self.del_device, link) for _ in range(300): _ip_cmd_add(device, 'vlan', link=link, vlan_id=100) _ip_cmd_delete(device) def test_exists(self): device = "test_dev_10" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') self.assertTrue(_ip_cmd_exists(device)) self.del_device(device) self.assertFalse(_ip_cmd_exists(device)) def test_add_bridge(self): device = "test_dev_11" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'bridge') self.assertTrue(self.exist_device(device)) base_path = "/sys/class/net/test_dev_11/bridge/%s" with open(base_path % "forward_delay", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "stp_state", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "multicast_snooping", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "ageing_time", "r") as f: value = int(f.readline().rstrip('\n')) # NOTE(sean-k-mooney): IEEE 8021-Q recommends that the default # ageing should be 300 and the linux kernel defaults to 300 # via an unconditional define. As such we expect this to be # 300 however since services like network-manager could change # the default on bridge creation we check that if it is not 300 # then the value should not be 0. self.assertTrue(300 == value or value != 0) def test_add_bridge_with_mac_ageing_0(self): device = "test_dev_12" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'bridge', ageing=0) self.assertTrue(self.exist_device(device)) base_path = "/sys/class/net/test_dev_12/bridge/%s" with open(base_path % "forward_delay", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "stp_state", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "ageing_time", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "multicast_snooping", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) def test_add_port_to_bridge(self): device = "test_dev_13" bridge = "test_dev_14" self.addCleanup(self.del_device, device) self.addCleanup(self.del_device, bridge) self.add_device(device, 'dummy') _ip_cmd_add(bridge, 'bridge') self.assertTrue(self.exist_device(device)) self.assertTrue(self.exist_device(bridge)) _ip_cmd_set(device, master=bridge) path = "/sys/class/net/{}/brif/{}".format(bridge, device) self.assertTrue(os.path.exists(path)) def test_add_tap(self): """Test creating a tap device.""" device = "test_tap_1" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'tuntap', mode='tap') self.assertTrue(self.exist_device(device)) # Verify it's a tap device by checking /sys/class/net//tun_flags tun_flags_path = "/sys/class/net/{}/tun_flags".format(device) if os.path.exists(tun_flags_path): with open(tun_flags_path, 'r') as f: flags_hex = f.read().strip() flags = int(flags_hex, 16) # IFF_TAP = 0x0002 IFF_TAP = 0x0002 self.assertTrue(flags & IFF_TAP, "TAP flag not set. Flags: {}".format(flags_hex)) def test_add_tap_with_multiqueue(self): """Test creating a tap device with multiqueue parameter. Note: The IFF_MULTI_QUEUE flag (0x0100) cannot be set via netlink. It is only set when a process opens /dev/net/tun with the IFF_MULTI_QUEUE flag in the ioctl. Libvirt sets this when attaching to the TAP device. This test verifies that: 1. The TAP device can be created with multiqueue=True without error 2. The device is properly created as a TAP device """ device = "test_tap_mq" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'tuntap', mode='tap', multiqueue=True) self.assertTrue(self.exist_device(device)) # Verify it's a tap device by checking /sys/class/net//tun_flags tun_flags_path = "/sys/class/net/{}/tun_flags".format(device) if os.path.exists(tun_flags_path): with open(tun_flags_path, 'r') as f: flags_hex = f.read().strip() flags = int(flags_hex, 16) # IFF_TAP = 0x0002 - verify it's a TAP device IFF_TAP = 0x0002 self.assertTrue( flags & IFF_TAP, "TAP flag not set. Flags: %s" % flags_hex) def test_add_tap_idempotent(self): """Test tap device creation is idempotent with exit code handling.""" device = "test_tap_idemp" self.addCleanup(self.del_device, device) # Create the device _ip_cmd_add(device, 'tuntap', mode='tap') self.assertTrue(self.exist_device(device)) # Try to create again with EEXIST in check_exit_code (17) # This should not raise an exception _ip_cmd_add(device, 'tuntap', mode='tap', check_exit_code=[0, 17]) self.assertTrue(self.exist_device(device)) def test_add_tun_device(self): """Test creating a tun device (not tap).""" device = "test_tun_1" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'tuntap', mode='tun') self.assertTrue(self.exist_device(device)) # Verify it's a tun device tun_flags_path = "/sys/class/net/{}/tun_flags".format(device) if os.path.exists(tun_flags_path): with open(tun_flags_path, 'r') as f: flags_hex = f.read().strip() flags = int(flags_hex, 16) # IFF_TUN = 0x0001 IFF_TUN = 0x0001 self.assertTrue(flags & IFF_TUN, "TUN flag not set. Flags: {}".format(flags_hex)) def test_tap_set_properties(self): """Test setting properties on a tap device.""" device = "test_tap_props" mac = "36:a7:e4:f9:01:02" self.addCleanup(self.del_device, device) # Create tap device _ip_cmd_add(device, 'tuntap', mode='tap') self.assertTrue(self.exist_device(device)) # Set MAC address and state _ip_cmd_set(device, address=mac, state='up') # TAP devices without a carrier will show state=DOWN but should be # administratively UP (UP flag in interface flags) self.assertTrue(self.is_admin_up(device), "Device should be administratively UP") self.assertEqual(mac, self.show_mac(device)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/functional/privsep.py0000664000175000017500000000151415140131732020456 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_privsep import capabilities as c from oslo_privsep import priv_context os_vif_pctxt = priv_context.PrivContext( 'os_vif', cfg_section='os_vif_privileged', pypath=__name__ + '.os_vif_pctxt', capabilities=[c.CAP_NET_ADMIN, c.CAP_DAC_OVERRIDE], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0265875 os_vif-4.3.0/os_vif/tests/unit/0000775000175000017500000000000015140132020015217 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/__init__.py0000664000175000017500000000000015140131732017327 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/base.py0000664000175000017500000000143315140131732016515 0ustar00zuulzuul# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools class TestCase(testtools.TestCase): """Test case base class for all unit tests.""" pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0265875 os_vif-4.3.0/os_vif/tests/unit/internal/0000775000175000017500000000000015140132020017033 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/internal/__init__.py0000664000175000017500000000000015140131732021143 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0275874 os_vif-4.3.0/os_vif/tests/unit/internal/ip/0000775000175000017500000000000015140132020017443 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/internal/ip/__init__.py0000664000175000017500000000000015140131732021553 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0275874 os_vif-4.3.0/os_vif/tests/unit/internal/ip/linux/0000775000175000017500000000000015140132020020602 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/internal/ip/linux/__init__.py0000664000175000017500000000000015140131732022712 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/internal/ip/linux/test_impl_pyroute2.py0000664000175000017500000002373715140131732025052 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 pyroute2 import iproute from pyroute2.netlink import exceptions as ipexc from pyroute2.netlink.rtnl import ifinfmsg from os_vif import exception from os_vif.internal.ip.linux import impl_pyroute2 from os_vif.tests.unit import base class TestIpCommand(base.TestCase): ERROR_CODE = 40 OTHER_ERROR_CODE = 50 DEVICE = 'device' MTU = 1500 MAC = 'ca:fe:ca:fe:ca:fe' UP = 'up' TYPE_VETH = 'veth' TYPE_VLAN = 'vlan' TYPE_BRIDGE = 'bridge' LINK = 'device2' VLAN_ID = 14 def setUp(self): super(TestIpCommand, self).setUp() self.ip = impl_pyroute2.PyRoute2() self.ip_link_p = mock.patch.object(iproute.IPRoute, 'link', create=True) self.ip_link = self.ip_link_p.start() def test_set(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[1], create=True) as mock_link_lookup: self.ip_link.return_value = [{'flags': 0x4000}] self.ip.set(self.DEVICE, state=self.UP, mtu=self.MTU, address=self.MAC, promisc=True) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) args = {'state': self.UP, 'mtu': self.MTU, 'address': self.MAC, 'flags': 0x4000 | ifinfmsg.IFF_PROMISC} calls = [mock.call('get', index=1), mock.call('set', index=1, **args)] self.ip_link.assert_has_calls(calls) def test_set_exit_code(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[1], create=True) as mock_link_lookup: self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE, msg="Error message") self.ip.set(self.DEVICE, check_exit_code=[self.ERROR_CODE]) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) self.ip_link.assert_called_once_with('set', index=1) self.assertRaises(ipexc.NetlinkError, self.ip.set, self.DEVICE, check_exit_code=[self.OTHER_ERROR_CODE]) def test_set_no_interface_found(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[], create=True) as mock_link_lookup: self.assertRaises(exception.NetworkInterfaceNotFound, self.ip.set, self.DEVICE) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) self.ip_link.assert_not_called() def test_add_veth(self): self.ip.add(self.DEVICE, self.TYPE_VETH, peer='peer') self.ip_link.assert_called_once_with( 'add', ifname=self.DEVICE, kind=self.TYPE_VETH, peer='peer') def test_add_vlan(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[1], create=True) as mock_link_lookup: self.ip.add(self.DEVICE, self.TYPE_VLAN, link=self.LINK, vlan_id=self.VLAN_ID) mock_link_lookup.assert_called_once_with(ifname=self.LINK) args = {'ifname': self.DEVICE, 'kind': self.TYPE_VLAN, 'vlan_id': self.VLAN_ID, 'link': 1} self.ip_link.assert_called_once_with('add', **args) def test_add_bridge(self): self.ip.add(self.DEVICE, self.TYPE_BRIDGE) args = {'ifname': self.DEVICE, 'kind': self.TYPE_BRIDGE, 'IFLA_BR_FORWARD_DELAY': 0, 'IFLA_BR_STP_STATE': 0, 'IFLA_BR_MCAST_SNOOPING': 0} self.ip_link.assert_called_once_with('add', **args) def test_add_bridge_with_ageing(self): self.ip.add(self.DEVICE, self.TYPE_BRIDGE, ageing=0) args = {'ifname': self.DEVICE, 'kind': self.TYPE_BRIDGE, 'IFLA_BR_AGEING_TIME': 0, 'IFLA_BR_FORWARD_DELAY': 0, 'IFLA_BR_STP_STATE': 0, 'IFLA_BR_MCAST_SNOOPING': 0} self.ip_link.assert_called_once_with('add', **args) def test_add_vlan_no_interface_found(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[], create=True) as mock_link_lookup: self.assertRaises(exception.NetworkInterfaceNotFound, self.ip.add, self.DEVICE, self.TYPE_VLAN, link=self.LINK) mock_link_lookup.assert_called_once_with(ifname=self.LINK) self.ip_link.assert_not_called() def test_add_other_type(self): self.assertRaises(exception.NetworkInterfaceTypeNotDefined, self.ip.add, self.DEVICE, 'type_not_defined') def test_add_exit_code(self): self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE, msg="Error message") self.ip.add(self.DEVICE, self.TYPE_VETH, peer='peer', check_exit_code=[self.ERROR_CODE]) self.ip_link.assert_called_once_with( 'add', ifname=self.DEVICE, kind=self.TYPE_VETH, peer='peer') self.assertRaises( exception.NetworkInterfaceNotFound, self.ip.add, self.DEVICE, self.TYPE_VLAN, peer='peer', check_exit_code=[self.OTHER_ERROR_CODE]) def test_delete(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[1], create=True) as mock_link_lookup: self.ip.delete(self.DEVICE) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) self.ip_link.assert_called_once_with('del', index=1) def test_delete_no_interface_found(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[], create=True) as mock_link_lookup: self.assertRaises(exception.NetworkInterfaceNotFound, self.ip.delete, self.DEVICE) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) def test_delete_exit_code(self): with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[1], create=True) as mock_link_lookup: self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE, msg="Error message") self.ip.delete(self.DEVICE, check_exit_code=[self.ERROR_CODE]) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) self.ip_link.assert_called_once_with('del', index=1) self.assertRaises(ipexc.NetlinkError, self.ip.delete, self.DEVICE, check_exit_code=[self.OTHER_ERROR_CODE]) def test_add_tap(self): """Test creating a basic tap device.""" self.ip.add(self.DEVICE, 'tuntap', mode='tap') args = {'ifname': self.DEVICE, 'kind': 'tuntap', 'mode': 'tap'} self.ip_link.assert_called_once_with('add', **args) def test_add_tap_default_mode(self): """Test creating tap device with default mode (should be 'tap').""" self.ip.add(self.DEVICE, 'tuntap') args = {'ifname': self.DEVICE, 'kind': 'tuntap', 'mode': 'tap'} self.ip_link.assert_called_once_with('add', **args) def test_add_tap_with_multiqueue(self): """Test creating tap device with multiqueue enabled.""" self.ip.add(self.DEVICE, 'tuntap', mode='tap', multiqueue=True) # Verify the call includes multiqueue flag args = {'ifname': self.DEVICE, 'kind': 'tuntap', 'mode': 'tap', 'multi_queue': True} self.ip_link.assert_called_once_with('add', **args) def test_add_tun_device(self): """Test creating a tun device (not tap).""" self.ip.add(self.DEVICE, 'tuntap', mode='tun') args = {'ifname': self.DEVICE, 'kind': 'tuntap', 'mode': 'tun'} self.ip_link.assert_called_once_with('add', **args) def test_add_tap_exit_code(self): """Test tap device creation with exit code handling.""" self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE, msg="Error message") # Should not raise when error code matches self.ip.add(self.DEVICE, 'tuntap', mode='tap', check_exit_code=[self.ERROR_CODE]) self.ip_link.assert_called_once() # Should raise when exit code doesn't match self.assertRaises( ipexc.NetlinkError, self.ip.add, self.DEVICE, 'tuntap', mode='tap', check_exit_code=[self.OTHER_ERROR_CODE]) def test_set_no_multiqueue_parameter(self): """Verify set() no longer accepts multiqueue parameter.""" with mock.patch.object( iproute.IPRoute, 'link_lookup', return_value=[1], create=True): self.ip_link.return_value = [{'flags': 0}] # Should work without multiqueue self.ip.set(self.DEVICE, state=self.UP, mtu=self.MTU, address=self.MAC) # Should raise TypeError if multiqueue is passed self.assertRaises(TypeError, self.ip.set, self.DEVICE, multiqueue=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/internal/ip/test_api.py0000664000175000017500000000146415140131732021643 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 os_vif.internal.ip import api from os_vif.internal.ip.linux import impl_pyroute2 from os_vif.tests.unit import base class TestIpApi(base.TestCase): def test_get_impl(self): self.assertIsInstance(api.ip, impl_pyroute2.PyRoute2) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/test_base.py0000664000175000017500000000610115140131732017551 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from oslo_serialization import jsonutils from oslo_versionedobjects import base from oslo_versionedobjects import fields from os_vif.objects import base as osv_base from os_vif.tests.unit import base as test_base class TestVersionedObjectPrintable(test_base.TestCase): @base.VersionedObjectRegistry.register_if(False) class OVOChild1(osv_base.VersionedObject, osv_base.VersionedObjectPrintableMixin): fields = { "child1_field1": fields.ListOfIntegersField() } @base.VersionedObjectRegistry.register_if(False) class OVOParent(osv_base.VersionedObject, osv_base.VersionedObjectPrintableMixin, base.ComparableVersionedObject): fields = { "parent_field1": fields.ListOfObjectsField("OVOChild1"), "parent_field2": fields.StringField(), } def setUp(self): super(TestVersionedObjectPrintable, self).setUp() child1_1 = self.OVOChild1(child1_field1=[1, 2, 3]) child1_2 = self.OVOChild1(child1_field1=[4, 5, 6]) self.obj = self.OVOParent( parent_field1=[child1_1, child1_2], parent_field2="test string") def test_print_object(self): out = str(self.obj) self.assertIn("'child1_field1': [1, 2, 3]}", out) self.assertIn("'child1_field1': [4, 5, 6]}", out) cmp = str({'parent_field2': "test string"}) cmp = cmp.replace('{', '').replace('}', '') self.assertIn(str(cmp), out) @mock.patch.object(base.VersionedObject, "obj_class_from_name", side_effect=[OVOParent, OVOChild1, OVOChild1]) def test_serialize_object(self, *mock): """Test jsonutils serialization is not affected by this new mixin.""" obj_orig = copy.deepcopy(self.obj) obj_orig_primitive = obj_orig.obj_to_primitive() str_orig_primitive = jsonutils.dumps(obj_orig_primitive) obj_new_primitive = jsonutils.loads(str_orig_primitive) obj_new = self.OVOParent.obj_from_primitive(obj_new_primitive) self.assertEqual(obj_orig_primitive, obj_new_primitive) self.assertEqual(obj_orig, obj_new) def test_import_non_ovo_class(self): """Test VersionedObjectPrintable could be inherited by non-OVO classes. """ class NonOVOClass(osv_base.VersionedObjectPrintableMixin): def __str__(self): return "NonOVOClass __str__ method" self.assertEqual("NonOVOClass __str__ method", str(NonOVOClass())) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/test_exception.py0000664000175000017500000000246715140131732020650 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 os_vif import exception from os_vif.tests.unit import base """Mostly inspired by os-brick's tests.""" class VIFExceptionTestCase(base.TestCase): def test_default_error_msg(self): class FakeVIFException(exception.ExceptionBase): msg_fmt = "default message" exc = FakeVIFException() self.assertEqual(str(exc), 'default message') def test_error_msg(self): self.assertEqual(str(exception.ExceptionBase('test')), 'test') def test_default_error_msg_with_kwargs(self): class FakeVIFException(exception.ExceptionBase): msg_fmt = "default message: %(foo)s" exc = FakeVIFException(foo="bar") self.assertEqual(str(exc), 'default message: bar') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/test_host_info.py0000664000175000017500000001663015140131732020637 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 vif_plug_linux_bridge import constants as lb_constants from vif_plug_ovs import constants as ovs_constants from os_vif import exception from os_vif import objects from os_vif.tests.unit import base class TestHostInfo(base.TestCase): def setUp(self): super(TestHostInfo, self).setUp() objects.register_all() self.host_info = objects.host_info.HostInfo( plugin_info=[ objects.host_info.HostPluginInfo( plugin_name=lb_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="1.0", max_version="3.0" ), ]), objects.host_info.HostPluginInfo( plugin_name=ovs_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="2.0", max_version="7.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1.0", max_version="2.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFVHostUser", min_version="1.0", max_version="2.0" ), ]) ]) # https://bugs.launchpad.net/oslo.versionedobjects/+bug/1563787 self.host_info.obj_reset_changes(recursive=True) def test_serialization(self): json = self.host_info.obj_to_primitive() self.assertEqual("os_vif", json["versioned_object.namespace"]) host_info = objects.host_info.HostInfo.obj_from_primitive(json) # Copied from test_vif.py: # # The __eq__ function works by using obj_to_primitive() # and this includes a list of changed fields. Very # occassionally the ordering of the list of changes # varies, causing bogus equality failures. This is # arguably a bug in oslo.versionedobjects since the # set of changes fields should not affect equality # comparisons. Remove this hack once this is fixed: # # https://bugs.launchpad.net/oslo.versionedobjects/+bug/1563787 host_info.obj_reset_changes(recursive=True) self.assertEqual(self.host_info, host_info) def test_plugin_existance(self): self.assertTrue(self.host_info.has_plugin(ovs_constants.PLUGIN_NAME)) self.assertFalse(self.host_info.has_plugin("fishfood")) def test_plugin_fetch(self): plugin = self.host_info.get_plugin(ovs_constants.PLUGIN_NAME) self.assertEqual(ovs_constants.PLUGIN_NAME, plugin.plugin_name) self.assertRaises(exception.NoMatchingPlugin, self.host_info.get_plugin, "fishfood") def test_vif_existance(self): plugin = self.host_info.get_plugin(ovs_constants.PLUGIN_NAME) self.assertTrue(plugin.has_vif("VIFOpenVSwitch")) self.assertFalse(plugin.has_vif("VIFFishFood")) def test_vif_fetch(self): plugin = self.host_info.get_plugin(ovs_constants.PLUGIN_NAME) vif = plugin.get_vif("VIFOpenVSwitch") self.assertEqual("VIFOpenVSwitch", vif.vif_object_name) self.assertRaises(exception.NoMatchingVIFClass, plugin.get_vif, "VIFFishFood") def test_common_version_no_obj(self): info = objects.host_info.HostVIFInfo( vif_object_name="VIFFishFood", min_version="1.0", max_version="1.8") self.assertRaises(exception.NoMatchingVIFClass, info.get_common_version) def test_common_version_no_version(self): info = objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1729.0", max_version="8753.0") self.assertRaises(exception.NoSupportedVIFVersion, info.get_common_version) def test_common_version_ok(self): info = objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1.0", max_version="10.0") ver = info.get_common_version() self.assertEqual(objects.vif.VIFOpenVSwitch.VERSION, ver) def test_filtering(self): host_info = objects.host_info.HostInfo( plugin_info=[ objects.host_info.HostPluginInfo( plugin_name=lb_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="1.0", max_version="3.0" ), ]), objects.host_info.HostPluginInfo( plugin_name=ovs_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="2.0", max_version="7.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1.0", max_version="2.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFVHostUser", min_version="1.0", max_version="2.0" ), ]) ]) host_info.filter_vif_types(["VIFBridge", "VIFOpenVSwitch"]) self.assertEqual(len(host_info.plugin_info), 2) plugin = host_info.plugin_info[0] self.assertEqual(len(plugin.vif_info), 1) self.assertEqual(plugin.vif_info[0].vif_object_name, "VIFBridge") plugin = host_info.plugin_info[1] self.assertEqual(len(plugin.vif_info), 2) self.assertEqual(plugin.vif_info[0].vif_object_name, "VIFBridge") self.assertEqual(plugin.vif_info[1].vif_object_name, "VIFOpenVSwitch") host_info.filter_vif_types(["VIFOpenVSwitch"]) self.assertEqual(len(host_info.plugin_info), 1) plugin = host_info.plugin_info[0] self.assertEqual(len(plugin.vif_info), 1) self.assertEqual(plugin.vif_info[0].vif_object_name, "VIFOpenVSwitch") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/test_objects.py0000664000175000017500000001422415140131732020275 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_versionedobjects import base as ovo_base from oslo_versionedobjects import fixture import os_vif from os_vif import objects from os_vif.tests.unit import base object_data = { 'HostInfo': '1.0-4dba5ce236ea2dc559de8764995dd247', 'HostPluginInfo': '1.0-5204e579864981c9891ecb5d1c9329f2', 'HostPortProfileInfo': '1.0-e0bc9228c1456b220830d67b05bc4bf2', 'HostVIFInfo': '1.1-00fdbeba3f9bb3bd2a723c17023ba182', 'FixedIP': '1.0-d1a0ec7e7b6ce021a784c54d44cce009', 'FixedIPList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'InstanceInfo': '1.0-84104d3435046b1a282ac8265ec2a976', 'Network': '1.1-27a8a3e236d1d239121668a590130154', 'Route': '1.0-5ca049cb82c4d4ec5edb1b839c1429c7', 'RouteList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'Subnet': '1.0-6a8c192ef7492120d1a5e0fd08e44272', 'SubnetList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'VIFBase': '1.0-4a5a8881dc999752cb050dd443458b6a', 'VIFBridge': '1.0-e78d355f3505361fafbf0797ffad484a', 'VIFDirect': '1.0-05c939280f4025fd1f7efb921a835c57', 'VIFGeneric': '1.0-c72e637ed620f0135ea50a9409a3f389', 'VIFHostDevice': '1.0-bb090f1869c3b4df36efda216ab97a61', 'VIFOpenVSwitch': '1.0-e78d355f3505361fafbf0797ffad484a', 'VIFPortProfile8021Qbg': '1.1-b3011621809dca9216b50579ce9d6b19', 'VIFPortProfile8021Qbh': '1.1-226b61b2e76ba452f7b31530cff80ac9', 'VIFPortProfileBase': '1.1-4982d1621df12ebd1f3b07948f3d0e5f', 'VIFPortProfileOpenVSwitch': '1.4-54fb321f2b70c37d392fadc226c889e7', 'VIFPortProfileFPOpenVSwitch': '1.4-20904c1dcdac0641cabf7b02525973bd', 'VIFPortProfileFPBridge': '1.1-49f1952bf50bab7a95112c908534751f', 'VIFPortProfileFPTap': '1.1-fd178229477604dfb65de5ce929488e5', 'VIFVHostUser': '1.1-1f95b43be1f884f090ca1f4d79adfd35', 'VIFPortProfileOVSRepresentor': '1.4-82b7292ee4cd6f808b724d2f5648932b', 'VIFNestedDPDK': '1.0-fdbaf6b20afd116529929b21aa7158dc', 'VIFPortProfileK8sDPDK': '1.1-e2a2abd112b14e0239e76b99d9b252ae', 'DatapathOffloadBase': '1.0-77509ea1ea0dd750d5864b9bd87d3f9d', 'DatapathOffloadRepresentor': '1.0-802a5dff22f73046df3742c815c51421', } class TestObjectVersions(base.TestCase): def setUp(self): super(TestObjectVersions, self).setUp() os_vif.objects.register_all() def test_versions(self): checker = fixture.ObjectVersionChecker( ovo_base.VersionedObjectRegistry.obj_classes()) expected, actual = checker.test_hashes(object_data) self.assertEqual(expected, actual, 'Some objects have changed; please make sure the ' 'versions have been bumped, and then update their ' 'hashes here.') def test_vif_vhost_user_obj_make_compatible(self): vif = objects.vif.VIFVHostUser( path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="vhu123") primitive = vif.obj_to_primitive()['versioned_object.data'] self.assertIn('vif_name', primitive) vif.obj_make_compatible(primitive, '1.0') self.assertNotIn('vif_name', primitive) def test_vif_port_profile_ovs_obj_make_compatible_1_4(self): """Test obj_make_compatible removes create_tap and multiqueue for versions < 1.4. """ profile = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True, multiqueue=True) primitive = profile.obj_to_primitive()['versioned_object.data'] self.assertIn('create_tap', primitive) self.assertIn('multiqueue', primitive) # For version 1.3, both create_tap and multiqueue should be removed profile.obj_make_compatible(primitive, '1.3') self.assertNotIn('create_tap', primitive) self.assertNotIn('multiqueue', primitive) def test_vif_port_profile_ovs_obj_make_compatible_1_3(self): """Test obj_make_compatible removes create_port for versions < 1.3.""" profile = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_port=True) primitive = profile.obj_to_primitive()['versioned_object.data'] self.assertIn('create_port', primitive) # For version 1.2, create_port should be removed profile.obj_make_compatible(primitive, '1.2') self.assertNotIn('create_port', primitive) def test_vif_port_profile_ovs_create_tap_multiqueue_fields(self): """Test that create_tap and multiqueue fields can be set.""" profile = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True, multiqueue=True) self.assertTrue(profile.create_tap) self.assertTrue(profile.multiqueue) # Test explicitly set default values profile_default = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=False, multiqueue=False) self.assertFalse(profile_default.create_tap) self.assertFalse(profile_default.multiqueue) def test_vif_port_profile_ovs_in_operator(self): """Test that 'in' operator works for checking field existence.""" profile = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True, multiqueue=True) self.assertIn('create_tap', profile) self.assertIn('multiqueue', profile) self.assertIn('interface_id', profile) self.assertNotIn('nonexistent_field', profile) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/test_os_vif.py0000664000175000017500000001472415140131732020136 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from oslo_config import cfg from stevedore import extension import os_vif from os_vif import exception from os_vif import objects from os_vif import opts from os_vif import plugin from os_vif.tests.unit import base class DemoPlugin(plugin.PluginBase): CONFIG_OPTS = ( cfg.BoolOpt("make_it_work", default=False, help="Make everything work correctly by setting this"), cfg.IntOpt("sleep_time", default=0, help="How long to artifically sleep") ) def describe(self): pass def plug(self, vif, instance_info, config): pass def unplug(self, vif, instance_info, config): pass class DemoPluginNoConfig(plugin.PluginBase): def describe(self): pass def plug(self, vif, instance_info, config): pass def unplug(self, vif, instance_info, config): pass class TestOSVIF(base.TestCase): def setUp(self): super(TestOSVIF, self).setUp() os_vif._EXT_MANAGER = None @mock.patch('stevedore.extension.ExtensionManager') def test_initialize(self, mock_EM): self.assertIsNone(os_vif._EXT_MANAGER) # Note: the duplicate call for initialize is to validate # that the extension manager is only initialized once os_vif.initialize() os_vif.initialize() mock_EM.assert_called_once_with( invoke_on_load=False, namespace='os_vif') self.assertIsNotNone(os_vif._EXT_MANAGER) def test_load_plugin(self): obj = DemoPlugin.load("demo") self.assertTrue(hasattr(cfg.CONF, "os_vif_demo")) self.assertTrue(hasattr(cfg.CONF.os_vif_demo, "make_it_work")) self.assertTrue(hasattr(cfg.CONF.os_vif_demo, "sleep_time")) self.assertEqual(cfg.CONF.os_vif_demo.make_it_work, False) self.assertEqual(cfg.CONF.os_vif_demo.sleep_time, 0) self.assertEqual(obj.config, cfg.CONF.os_vif_demo) def test_load_plugin_no_config(self): obj = DemoPluginNoConfig.load("demonocfg") self.assertFalse(hasattr(cfg.CONF, "os_vif_demonocfg")) self.assertIsNone(obj.config) def test_plug_not_initialized(self): self.assertRaises( exception.LibraryNotInitialized, os_vif.plug, None, None) def test_unplug_not_initialized(self): self.assertRaises( exception.LibraryNotInitialized, os_vif.plug, None, None) @mock.patch.object(DemoPlugin, "plug") def test_plug(self, mock_plug): plg = extension.Extension(name="demo", entry_point="os-vif", plugin=DemoPlugin, obj=None) with mock.patch( 'stevedore.extension.ExtensionManager.names', return_value=['foobar'], ), mock.patch( 'stevedore.extension.ExtensionManager.__getitem__', return_value=plg, ): os_vif.initialize() info = objects.instance_info.InstanceInfo() vif = objects.vif.VIFBridge( id='9a12694f-f95e-49fa-9edb-70239aee5a2c', plugin='foobar') os_vif.plug(vif, info) mock_plug.assert_called_once_with(vif, info) @mock.patch.object(DemoPlugin, "unplug") def test_unplug(self, mock_unplug): plg = extension.Extension(name="demo", entry_point="os-vif", plugin=DemoPlugin, obj=None) with mock.patch( 'stevedore.extension.ExtensionManager.names', return_value=['foobar'] ), mock.patch( 'stevedore.extension.ExtensionManager.__getitem__', return_value=plg, ): os_vif.initialize() info = objects.instance_info.InstanceInfo() vif = objects.vif.VIFBridge( id='9a12694f-f95e-49fa-9edb-70239aee5a2c', plugin='foobar') os_vif.unplug(vif, info) mock_unplug.assert_called_once_with(vif, info) def test_host_info_all(self): os_vif.initialize() info = os_vif.host_info() # NOTE(sean-k-mooney): as out of tree plugins could be # visable in path assert only at at least all the in # intree plugins are loaded instead of an exact match. self.assertTrue(len(info.plugin_info) >= 3) plugins = {p.plugin_name: p for p in info.plugin_info} in_tree_plugin_names = ("linux_bridge", "ovs", "noop") self.assertTrue(all(name in plugins for name in in_tree_plugin_names)) lb = plugins["linux_bridge"] self.assertTrue(any("VIFBridge" == vif.vif_object_name for vif in lb.vif_info)) ovs = plugins["ovs"] self.assertTrue(len(ovs.vif_info) >= 4) vif_names = (vif.vif_object_name for vif in ovs.vif_info) ovs_vifs = ("VIFBridge", "VIFOpenVSwitch", "VIFVHostUser", "VIFHostDevice") self.assertTrue(all(name in ovs_vifs for name in vif_names)) noop = plugins["noop"] self.assertTrue(any("VIFVHostUser" == vif.vif_object_name for vif in noop.vif_info)) def test_host_info_filtered(self): os_vif.initialize() info = os_vif.host_info(permitted_vif_type_names=["VIFOpenVSwitch"]) self.assertEqual(len(info.plugin_info), 1) self.assertEqual(info.plugin_info[0].plugin_name, "ovs") vif_info = info.plugin_info[0].vif_info self.assertEqual(len(vif_info), 1) self.assertEqual(vif_info[0].vif_object_name, "VIFOpenVSwitch") def test_list_opts_entrypoints(self): list_opts = opts.list_plugins_opts() for group in list_opts: for opt in group[1]: self.assertTrue("oslo_config.cfg" == opt.__module__) self.assertGreaterEqual(len(list_opts), 3) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/tests/unit/test_vif.py0000664000175000017500000004312015140131732017425 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 warnings import os_vif from os_vif import objects from os_vif.tests.unit import base class TestVIFS(base.TestCase): def setUp(self): super(TestVIFS, self).setUp() os_vif.objects.register_all() def _test_vif(self, cls, **kwargs): vif = cls(**kwargs) prim = vif.obj_to_primitive() self.assertEqual("os_vif", prim["versioned_object.namespace"]) vif2 = objects.vif.VIFBase.obj_from_primitive(prim) # The __eq__ function works by using obj_to_primitive() # and this includes a list of changed fields. Very # occassionally the ordering of the list of changes # varies, causing bogus equality failures. This is # arguably a bug in oslo.versionedobjects since the # set of changes fields should not affect equality # comparisons. Remove this hack once this is fixed: # # https://bugs.launchpad.net/oslo.versionedobjects/+bug/1563787 vif.obj_reset_changes(recursive=True) vif2.obj_reset_changes(recursive=True) self.assertEqual(vif, vif2) def test_vif_generic(self): self._test_vif(objects.vif.VIFGeneric, vif_name="vif123") def test_vif_bridge_plain(self): self._test_vif(objects.vif.VIFBridge, vif_name="vif123", bridge_name="br0") def test_port_profile_base_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileBase( datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertNotIn('datapath_type', data) def test_vif_bridge_ovs(self): prof = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev') self._test_vif(objects.vif.VIFOpenVSwitch, vif_name="vif123", bridge_name="br0", port_profile=prof) def test_port_profile_ovs_backport_1_0(self): obj = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev') primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertNotIn('datapath_type', data) def test_port_profile_ovs_backport_1_1(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.1') self.assertEqual('1.1', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('netdev', data['datapath_type']) self.assertNotIn('datapath_offload', data) def test_port_profile_ovs_backport_1_2(self): obj = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", create_port=True) primitive = obj.obj_to_primitive(target_version='1.2') self.assertEqual('1.2', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertNotIn('create_port', data) def test_vif_direct_plain(self): self._test_vif(objects.vif.VIFDirect, vif_name="vif123", dev_address="0002:24:12.3") def test_vif_direct_vepa_qbg(self): prof = objects.vif.VIFPortProfile8021Qbg( manager_id=8, type_id=23, type_id_version=523, instance_id="72a00fee-2fbb-43e6-a592-c858d056fcfc") self._test_vif(objects.vif.VIFDirect, vif_name="vif123", port_profile=prof, dev_address="0002:24:12.3") def test_vif_direct_vepa_qbh(self): prof = objects.vif.VIFPortProfile8021Qbh( profile_id="fishfood") self._test_vif(objects.vif.VIFDirect, vif_name="vif123", port_profile=prof, dev_address="0002:24:12.3") def test_vif_vhost_user(self): self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="vhu123") def test_vif_vhost_user_fp_ovs(self): prof = objects.vif.VIFPortProfileFPOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee8", profile_id="fishfood", datapath_type='netdev', bridge_name="br-int", hybrid_plug=False) self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_port_profile_fp_ovs_backport_1_0(self): obj = objects.vif.VIFPortProfileFPOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', bridge_name="br-int", hybrid_plug=False) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('br-int', data['bridge_name']) self.assertEqual(False, data['hybrid_plug']) self.assertNotIn('datapath_type', data) def test_port_profile_fp_ovs_backport_1_1(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileFPOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', bridge_name="br-int", hybrid_plug=False, datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.1') self.assertEqual('1.1', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('br-int', data['bridge_name']) self.assertEqual(False, data['hybrid_plug']) self.assertEqual('netdev', data['datapath_type']) self.assertNotIn('datapath_offload', data) def test_vif_vhost_user_ovs_representor(self): prof = objects.vif.VIFPortProfileOVSRepresentor( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee8", profile_id="fishfood", datapath_type='netdev', representor_name="tap123", representor_address="0002:24:12.3") self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_port_profile_ovs_representor_backport_1_0(self): obj = objects.vif.VIFPortProfileOVSRepresentor( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', representor_name="tap123", representor_address="0002:24:12.3") primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('tap123', data['representor_name']) self.assertEqual("0002:24:12.3", data['representor_address']) self.assertNotIn('datapath_type', data) def test_port_profile_ovs_representor_backport_1_1(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileOVSRepresentor( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', representor_name="tap123", representor_address="0002:24:12.3", datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.1') self.assertEqual('1.1', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('tap123', data['representor_name']) self.assertEqual("0002:24:12.3", data['representor_address']) self.assertEqual('netdev', data['datapath_type']) self.assertNotIn('datapath_offload', data) def test_vif_vhost_user_generic_representor(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") prof = objects.vif.VIFPortProfileBase( datapath_offload=datapath_offload, ) self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.SERVER, vif_name="felix", port_profile=prof) def test_vif_vhost_user_fp_lb(self): prof = objects.vif.VIFPortProfileFPBridge(bridge_name="brq456") self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_vif_vhost_user_fp_tap(self): prof = objects.vif.VIFPortProfileFPTap(mac_address="fa:16:3e:4c:2c:30") self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_vif_host_dev_plain(self): self._test_vif( objects.vif.VIFHostDevice, dev_type=objects.fields.VIFHostDeviceDevType.ETHERNET, dev_address="0002:24:12.3") def test_vif_host_dev_vepa_qbh(self): prof = objects.vif.VIFPortProfile8021Qbh( profile_id="fishfood") self._test_vif(objects.vif.VIFHostDevice, dev_address="0002:24:12.3", port_profile=prof) def test_vif_nested_dpdk_k8s(self): prof = objects.vif.VIFPortProfileK8sDPDK( l3_setup=False, selflink="/some/url", resourceversion="1") self._test_vif( objects.vif.VIFNestedDPDK, pci_adress="0002:24:12.3", dev_driver="virtio_pci", port_profile=prof) def test_port_profile_fp_bridge_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileFPBridge( bridge_name='joe', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('joe', data['bridge_name']) self.assertNotIn('datapath_type', data) def test_port_profile_fp_tap_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileFPTap( mac_address='00:de:ad:be:ef:01', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('00:de:ad:be:ef:01', data['mac_address']) self.assertNotIn('datapath_type', data) def test_port_profile_8021qbg_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfile8021Qbg( manager_id=42, type_id=43, type_id_version=44, instance_id='07bd6cea-fb37-4594-b769-90fc51854ee9', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual(42, data['manager_id']) self.assertEqual(43, data['type_id']) self.assertEqual(44, data['type_id_version']) self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['instance_id']) self.assertNotIn('datapath_type', data) def test_port_profile_8021qbh_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfile8021Qbh( profile_id='catfood', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('catfood', data['profile_id']) self.assertNotIn('datapath_type', data) def test_port_profile_dpdk_k8s_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileK8sDPDK( l3_setup=False, selflink="/some/url", resourceversion="1", datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual(False, data['l3_setup']) self.assertEqual("/some/url", data['selflink']) self.assertEqual("1", data['resourceversion']) self.assertNotIn('datapath_type', data) def test_vif_host_dev_ovs_offload(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") prof = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee8", profile_id="fishfood", datapath_type='netdev', datapath_offload=datapath_offload) self._test_vif( objects.vif.VIFHostDevice, dev_type=objects.fields.VIFHostDeviceDevType.ETHERNET, dev_address="0002:24:12.3", port_profile=prof) def test_pending_warnings_emitted_class_direct(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") pp = objects.vif.VIFPortProfileOVSRepresentor() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) self.assertEqual(pp.VERSION, objects.vif.VIFPortProfileOVSRepresentor.VERSION) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/utils.py0000664000175000017500000000131115140131732014615 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 set_mask(data, mask): return data | mask def unset_mask(data, mask, bit_size=32): return data & ((2 ** bit_size - 1) ^ mask) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/os_vif/version.py0000664000175000017500000000131115140131732015142 0ustar00zuulzuul# 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 pbr.version version_info = pbr.version.VersionInfo('os-vif') __version__ = version_info.version_string() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0535874 os_vif-4.3.0/os_vif.egg-info/0000775000175000017500000000000015140132020014570 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/PKG-INFO0000644000175000017500000000463615140132017015702 0ustar00zuulzuulMetadata-Version: 2.4 Name: os_vif Version: 4.3.0 Summary: A library for plugging and unplugging virtual interfaces in OpenStack. Home-page: https://docs.openstack.org/os-vif/latest/ 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 :: Only Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr>=3.0.0 Requires-Dist: oslo.concurrency>=3.20.0 Requires-Dist: oslo.config>=5.1.0 Requires-Dist: oslo.log>=3.30.0 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.privsep>=1.23.0 Requires-Dist: oslo.serialization>=2.20.0 Requires-Dist: oslo.utils>=2.0.0 Requires-Dist: oslo.versionedobjects>=1.28.0 Requires-Dist: ovsdbapp>=0.12.1 Requires-Dist: pyroute2>=0.5.2 Requires-Dist: stevedore>=1.20.0 Requires-Dist: debtcollector>=1.19.0 Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license-file Dynamic: requires-dist Dynamic: requires-python Dynamic: summary ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/os-vif.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ====== os-vif ====== .. image:: https://img.shields.io/pypi/v/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Downloads A library for plugging and unplugging virtual interfaces in OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/os-vif/latest/ * Source: https://opendev.org/openstack/os-vif * Bugs: https://bugs.launchpad.net/os-vif * Release Notes: https://docs.openstack.org/releasenotes/os-vif ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/SOURCES.txt0000664000175000017500000001562715140132017016475 0ustar00zuulzuul.coveragerc .mailmap .pre-commit-config.yaml .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst bindep.txt pyproject.toml requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/contributor/contributing.rst doc/source/reference/glossary.rst doc/source/user/host-info.rst doc/source/user/usage.rst doc/source/user/vif-types.rst doc/source/user/plugins/linux-bridge.rst doc/source/user/plugins/noop.rst doc/source/user/plugins/ovs.rst os_vif/__init__.py os_vif/exception.py os_vif/i18n.py os_vif/opts.py os_vif/plugin.py os_vif/utils.py os_vif/version.py os_vif.egg-info/PKG-INFO os_vif.egg-info/SOURCES.txt os_vif.egg-info/dependency_links.txt os_vif.egg-info/entry_points.txt os_vif.egg-info/not-zip-safe os_vif.egg-info/pbr.json os_vif.egg-info/requires.txt os_vif.egg-info/top_level.txt os_vif/internal/__init__.py os_vif/internal/ip/__init__.py os_vif/internal/ip/api.py os_vif/internal/ip/ip_command.py os_vif/internal/ip/linux/__init__.py os_vif/internal/ip/linux/impl_pyroute2.py os_vif/objects/__init__.py os_vif/objects/base.py os_vif/objects/fields.py os_vif/objects/fixed_ip.py os_vif/objects/host_info.py os_vif/objects/instance_info.py os_vif/objects/network.py os_vif/objects/route.py os_vif/objects/subnet.py os_vif/objects/vif.py os_vif/tests/__init__.py os_vif/tests/functional/__init__.py os_vif/tests/functional/base.py os_vif/tests/functional/privsep.py os_vif/tests/functional/internal/__init__.py os_vif/tests/functional/internal/command/__init__.py os_vif/tests/functional/internal/command/ip/__init__.py os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py os_vif/tests/unit/__init__.py os_vif/tests/unit/base.py os_vif/tests/unit/test_base.py os_vif/tests/unit/test_exception.py os_vif/tests/unit/test_host_info.py os_vif/tests/unit/test_objects.py os_vif/tests/unit/test_os_vif.py os_vif/tests/unit/test_vif.py os_vif/tests/unit/internal/__init__.py os_vif/tests/unit/internal/ip/__init__.py os_vif/tests/unit/internal/ip/test_api.py os_vif/tests/unit/internal/ip/linux/__init__.py os_vif/tests/unit/internal/ip/linux/test_impl_pyroute2.py playbooks/openstack-tox-functional-ovs-with-sudo/Debian.yaml playbooks/openstack-tox-functional-ovs-with-sudo/Gentoo.yaml playbooks/openstack-tox-functional-ovs-with-sudo/RedHat.yaml playbooks/openstack-tox-functional-ovs-with-sudo/Suse.yaml playbooks/openstack-tox-functional-ovs-with-sudo/pre.yaml releasenotes/notes/OVSVif-hybrid-unplug-f37bf57bc8e913de.yaml releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml releasenotes/notes/add-fast-path-vhostuser-support-fe87e558326909b6.yaml releasenotes/notes/add-no-op-plugin-763a6703e7328a24.yaml releasenotes/notes/add-ovs-representor-portprofile-5f8290e5a40bf0a4.yaml releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml releasenotes/notes/add-ovsdb-native-322fffb49c91503d.yaml releasenotes/notes/add-tap-creation-support-2069718-abc123.yaml releasenotes/notes/always-plug-vifs-for-ovs-1d033fc49a9c6c4e.yaml releasenotes/notes/brctl-removal-a5b0e69b865afa57.yaml releasenotes/notes/bug-1892132-812e6d5ce0588ebb.yaml releasenotes/notes/contextlib-and-nested-with-statements-2747a9ebb9a5bfd7.yaml releasenotes/notes/default-qos-policy-for-ovs-26f8806046a59c82.yaml releasenotes/notes/default-to-native-ovsdb-driver-112fb5adf6e19a30.yaml releasenotes/notes/deprecate-linuxbridge-support-d278d4bbdff8e8bc.yaml releasenotes/notes/deprecate-windows-support-49f5ca1b1a1f4d66.yaml releasenotes/notes/do-not-force-mac-ageing-c6e8d750130c5740.yaml releasenotes/notes/drop-py36-support-0e9b07073f6ad73f.yaml releasenotes/notes/drop-python2-support-7a4bc7d31253c1e5.yaml releasenotes/notes/ensure-ovs-bridge-a0c1b51f469c92d0.yaml releasenotes/notes/extend-vhostuser-object-fada14a1457d4e56.yaml releasenotes/notes/fix-broken-dataplane-on-nova-restart-with-isolate_vif-71617a41741b33e8.yaml releasenotes/notes/fix-ovs-plugin-describe-049750609559f1ba.yaml releasenotes/notes/fix-stevedore-entrypoints-8002ec7a5166c977.yaml releasenotes/notes/fix-vif-openvswitch-fa0d19be9dd668e1.yaml releasenotes/notes/generic-datapath-offloads-41cabb6842b41533.yaml releasenotes/notes/initial-release-2c71d6bbf55f763b.yaml releasenotes/notes/oslo-config-opts-entrypoints-e83f907b686d774a.yaml releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml releasenotes/notes/port-profile-info-linux-bridge-4800f5a0b7328615.yaml releasenotes/notes/port-profile-info-ovs-63b46a3eafc11de2.yaml releasenotes/notes/prevent-lb-reply-arp-6459133bfb056069.yaml releasenotes/notes/remove-py38-e0701b3363079bbf.yaml releasenotes/notes/remove-windows-23df1c587d505d72.yaml releasenotes/notes/remove_iptools_implementation-2eb866573a680e61.yaml releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.yaml releasenotes/notes/vhost-user-mtu-support-cbc7d02a6665fab1.yaml releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/2025.1.rst releasenotes/source/2025.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder vif_plug_linux_bridge/__init__.py vif_plug_linux_bridge/constants.py vif_plug_linux_bridge/iptables.py vif_plug_linux_bridge/linux_bridge.py vif_plug_linux_bridge/linux_net.py vif_plug_linux_bridge/privsep.py vif_plug_linux_bridge/tests/__init__.py vif_plug_linux_bridge/tests/unit/__init__.py vif_plug_linux_bridge/tests/unit/test_linux_net.py vif_plug_linux_bridge/tests/unit/test_plugin.py vif_plug_noop/__init__.py vif_plug_noop/noop.py vif_plug_noop/tests/__init__.py vif_plug_noop/tests/unit/__init__.py vif_plug_noop/tests/unit/test_plugin.py vif_plug_ovs/__init__.py vif_plug_ovs/constants.py vif_plug_ovs/exception.py vif_plug_ovs/linux_net.py vif_plug_ovs/ovs.py vif_plug_ovs/privsep.py vif_plug_ovs/ovsdb/__init__.py vif_plug_ovs/ovsdb/api.py vif_plug_ovs/ovsdb/impl_idl.py vif_plug_ovs/ovsdb/impl_vsctl.py vif_plug_ovs/ovsdb/ovsdb_lib.py vif_plug_ovs/tests/__init__.py vif_plug_ovs/tests/functional/__init__.py vif_plug_ovs/tests/functional/base.py vif_plug_ovs/tests/functional/test_plugin.py vif_plug_ovs/tests/functional/ovsdb/__init__.py vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py vif_plug_ovs/tests/unit/__init__.py vif_plug_ovs/tests/unit/test_linux_net.py vif_plug_ovs/tests/unit/test_plugin.py vif_plug_ovs/tests/unit/ovsdb/__init__.py vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/dependency_links.txt0000664000175000017500000000000115140132017020644 0ustar00zuulzuul ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/entry_points.txt0000664000175000017500000000031615140132017020074 0ustar00zuulzuul[os_vif] linux_bridge = vif_plug_linux_bridge.linux_bridge:LinuxBridgePlugin noop = vif_plug_noop.noop:NoOpPlugin ovs = vif_plug_ovs.ovs:OvsPlugin [oslo.config.opts] os_vif = os_vif.opts:list_plugins_opts ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/not-zip-safe0000664000175000017500000000000115140132017017024 0ustar00zuulzuul ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/pbr.json0000664000175000017500000000005615140132017016255 0ustar00zuulzuul{"git_version": "8cbe6ee", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/requires.txt0000664000175000017500000000040315140132017017173 0ustar00zuulzuulpbr>=3.0.0 oslo.concurrency>=3.20.0 oslo.config>=5.1.0 oslo.log>=3.30.0 oslo.i18n>=3.15.3 oslo.privsep>=1.23.0 oslo.serialization>=2.20.0 oslo.utils>=2.0.0 oslo.versionedobjects>=1.28.0 ovsdbapp>=0.12.1 pyroute2>=0.5.2 stevedore>=1.20.0 debtcollector>=1.19.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042383.0 os_vif-4.3.0/os_vif.egg-info/top_level.txt0000664000175000017500000000007015140132017017325 0ustar00zuulzuulos_vif vif_plug_linux_bridge vif_plug_noop vif_plug_ovs ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0045874 os_vif-4.3.0/playbooks/0000775000175000017500000000000015140132020013614 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0285873 os_vif-4.3.0/playbooks/openstack-tox-functional-ovs-with-sudo/0000775000175000017500000000000015140132020023301 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/playbooks/openstack-tox-functional-ovs-with-sudo/Debian.yaml0000664000175000017500000000011015140131732025350 0ustar00zuulzuul--- ovs_package: "openvswitch-switch" ovs_service: "openvswitch-switch" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/playbooks/openstack-tox-functional-ovs-with-sudo/Gentoo.yaml0000664000175000017500000000010415140131732025424 0ustar00zuulzuul--- ovs_package: "net-misc/openvswitch" ovs_service: "ovs-vswitchd" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/playbooks/openstack-tox-functional-ovs-with-sudo/RedHat.yaml0000664000175000017500000000007215140131732025344 0ustar00zuulzuul--- ovs_package: "openvswitch" ovs_service: "openvswitch" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/playbooks/openstack-tox-functional-ovs-with-sudo/Suse.yaml0000664000175000017500000000007215140131732025114 0ustar00zuulzuul--- ovs_package: "openvswitch" ovs_service: "openvswitch" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/playbooks/openstack-tox-functional-ovs-with-sudo/pre.yaml0000664000175000017500000000106615140131732024767 0ustar00zuulzuul- hosts: all name: Functional tests pre-tasks tasks: - name: Include OS-specific variables include_vars: "{{ item }}" with_first_found: - "{{ ansible_distribution }}.yaml" - "{{ ansible_os_family }}.yaml" - name: Install Open vSwitch become: yes package: name: "{{ ovs_package }}" state: present register: ovs_installed - name: Start Open vSwitch become: yes service: name: "{{ ovs_service }}" state: started enabled: yes register: ovs_running ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/pyproject.toml0000664000175000017500000000014415140131732014535 0ustar00zuulzuul[build-system] requires = ["pbr>=5.7.0", "setuptools>=64.0.0", "wheel"] build-backend = "pbr.build" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0045874 os_vif-4.3.0/releasenotes/0000775000175000017500000000000015140132020014302 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0375874 os_vif-4.3.0/releasenotes/notes/0000775000175000017500000000000015140132020015432 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/OVSVif-hybrid-unplug-f37bf57bc8e913de.yaml0000664000175000017500000000064315140131732024626 0ustar00zuulzuul--- feature: - | The unplug() method for VIFOpenVSwitch type now checks for existence of linux bridge used in hybrid plugging mechanism. In case it exists, the interfaces related to the hybrid plugging are deleted too. This is useful in particular for cold migration use case when target node has updated port binding that doesn't use hybrid plugging, while the original plugging was hybrid. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml0000664000175000017500000000057115140131732025055 0ustar00zuulzuul--- features: - | Added an abstract OVSDB API in ``vif_plug_ovs``. All calls to OVS database will de done using this unique API. Command line implementation using ``ovs-vsctl`` was refactored as a backend for this abstract API. A new configuration variable, ``ovsdb_interface``, is added to select the interface for interacting with the OVS database. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/add-fast-path-vhostuser-support-fe87e558326909b6.yaml0000664000175000017500000000043015140131732026616 0ustar00zuulzuul--- features: - New port profiles have been added to describe vhostuser fast path VIFs. In particular fast path vhostuser ports can be used with ovs, linuxbridge and calico networks. Thus for each kind of network a dedicated port profile class has been defined. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/add-no-op-plugin-763a6703e7328a24.yaml0000664000175000017500000000045415140131732023402 0ustar00zuulzuul--- features: - | A new VIF plugin, ``vif_plug_noop``, has been added which can be used with network backends that do not require any action to be performed when a network interface is plugged. This plugin allow for use of, for example, the generic vhost user VIF type without OVS. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/add-ovs-representor-portprofile-5f8290e5a40bf0a4.yaml0000664000175000017500000000044515140131732027007 0ustar00zuulzuul--- features: - A new port profile has been added to describe VF representors on OVS-based switches, as featured in Linux kernel 4.8 and later. This port profile can currently be used with Agilio OVS networks. It is intended to be flexible enough to be used by multiple vendors. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml0000664000175000017500000000035215140131732025717 0ustar00zuulzuul--- features: - The ovs plugin has been extended to support vhost-user interfaces. vhost-user is a userspace protocol for high speed virtual networking introduced in qemu 2.1 and first supported in ovs 2.4 with dpdk 2.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/add-ovsdb-native-322fffb49c91503d.yaml0000664000175000017500000000104615140131732023675 0ustar00zuulzuul--- features: - | Added native implementation of OVSDB API in ``vif_plug_ovs``. Both ``vsctl`` and ``native`` APIs could be selected by setting the configuration variable ``ovsdb_interface``. A new configuration variable, ``ovsdb_connection``, is added. This variable defines the connection string for the OVSDB backend. other: - | Changed default value of ``ovsdb_connection`` to "tcp:127.0.0.1:6640", to match the default value set in Neutron project. This connection string is needed by OVSDB native interface. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/add-tap-creation-support-2069718-abc123.yaml0000664000175000017500000000224715140131732024771 0ustar00zuulzuul--- features: - | Added support for os-vif to pre-create TAP devices when using OVS/OVN. The ``VIFPortProfileOpenVSwitch`` object now supports two new fields: * ``create_tap``: When set to ``True``, os-vif will create the TAP device with the correct MAC address and MTU before adding it to OVS. * ``multiqueue``: When set to ``True`` along with ``create_tap``, the TAP device will be created with multiqueue support enabled for improved performance with multiple vCPUs. The multiqueue setting is determined by Nova from the ``hw:vif_multiqueue_enabled`` flavor extra spec or image property. When the ``[ovn]/ovs_create_tap`` config option is enabled in Neutron's ML2/OVN mechanism driver, Neutron sets the ``ovs_create_tap`` flag in the vif_details during port binding. Nova then reads this value and propagates it to os-vif via the ``create_tap`` field, and configures libvirt with ``managed="no"`` so it uses the pre-created TAP device. This reduces live migration downtime by ensuring the network is fully wired before the VM starts. See `bug 2069718 `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/always-plug-vifs-for-ovs-1d033fc49a9c6c4e.yaml0000664000175000017500000000403715140131732025433 0ustar00zuulzuul--- features: - | In this release the OVS plugin was extended to always plug VIFs even when libvirt could plug the vif. This will enable faster migration leveraging the multiple port bindings work completed in the Rocky release. security: - | In this release an edgecase where libvirt plugged the VIF instead of os-vif was addressed. Previously if ``ovs_hybrid_plug`` was set to ``False`` in the port binding details, os-vif would only ensure the ovs bridge existed and the plugging would be done by libvirt. As a result during live migration, there was a short interval where a guest could receive tagged broadcast, multicast, or flooded traffic to/from another tenant. This vulnerability is described in `bug 1734320`_. By ensuring that os-vif always creates the OVS port as part of vif plugging we enable neutron to isolate the port prior to nova resuming the VM on the destination node. Note that as Nova cannot rely on Neutron to send ``network-vif-plugged`` events on completion of wiring up an interface it cannot wait to receive a notification before proceeding with the migration. As a result this is a partial mitigation and additional changes will be required to fully address this bug. .. _bug 1734320: https://bugs.launchpad.net/neutron/+bug/1734320 - | A new config option was introduced for the OVS VIF plugin. The ``isolate_vif`` option was added as a partial mitigation of `bug 1734320`_. The ``isolate_vif`` option defaults to ``False`` for backwards compatibility with SDN controller based OpenStack deployments. For all deployments using the reference implementation of ML2/OVS with the neutron L2 agents, ``isolate_vif`` should be set to ``True``. This option instructs the OVS plugin to assign the VIF to the Neutron dead VLAN (4095) when attaching the interface to OVS. By setting the VIF's VLAN to this dead VLAN number, we eliminate the small attack vector that exists for other tenants to read packets during the VIF's bring up. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/brctl-removal-a5b0e69b865afa57.yaml0000664000175000017500000000102615140131732023401 0ustar00zuulzuul--- other: - | With this release, packagers of ``os-vif`` no longer need to create a dependency on ``brctl``. ``brctl`` is largely considered obsolete and has been replaced with iproute2 by default in many linux distributions. RHEL 8 will not ship ``brctl`` in its default repos. As part of a larger effort to remove usage of ``brctl`` from OpenStack ``os-vif`` has replaced its usage of ``brctl`` with ``pyroute2``. This does not introduce any new requirements as ``pyroute2`` is already a requirement. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/bug-1892132-812e6d5ce0588ebb.yaml0000664000175000017500000000061515140131732022237 0ustar00zuulzuul--- fixes: - | Linux kernel 5.8 changed the sysfs interface that is used to discover the interfaces used for OVS offloads for certain NIC models. This results in network plugging failure, as described in `bug #1892132`_. This release fixes the plugging issue by properly handling the new sysfs structure. .. _bug #1892132: https://bugs.launchpad.net/os-vif/+bug/1892132 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/contextlib-and-nested-with-statements-2747a9ebb9a5bfd7.yaml0000664000175000017500000000051115140131732030243 0ustar00zuulzuul--- fixes: - The use of contextlib and with nested statements is deprecated. "with nested" statements are not python 3 compatible as with statement now directly support context managers. The use of contextlib and "with nested" statements has been removed from all unittests in favor of the @mock decorator syntax. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/default-qos-policy-for-ovs-26f8806046a59c82.yaml0000664000175000017500000000373315140131732025456 0ustar00zuulzuul--- upgrade: - | A new config option has been added to the OpenvSwitch plugin ``[os_vif_ovs]default_qos_type``. This option controls the Default tc qdisc applied to a kernel interface attached to OpenvSwitch on Linux hosts. As of this release, the default tc qdisc is ``linux-noop`` other supported values are ``linux-htb``, ``linux-hfsc``, ``linux-sfq``, ``linux-codel`` and ``linux-fq_codel``. before this release the default qdisc was undefined. older kernels did not apply /proc/sys/net/core/default_qdisc to tap devices. newer kernels such as the one found in rhel 9 do. This can significantly impact performance. See bug https://bugs.launchpad.net/os-vif/+bug/2017868 for more details. The default ``linux-noop`` should perform well for all use-cases so no explicit action is required on upgrade however it should be noted that the default_qos_type is only set when a port is first created. As such this fix will not take effect until the next time the vm interface is recreated. If you change this value for an existing port it will only take effect after a hard reboot of the VM or a move operation. fixes: - | A significant performance regression was observed on a subset of Linux kernels and sysctl configurations resulting in a reduction of throughput to between 10% of the prior performance for small packets and 50% for large packets. This has now been resolved by setting a default qos_type on ovs interfaces when they are first created. To mimic libvirt's undocumented behavior the ``linux-noop`` type is set on the ovs port when it is first created. This will be overridden by neutron if a qos policy is defined for a port and is simply the initial value to use when first adding a port to OpenvSwitch. The default QoS type applied can be controlled by the ``[os_vif_ovs]default_qos_type`` config operation. See bug https://bugs.launchpad.net/os-vif/+bug/2017868 for more details.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/default-to-native-ovsdb-driver-112fb5adf6e19a30.yaml0000664000175000017500000000130715140131732026546 0ustar00zuulzuul--- deprecations: - | The ``vsctl`` ovsdb driver is now deprecated for removal. The default ovsdb interface has now been updated to ``native``. This will use the ovs python binding instead of invoking the ``ovs-vsctl`` CLI. The ``native`` backend both outperforms the ``vsctl`` backend and require no elevated privileges to configure the ovsdb. This both improves security and reduces plug and unplug time. upgrade: - | os-vif now uses the ``native`` ovsdb driver instead of ``vsctl`` driver. This reduces the number of privileged call that os-vif need to make and generally improves plugging performance. In future release the ``vsctl`` backend will be removed. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/deprecate-linuxbridge-support-d278d4bbdff8e8bc.yaml0000664000175000017500000000044415140131732027041 0ustar00zuulzuul--- deprecations: - | The LinuxBridge mechanism driver has been removed in neutron. os-vif has a policy of only shipping plugins for in-tree neutron ml2 drivers. As a result the linux_bridge os-vif plugin is now deprecated and will be removed in a future openstack release. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/deprecate-windows-support-49f5ca1b1a1f4d66.yaml0000664000175000017500000000012315140131732025752 0ustar00zuulzuul--- features: - | Support for Windows operating systems has been deprecated. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/do-not-force-mac-ageing-c6e8d750130c5740.yaml0000664000175000017500000000121015140131732024655 0ustar00zuulzuul--- fixes: - | As part of a `bug #1715317`_, MAC ageing was disabled for the intermediate bridge created as part of the hybrid plug mechanism. During the removal of ``brctl``, this behavior was inadvertently applied to all linux bridges created by os-vif including those used in the linuxbridge driver. As a result this can lead to packet flooding (see bug #1837252) when instances are migrated. This behavior has been reverted so that the default mac ageing is determined by the kernel and is not set when using the os-vif linux bridge plugin. .. _bug #1715317: https://bugs.launchpad.net/os-vif/+bug/1837252././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/drop-py36-support-0e9b07073f6ad73f.yaml0000664000175000017500000000055515140131732024030 0ustar00zuulzuul--- other: - | The yoga release of OpenStack was the final release to support python 3.6 as a result os-vif has now dropped support for python 3.6. The final release of os-vif to support python 3.6 was 2.8.0. os-vif now requires python 3.8 and is tested with 3.8, 3.9 and 3.10. newer versions may work but will not be tested in the zed cycle. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/drop-python2-support-7a4bc7d31253c1e5.yaml0000664000175000017500000000011615140131732024611 0ustar00zuulzuul--- upgrade: - | Python 2 is no longer supported. Python 3 is required. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/ensure-ovs-bridge-a0c1b51f469c92d0.yaml0000664000175000017500000000036015140131732024073 0ustar00zuulzuul--- features: - The ovs plugin has been modified to ensure that the specified OVS bridge that the vif will be attached to has been created. If the OVS bridge does not exist, it will be created with the proper datapath_type. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/extend-vhostuser-object-fada14a1457d4e56.yaml0000664000175000017500000000054215140131732025413 0ustar00zuulzuul--- features: - The vhostuser vif object has been modified to add the name of the vhostuser port. Previously to this modification, it was responsibility of ovs plugin to compute such name. This should not be necessary with this new field. Because of this new field the VIFVHostUser object version has been updated accordingly (to 1.1). ././@PaxHeader0000000000000000000000000000021300000000000010211 xustar00117 path=os_vif-4.3.0/releasenotes/notes/fix-broken-dataplane-on-nova-restart-with-isolate_vif-71617a41741b33e8.yaml 22 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/fix-broken-dataplane-on-nova-restart-with-isolate_vif-71617a41741b330000664000175000017500000000035415140131732031430 0ustar00zuulzuul--- fixes: - | An issue when after nova-compute service restart configured with `isolate_vif=True` instances lost connectivity unless neutron rebinds ports. Which is normally happening by agent restart or port update. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/fix-ovs-plugin-describe-049750609559f1ba.yaml0000664000175000017500000000034715140131732025002 0ustar00zuulzuul--- fixes: - The OpenVSwitch plugin was registered with an entrypoint name of "ovs", but its describe method mistakenly reported that its name was "ovs_hybrid". The latter has been fixed to match the registered name. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/fix-stevedore-entrypoints-8002ec7a5166c977.yaml0000664000175000017500000000040115140131732025565 0ustar00zuulzuul--- fixes: - os-vif plugins were previously incorrectly registered in both the setup.py and setup.cfg. All plugin registration have been removed form the setup.py as they were not used and may have blocked registration of out of tree plugins. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/fix-vif-openvswitch-fa0d19be9dd668e1.yaml0000664000175000017500000000031015140131732024624 0ustar00zuulzuul--- fixes: - The ovs plugin now handles vifs of type VIFOpenVSwitch properly. Before, it would improperly create an extraneous linux bridge and veth pair attached to the target OVS bridge. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/generic-datapath-offloads-41cabb6842b41533.yaml0000664000175000017500000000107615140131732025446 0ustar00zuulzuul--- features: - A new set of attributes to port profiles has been introduced, namely ``Datapath Offload Types``, with ``DatapathOffloadRepresentor`` allowing os-vif to pass the required metadata for representors conforming to the kernel switchdev representor model. deprecations: - The API for ``VIFPortProfileOVSRepresentor`` has been frozen pending deprecation of the class. Users should transition to setting the ``datapath_offload`` of ``VIFPortProfileOpenVSwitch`` to a ``DatapathOffloadRepresentor`` object to pass representor information. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/initial-release-2c71d6bbf55f763b.yaml0000664000175000017500000000076015140131732023701 0ustar00zuulzuul--- prelude: > Initial release of os-vif features: - There is an object model describing the different ways a virtual network interface can be configured on the host. There is a plugin API contract defined to enable configuration of the host OS to match a desired VIF setup There is an object model describing the plugins available on the host. Two built-in plugins provide support for Linux Bridge and OpenVSwitch integration on Linux compute hosts. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/oslo-config-opts-entrypoints-e83f907b686d774a.yaml0000664000175000017500000000026315140131732026306 0ustar00zuulzuul--- features: - | We now create entrypoints for oslo.config options to allow automated documentation and validation of the configurable options for all the plugins. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml0000664000175000017500000000317315140131732023464 0ustar00zuulzuul--- fixes: - | The os-vif OVS plugin now supports using per-port OVS bridges when hybrid plug is not used. This is disabled by default and can be enabled by defining ``[os_vif_ovs]/per_port_bridge=True`` in the compute service nova.conf. This capability should only be enabled if you are deploying with ml2/ovn and experience packet loss during live migrations. This is not supported on windows or when using ironic smartnic ports. This option was introduced to address bug: #1933517. When using OVN as a network backend OVN requires the OVS interface to both have an ofport-id and the neutron port uuid defined in the external_ids field. When the port is plugged if ``[os_vif_ovs]/per_port_bridge`` is not enabled then the OVS port will not be assigned an openflow port id until the tap device is created on the host. On loaded system with many flows and ports it can take a few second for OVN to detect the creation of the tap device and install the correct flows. During that interval packets can be dropped. When ``[os_vif_ovs]/per_port_bridge`` is enabled, os-vif will add the VM tap device to a new bridge that is connected to the integration bridge via a patch port. This enables OVN to install the openflow rules on the integration bridge before the tap is created reducing the possibility for packet loss during a live migration. By default per port bridges are disabled and this feature is considered experimental, however it will likely be enabled by default in the future after we gain experience with how this bridge topology scales in larger deployments. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/port-profile-info-linux-bridge-4800f5a0b7328615.yaml0000664000175000017500000000052215140131732026257 0ustar00zuulzuul--- features: - | In ``vif_plug_linux_bridge``, a new field called ``supported_port_profiles`` is added to ``HostVIFInfo`` objects. This field is a list of ``HostPortProfileInfo`` objects describing the supported port profiles for each specific VIF type. Currently this field is only being used in ``vif_plug_ovs``. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/port-profile-info-ovs-63b46a3eafc11de2.yaml0000664000175000017500000000057715140131732025062 0ustar00zuulzuul--- features: - | In ``vif_plug_ovs``, a new field called ``supported_port_profiles`` is added to ``HostVIFInfo`` objects. This field is a list of ``HostPortProfileInfo`` objects describing the supported port profiles for each specific VIF type. Currently two port profiles are supported: ``VIFPortProfileOpenVSwitch`` and ``VIFPortProfileOVSRepresentor``. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/prevent-lb-reply-arp-6459133bfb056069.yaml0000664000175000017500000000053615140131732024322 0ustar00zuulzuul--- security: - | Prevent Linux Bridge from replying to ARP messages. It should reply only if the target IP address is a local address configured on the incoming interface and it should always use the best local address. See `The ARP flux problem `_ for more information. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/remove-py38-e0701b3363079bbf.yaml0000664000175000017500000000016615140131732022555 0ustar00zuulzuul--- upgrade: - | Support for Python 3.8 has been removed. Now the minimum python version supported is 3.9 . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/remove-windows-23df1c587d505d72.yaml0000664000175000017500000000012215140131732023446 0ustar00zuulzuul--- upgrade: - | This library no longer supports Windows Operating Systems. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/remove_iptools_implementation-2eb866573a680e61.yaml0000664000175000017500000000045215140131732026567 0ustar00zuulzuul--- upgrade: - | Removed IPTools implementation. IPTools driver was implemented to avoid a bug in pyroute2 library, currently solved. This implementation was marked as "deprecated" two releases ago. IP Linux commands now use `Pyroute2`_. .. _Pyroute2: https://docs.pyroute2.org/ ././@PaxHeader0000000000000000000000000000020700000000000010214 xustar00113 path=os_vif-4.3.0/releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.yaml 22 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.y0000664000175000017500000000172515140131732031515 0ustar00zuulzuul--- security: - | In 1.13.0 it was reported that bug #1734320 was partially resolved by change Iaf15fa7a678ec2624f7c12f634269c465fbad930. It has since emerged that that change introduced another bug due to an interaction with libvirt. It was understood that libvirt would not recreate the ovs port if it was present on the ovs bridge when spawning a vm however on inspection of the libvirt code this is not the case. In this release we have reverted the change to os-vif and libvirt will be the only entity to create the ovs port when vif_type is set to ovs and hybrid_plug is set to false in the neutron port binding details. Bug #1734320 is not expected to be present if hybrid_plug=true or vif_type vhost-user is used on linux. On windows if hybrid_plug is false on bug #1734320 is also not expected to be present. A new mitigation to bug #1734320 will be developed for the remaining case of hybrid_plug=false on linux. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/vhost-user-mtu-support-cbc7d02a6665fab1.yaml0000664000175000017500000000055215140131732025331 0ustar00zuulzuul--- features: - In the ocata cycle support was added for setting the MTU of vhost-user port with ovs. - vhost-user MTU support enable jumbo frames to be used with vhost-user interfaces. other: - vhost-user MTU support requires ovs 2.6 or newer. On older versions of ovs, the MTU request will not be made and jumbo frames are not supported. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml0000664000175000017500000000117615140131732024731 0ustar00zuulzuul--- features: - vhost-user reconnect is a new feature of qemu that allows a vhost-user frontend(e.g. qemu) to reconnect to a vhost-user backend (e.g. ovs with dpdk) in the event that backend is restarted while the interface is in use. vhost-user reconnect leverages qemu vhost-user server mode with ovs-dpdk in client mode. This configuration requires ovs 2.6 with dpdk 16.07 and qemu 2.7 or newer to function. When qemu server mode is used with older qemu versions such as 2.5, vhost-user will still function with ovs 2.6 and dpdk 16.07, however, reconnect functionality will not be available. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0425875 os_vif-4.3.0/releasenotes/source/0000775000175000017500000000000015140132020015602 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/2023.1.rst0000664000175000017500000000021015140131732017063 0ustar00zuulzuul=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/2023.2.rst0000664000175000017500000000020215140131732017065 0ustar00zuulzuul=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/2024.1.rst0000664000175000017500000000021015140131732017064 0ustar00zuulzuul=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/2024.2.rst0000664000175000017500000000020215140131732017066 0ustar00zuulzuul=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/2025.1.rst0000664000175000017500000000020215140131732017066 0ustar00zuulzuul=========================== 2025.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2025.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/2025.2.rst0000664000175000017500000000020215140131732017067 0ustar00zuulzuul=========================== 2025.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2025.2 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0435874 os_vif-4.3.0/releasenotes/source/_static/0000775000175000017500000000000015140132020017230 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000015140131732021512 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0435874 os_vif-4.3.0/releasenotes/source/_templates/0000775000175000017500000000000015140132020017737 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000015140131732022221 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/conf.py0000664000175000017500000000346315140131732017120 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # # os-vif Release Notes documentation build configuration file # -- 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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = '2017, OpenStack Foundation' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # 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'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/index.rst0000664000175000017500000000041315140131732017452 0ustar00zuulzuul============= Release Notes ============= .. toctree:: :maxdepth: 1 unreleased 2025.2 2025.1 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata newton ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/newton.rst0000664000175000017500000000023215140131732017654 0ustar00zuulzuul=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/ocata.rst0000664000175000017500000000023015140131732017427 0ustar00zuulzuul=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/pike.rst0000664000175000017500000000017215140131732017275 0ustar00zuulzuul========================= Pike Series Release Notes ========================= .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/queens.rst0000664000175000017500000000020215140131732017637 0ustar00zuulzuul=========================== Queens Series Release Notes =========================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/rocky.rst0000664000175000017500000000022115140131732017467 0ustar00zuulzuul=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/stein.rst0000664000175000017500000000017615140131732017473 0ustar00zuulzuul========================== Stein Series Release Notes ========================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/train.rst0000664000175000017500000000017615140131732017466 0ustar00zuulzuul========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/unreleased.rst0000664000175000017500000000015315140131732020473 0ustar00zuulzuul============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/ussuri.rst0000664000175000017500000000020215140131732017671 0ustar00zuulzuul=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/victoria.rst0000664000175000017500000000022015140131732020157 0ustar00zuulzuul============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/wallaby.rst0000664000175000017500000000021415140131732017775 0ustar00zuulzuul============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/xena.rst0000664000175000017500000000020015140131732017270 0ustar00zuulzuul========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/yoga.rst0000664000175000017500000000020015140131732017274 0ustar00zuulzuul========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/releasenotes/source/zed.rst0000664000175000017500000000017415140131732017131 0ustar00zuulzuul======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/requirements.txt0000664000175000017500000000127415140131732015112 0ustar00zuulzuul# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. pbr>=3.0.0 # Apache-2.0 oslo.concurrency>=3.20.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.privsep>=1.23.0 # Apache-2.0 oslo.serialization>=2.20.0 # Apache-2.0 oslo.utils>=2.0.0 # Apache-2.0 oslo.versionedobjects>=1.28.0 # Apache-2.0 ovsdbapp>=0.12.1 # Apache-2.0 pyroute2>=0.5.2;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) stevedore>=1.20.0 # Apache-2.0 debtcollector>=1.19.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0545874 os_vif-4.3.0/setup.cfg0000664000175000017500000000224015140132020013430 0ustar00zuulzuul[metadata] name = os_vif summary = A library for plugging and unplugging virtual interfaces in OpenStack. description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/os-vif/latest/ 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 :: Only Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: Implementation :: CPython [files] packages = os_vif vif_plug_linux_bridge vif_plug_ovs vif_plug_noop [entry_points] os_vif = linux_bridge = vif_plug_linux_bridge.linux_bridge:LinuxBridgePlugin ovs = vif_plug_ovs.ovs:OvsPlugin noop = vif_plug_noop.noop:NoOpPlugin oslo.config.opts = os_vif = os_vif.opts:list_plugins_opts [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/setup.py0000664000175000017500000000127215140131732013336 0ustar00zuulzuul# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/test-requirements.txt0000664000175000017500000000020515140131732016060 0ustar00zuulzuulcoverage>=4.4.1 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 ovs>=2.9.2 stestr>=3.1.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/tox.ini0000664000175000017500000000460115140131732013136 0ustar00zuulzuul[tox] minversion = 3.18.0 envlist = py3,pep8,docs,releasenotes,cover [testenv] usedevelop = True setenv = passenv = OS_VIF_CHECK_PARAMETER_TIMEOUT deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run --exclude-regex ".tests.functional" {posargs} [testenv:functional] setenv = {[testenv]setenv} commands = stestr run --exclude-regex ".tests.unit" {posargs} [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] deps = {[testenv:docs]deps} allowlist_externals = rm make commands = rm -rf doc/build/pdf sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] deps = {[testenv:docs]deps} commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:venv] commands = {posargs} deps = {[testenv]deps} -r{toxinidir}/doc/requirements.txt [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source os_vif,vif_plug_linux_bridge,vif_plug_ovs,vif_plug_noop --parallel-mode commands = stestr run --exclude-regex ".tests.functional" {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:pep8] description = Run style checks. skip_install = true deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure [flake8] # E123, E125 skipped as they are invalid PEP-8. # Following checks are ignored on purpose. # # H404, H405 skipped on purpose per jay pipes discussion. # W504 line break after binary operator show-source = True ignore = E123,E125,E126,E127,E128,H404,H405,W504 enable-extensions = H106,H203 builtins = _ exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build max-complexity = 30 [hacking] import_exceptions = os_vif.i18n [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed # separately, outside of the requirements files. skipsdist=True usedevelop=False deps = bindep commands = bindep test ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0445874 os_vif-4.3.0/vif_plug_linux_bridge/0000775000175000017500000000000015140132020016157 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/__init__.py0000664000175000017500000000000015140131732020267 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/constants.py0000664000175000017500000000113215140131732020553 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. PLUGIN_NAME = 'linux_bridge' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/iptables.py0000664000175000017500000005024115140131732020347 0ustar00zuulzuul# Derived from nova/network/linux_net.py # # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # 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(jaypipes): Replace this entire module with use of the python-iptables # library: https://github.com/ldx/python-iptables import inspect import os import re from oslo_concurrency import lockutils from oslo_concurrency import processutils from vif_plug_linux_bridge import privsep # NOTE(vish): Iptables supports chain names of up to 28 characters, and we # add up to 12 characters to binary_name which is used as a prefix, # so we limit it to 16 characters. # (max_chain_name_length - len('-POSTROUTING') == 16) def get_binary_name(): """Grab the name of the binary we're running in.""" return os.path.basename(inspect.stack()[-1][1])[:16] binary_name = get_binary_name() @privsep.vif_plug.entrypoint def iptables_save(): return processutils.execute('iptables-save', '-c', attempts=5) @privsep.vif_plug.entrypoint def ip6tables_save(): return processutils.execute('ip6tables-save', '-c', attempts=5) @privsep.vif_plug.entrypoint def iptables_restore(input): return processutils.execute('iptables-restore', '-c', attempts=5, process_input=input) @privsep.vif_plug.entrypoint def ip6tables_restore(input): return processutils.execute('ip6tables-restore', '-c', attempts=5, process_input=input) class IptablesRule(object): """An iptables rule. You shouldn't need to use this class directly, it's only used by IptablesManager. """ def __init__(self, chain, rule, wrap=True, top=False): self.chain = chain self.rule = rule self.wrap = wrap self.top = top def __eq__(self, other): return ((self.chain == other.chain) and (self.rule == other.rule) and (self.top == other.top) and (self.wrap == other.wrap)) def __ne__(self, other): return not self == other def __repr__(self): if self.wrap: chain = '%s-%s' % (binary_name, self.chain) else: chain = self.chain # new rules should have a zero [packet: byte] count return '[0:0] -A %s %s' % (chain, self.rule) class IptablesTable(object): """An iptables table.""" def __init__(self): self.rules = [] self.remove_rules = [] self.chains = set() self.unwrapped_chains = set() self.remove_chains = set() self.dirty = True def has_chain(self, name, wrap=True): if wrap: return name in self.chains else: return name in self.unwrapped_chains def add_chain(self, name, wrap=True): """Adds a named chain to the table. The chain name is wrapped to be unique for the component creating it, so different components of Nova can safely create identically named chains without interfering with one another. At the moment, its wrapped name is -, so if nova-compute creates a chain named 'OUTPUT', it'll actually end up named 'nova-compute-OUTPUT'. """ if wrap: self.chains.add(name) else: self.unwrapped_chains.add(name) self.dirty = True def remove_chain(self, name, wrap=True): """Remove named chain. This removal "cascades". All rule in the chain are removed, as are all rules in other chains that jump to it. If the chain is not found, this is merely logged. """ if wrap: chain_set = self.chains else: chain_set = self.unwrapped_chains if name not in chain_set: return self.dirty = True # non-wrapped chains and rules need to be dealt with specially, # so we keep a list of them to be iterated over in apply() if not wrap: self.remove_chains.add(name) chain_set.remove(name) if not wrap: self.remove_rules += [r for r in self.rules if r.chain == name] self.rules = [r for r in self.rules if r.chain != name] if wrap: jump_snippet = '-j %s-%s' % (binary_name, name) else: jump_snippet = '-j %s' % (name,) if not wrap: self.remove_rules += [r for r in self.rules if jump_snippet in r.rule] self.rules = [r for r in self.rules if jump_snippet not in r.rule] def add_rule(self, chain, rule, wrap=True, top=False): """Add a rule to the table. This is just like what you'd feed to iptables, just without the '-A ' bit at the start. However, if you need to jump to one of your wrapped chains, prepend its name with a '$' which will ensure the wrapping is applied correctly. """ if wrap and chain not in self.chains: raise ValueError(_('Unknown chain: %r') % chain) if '$' in rule: rule = ' '.join(map(self._wrap_target_chain, rule.split(' '))) rule_obj = IptablesRule(chain, rule, wrap, top) if rule_obj not in self.rules: self.rules.append(IptablesRule(chain, rule, wrap, top)) self.dirty = True def _wrap_target_chain(self, s): if s.startswith('$'): return '%s-%s' % (binary_name, s[1:]) return s def remove_rule(self, chain, rule, wrap=True, top=False): """Remove a rule from a chain. Note: The rule must be exactly identical to the one that was added. You cannot switch arguments around like you can with the iptables CLI tool. """ try: self.rules.remove(IptablesRule(chain, rule, wrap, top)) if not wrap: self.remove_rules.append(IptablesRule(chain, rule, wrap, top)) self.dirty = True except ValueError: pass def remove_rules_regex(self, regex): """Remove all rules matching regex.""" if isinstance(regex, str): regex = re.compile(regex) num_rules = len(self.rules) self.rules = [r for r in self.rules if not regex.match(str(r))] removed = num_rules - len(self.rules) if removed > 0: self.dirty = True return removed def empty_chain(self, chain, wrap=True): """Remove all rules from a chain.""" chained_rules = [rule for rule in self.rules if rule.chain == chain and rule.wrap == wrap] if chained_rules: self.dirty = True for rule in chained_rules: self.rules.remove(rule) class IptablesManager(object): """Wrapper for iptables. See IptablesTable for some usage docs A number of chains are set up to begin with. First, nova-filter-top. It's added at the top of FORWARD and OUTPUT. Its name is not wrapped, so it's shared between the various nova workers. It's intended for rules that need to live at the top of the FORWARD and OUTPUT chains. It's in both the ipv4 and ipv6 set of tables. For ipv4 and ipv6, the built-in INPUT, OUTPUT, and FORWARD filter chains are wrapped, meaning that the "real" INPUT chain has a rule that jumps to the wrapped INPUT chain, etc. Additionally, there's a wrapped chain named "local" which is jumped to from nova-filter-top. For ipv4, the built-in PREROUTING, OUTPUT, and POSTROUTING nat chains are wrapped in the same was as the built-in filter chains. Additionally, there's a snat chain that is applied after the POSTROUTING chain. """ def __init__(self, use_ipv6=False, iptables_top_regex=None, iptables_bottom_regex=None, iptables_drop_action='DROP', forward_bridge_interface=None): self.use_ipv6 = use_ipv6 self.iptables_top_regex = iptables_top_regex self.iptables_bottom_regex = iptables_bottom_regex self.iptables_drop_action = iptables_drop_action self.forward_bridge_interface = forward_bridge_interface or ['all'] self.ipv4 = {'filter': IptablesTable(), 'nat': IptablesTable(), 'mangle': IptablesTable()} self.ipv6 = {'filter': IptablesTable()} self.iptables_apply_deferred = False # Add a nova-filter-top chain. It's intended to be shared # among the various nova components. It sits at the very top # of FORWARD and OUTPUT. for tables in [self.ipv4, self.ipv6]: tables['filter'].add_chain('nova-filter-top', wrap=False) tables['filter'].add_rule('FORWARD', '-j nova-filter-top', wrap=False, top=True) tables['filter'].add_rule('OUTPUT', '-j nova-filter-top', wrap=False, top=True) tables['filter'].add_chain('local') tables['filter'].add_rule('nova-filter-top', '-j $local', wrap=False) # Wrap the built-in chains builtin_chains = {4: {'filter': ['INPUT', 'OUTPUT', 'FORWARD'], 'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING'], 'mangle': ['POSTROUTING']}, 6: {'filter': ['INPUT', 'OUTPUT', 'FORWARD']}} for ip_version in builtin_chains: if ip_version == 4: tables = self.ipv4 elif ip_version == 6: tables = self.ipv6 for table, chains in builtin_chains[ip_version].items(): for chain in chains: tables[table].add_chain(chain) tables[table].add_rule(chain, '-j $%s' % (chain,), wrap=False) # Add a nova-postrouting-bottom chain. It's intended to be shared # among the various nova components. We set it as the last chain # of POSTROUTING chain. self.ipv4['nat'].add_chain('nova-postrouting-bottom', wrap=False) self.ipv4['nat'].add_rule('POSTROUTING', '-j nova-postrouting-bottom', wrap=False) # We add a snat chain to the shared nova-postrouting-bottom chain # so that it's applied last. self.ipv4['nat'].add_chain('snat') self.ipv4['nat'].add_rule('nova-postrouting-bottom', '-j $snat', wrap=False) # And then we add a float-snat chain and jump to first thing in # the snat chain. self.ipv4['nat'].add_chain('float-snat') self.ipv4['nat'].add_rule('snat', '-j $float-snat') def defer_apply_on(self): self.iptables_apply_deferred = True def defer_apply_off(self): self.iptables_apply_deferred = False self.apply() def dirty(self): for table in self.ipv4.values(): if table.dirty: return True if self.use_ipv6: for table in self.ipv6.values(): if table.dirty: return True return False def apply(self): if self.iptables_apply_deferred: return if self.dirty(): self._apply() @lockutils.synchronized('nova-iptables', external=True) def _apply(self): """Apply the current in-memory set of iptables rules. This will blow away any rules left over from previous runs of the same component of Nova, and replace them with our current set of rules. This happens atomically, thanks to iptables-restore. """ s = [(iptables_save, iptables_restore, self.ipv4)] if self.use_ipv6: s += [(ip6tables_save, ip6tables_restore, self.ipv6)] for save, restore, tables in s: all_tables, _err = save() all_lines = all_tables.split('\n') for table_name, table in tables.items(): start, end = self._find_table(all_lines, table_name) all_lines[start:end] = self._modify_rules( all_lines[start:end], table, table_name) table.dirty = False restore('\n'.join(all_lines)) def _find_table(self, lines, table_name): if len(lines) < 3: # length only <2 when fake iptables return (0, 0) try: start = lines.index('*%s' % table_name) - 1 except ValueError: # Couldn't find table_name return (0, 0) end = lines[start:].index('COMMIT') + start + 2 return (start, end) def _modify_rules(self, current_lines, table, table_name): unwrapped_chains = table.unwrapped_chains chains = sorted(table.chains) remove_chains = table.remove_chains rules = table.rules remove_rules = table.remove_rules if not current_lines: fake_table = ['#Generated by nova', '*' + table_name, 'COMMIT', '#Completed by nova'] current_lines = fake_table # Remove any trace of our rules new_filter = [line for line in current_lines if binary_name not in line] top_rules = [] bottom_rules = [] if self.iptables_top_regex: regex = re.compile(self.iptables_top_regex) temp_filter = [line for line in new_filter if regex.search(line)] for rule_str in temp_filter: new_filter = [s for s in new_filter if s.strip() != rule_str.strip()] top_rules = temp_filter if self.iptables_bottom_regex: regex = re.compile(self.iptables_bottom_regex) temp_filter = [line for line in new_filter if regex.search(line)] for rule_str in temp_filter: new_filter = [s for s in new_filter if s.strip() != rule_str.strip()] bottom_rules = temp_filter seen_chains = False rules_index = 0 for rules_index, rule in enumerate(new_filter): if not seen_chains: if rule.startswith(':'): seen_chains = True else: if not rule.startswith(':'): break if not seen_chains: rules_index = 2 our_rules = top_rules bot_rules = [] for rule in rules: rule_str = str(rule) if rule.top: # rule.top == True means we want this rule to be at the top. # Further down, we weed out duplicates from the bottom of the # list, so here we remove the dupes ahead of time. # We don't want to remove an entry if it has non-zero # [packet:byte] counts and replace it with [0:0], so let's # go look for a duplicate, and over-ride our table rule if # found. # ignore [packet:byte] counts at beginning of line if rule_str.startswith('['): rule_str = rule_str.split(']', 1)[1] dup_filter = [s for s in new_filter if rule_str.strip() in s.strip()] new_filter = [s for s in new_filter if rule_str.strip() not in s.strip()] # if no duplicates, use original rule if dup_filter: # grab the last entry, if there is one dup = list(dup_filter)[-1] rule_str = str(dup) else: rule_str = str(rule) rule_str.strip() our_rules += [rule_str] else: bot_rules += [rule_str] our_rules += bot_rules new_filter = list(new_filter) new_filter[rules_index:rules_index] = our_rules new_filter[rules_index:rules_index] = [':%s - [0:0]' % (name,) for name in unwrapped_chains] new_filter[rules_index:rules_index] = [':%s-%s - [0:0]' % (binary_name, name,) for name in chains] commit_index = new_filter.index('COMMIT') new_filter[commit_index:commit_index] = bottom_rules seen_lines = set() def _weed_out_duplicates(line): # ignore [packet:byte] counts at beginning of lines if line.startswith('['): line = line.split(']', 1)[1] line = line.strip() if line in seen_lines: return False else: seen_lines.add(line) return True def _weed_out_removes(line): # We need to find exact matches here if line.startswith(':'): # it's a chain, for example, ":nova-billing - [0:0]" # strip off everything except the chain name line = line.split(':')[1] line = line.split('- [')[0] line = line.strip() for chain in remove_chains: if chain == line: remove_chains.remove(chain) return False elif line.startswith('['): # it's a rule # ignore [packet:byte] counts at beginning of lines line = line.split(']', 1)[1] line = line.strip() for rule in remove_rules: # ignore [packet:byte] counts at beginning of rules rule_str = str(rule) rule_str = rule_str.split(' ', 1)[1] rule_str = rule_str.strip() if rule_str == line: remove_rules.remove(rule) return False # Leave it alone return True # We filter duplicates, letting the *last* occurrence take # precedence. We also filter out anything in the "remove" # lists. new_filter = list(new_filter) new_filter.reverse() new_filter = filter(_weed_out_duplicates, new_filter) new_filter = filter(_weed_out_removes, new_filter) new_filter = list(new_filter) new_filter.reverse() # flush lists, just in case we didn't find something remove_chains.clear() for rule in remove_rules: remove_rules.remove(rule) return new_filter def get_gateway_rules(self, bridge): interfaces = self.forward_bridge_interface if 'all' in interfaces: return [('FORWARD', '-i %s -j ACCEPT' % bridge), ('FORWARD', '-o %s -j ACCEPT' % bridge)] rules = [] for iface in self.forward_bridge_interface: if iface: rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (bridge, iface))) rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (iface, bridge))) rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (bridge, bridge))) rules.append(('FORWARD', '-i %s -j %s' % (bridge, self.iptables_drop_action))) rules.append(('FORWARD', '-o %s -j %s' % (bridge, self.iptables_drop_action))) return rules ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/linux_bridge.py0000664000175000017500000001234115140131732021216 0ustar00zuulzuul# Derived from nova/virt/libvirt/vif.py # # Copyright (C) 2011 Midokura KK # Copyright (C) 2011 Nicira, Inc # Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import debtcollector.removals from os_vif import objects from os_vif import plugin from oslo_config import cfg from vif_plug_linux_bridge import constants from vif_plug_linux_bridge import iptables from vif_plug_linux_bridge import linux_net class LinuxBridgePlugin(plugin.PluginBase): """A VIF type that uses a standard Linux bridge device.""" CONFIG_OPTS = ( cfg.BoolOpt('use_ipv6', default=False, help='Use IPv6', deprecated_group="DEFAULT"), cfg.StrOpt('iptables_top_regex', default='', help='Regular expression to match the iptables rule that ' 'should always be on the top.', deprecated_group="DEFAULT"), cfg.StrOpt('iptables_bottom_regex', default='', help='Regular expression to match the iptables rule that ' 'should always be on the bottom.', deprecated_group="DEFAULT"), cfg.StrOpt('iptables_drop_action', default='DROP', help='The table that iptables to jump to when a packet is ' 'to be dropped.', deprecated_group="DEFAULT"), cfg.MultiStrOpt('forward_bridge_interface', default=['all'], help='An interface that bridges can forward to. If ' 'this is set to all then all traffic will be ' 'forwarded. Can be specified multiple times.', deprecated_group="DEFAULT"), cfg.StrOpt('vlan_interface', help='VLANs will bridge into this interface if set', deprecated_group="DEFAULT"), cfg.StrOpt('flat_interface', help='FlatDhcp will bridge into this interface if set', deprecated_group="DEFAULT"), cfg.IntOpt('network_device_mtu', default=1500, help='MTU setting for network interface.', deprecated_group="DEFAULT"), ) def __init__(self, config): super(LinuxBridgePlugin, self).__init__(config) ipm = iptables.IptablesManager( use_ipv6=config.use_ipv6, iptables_top_regex=config.iptables_top_regex, iptables_bottom_regex=config.iptables_bottom_regex, iptables_drop_action=config.iptables_drop_action, forward_bridge_interface=config.forward_bridge_interface) linux_net.configure(ipm) def describe(self): return objects.host_info.HostPluginInfo( plugin_name=constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFBridge.__name__, min_version="1.0", max_version="1.0", # NOTE(ralonsoh): currently 'supported_port_profiles' is # only being used with OVS HostVIFInfo objects. supported_port_profiles=[]), ]) @debtcollector.removals.remove( message="LinuxBridgePlugin is deprecated and will be removed in " "a future release", category=DeprecationWarning) def plug(self, vif, instance_info): """Ensure that the bridge exists, and add VIF to it.""" network = vif.network bridge_name = vif.bridge_name if not network.multi_host and network.should_provide_bridge: mtu = network.mtu or self.config.network_device_mtu if network.should_provide_vlan: iface = self.config.vlan_interface or network.bridge_interface linux_net.ensure_vlan_bridge(network.vlan, bridge_name, iface, mtu=mtu) else: iface = self.config.flat_interface or network.bridge_interface # only put in iptables rules if Neutron not filtering install_filters = not vif.has_traffic_filtering linux_net.ensure_bridge(bridge_name, iface, filtering=install_filters, mtu=mtu) @debtcollector.removals.remove( message="LinuxBridgePlugin is deprecated and will be removed in " "a future release", category=DeprecationWarning) def unplug(self, vif, instance_info): # Nothing required to unplug a port for a VIF using standard # Linux bridge device... pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/linux_net.py0000664000175000017500000002241115140131732020547 0ustar00zuulzuul# Derived from nova/network/linux_net.py # # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # 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. """Implements vlans, bridges, and iptables rules using linux utilities.""" import os from os_vif.internal.ip.api import ip as ip_lib from oslo_concurrency import lockutils from oslo_concurrency import processutils from oslo_log import log as logging from vif_plug_linux_bridge import privsep LOG = logging.getLogger(__name__) _IPTABLES_MANAGER = None def _set_device_mtu(dev, mtu): """Set the device MTU.""" if mtu: ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254]) else: LOG.debug("MTU not set on %(interface_name)s interface", {'interface_name': dev}) def _ip_bridge_cmd(action, params, device): """Build commands to add/del ips to bridges/devices.""" cmd = ['ip', 'addr', action] cmd.extend(params) cmd.extend(['dev', device]) return cmd @privsep.vif_plug.entrypoint def ensure_vlan_bridge(vlan_num, bridge, bridge_interface, net_attrs=None, mac_address=None, mtu=None): """Create a vlan and bridge unless they already exist.""" interface = _ensure_vlan_privileged(vlan_num, bridge_interface, mac_address, mtu=mtu) _ensure_bridge_privileged(bridge, interface, net_attrs) _ensure_bridge_filtering(bridge, None) return interface @lockutils.synchronized('nova-lock_vlan', external=True) def _ensure_vlan_privileged(vlan_num, bridge_interface, mac_address, mtu): """Create a vlan unless it already exists. This assumes the caller is already annotated to run with elevated privileges. """ interface = 'vlan%s' % vlan_num if not ip_lib.exists(interface): LOG.debug('Starting VLAN interface %s', interface) ip_lib.add(interface, 'vlan', link=bridge_interface, vlan_id=vlan_num, check_exit_code=[0, 2, 254]) # (danwent) the bridge will inherit this address, so we want to # make sure it is the value set from the NetworkManager if mac_address: ip_lib.set(interface, address=mac_address, check_exit_code=[0, 2, 254]) ip_lib.set(interface, state='up', check_exit_code=[0, 2, 254]) # NOTE(vish): set mtu every time to ensure that changes to mtu get # propagated _set_device_mtu(interface, mtu) return interface @lockutils.synchronized('nova-lock_bridge', external=True) def ensure_bridge(bridge, interface, net_attrs=None, gateway=True, filtering=True, mtu=None): _ensure_bridge_privileged(bridge, interface, net_attrs, gateway, filtering=filtering, mtu=mtu) if filtering: _ensure_bridge_filtering(bridge, gateway) # TODO(sean-k-mooney): extract into common module def _disable_ipv6(bridge): """disable ipv6 for bridge if available, must be called from privsep context. :param bridge: string bridge name """ # NOTE(sean-k-mooney): os-vif disables ipv6 to ensure the Bridge # does not aquire an ipv6 auto config or link local adress. # This is required to prevent bug 1302080. # https://bugs.launchpad.net/neutron/+bug/1302080 disv6 = ('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % bridge) if os.path.exists(disv6): with open(disv6, 'w') as f: f.write('1') # TODO(ralonsoh): extract into common module def _arp_filtering(bridge): """Prevent the bridge from replying to ARP messages with machine local IPs 1. Reply only if the target IP address is local address configured on the incoming interface. 2. Always use the best local address. """ arp_params = [('/proc/sys/net/ipv4/conf/%s/arp_ignore' % bridge, '1'), ('/proc/sys/net/ipv4/conf/%s/arp_announce' % bridge, '2')] for parameter, value in arp_params: if os.path.exists(parameter): with open(parameter, 'w') as f: f.write(value) def _update_bridge_routes(interface, bridge): """Updates routing table for a given bridge and interface. :param interface: string interface name :param bridge: string bridge name """ # TODO(sean-k-mooney): investigate deleting all this route # handling code. The vm tap devices should never have an ip, # this is old nova networks code and i dont think it will ever # be needed in os-vif. # NOTE(vish): This will break if there is already an ip on the # interface, so we move any ips to the bridge # NOTE(danms): We also need to copy routes to the bridge so as # not to break existing connectivity on the interface old_routes = [] out, _ = processutils.execute('ip', 'route', 'show', 'dev', interface) for line in out.split('\n'): fields = line.split() if fields and 'via' in fields: old_routes.append(fields) processutils.execute('ip', 'route', 'del', *fields) out, _ = processutils.execute('ip', 'addr', 'show', 'dev', interface, 'scope', 'global') for line in out.split('\n'): fields = line.split() if fields and fields[0] == 'inet': if fields[-2] in ('secondary', 'dynamic', ): params = fields[1:-2] else: params = fields[1:-1] processutils.execute(*_ip_bridge_cmd('del', params, fields[-1]), check_exit_code=[0, 2, 254]) processutils.execute(*_ip_bridge_cmd('add', params, bridge), check_exit_code=[0, 2, 254]) for fields in old_routes: processutils.execute('ip', 'route', 'add', *fields) @privsep.vif_plug.entrypoint def _ensure_bridge_privileged(bridge, interface, net_attrs, gateway, filtering=True, mtu=None): """Create a bridge unless it already exists. :param interface: the interface to create the bridge on. :param net_attrs: dictionary with attributes used to create bridge. :param gateway: whether or not the bridge is a gateway. :param filtering: whether or not to create filters on the bridge. :param mtu: MTU of bridge. If net_attrs is set, it will add the net_attrs['gateway'] to the bridge using net_attrs['broadcast'] and net_attrs['cidr']. It will also add the ip_v6 address specified in net_attrs['cidr_v6'] if use_ipv6 is set. The code will attempt to move any ips that already exist on the interface onto the bridge and reset the default gateway if necessary. """ if not ip_lib.exists(bridge): LOG.debug('Starting Bridge %s', bridge) ip_lib.add(bridge, 'bridge') _disable_ipv6(bridge) _arp_filtering(bridge) ip_lib.set(bridge, state='up') if interface and ip_lib.exists(interface): LOG.debug('Adding interface %(interface)s to bridge %(bridge)s', {'interface': interface, 'bridge': bridge}) ip_lib.set(interface, master=bridge, state='up', check_exit_code=[0, 2, 254]) _set_device_mtu(interface, mtu) _update_bridge_routes(interface, bridge) # NOTE(sean-k-mooney): # The bridge mtu cannot be set until after an # interface is added due to bug: # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1399064 _set_device_mtu(bridge, mtu) def _ensure_bridge_filtering(bridge, gateway): # This method leaves privsep usage to iptables manager # Don't forward traffic unless we were told to be a gateway LOG.debug("Ensuring filtering %s to %s", bridge, gateway) global _IPTABLES_MANAGER ipv4_filter = _IPTABLES_MANAGER.ipv4['filter'] if gateway: for rule in _IPTABLES_MANAGER.get_gateway_rules(bridge): ipv4_filter.add_rule(*rule) else: ipv4_filter.add_rule('FORWARD', ('--in-interface %s -j %s' % (bridge, _IPTABLES_MANAGER.iptables_drop_action))) ipv4_filter.add_rule('FORWARD', ('--out-interface %s -j %s' % (bridge, _IPTABLES_MANAGER.iptables_drop_action))) _IPTABLES_MANAGER.apply() def configure(iptables_mgr): """Configure the iptables manager impl. :param iptables_mgr: the iptables manager instance """ global _IPTABLES_MANAGER _IPTABLES_MANAGER = iptables_mgr ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/privsep.py0000664000175000017500000000156415140131732020240 0ustar00zuulzuul# # Copyright (C) 2016 Red Hat, 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_privsep import capabilities as c from oslo_privsep import priv_context vif_plug = priv_context.PrivContext( "vif_plug_linux_bridge", cfg_section="vif_plug_linux_bridge_privileged", pypath=__name__ + ".vif_plug", capabilities=[c.CAP_NET_ADMIN], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0445874 os_vif-4.3.0/vif_plug_linux_bridge/tests/0000775000175000017500000000000015140132020017321 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/tests/__init__.py0000664000175000017500000000000015140131732021431 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0455873 os_vif-4.3.0/vif_plug_linux_bridge/tests/unit/0000775000175000017500000000000015140132020020300 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/tests/unit/__init__.py0000664000175000017500000000000015140131732022410 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/tests/unit/test_linux_net.py0000664000175000017500000001512415140131732023732 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import fixtures import testtools from oslo_concurrency import lockutils from oslo_concurrency import processutils from oslo_config import cfg from oslo_config import fixture as config_fixture from oslo_log.fixture import logging_error as log_fixture from os_vif.internal.ip.api import ip as ip_lib from vif_plug_linux_bridge import linux_net from vif_plug_linux_bridge import privsep CONF = cfg.CONF class LinuxNetTest(testtools.TestCase): def setUp(self): super(LinuxNetTest, self).setUp() privsep.vif_plug.set_client_mode(False) lock_path = self.useFixture(fixtures.TempDir()).path self.fixture = self.useFixture( config_fixture.Config(lockutils.CONF)) self.fixture.config(lock_path=lock_path, group='oslo_concurrency') self.useFixture(log_fixture.get_logging_handle_error_fixture()) @mock.patch.object(ip_lib, "set") def test_set_device_mtu(self, mock_ip_set): linux_net._set_device_mtu(dev='fakedev', mtu=1500) mock_ip_set.assert_called_once_with('fakedev', mtu=1500, check_exit_code=[0, 2, 254]) @mock.patch.object(processutils, "execute") def test_set_device_invalid_mtu(self, mock_exec): linux_net._set_device_mtu(dev='fakedev', mtu=None) mock_exec.assert_not_called() @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "set") @mock.patch.object(ip_lib, "exists", return_value=False) @mock.patch.object(linux_net, "_set_device_mtu") def test_ensure_vlan(self, mock_set_mtu, mock_dev_exists, mock_ip_set, mock_ip_add): linux_net._ensure_vlan_privileged(123, 'fake-bridge', mac_address='fake-mac', mtu=1500) self.assertTrue(mock_dev_exists.called) set_calls = [mock.call('vlan123', address='fake-mac', check_exit_code=[0, 2, 254]), mock.call('vlan123', state='up', check_exit_code=[0, 2, 254])] mock_ip_add.assert_called_once_with( 'vlan123', 'vlan', link='fake-bridge', vlan_id=123, check_exit_code=[0, 2, 254]) mock_ip_set.assert_has_calls(set_calls) mock_set_mtu.assert_called_once_with('vlan123', 1500) @mock.patch.object(linux_net, "_ensure_bridge_privileged") @mock.patch.object(linux_net, "_ensure_bridge_filtering") def test_ensure_bridge(self, mock_filtering, mock_priv): linux_net.ensure_bridge("br0", None, filtering=False) mock_priv.assert_called_once_with("br0", None, None, True, filtering=False, mtu=None) mock_filtering.assert_not_called() linux_net.ensure_bridge("br0", None, filtering=True) mock_filtering.assert_called_once_with("br0", True) @mock.patch.object(ip_lib, "exists", return_value=False) @mock.patch.object(ip_lib, "add") def test_ensure_bridge_addbr_exception(self, mock_add, mock_dev_exists): mock_add.side_effect = ValueError() with testtools.ExpectedException(ValueError): linux_net.ensure_bridge("br0", None, filtering=False) @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "set") @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(linux_net, "_update_bridge_routes") @mock.patch.object(ip_lib, "exists") def test_ensure_bridge_priv_mtu_not_called(self, mock_dev_exists, mock_routes, mock_disable_ipv6, mock_set_mtu, mock_arp_filtering, mock_ip_set, mock_add): """This test validates that mtus are updated only if an interface is added to the bridge """ mock_dev_exists.return_value = False linux_net._ensure_bridge_privileged("fake-bridge", None, None, False, mtu=1500) mock_set_mtu.assert_not_called() mock_ip_set.assert_called_once_with('fake-bridge', state='up') @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "set") @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(linux_net, "_update_bridge_routes") @mock.patch.object(ip_lib, "exists") def test_ensure_bridge_priv_mtu_order(self, mock_dev_exists, mock_routes, mock_disable_ipv6, mock_set_mtu, mock_arp_filtering, mock_ip_set, mock_add): """This test validates that when adding an interface to a bridge, the interface mtu is updated first followed by the bridge. This is required to work around https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1399064 """ mock_dev_exists.side_effect = [False, True] linux_net._ensure_bridge_privileged("fake-bridge", "fake-interface", None, False, mtu=1500) calls = [mock.call('fake-interface', 1500), mock.call('fake-bridge', 1500)] mock_set_mtu.assert_has_calls(calls) calls = [mock.call('fake-bridge', state='up'), mock.call('fake-interface', master='fake-bridge', state='up', check_exit_code=[0, 2, 254])] mock_ip_set.assert_has_calls(calls) @mock.patch('builtins.open') @mock.patch("os.path.exists") def test__disable_ipv6(self, mock_exists, mock_open): exists_path = "/proc/sys/net/ipv6/conf/br0/disable_ipv6" mock_exists.return_value = False linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_not_called() mock_exists.reset_mock() mock_exists.return_value = True linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_called_once_with(exists_path, 'w') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_linux_bridge/tests/unit/test_plugin.py0000664000175000017500000001130315140131732023216 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import testtools from os_vif import objects from vif_plug_linux_bridge import constants from vif_plug_linux_bridge import linux_bridge from vif_plug_linux_bridge import linux_net class PluginTest(testtools.TestCase): def __init__(self, *args, **kwargs): super(PluginTest, self).__init__(*args, **kwargs) objects.register_all() self.instance = objects.instance_info.InstanceInfo( name='demo', uuid='f0000000-0000-0000-0000-000000000001') @mock.patch.object(linux_net, 'ensure_vlan_bridge') @mock.patch.object(linux_net, 'ensure_bridge') def test_plug_bridge(self, mock_ensure_bridge, mock_ensure_vlan_bridge): network = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br0') vif = objects.vif.VIFBridge( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=network, dev_name='tap-xxx-yyy-zzz', bridge_name="br0") plugin = linux_bridge.LinuxBridgePlugin.load(constants.PLUGIN_NAME) plugin.plug(vif, self.instance) mock_ensure_bridge.assert_not_called() mock_ensure_vlan_bridge.assert_not_called() def test_plug_bridge_create_br_mtu_in_model(self): self._test_plug_bridge_create_br(mtu=1234) def test_plug_bridge_create_br_mtu_from_config(self): self._test_plug_bridge_create_br() @mock.patch.object(linux_net, 'ensure_vlan_bridge') @mock.patch.object(linux_net, 'ensure_bridge') def _test_plug_bridge_create_br(self, mock_ensure_bridge, mock_ensure_vlan_bridge, mtu=None): network = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br0', bridge_interface='eth0', should_provide_bridge=True, mtu=mtu) vif = objects.vif.VIFBridge( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=network, dev_name='tap-xxx-yyy-zzz', has_traffic_filtering=True, bridge_name="br0") plugin = linux_bridge.LinuxBridgePlugin.load(constants.PLUGIN_NAME) plugin.plug(vif, self.instance) mock_ensure_bridge.assert_called_with("br0", "eth0", filtering=False, mtu=mtu or 1500) mock_ensure_vlan_bridge.assert_not_called() mock_ensure_bridge.reset_mock() vif.has_traffic_filtering = False plugin.plug(vif, self.instance) mock_ensure_bridge.assert_called_with("br0", "eth0", filtering=True, mtu=mtu or 1500) def test_plug_bridge_create_br_vlan_mtu_in_model(self): self._test_plug_bridge_create_br_vlan(mtu=1234) def test_plug_bridge_create_br_vlan_mtu_from_config(self): self._test_plug_bridge_create_br_vlan() @mock.patch.object(linux_net, 'ensure_vlan_bridge') @mock.patch.object(linux_net, 'ensure_bridge') def _test_plug_bridge_create_br_vlan(self, mock_ensure_bridge, mock_ensure_vlan_bridge, mtu=None): network = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br0', bridge_interface='eth0', vlan=99, should_provide_bridge=True, should_provide_vlan=True, mtu=mtu) vif = objects.vif.VIFBridge( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=network, dev_name='tap-xxx-yyy-zzz', bridge_name="br0") plugin = linux_bridge.LinuxBridgePlugin.load(constants.PLUGIN_NAME) plugin.plug(vif, self.instance) mock_ensure_bridge.assert_not_called() mock_ensure_vlan_bridge.assert_called_with( 99, "br0", "eth0", mtu=mtu or 1500) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0465875 os_vif-4.3.0/vif_plug_noop/0000775000175000017500000000000015140132020014457 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_noop/__init__.py0000664000175000017500000000000015140131732016567 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_noop/noop.py0000664000175000017500000000313515140131732016017 0ustar00zuulzuul# Copyright (C) 2011 Midokura KK # Copyright (C) 2011 Nicira, Inc # Copyright 2011 OpenStack Foundation # Copyright 2018 Intel 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. from os_vif import objects from os_vif import plugin class NoOpPlugin(plugin.PluginBase): """A no op plugin The no op plugin can be used for any vif type that requires no action to be performed on the backend network when a vif is plugged. Currently only the VIFVHostUser VIF type is supported. This pluggin allows for the use of generic vhost user without ovs. """ def describe(self): return objects.host_info.HostPluginInfo( plugin_name="noop", vif_info=[ objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFVHostUser.__name__, min_version="1.0", max_version="1.0", supported_port_profiles=[]) ]) def plug(self, vif, instance_info): pass def unplug(self, vif, instance_info): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0465875 os_vif-4.3.0/vif_plug_noop/tests/0000775000175000017500000000000015140132020015621 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_noop/tests/__init__.py0000664000175000017500000000000015140131732017731 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0465875 os_vif-4.3.0/vif_plug_noop/tests/unit/0000775000175000017500000000000015140132020016600 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_noop/tests/unit/__init__.py0000664000175000017500000000000015140131732020710 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_noop/tests/unit/test_plugin.py0000664000175000017500000000232415140131732021521 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 testtools from os_vif import objects from vif_plug_noop import noop class PluginTest(testtools.TestCase): def __init__(self, *args, **kwargs): super(PluginTest, self).__init__(*args, **kwargs) objects.register_all() self.plugin = noop.NoOpPlugin.load("noop") def test_plug_noop(self): self.assertIn("plug", dir(self.plugin)) self.plugin.plug(None, None) def test_unplug_noop(self): self.assertIn("unplug", dir(self.plugin)) self.plugin.unplug(None, None) def test_describe_noop(self): self.assertIn("describe", dir(self.plugin)) self.assertTrue(len(self.plugin.describe().vif_info) > 0) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0485873 os_vif-4.3.0/vif_plug_ovs/0000775000175000017500000000000015140132020014313 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/__init__.py0000664000175000017500000000000015140131732016423 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/constants.py0000664000175000017500000000157415140131732016721 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. PLUGIN_NAME = 'ovs' OVS_VHOSTUSER_INTERFACE_TYPE = 'dpdkvhostuser' OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE = 'dpdkvhostuserclient' OVS_VHOSTUSER_PREFIX = 'vhu' OVS_DATAPATH_SYSTEM = 'system' OVS_DATAPATH_NETDEV = 'netdev' OVS_DPDK_INTERFACE_TYPE = 'dpdk' # Neutron dead VLAN. DEAD_VLAN = 4095 TRUNK_BR_PREFIX = 'tbr-' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/exception.py0000664000175000017500000000304115140131732016672 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 os_vif.i18n import _ from os_vif import exception as osv_exception class AgentError(osv_exception.ExceptionBase): msg_fmt = _('Error during following call to agent: %(method)s') class MissingPortProfile(osv_exception.ExceptionBase): msg_fmt = _('A port profile is mandatory for the OpenVSwitch plugin') class WrongPortProfile(osv_exception.ExceptionBase): msg_fmt = _('Port profile %(profile)s is not a subclass ' 'of VIFPortProfileOpenVSwitch') class RepresentorNotFound(osv_exception.ExceptionBase): msg_fmt = _('Failed getting representor port for PF %(ifname)s with ' '%(vf_num)s') class PciDeviceNotFoundById(osv_exception.ExceptionBase): msg_fmt = _("PCI device %(id)s not found") class TapCreationNotSupported(osv_exception.ExceptionBase): msg_fmt = _('Tap device creation requested for unsupported VIF type. ' 'create_tap is only supported for VIFOpenVSwitch, got ' '%(vif_type)s') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/linux_net.py0000664000175000017500000003503115140131732016705 0ustar00zuulzuul# Derived from nova/network/linux_net.py # # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # 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. """Implements vlans, bridges using linux utilities.""" import glob import os import re from os_vif.internal.ip.api import ip as ip_lib from oslo_concurrency import processutils from oslo_log import log as logging from oslo_utils import excutils from vif_plug_ovs import exception from vif_plug_ovs import privsep LOG = logging.getLogger(__name__) VIRTFN_RE = re.compile(r"virtfn(\d+)") # phys_port_name only contains the VF number INT_RE = re.compile(r"^(\d+)$") # phys_port_name contains VF## or vf## VF_RE = re.compile(r"vf(\d+)", re.IGNORECASE) # phys_port_name contains PF## or pf## PF_RE = re.compile(r"pf(\d+)", re.IGNORECASE) # bus_info (bdf) contains :. PF_FUNC_RE = re.compile(r"\.(\d+)", 0) # phys_port_name contains p## UPLINK_PORT_RE = re.compile(r"p(\d+)", re.IGNORECASE) _SRIOV_TOTALVFS = "sriov_totalvfs" NIC_NAME_LEN = 14 def _update_device_mtu(dev, mtu): if not mtu: return set_device_mtu(dev, mtu) @privsep.vif_plug.entrypoint def delete_net_dev(dev): """Delete a network device only if it exists.""" if ip_lib.exists(dev): try: ip_lib.delete(dev, check_exit_code=[0, 2, 254]) LOG.debug("Net device removed: '%s'", dev) except processutils.ProcessExecutionError: with excutils.save_and_reraise_exception(): LOG.error("Failed removing net device: '%s'", dev) @privsep.vif_plug.entrypoint def create_veth_pair(dev1_name, dev2_name, mtu): """Create a pair of veth devices with the specified names, deleting any previous devices with those names. """ for dev in [dev1_name, dev2_name]: delete_net_dev(dev) ip_lib.add(dev1_name, 'veth', peer=dev2_name) for dev in [dev1_name, dev2_name]: ip_lib.set(dev, state='up') ip_lib.set(dev, promisc='on') _update_device_mtu(dev, mtu) @privsep.vif_plug.entrypoint def update_veth_pair(dev1_name, dev2_name, mtu): """Update a pair of veth devices with new configuration.""" for dev in [dev1_name, dev2_name]: _update_device_mtu(dev, mtu) @privsep.vif_plug.entrypoint def create_tap(dev, mtu, mac, multiqueue=False): """Create a tap device with the specified configuration. Creates a tap device using pyroute2's netlink interface, with optional multiqueue support for better performance with multiple VCPUs. :param dev: Device name (string) :param mtu: MTU value (integer) :param mac: MAC address (string) :param multiqueue: Enable multiqueue support (boolean, default False) Requires Linux kernel 3.8+ """ # Create the tap device with optional multiqueue support # Use check_exit_code=[0, 17] to handle EEXIST (device already exists) ip_lib.add(dev, 'tuntap', mode='tap', multiqueue=multiqueue, check_exit_code=[0, 17]) # Configure the device state and MAC address ip_lib.set(dev, state='up', address=mac, check_exit_code=[0, 2, 254]) # Set MTU if specified _update_device_mtu(dev, mtu) def _disable_ipv6(bridge): """Disable ipv6 if available for bridge. Must be called from privsep context. """ # NOTE(sean-k-mooney): os-vif disables ipv6 to ensure the Bridge # does not acquire an ipv6 auto config or link local address. # This is required to prevent bug 1302080. # https://bugs.launchpad.net/neutron/+bug/1302080 disv6 = ('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % bridge) if os.path.exists(disv6): with open(disv6, 'w') as f: f.write('1') # TODO(ralonsoh): extract into common module def _arp_filtering(bridge): """Prevent the bridge from replying to ARP messages with machine local IPs 1. Reply only if the target IP address is local address configured on the incoming interface. 2. Always use the best local address. """ arp_params = [('/proc/sys/net/ipv4/conf/%s/arp_ignore' % bridge, '1'), ('/proc/sys/net/ipv4/conf/%s/arp_announce' % bridge, '2')] for parameter, value in arp_params: if os.path.exists(parameter): with open(parameter, 'w') as f: f.write(value) @privsep.vif_plug.entrypoint def ensure_bridge(bridge): if not ip_lib.exists(bridge): # NOTE(sean-k-mooney): we set mac ageing to 0 to disable mac ageing # on the hybrid plug bridge to avoid packet loss during live # migration. This avoids bug #1715317 and related bug #1414559 ip_lib.add(bridge, 'bridge', ageing=0) _disable_ipv6(bridge) _arp_filtering(bridge) # we bring up the bridge to allow it to switch packets set_interface_state(bridge, 'up') @privsep.vif_plug.entrypoint def delete_bridge(bridge, dev): if ip_lib.exists(bridge): # Note(sean-k-mooney): this will detach all ports on # the bridge before deleting the bridge. ip_lib.delete(bridge, check_exit_code=[0, 2, 254]) # however it will not set the detached interface down # so we set the dev down if dev is not None and exists. if dev and ip_lib.exists(dev): set_interface_state(dev, "down") @privsep.vif_plug.entrypoint def add_bridge_port(bridge, dev): ip_lib.set(dev, master=bridge) @privsep.vif_plug.entrypoint def set_device_mtu(dev, mtu): """Set the device MTU.""" if ip_lib.exists(dev): ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254]) @privsep.vif_plug.entrypoint def set_interface_state(interface_name, port_state): ip_lib.set(interface_name, state=port_state, check_exit_code=[0, 2, 254]) def _parse_vf_number(phys_port_name): """Parses phys_port_name and returns VF number or None. To determine the VF number of a representor, parse phys_port_name in the following sequence and return the first valid match. If none match, then the representor is not for a VF. """ match = INT_RE.search(phys_port_name) if match: return match.group(1) match = VF_RE.search(phys_port_name) if match: return match.group(1) return None def _parse_pf_number(phys_port_name): """Parses phys_port_name and returns PF number or None. To determine the PF number of a representor, parse phys_port_name in the following sequence and return the first valid match. If none match, then the representor is not for a PF. """ match = PF_RE.search(phys_port_name) if match: return match.group(1) return None # This function is taken from nova/pci/utils.py def get_function_by_ifname(ifname): """Given the device name, returns the PCI address of a device and returns True if the address is in a physical function. """ dev_path = "/sys/class/net/%s/device" % ifname sriov_totalvfs = 0 if os.path.isdir(dev_path): try: # sriov_totalvfs contains the maximum possible VFs for this PF dev_path_file = os.path.join(dev_path, _SRIOV_TOTALVFS) with open(dev_path_file, 'r') as fd: sriov_totalvfs = int(fd.readline().rstrip()) return (os.readlink(dev_path).strip("./"), sriov_totalvfs > 0) except (IOError, ValueError): return os.readlink(dev_path).strip("./"), False return None, False def _get_pf_func(pf_ifname): """Gets PF function number using pf_ifname and returns function number or None. """ address_str, pf = get_function_by_ifname(pf_ifname) if not address_str: return None match = PF_FUNC_RE.search(address_str) if match: return match.group(1) return None def get_representor_port(pf_ifname, vf_num): """Get the representor netdevice which is corresponding to the VF. This method gets PF interface name and number of VF. It iterates over all the interfaces under the PF location and looks for interface that has the VF number in the phys_port_name. That interface is the representor for the requested VF. """ pf_sw_id = None try: pf_sw_id = _get_phys_switch_id(pf_ifname) except (OSError, IOError): raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num) pf_subsystem_file = "/sys/class/net/%s/subsystem" % pf_ifname try: devices = os.listdir(pf_subsystem_file) except (OSError, IOError): raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num) ifname_pf_func = _get_pf_func(pf_ifname) if ifname_pf_func is None: raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num) for device in devices: try: device_sw_id = _get_phys_switch_id(device) if not device_sw_id or device_sw_id != pf_sw_id: continue except (OSError, IOError): continue try: phys_port_name = _get_phys_port_name(device) if phys_port_name is None: continue except (OSError, IOError): continue # If the phys_port_name of the VF-rep is of the format pfXvfY # (or vfY@pfX), then match "X" (parent PF's func number) with # the PCI func number of pf_ifname. rep_parent_pf_func = _parse_pf_number(phys_port_name) if rep_parent_pf_func is not None: if int(rep_parent_pf_func) != int(ifname_pf_func): continue representor_num = _parse_vf_number(phys_port_name) # Note: representor_num can be 0, referring to VF0 if representor_num is None: continue # At this point we're confident we have a representor. try: if int(representor_num) == int(vf_num): return device except (ValueError): continue raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num) def _get_sysfs_netdev_path(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 _is_switchdev(netdev): """Returns True if a netdev has a readable phys_switch_id""" try: phys_switch_id = _get_phys_switch_id(netdev) if phys_switch_id != "" and phys_switch_id is not None: return True except (OSError, IOError): return False return False def get_ifname_by_pci_address(pci_addr, pf_interface=False, switchdev=False): """Get the interface name based on a VF's pci address :param pci_addr: the PCI address of the VF :param pf_interface: if True, look for the netdev of the parent PF :param switchdev: if True, ensure that phys_switch_id is valid :returns: netdev interface name The returned interface name is either the parent PF or that of the VF itself based on the argument of pf_interface. """ dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface) try: devices = os.listdir(dev_path) # Return the first netdev in case of switchdev=False if not switchdev: return devices[0] elif pf_interface: fallback_netdev = None for netdev in devices: # Return the uplink representor in case of switchdev=True if _is_switchdev(netdev): fallback_netdev = netdev if fallback_netdev is None \ else fallback_netdev phys_port_name = _get_phys_port_name(netdev) if phys_port_name is not None and \ UPLINK_PORT_RE.search(phys_port_name): return netdev # Fallback to first switchdev netdev in case of switchdev=True if fallback_netdev is not None: return fallback_netdev except Exception: raise exception.PciDeviceNotFoundById(id=pci_addr) raise exception.PciDeviceNotFoundById(id=pci_addr) def get_vf_num_by_pci_address(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 number can be obtained from the PCI device filesystem. """ virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr) vf_num = None 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: raise exception.PciDeviceNotFoundById(id=pci_addr) return vf_num def get_dpdk_representor_port_name(port_id): devname = "vfr" + port_id return devname[:NIC_NAME_LEN] def get_pf_pci_from_vf(vf_pci): """Get physical function PCI address of a VF :param vf_pci: the PCI address of the VF :return: the PCI address of the PF """ physfn_path = os.readlink("/sys/bus/pci/devices/%s/physfn" % vf_pci) return os.path.basename(physfn_path) def _get_phys_port_name(ifname): """Get the interface name and return its phys_port_name :param ifname: The interface name :return: The phys_port_name of the given ifname """ phys_port_name_path = "/sys/class/net/%s/phys_port_name" % ifname if not os.path.isfile(phys_port_name_path): return None with open(phys_port_name_path, 'r') as fd: return fd.readline().strip() def _get_phys_switch_id(ifname): """Get the interface name and return its phys_switch_id :param ifname: The interface name :return: The phys_switch_id of the given ifname """ phys_port_name_path = "/sys/class/net/%s/phys_switch_id" % ifname if not os.path.isfile(phys_port_name_path): return None with open(phys_port_name_path, 'r') as fd: return fd.readline().strip() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/ovs.py0000664000175000017500000006145215140131732015515 0ustar00zuulzuul# Derived from nova/virt/libvirt/vif.py # # Copyright (C) 2011 Midokura KK # Copyright (C) 2011 Nicira, Inc # Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg from oslo_log import log as logging from os_vif import exception as osv_exception from os_vif.internal.ip.api import ip as ip_lib from os_vif import objects from os_vif import plugin from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs import linux_net from vif_plug_ovs.ovsdb import api as ovsdb_api from vif_plug_ovs.ovsdb import ovsdb_lib LOG = logging.getLogger(__name__) def is_trunk_bridge(bridge_name): return bridge_name.startswith(constants.TRUNK_BR_PREFIX) class OvsPlugin(plugin.PluginBase): """An OVS plugin that can setup VIFs in many ways The OVS plugin supports several different VIF types, VIFBridge and VIFOpenVSwitch, and will choose the appropriate plugging action depending on the type of VIF config it receives. If given a VIFBridge, then it will create connect the VM via a regular Linux bridge device to allow security group rules to be applied to VM traffic. """ NIC_NAME_LEN = 14 CONFIG_OPTS = ( cfg.IntOpt('network_device_mtu', default=1500, help='MTU setting for network interface.', deprecated_group="DEFAULT"), cfg.IntOpt('ovs_vsctl_timeout', default=120, help='Amount of time, in seconds, that ovs_vsctl should ' 'wait for a response from the database. 0 is to wait ' 'forever.', deprecated_group="DEFAULT"), cfg.StrOpt('ovsdb_connection', default='tcp:127.0.0.1:6640', help='The connection string for the OVSDB backend. ' 'When executing commands using the native or vsctl ' 'ovsdb interface drivers this config option defines ' 'the ovsdb endpoint used.'), cfg.StrOpt('ovsdb_interface', choices=list(ovsdb_api.interface_map), default='native', deprecated_for_removal=True, deprecated_since='2.2.0', deprecated_reason=""" os-vif has supported ovsdb access via python bindings since Stein (1.15.0), starting in Victoria (2.2.0) the ovs-vsctl driver is now deprecated for removal and in future releases it will be be removed. """, help='The interface for interacting with the OVSDB'), # NOTE(sean-k-mooney): This value is a bool for two reasons. # First I want to allow this config option to be reusable with # non ml2/ovs deployment in the future if required, as such I do not # want to encode how the isolation is done in the config option. # Second in the case of ml2/ovs the isolation is based on VLAN tags. # The 802.1Q IEEE spec that defines the VLAN format reserved two VLAN # id values, VLAN ID 0 means the packet is a member of no VLAN # and VLAN ID 4095 is reserved for implementation defined use. # Using VLAN ID 0 would not provide isolation and all other VLAN IDs # except VLAN ID 4095 are valid for the ml2/ovs agent to use for a # tenant network's local VLAN ID. As such only VLAN ID 4095 is valid # to use for vif isolation which is defined in Neutron as the # dead VLAN, a VLAN on which all traffic will be dropped. cfg.BoolOpt('isolate_vif', default=False, help='Controls if VIF should be isolated when plugged ' 'to the ovs bridge. This should only be set to True ' 'when using the neutron ovs ml2 agent.'), cfg.BoolOpt('per_port_bridge', default=False, help='Controls if VIF should be plugged into a per-port ' 'bridge. This is experimental and controls the plugging ' 'behavior when not using hybrid-plug.' 'This is only used on linux and should be set to false ' 'in all other cases such as ironic smartnic ports.'), cfg.StrOpt('default_qos_type', choices=[ 'linux-htb', 'linux-hfsc', 'linux-sfq', 'linux-codel', 'linux-fq_codel', 'linux-noop' ], default='linux-noop', help=""" The default qos type to apply to ovs ports. linux-noop is the default. ovs will not modify the qdisc on the port if linux-noop is specified. This allows operators to manage QOS out of band of OVS. For more information see the ovs man pages https://manpages.debian.org/testing/openvswitch-common/ovs-vswitchd.conf.db.5.en.html#type~4 Note: This will only be set when a port is first created on the ovs bridge to ensure that the qos type can be managed via neutron if required for bandwidth limiting and other use-cases. """), ) def __init__(self, config): super(OvsPlugin, self).__init__(config) self.ovsdb = ovsdb_lib.BaseOVS(self.config) @staticmethod def gen_port_name(prefix, vif_id, max_length=NIC_NAME_LEN): return ("%s%s" % (prefix, vif_id))[:max_length] @staticmethod def get_veth_pair_names(vif): return (OvsPlugin.gen_port_name("qvb", vif.id), OvsPlugin.gen_port_name("qvo", vif.id)) def describe(self): pp_ovs = objects.host_info.HostPortProfileInfo( profile_object_name=objects.vif.VIFPortProfileOpenVSwitch.__name__, # noqa min_version="1.0", max_version="1.0", ) pp_ovs_representor = objects.host_info.HostPortProfileInfo( profile_object_name=objects.vif.VIFPortProfileOVSRepresentor.__name__, # noqa min_version="1.0", max_version="1.0", ) return objects.host_info.HostPluginInfo( plugin_name=constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFBridge.__name__, min_version="1.0", max_version="1.0", supported_port_profiles=[pp_ovs]), objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFOpenVSwitch.__name__, min_version="1.0", max_version="1.0", supported_port_profiles=[pp_ovs]), objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFVHostUser.__name__, min_version="1.0", max_version="1.0", supported_port_profiles=[pp_ovs, pp_ovs_representor]), objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFHostDevice.__name__, min_version="1.0", max_version="1.0", supported_port_profiles=[pp_ovs, pp_ovs_representor]), ]) def _get_mtu(self, vif): if vif.network and vif.network.mtu: return vif.network.mtu return self.config.network_device_mtu def supports_tc_qdisc(self, vif) -> bool: if self._get_vif_datapath_type(vif) != constants.OVS_DATAPATH_SYSTEM: return False return True def _isolate_vif(self, vif_name, bridge): # NOTE(vsaienko): don't break traffic if port already exists, # we assume it is called when nova-compute is initialized and # since port is present it should be bound already. return (self.config.isolate_vif and not self.ovsdb.port_exists(vif_name, bridge)) def _create_vif_port(self, vif, vif_name, instance_info, **kwargs): mtu = self._get_mtu(vif) # NOTE(sean-k-mooney): As part of a partial fix to bug #1734320 # we introduced the isolate_vif config option to enable isolation # of the vif prior to neutron wiring up the interface. To do # this we take advantage of the fact the ml2/ovs uses the # implementation defined VLAN 4095 as a dead VLAN to indicate # that all packets should be dropped. We only enable this # behaviour conditionally as it is not portable to SDN based # deployment such as ODL or OVN as such operator must opt-in # to this behaviour by setting the isolate_vif config option. # TODO(sean-k-mooney): Extend neutron to record what ml2 driver # bound the interface in the vif binding details so isolation # can be enabled automatically in the future. bridge = kwargs.pop('bridge', vif.network.bridge) # See bug #2069543. if (self._isolate_vif(vif_name, bridge) and not is_trunk_bridge(bridge)): kwargs['tag'] = constants.DEAD_VLAN kwargs['vlan_mode'] = 'trunk' kwargs['trunks'] = constants.DEAD_VLAN qos_type = self._get_qos_type(vif) if qos_type is not None: # NOTE(sean-k-mooney): If the port is not already created # on the bridge we need to set the default qos type to # ensure that the port is created with the correct qos # type. This is only needed for the linux kernel datapath # as the qos type is not managed by neutron for the other # datapaths. # This is a mitigation for the performance regression # introduced by the fix for bug #1734320. See bug #2017868 # for more details. if not self.ovsdb.port_exists(vif_name, bridge): kwargs['qos_type'] = qos_type self.ovsdb.create_ovs_vif_port( bridge, vif_name, vif.port_profile.interface_id, vif.address, instance_info.uuid, mtu=mtu, **kwargs) # Check if tap creation is requested: # - 'field in profile.fields' checks if field exists in schema # - 'field in profile' checks if the attribute is set on instance profile = vif.port_profile create_tap = ( isinstance(profile, objects.vif.VIFPortProfileOpenVSwitch) and 'create_tap' in profile.fields and 'create_tap' in profile and profile.create_tap ) if create_tap: # Validate VIF type - only VIFOpenVSwitch supports tap creation if not isinstance(vif, objects.vif.VIFOpenVSwitch): raise exception.TapCreationNotSupported( vif_type=vif.__class__.__name__) # Get multiqueue setting from port profile if available # 'field in profile.fields' checks schema, 'field in profile' # checks if the attribute is set multiqueue = ('multiqueue' in profile.fields and 'multiqueue' in profile and profile.multiqueue) # Create the tap device with proper MAC and MTU if it doesn't # already exist (e.g., from a previous plug during init_host) if not ip_lib.exists(vif_name): linux_net.create_tap( vif_name, mtu, vif.address, multiqueue=multiqueue) def _update_vif_port(self, vif, vif_name): mtu = self._get_mtu(vif) self.ovsdb.update_ovs_vif_port(vif_name, mtu) def _delete_tap_if_required(self, vif, vif_name): """Delete tap device if it was created via create_tap flag. :param vif: VIF object :param vif_name: Name of the tap device """ # Check if this VIF had a tap device created for it: # - 'field in profile.fields' checks if field exists in schema # - 'field in profile' checks if the attribute is set on instance profile = getattr(vif, 'port_profile', None) create_tap = ( profile is not None and isinstance(profile, objects.vif.VIFPortProfileOpenVSwitch) and isinstance(vif, objects.vif.VIFOpenVSwitch) and 'create_tap' in profile.fields and 'create_tap' in profile and profile.create_tap ) if create_tap and ip_lib.exists(vif_name): linux_net.delete_net_dev(vif_name) @staticmethod def _get_vif_datapath_type(vif, datapath=constants.OVS_DATAPATH_SYSTEM): profile = vif.port_profile if 'datapath_type' not in profile or not profile.datapath_type: return datapath return profile.datapath_type def _plug_vhostuser(self, vif, instance_info): vif_name = OvsPlugin.gen_port_name( constants.OVS_VHOSTUSER_PREFIX, vif.id) args = {} args['datapath_type'] = self._get_vif_datapath_type(vif, datapath=constants.OVS_DATAPATH_NETDEV) if vif.mode == "client": args['interface_type'] = \ constants.OVS_VHOSTUSER_INTERFACE_TYPE else: args['interface_type'] = \ constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE args['vhost_server_path'] = vif.path self._create_vif_port( vif, vif_name, instance_info, **args) def _plug_bridge(self, vif, instance_info): """Plug using hybrid strategy Create a per-VIF linux bridge, then link that bridge to the OVS integration bridge via a veth device, setting up the other end of the veth device just like a normal OVS port. Then boot the VIF on the linux bridge using standard libvirt mechanisms. """ v1_name, v2_name = self.get_veth_pair_names(vif) linux_net.ensure_bridge(vif.bridge_name) mtu = self._get_mtu(vif) if not ip_lib.exists(v2_name): linux_net.create_veth_pair(v1_name, v2_name, mtu) linux_net.add_bridge_port(vif.bridge_name, v1_name) self.ovsdb.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) self._create_vif_port(vif, v2_name, instance_info) else: linux_net.update_veth_pair(v1_name, v2_name, mtu) self._update_vif_port(vif, v2_name) def _plug_port_bridge(self, vif, instance_info): """Create a per-VIF OVS bridge and patch pair.""" # NOTE(sean-k-mooney): the port name prefix should not be # changed to avoid losing ports on upgrade. port_bridge_name = self.gen_port_name('pb', vif.id) port_bridge_patch = self.gen_port_name('pbp', vif.id, max_length=64) int_bridge_name = vif.network.bridge int_bridge_patch = self.gen_port_name('ibp', vif.id, max_length=64) self.ovsdb.ensure_ovs_bridge( int_bridge_name, self._get_vif_datapath_type(vif)) self.ovsdb.ensure_ovs_bridge( port_bridge_name, self._get_vif_datapath_type(vif)) self._create_vif_port( vif, vif.vif_name, instance_info, bridge=port_bridge_name, set_ids=False ) tag = (constants.DEAD_VLAN if self._isolate_vif(int_bridge_patch, int_bridge_name) else None) iface_id = vif.id mac = vif.address instance_id = instance_info.uuid LOG.debug( 'creating patch port pair \n' f'{port_bridge_name}: ({port_bridge_patch}) -> ' f'{int_bridge_name}: ({int_bridge_patch})' ) self.ovsdb.create_patch_port_pair( port_bridge_name, port_bridge_patch, int_bridge_name, int_bridge_patch, iface_id, mac, instance_id, tag=tag) def _plug_vif_generic(self, vif, instance_info): """Create a per-VIF OVS port.""" self.ovsdb.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) # NOTE(sean-k-mooney): as part of a partial revert of # change Iaf15fa7a678ec2624f7c12f634269c465fbad930 # (always create ovs port during plug), we stopped calling # self._create_vif_port(vif, vif.vif_name, instance_info). # Calling _create_vif_port here was intended to ensure # that the vif was wired up by neutron before the vm was # spawned on boot or live migration to partially resolve # #1734320. When the "always create ovs port during plug" change # was written it was understood by me that libvirt would not # modify ovs if the port exists but in fact it deletes and # recreates the port. This both undoes the effort to resolve # bug #1734320 and introduces other issues for neutron. # this comment will be removed when we actually fix #1734320 in # all cases. # NOTE(hamdyk): As a WA to the above note, one can use # VIFPortProfileOpenVSwitch.create_port flag to explicitly # plug the port to the switch. if ("create_port" in vif.port_profile and vif.port_profile.create_port): self._create_vif_port(vif, vif.vif_name, instance_info) def _plug_vf(self, vif, instance_info): datapath = self._get_vif_datapath_type(vif) self.ovsdb.ensure_ovs_bridge(vif.network.bridge, datapath) pci_slot = vif.dev_address vf_num = linux_net.get_vf_num_by_pci_address(pci_slot) args = [] kwargs = {} if datapath == constants.OVS_DATAPATH_SYSTEM: pf_ifname = linux_net.get_ifname_by_pci_address( pci_slot, pf_interface=True, switchdev=True) representor = linux_net.get_representor_port(pf_ifname, vf_num) linux_net.set_interface_state(representor, 'up') args = [vif, representor, instance_info] else: representor = linux_net.get_dpdk_representor_port_name( vif.id) pf_pci = linux_net.get_pf_pci_from_vf(pci_slot) args = [vif, representor, instance_info] kwargs = {'interface_type': constants.OVS_DPDK_INTERFACE_TYPE, 'pf_pci': pf_pci, 'vf_num': vf_num} self._create_vif_port(*args, **kwargs) def plug(self, vif, instance_info): if not hasattr(vif, "port_profile"): raise exception.MissingPortProfile() if not isinstance(vif.port_profile, objects.vif.VIFPortProfileOpenVSwitch): raise exception.WrongPortProfile( profile=vif.port_profile.__class__.__name__) if isinstance(vif, objects.vif.VIFOpenVSwitch): if self.config.per_port_bridge: self._plug_port_bridge(vif, instance_info) else: self._plug_vif_generic(vif, instance_info) elif isinstance(vif, objects.vif.VIFBridge): self._plug_bridge(vif, instance_info) elif isinstance(vif, objects.vif.VIFVHostUser): self._plug_vhostuser(vif, instance_info) elif isinstance(vif, objects.vif.VIFHostDevice): self._plug_vf(vif, instance_info) else: # This should never be raised. raise osv_exception.PlugException( vif=vif, err="This vif type is not supported by this plugin") def _delete_bridge_if_trunk(self, vif): if is_trunk_bridge(vif.network.bridge): self.ovsdb.delete_ovs_bridge(vif.network.bridge) def _unplug_vhostuser(self, vif, instance_info): self.ovsdb.delete_ovs_vif_port(vif.network.bridge, OvsPlugin.gen_port_name( constants.OVS_VHOSTUSER_PREFIX, vif.id)) self._delete_bridge_if_trunk(vif) def _unplug_bridge(self, vif, instance_info, linux_bridge_name): """UnPlug using hybrid strategy Unhook port from OVS, unhook port from bridge, delete bridge, and delete both veth devices. """ v1_name, v2_name = self.get_veth_pair_names(vif) linux_net.delete_bridge(linux_bridge_name, v1_name) qos_type = self._get_qos_type(vif) self.ovsdb.delete_ovs_vif_port( vif.network.bridge, v2_name, qos_type=qos_type ) self._delete_bridge_if_trunk(vif) def _get_qos_type(self, vif): qos_type = None if self.supports_tc_qdisc(vif): qos_type = self.config.default_qos_type return qos_type def _unplug_port_bridge(self, vif, instance_info): """Create a per-VIF OVS bridge and patch pair.""" # Delete tap device if it was created self._delete_tap_if_required(vif, vif.vif_name) # NOTE(sean-k-mooney): the port name prefix should not be # changed to avoid loosing ports on upgrade. port_bridge_name = self.gen_port_name('pb', vif.id) port_bridge_patch = self.gen_port_name('pbp', vif.id, max_length=64) int_bridge_patch = self.gen_port_name('ibp', vif.id, max_length=64) self.ovsdb.delete_ovs_vif_port(vif.network.bridge, int_bridge_patch) self.ovsdb.delete_ovs_vif_port(port_bridge_name, port_bridge_patch) qos_type = self._get_qos_type(vif) self.ovsdb.delete_ovs_vif_port( port_bridge_name, vif.vif_name, qos_type=qos_type ) self.ovsdb.delete_ovs_bridge(port_bridge_name) self._delete_bridge_if_trunk(vif) def _unplug_vif_generic(self, vif, instance_info): """Remove port from OVS.""" # Delete tap device if it was created self._delete_tap_if_required(vif, vif.vif_name) # NOTE(sean-k-mooney): even with the partial revert of change # Iaf15fa7a678ec2624f7c12f634269c465fbad930 this should be correct # so this is not removed. qos_type = self._get_qos_type(vif) self.ovsdb.delete_ovs_vif_port( vif.network.bridge, vif.vif_name, qos_type=qos_type ) self._delete_bridge_if_trunk(vif) def _unplug_vf(self, vif): """Remove port from OVS.""" datapath = self._get_vif_datapath_type(vif) if datapath == constants.OVS_DATAPATH_SYSTEM: pci_slot = vif.dev_address pf_ifname = linux_net.get_ifname_by_pci_address( pci_slot, pf_interface=True, switchdev=True) vf_num = linux_net.get_vf_num_by_pci_address(pci_slot) representor = linux_net.get_representor_port(pf_ifname, vf_num) else: representor = linux_net.get_dpdk_representor_port_name( vif.id) # The representor interface can't be deleted because it bind the # SR-IOV VF, therefore we just need to remove it from the ovs bridge # and set the status to down qos_type = self._get_qos_type(vif) self.ovsdb.delete_ovs_vif_port( vif.network.bridge, representor, delete_netdev=False, qos_type=qos_type ) if datapath == constants.OVS_DATAPATH_SYSTEM: linux_net.set_interface_state(representor, 'down') self._delete_bridge_if_trunk(vif) def unplug(self, vif, instance_info): if not hasattr(vif, "port_profile"): raise exception.MissingPortProfile() if not isinstance(vif.port_profile, objects.vif.VIFPortProfileOpenVSwitch): raise exception.WrongPortProfile( profile=vif.port_profile.__class__.__name__) if isinstance(vif, objects.vif.VIFOpenVSwitch): if self.config.per_port_bridge: self._unplug_port_bridge(vif, instance_info) else: linux_bridge_name = self.gen_port_name('qbr', vif.id) if ip_lib.exists(linux_bridge_name): self._unplug_bridge(vif, instance_info, linux_bridge_name) else: self._unplug_vif_generic(vif, instance_info) elif isinstance(vif, objects.vif.VIFBridge): self._unplug_bridge(vif, instance_info, vif.bridge_name) elif isinstance(vif, objects.vif.VIFVHostUser): self._unplug_vhostuser(vif, instance_info) elif isinstance(vif, objects.vif.VIFHostDevice): self._unplug_vf(vif) else: # this should never be raised. raise osv_exception.UnplugException( vif=vif, err="This vif type is not supported by this plugin") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0495875 os_vif-4.3.0/vif_plug_ovs/ovsdb/0000775000175000017500000000000015140132020015430 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/ovsdb/__init__.py0000664000175000017500000000000015140131732017540 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/ovsdb/api.py0000664000175000017500000000241515140131732016566 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_utils import importutils interface_map = { 'vsctl': 'vif_plug_ovs.ovsdb.impl_vsctl', 'native': 'vif_plug_ovs.ovsdb.impl_idl', } def get_instance(context, iface_name=None): """Return the configured OVSDB API implementation""" iface = importutils.import_module( interface_map[iface_name or context.interface]) return iface.api_factory(context) class ImplAPI(metaclass=abc.ABCMeta): @abc.abstractmethod def has_table_column(self, table, column): """Check if a column exists in a database table :param table: (string) table name :param column: (string) column name :return: True if the column exists, False if not. """ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/ovsdb/impl_idl.py0000664000175000017500000000572415140131732017614 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 functools import socket from ovs.db import idl from ovs import socket_util from ovs import stream from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import vlog from ovsdbapp.schema.open_vswitch import impl_idl from vif_plug_ovs.ovsdb import api REQUIRED_TABLES = ('Interface', 'Port', 'Bridge', 'Open_vSwitch', 'QoS') def idl_factory(config): conn = config.connection schema_name = 'Open_vSwitch' helper = idlutils.get_schema_helper(conn, schema_name) for table in REQUIRED_TABLES: helper.register_table(table) return idl.Idl(conn, helper) def api_factory(config): conn = connection.Connection( idl=idl_factory(config), timeout=config.timeout) return NeutronOvsdbIdl(conn) class NeutronOvsdbIdl(impl_idl.OvsdbIdl, api.ImplAPI): """IDL interface for OVS database back-end This class provides an OVSDB IDL (Open vSwitch Database Interface Definition Language) interface to the OVS back-end. """ def __init__(self, conn): vlog.use_python_logger() super(NeutronOvsdbIdl, self).__init__(conn) def _get_table_columns(self, table): return list(self.tables[table].columns) def has_table_column(self, table, column): return column in self._get_table_columns(table) # this is derived form https://review.opendev.org/c/openstack/neutron/+/794892 def add_keepalives(fn): @functools.wraps(fn) def _open(*args, **kwargs): error, sock = fn(*args, **kwargs) try: sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) except socket.error as e: sock.close() return socket_util.get_exception_errno(e), None return error, sock return _open class NoProbesMixin: @staticmethod def needs_probes(): # If we are using keepalives, we can force probe_interval=0 return False class TCPStream(stream.TCPStream, NoProbesMixin): @classmethod @add_keepalives def _open(cls, suffix, dscp): return super()._open(suffix, dscp) class SSLStream(stream.SSLStream, NoProbesMixin): @classmethod @add_keepalives def _open(cls, suffix, dscp): return super()._open(suffix, dscp) # Overwriting globals in a library is clearly a good idea stream.Stream.register_method("tcp", TCPStream) stream.Stream.register_method("ssl", SSLStream) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/ovsdb/impl_vsctl.py0000664000175000017500000003414515140131732020176 0ustar00zuulzuul# Derived from neutron/agent/ovsdb/impl_vsctl.py # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.abc import itertools import uuid from oslo_concurrency import processutils from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils from oslo_utils import uuidutils from ovsdbapp import api as ovsdb_api from vif_plug_ovs.ovsdb import api from vif_plug_ovs import privsep LOG = logging.getLogger(__name__) def _val_to_py(val): """Convert a json ovsdb return value to native python object""" if isinstance(val, collections.abc.Sequence) and len(val) == 2: if val[0] == "uuid": return uuid.UUID(val[1]) elif val[0] == "set": return [_val_to_py(x) for x in val[1]] elif val[0] == "map": return {_val_to_py(x): _val_to_py(y) for x, y in val[1]} return val def _py_to_val(pyval): """Convert python value to ovs-vsctl value argument""" if isinstance(pyval, bool): return 'true' if pyval is True else 'false' elif pyval == '': return '""' else: # NOTE(twilson) If a Command object, return its record_id as a value return getattr(pyval, "record_id", pyval) def api_factory(context): return OvsdbVsctl(context) @privsep.vif_plug.entrypoint def _run_vsctl(full_args): # NOTE(ralonsoh): this function is defined outside the class Transaction # to allow oslo_privsep.PrivContext.entrypoint to wrap # the function correctly. return processutils.execute(*full_args)[0].rstrip() class Transaction(ovsdb_api.Transaction): def __init__(self, context, check_error=False, log_errors=True, opts=None): self.context = context self.check_error = check_error self.log_errors = log_errors self.opts = ['--timeout=%d' % self.context.timeout, '--oneline', '--format=json'] if self.context.connection: self.opts += ['--db=%s' % self.context.connection] if opts: self.opts += opts self.commands = [] def add(self, command): self.commands.append(command) return command def commit(self): args = [] for cmd in self.commands: cmd.result = None args += cmd.vsctl_args() res = self.run_vsctl(args) if res is None: return res = res.replace(r'\\', '\\').splitlines() for i, record in enumerate(res): self.commands[i].result = record return [cmd.result for cmd in self.commands] def run_vsctl(self, args): full_args = ["ovs-vsctl"] + self.opts + args try: # We log our own errors, so never have utils.execute do it return _run_vsctl(full_args) except Exception as e: with excutils.save_and_reraise_exception() as ctxt: if self.log_errors: LOG.error("Unable to execute %(cmd)s. Exception: " "%(exception)s", {'cmd': full_args, 'exception': e}) if not self.check_error: ctxt.reraise = False class BaseCommand(ovsdb_api.Command): def __init__(self, context, cmd, opts=None, args=None): self.context = context self.cmd = cmd self.opts = [] if opts is None else opts self.args = [] if args is None else args self._result = None @property def result(self): return self._result @result.setter def result(self, value): self._result = value def execute(self, check_error=False, log_errors=True): with Transaction(self.context, check_error=check_error, log_errors=log_errors) as txn: txn.add(self) return self.result def vsctl_args(self): return itertools.chain(('--',), self.opts, (self.cmd,), self.args) class MultiLineCommand(BaseCommand): """Command for ovs-vsctl commands that return multiple lines""" @property def result(self): return self._result @result.setter def result(self, raw_result): self._result = raw_result.split(r'\n') if raw_result else [] class DbCommand(BaseCommand): def __init__(self, context, cmd, opts=None, args=None, columns=None): if opts is None: opts = [] if columns: opts += ['--columns=%s' % ",".join(columns)] super(DbCommand, self).__init__(context, cmd, opts, args) @property def result(self): return self._result @result.setter def result(self, raw_result): # If check_error=False, run_vsctl can return None if not raw_result: self._result = None return try: json = jsonutils.loads(raw_result) except (ValueError, TypeError) as e: # This shouldn't happen, but if it does and we check_errors # log and raise. with excutils.save_and_reraise_exception(): LOG.error("Could not parse: %(raw_result)s. Exception: " "%(exception)s", {'raw_result': raw_result, 'exception': e}) headings = json['headings'] data = json['data'] results = [] for record in data: obj = {} for pos, heading in enumerate(headings): obj[heading] = _val_to_py(record[pos]) results.append(obj) self._result = results class DbGetCommand(DbCommand): @DbCommand.result.setter def result(self, val): # super()'s never worked for setters http://bugs.python.org/issue14965 DbCommand.result.fset(self, val) # DbCommand will return [{'column': value}] and we just want value. if self._result: self._result = list(self._result[0].values())[0] class DbCreateCommand(BaseCommand): def __init__(self, context, opts=None, args=None): super(DbCreateCommand, self).__init__(context, "create", opts, args) # NOTE(twilson) pre-commit result used for intra-transaction reference self.record_id = "@%s" % uuidutils.generate_uuid() self.opts.append("--id=%s" % self.record_id) @property def result(self): return self._result @result.setter def result(self, val): self._result = uuid.UUID(val) if val else val class BrExistsCommand(DbCommand): @DbCommand.result.setter def result(self, val): self._result = val is not None def execute(self): return super(BrExistsCommand, self).execute(check_error=False, log_errors=False) class OvsdbVsctl(ovsdb_api.API, api.ImplAPI): def __init__(self, context): super(OvsdbVsctl, self).__init__() self.context = context def create_transaction(self, check_error=False, log_errors=True, **kwargs): return Transaction(self.context, check_error, log_errors, **kwargs) def add_manager(self, connection_uri): # This will add a new manager without overriding existing ones. conn_uri = 'target="%s"' % connection_uri args = ['create', 'Manager', conn_uri, '--', 'add', 'Open_vSwitch', '.', 'manager_options', '@manager'] return BaseCommand(self.context, '--id=@manager', args=args) def get_manager(self): return MultiLineCommand(self.context, 'get-manager') def remove_manager(self, connection_uri): args = ['get', 'Manager', connection_uri, '--', 'remove', 'Open_vSwitch', '.', 'manager_options', '@manager'] return BaseCommand(self.context, '--id=@manager', args=args) def add_br(self, name, may_exist=True, datapath_type=None): opts = ['--may-exist'] if may_exist else None params = [name] if datapath_type: params += ['--', 'set', 'Bridge', name, 'datapath_type=%s' % datapath_type] return BaseCommand(self.context, 'add-br', opts, params) def del_br(self, name, if_exists=True): opts = ['--if-exists'] if if_exists else None return BaseCommand(self.context, 'del-br', opts, [name]) def br_exists(self, name): return BrExistsCommand(self.context, 'list', args=['Bridge', name]) def port_to_br(self, name): return BaseCommand(self.context, 'port-to-br', args=[name]) def iface_to_br(self, name): return BaseCommand(self.context, 'iface-to-br', args=[name]) def list_br(self): return MultiLineCommand(self.context, 'list-br') def br_get_external_id(self, name, field): return BaseCommand(self.context, 'br-get-external-id', args=[name, field]) def db_create(self, table, **col_values): args = [table] args += _set_colval_args(*col_values.items()) return DbCreateCommand(self.context, args=args) def db_destroy(self, table, record): args = [table, record] return BaseCommand(self.context, 'destroy', args=args) def db_set(self, table, record, *col_values): args = [table, record] args += _set_colval_args(*col_values) return BaseCommand(self.context, 'set', args=args) def db_add(self, table, record, column, *values): args = [table, record, column] for value in values: if isinstance(value, collections.abc.Mapping): args += ["{}={}".format(_py_to_val(k), _py_to_val(v)) for k, v in value.items()] else: args.append(_py_to_val(value)) return BaseCommand(self.context, 'add', args=args) def db_clear(self, table, record, column): return BaseCommand(self.context, 'clear', args=[table, record, column]) def db_get(self, table, record, column): # Use the 'list' command as it can return json and 'get' cannot so that # we can get real return types instead of treating everything as string # NOTE: openvswitch can return a single atomic value for fields that # are sets, but only have one value. This makes directly iterating over # the result of a db_get() call unsafe. return DbGetCommand(self.context, 'list', args=[table, record], columns=[column]) def db_list(self, table, records=None, columns=None, if_exists=False): opts = ['--if-exists'] if if_exists else None args = [table] if records: args += records return DbCommand(self.context, 'list', opts=opts, args=args, columns=columns) def db_find(self, table, *conditions, **kwargs): columns = kwargs.pop('columns', None) args = itertools.chain([table], *[_set_colval_args(c) for c in conditions]) return DbCommand(self.context, 'find', args=args, columns=columns) def set_controller(self, bridge, controllers): return BaseCommand(self.context, 'set-controller', args=[bridge] + list(controllers)) def del_controller(self, bridge): return BaseCommand(self.context, 'del-controller', args=[bridge]) def get_controller(self, bridge): return MultiLineCommand(self.context, 'get-controller', args=[bridge]) def set_fail_mode(self, bridge, mode): return BaseCommand(self.context, 'set-fail-mode', args=[bridge, mode]) def add_port(self, bridge, port, may_exist=True): opts = ['--may-exist'] if may_exist else None return BaseCommand(self.context, 'add-port', opts, [bridge, port]) def del_port(self, port, bridge=None, if_exists=True): opts = ['--if-exists'] if if_exists else None args = filter(None, [bridge, port]) return BaseCommand(self.context, 'del-port', opts, args) def list_ports(self, bridge): return MultiLineCommand(self.context, 'list-ports', args=[bridge]) def list_ifaces(self, bridge): return MultiLineCommand(self.context, 'list-ifaces', args=[bridge]) def db_list_rows(self, table, record=None, if_exists=False): raise NotImplementedError() def db_find_rows(self, table, *conditions, **kwargs): raise NotImplementedError() def db_remove(self, table, record, column, *values, **keyvalues): raise NotImplementedError() def has_table_column(self, table, column): try: self.db_list(table, columns=[column]).execute(check_error=True) return True except processutils.ProcessExecutionError as e: msg = ('ovs-vsctl: %s does not contain a column whose name ' 'matches "%s"' % (table, column)) if msg in e.stderr: return False raise e def _set_colval_args(*col_values): args = [] # TODO(twilson) This is ugly, but set/find args are very similar except for # op. Will try to find a better way to default this op to '=' for entry in col_values: if len(entry) == 2: col, op, val = entry[0], '=', entry[1] else: col, op, val = entry if isinstance(val, collections.abc.Mapping): args += ["%s:%s%s%s" % ( col, k, op, _py_to_val(v)) for k, v in val.items()] elif (isinstance(val, collections.abc.Sequence) and not isinstance(val, str)): if len(val) == 0: args.append("%s%s%s" % (col, op, "[]")) else: args.append( "%s%s%s" % (col, op, ",".join(map(_py_to_val, val)))) else: args.append("%s%s%s" % (col, op, _py_to_val(val))) return args ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/ovsdb/ovsdb_lib.py0000664000175000017500000002612615140131732017765 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from oslo_log import log as logging from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs import ovs from vif_plug_ovs.ovsdb import api as ovsdb_api LOG = logging.getLogger(__name__) QOS_UUID_NAMESPACE = uuid.UUID("68da264a-847f-42a8-8ab0-5e774aee3d95") class BaseOVS(object): def __init__(self, config): self.timeout = config.ovs_vsctl_timeout self.connection = config.ovsdb_connection self.interface = config.ovsdb_interface self._ovsdb = None # NOTE(sean-k-mooney): when using the native ovsdb bindings # creating an instance of the ovsdb api connects to the ovsdb # to initialize the library based on the schema version # of the ovsdb. To avoid that we lazy load the ovsdb # instance the first time we need it via a property. @property def ovsdb(self): if not self._ovsdb: self._ovsdb = ovsdb_api.get_instance(self) return self._ovsdb def _ovs_supports_mtu_requests(self): return self.ovsdb.has_table_column('Interface', 'mtu_request') def _set_mtu_request(self, txn, dev, mtu): txn.add( self.ovsdb.db_set( 'Interface', dev, ('mtu_request', mtu) ) ) def update_device_mtu(self, txn, dev, mtu, interface_type=None): if not mtu: return if interface_type not in [ constants.OVS_VHOSTUSER_INTERFACE_TYPE, constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE]: linux_net.set_device_mtu(dev, mtu) elif self._ovs_supports_mtu_requests(): self._set_mtu_request(txn, dev, mtu) else: LOG.debug("MTU not set on %(interface_name)s interface " "of type %(interface_type)s.", {'interface_name': dev, 'interface_type': interface_type}) def ensure_ovs_bridge(self, bridge, datapath_type): return self.ovsdb.add_br(bridge, may_exist=True, datapath_type=datapath_type).execute() def delete_ovs_bridge(self, bridge): """Delete ovs bridge by name :param bridge: bridge name as a string .. note:: Do Not call with br-int !!! """ # TODO(sean-k-mooney): when we fix bug: #1914886 # add a guard against deleting the integration bridge # after adding a config option to store its name. return self.ovsdb.del_br(bridge).execute() def create_patch_port_pair( self, port_bridge, port_bridge_port, int_bridge, int_bridge_port, iface_id, mac, instance_id, tag=None ): """Create a patch port pair between any two bridges. :param port_bridge: the source bridge name for the patch port pair. :param port_bridge_port: the name of the patch port on the source bridge. :param int_bridge: the target bridge name, typically br-int. :param int_bridge_port: the name of the patch port on the target bridge. :param iface_id: neutron port ID. :param mac: port MAC. :param instance_id: instance uuid. :param mtu: port MTU. :param tag: OVS interface tag used for vlan isolation. """ # NOTE(sean-k-mooney): we use a transaction here for 2 reasons: # 1.) if using the vsctl client its faster # 2.) in all cases we either want to fully create the patch port # pair or not create it atomically. By using a transaction we know # that we will never be in a mixed state where it was partly created. with self.ovsdb.transaction() as txn: # create integration bridge patch peer external_ids = { 'iface-id': iface_id, 'iface-status': 'active', 'attached-mac': mac, 'vm-uuid': instance_id } col_values = [ ('external_ids', external_ids), ('type', 'patch'), ('options', {'peer': port_bridge_port}) ] txn.add(self.ovsdb.add_port(int_bridge, int_bridge_port)) if tag: txn.add( self.ovsdb.db_set('Port', int_bridge_port, ('tag', tag))) txn.add( self.ovsdb.db_set('Interface', int_bridge_port, *col_values)) # create port bridge patch peer col_values = [ ('type', 'patch'), ('options', {'peer': int_bridge_port}) ] txn.add(self.ovsdb.add_port(port_bridge, port_bridge_port)) txn.add( self.ovsdb.db_set('Interface', port_bridge_port, *col_values)) def create_ovs_vif_port( self, bridge, dev, iface_id, mac, instance_id, mtu=None, interface_type=None, vhost_server_path=None, tag=None, pf_pci=None, vf_num=None, set_ids=True, datapath_type=None, qos_type=None, vlan_mode=None, trunks=None ): """Create OVS port :param bridge: bridge name to create the port on. :param dev: port name. :param iface_id: port ID. :param mac: port MAC. :param instance_id: VM ID on which the port is attached to. :param mtu: port MTU. :param interface_type: OVS interface type. :param vhost_server_path: path to socket file of vhost server. :param tag: OVS interface tag. :param pf_pci: PCI address of PF for dpdk representor port. :param vf_num: VF number of PF for dpdk representor port. :param set_ids: set external ids on port (bool). :param datapath_type: datapath type for port's bridge :param qos_type: qos type for a port .. note:: create DPDK representor port by setting all three values: `interface_type`, `pf_pci` and `vf_num`. if interface type is not `OVS_DPDK_INTERFACE_TYPE` then `pf_pci` and `vf_num` values are ignored. """ external_ids = {'iface-id': iface_id, 'iface-status': 'active', 'attached-mac': mac, 'vm-uuid': instance_id} # Note(lajoskatona): Neutron fills external_ids for trunk, see: # https://opendev.org/openstack/neutron/src/commit/ # 1bc4b526e9c743423069ab4cf6ef3883d5e48217/neutron/services/trunk/ # drivers/openvswitch/agent/ovsdb_handler.py#L418 # The following keys are added there: bridge_name, trunk_id and # subport_ids. These values are used during the cleanup after the # deletion of the trunk. It can happen that Neutron can't fill these # fields. # In os-vif when the plug happens we can use the same transaction to # add bridge_name to external_ids in case of it is a trunk. # By this Neutron can do the cleanup of trunk related interfaces. if ovs.is_trunk_bridge(bridge): external_ids['bridge_name'] = bridge col_values = [('external_ids', external_ids)] if set_ids else [] if interface_type: col_values.append(('type', interface_type)) if vhost_server_path: col_values.append(('options', {'vhost-server-path': vhost_server_path})) if (interface_type == constants.OVS_DPDK_INTERFACE_TYPE and pf_pci and vf_num): devargs_string = "{PF_PCI},representor=[{VF_NUM}]".format( PF_PCI=pf_pci, VF_NUM=vf_num) col_values.append(('options', {'dpdk-devargs': devargs_string})) # create qos record if qos type is specified # and get the qos id. This is done outside of the transaction # because we need the qos id to set the qos on the port. # The qos uuid cannot be set when creating the record so we # have to look it up after the record is created. this means # we need to create the qos record outside of the transaction # that creates the port. qid = None if qos_type: self.delete_qos_if_exists(dev, qos_type) qos_id = uuid.uuid5(QOS_UUID_NAMESPACE, dev) qos_external_ids = {'id': str(qos_id), '_type': qos_type} self.ovsdb.db_create( 'QoS', type=qos_type, external_ids=qos_external_ids ).execute(check_error=True) record = self.get_qos(dev, qos_type) qid = record[0]['_uuid'] with self.ovsdb.transaction() as txn: if datapath_type: txn.add(self.ovsdb.add_br(bridge, may_exist=True, datapath_type=datapath_type)) txn.add(self.ovsdb.add_port(bridge, dev)) if tag: txn.add(self.ovsdb.db_set('Port', dev, ('tag', tag))) if vlan_mode: txn.add(self.ovsdb.db_set('Port', dev, ('vlan_mode', vlan_mode))) if trunks: txn.add(self.ovsdb.db_set('Port', dev, ('trunks', trunks))) if qid: txn.add(self.ovsdb.db_set('Port', dev, ('qos', qid))) if col_values: txn.add(self.ovsdb.db_set('Interface', dev, *col_values)) self.update_device_mtu( txn, dev, mtu, interface_type=interface_type ) def port_exists(self, port_name, bridge): ports = self.ovsdb.list_ports(bridge).execute() return ports is not None and port_name in ports def get_qos(self, dev, qos_type): qos_id = uuid.uuid5(QOS_UUID_NAMESPACE, dev) external_ids = {'id': str(qos_id), '_type': qos_type} return self.ovsdb.db_find( 'QoS', ('external_ids', '=', external_ids), colmuns=['_uuid'] ).execute() def delete_qos_if_exists(self, dev, qos_type): qos_ids = self.get_qos(dev, qos_type) if qos_ids is not None and len(qos_ids) > 0: for qos_id in qos_ids: if '_uuid' in qos_id: self.ovsdb.db_destroy( 'QoS', str(qos_id['_uuid']) ).execute() def update_ovs_vif_port(self, dev, mtu=None, interface_type=None): with self.ovsdb.transaction() as txn: self.update_device_mtu( txn, dev, mtu, interface_type=interface_type ) def delete_ovs_vif_port( self, bridge, dev, delete_netdev=True, qos_type=None ): self.ovsdb.del_port(dev, bridge=bridge, if_exists=True).execute() if qos_type: self.delete_qos_if_exists(dev, qos_type) if delete_netdev: linux_net.delete_net_dev(dev) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/privsep.py0000664000175000017500000000211415140131732016364 0ustar00zuulzuul# # Copyright (C) 2016 Red Hat, 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_privsep import capabilities as c from oslo_privsep import priv_context vif_plug = priv_context.PrivContext( "vif_plug_ovs", cfg_section="vif_plug_ovs_privileged", pypath=__name__ + ".vif_plug", capabilities=[ c.CAP_NET_ADMIN, ], ) vif_plug_test = priv_context.PrivContext( "vif_plug_ovs", cfg_section="vif_plug_ovs_privileged", pypath=__name__ + ".vif_plug_test", capabilities=[ c.CAP_NET_ADMIN, c.CAP_DAC_OVERRIDE, ], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0505874 os_vif-4.3.0/vif_plug_ovs/tests/0000775000175000017500000000000015140132020015455 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/__init__.py0000664000175000017500000000000015140131732017565 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0505874 os_vif-4.3.0/vif_plug_ovs/tests/functional/0000775000175000017500000000000015140132020017617 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/functional/__init__.py0000664000175000017500000000000015140131732021727 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/functional/base.py0000664000175000017500000000401315140131732021112 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 functools import os from os_vif.tests.functional import base as os_vif_base wait_until_true = os_vif_base.wait_until_true class VifPlugOvsBaseFunctionalTestCase(os_vif_base.BaseFunctionalTestCase): """Base class for vif_plug_ovs functional tests.""" COMPONENT_NAME = 'vif_plug_ovs' PRIVILEGED_GROUP = 'vif_plug_ovs_privileged' def _check_bridge(self, name): return self._ovsdb.br_exists(name).execute() def _check_port(self, name, bridge): return self.ovs.port_exists(name, bridge) @functools.cache def _get_timeout(self): return int(os.environ.get('OS_VIF_CHECK_PARAMETER_TIMEOUT', '10')) def _check_parameter(self, table, port, parameter, expected_value): def get_value(): return self._ovsdb.db_get(table, port, parameter).execute() def check_value(): val = get_value() return val == expected_value self.assertTrue( wait_until_true( check_value, timeout=self._get_timeout(), sleep=0.5), f"Parameter {parameter} of {table} {port} is {get_value()} " f"not {expected_value}" ) def _add_bridge(self, name, may_exist=True, datapath_type=None): self._ovsdb.add_br(name, may_exist=may_exist, datapath_type=datapath_type).execute() self.assertTrue(self._check_bridge(name)) def _del_bridge(self, name): self._ovsdb.del_br(name).execute() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0515873 os_vif-4.3.0/vif_plug_ovs/tests/functional/ovsdb/0000775000175000017500000000000015140132020020734 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/functional/ovsdb/__init__.py0000664000175000017500000000000015140131732023044 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py0000664000175000017500000003231515140131732024325 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 fixtures import random from unittest import mock import testscenarios from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import uuidutils from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs import ovs from vif_plug_ovs.ovsdb import ovsdb_lib from vif_plug_ovs import privsep from vif_plug_ovs.tests.functional import base CONF = cfg.CONF @privsep.vif_plug_test.entrypoint def run_privileged(*full_args): return processutils.execute(*full_args)[0].rstrip() class TestOVSDBLib(testscenarios.WithScenarios, base.VifPlugOvsBaseFunctionalTestCase): scenarios = [ ('native', {'interface': 'native'}), ('vsctl', {'interface': 'vsctl'}) ] def setUp(self): super(TestOVSDBLib, self).setUp() run_privileged('ovs-vsctl', 'set-manager', 'ptcp:6640') # NOTE: (ralonsoh) load default configuration variables "CONFIG_OPTS" ovs.OvsPlugin.load('ovs') self.flags(ovsdb_interface=self.interface, group='os_vif_ovs') self.ovs = ovsdb_lib.BaseOVS(CONF.os_vif_ovs) self._ovsdb = self.ovs.ovsdb self.brname = ('br' + str(random.randint(1000, 9999)) + '-' + self.interface) # Make sure exceptions pass through by calling do_post_commit directly post_commit = ( 'ovsdbapp.schema.open_vswitch.impl_idl.' 'OvsVsctlTransaction.post_commit' ) # "this" is the self parmater which is a reference to the # OvsVsctlTransaction instance on which do_post_commit is defiend. def direct_post_commit(this, transaction): this.do_post_commit(transaction) self.useFixture(fixtures.MonkeyPatch(post_commit, direct_post_commit)) def _add_port(self, bridge, port, may_exist=True): with self._ovsdb.transaction() as txn: txn.add(self._ovsdb.add_port(bridge, port, may_exist=may_exist)) txn.add(self._ovsdb.db_set('Interface', port, ('type', 'internal'))) self.assertIn(port, self._list_ports_in_bridge(bridge)) def _list_ports_in_bridge(self, bridge): return self._ovsdb.list_ports(bridge).execute() def test__set_mtu_request(self): port_name = 'port1-' + self.interface self._add_bridge(self.brname) self.addCleanup(self._del_bridge, self.brname) self._add_port(self.brname, port_name) if self.ovs._ovs_supports_mtu_requests(): with self._ovsdb.transaction() as txn: self.ovs._set_mtu_request(txn, port_name, 1000) self._check_parameter('Interface', port_name, 'mtu', 1000) with self._ovsdb.transaction() as txn: self.ovs._set_mtu_request(txn, port_name, 1500) self._check_parameter('Interface', port_name, 'mtu', 1500) else: self.skipTest('Current version of Open vSwitch does not support ' '"mtu_request" parameter') def test_create_ovs_vif_port(self): port_name = 'port2-' + self.interface iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = constants.OVS_VHOSTUSER_INTERFACE_TYPE vhost_server_path = '/fake/path' mtu = 1500 self._add_bridge(self.brname) self.addCleanup(self._del_bridge, self.brname) self.ovs.create_ovs_vif_port(self.brname, port_name, iface_id, mac, instance_id, mtu=mtu, interface_type=interface_type, vhost_server_path=vhost_server_path, tag=2000) expected_external_ids = {'iface-status': 'active', 'iface-id': iface_id, 'attached-mac': mac, 'vm-uuid': instance_id} self._check_parameter('Interface', port_name, 'external_ids', expected_external_ids) self._check_parameter('Interface', port_name, 'type', interface_type) expected_vhost_server_path = {'vhost-server-path': vhost_server_path} self._check_parameter( 'Interface', port_name, 'options', expected_vhost_server_path ) self._check_parameter('Port', port_name, 'tag', 2000) self._check_parameter('Port', port_name, 'qos', []) @mock.patch.object(linux_net, 'delete_net_dev') def test_delete_ovs_vif_port(self, *mock): port_name = 'port3-' + self.interface self._add_bridge(self.brname) self.addCleanup(self._del_bridge, self.brname) self._add_port(self.brname, port_name) self.ovs.delete_ovs_vif_port(self.brname, port_name) self.assertNotIn(port_name, self._list_ports_in_bridge(self.brname)) def test_ensure_ovs_bridge(self): bridge_name = 'bridge2-' + self.interface self.ovs.ensure_ovs_bridge(bridge_name, constants.OVS_DATAPATH_SYSTEM) self.assertTrue(self._check_bridge(bridge_name)) self.addCleanup(self._del_bridge, bridge_name) def test_create_patch_port_pair(self): port_bridge = 'fake-pb' port_bridge_port = 'fake-pbp' int_bridge = 'pb-int' int_bridge_port = 'fake-ibp' iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() # deleting a bridge deletes all ports on bridges so we register the # bridge cleanup first so if we fail anywhere it runs. self.addCleanup(self._del_bridge, port_bridge) self.addCleanup(self._del_bridge, int_bridge) self.ovs.ensure_ovs_bridge(port_bridge, constants.OVS_DATAPATH_SYSTEM) self.ovs.ensure_ovs_bridge(int_bridge, constants.OVS_DATAPATH_SYSTEM) self.ovs.create_patch_port_pair( port_bridge, port_bridge_port, int_bridge, int_bridge_port, iface_id, mac, instance_id, tag=2000) self.assertTrue(self._check_bridge(port_bridge)) self.assertTrue(self._check_bridge(int_bridge)) expected_external_ids = {'iface-status': 'active', 'iface-id': iface_id, 'attached-mac': mac, 'vm-uuid': instance_id} self._check_parameter( 'Interface', int_bridge_port, 'external_ids', expected_external_ids) self._check_parameter('Interface', int_bridge_port, 'type', 'patch') port_opts = {'peer': port_bridge_port} self._check_parameter( 'Interface', int_bridge_port, 'options', port_opts) self._check_parameter('Port', int_bridge_port, 'tag', 2000) port_opts = {'peer': int_bridge_port} self._check_parameter( 'Interface', port_bridge_port, 'options', port_opts) def test_create_ovs_vif_port_with_default_qos(self): port_name = 'def-qos-port-' + self.interface iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() mtu = 1500 interface_type = 'internal' qos_type = CONF.os_vif_ovs.default_qos_type self.addCleanup(self._del_bridge, self.brname) self._add_bridge(self.brname) self.addCleanup( self.ovs.delete_ovs_vif_port, self.brname, port_name, delete_netdev=False, qos_type=qos_type ) self.ovs.create_ovs_vif_port( self.brname, port_name, iface_id, mac, instance_id, mtu=mtu, interface_type=interface_type, tag=2000, qos_type=qos_type ) # first we assert that the standard parameters are set correctly expected_external_ids = {'iface-status': 'active', 'iface-id': iface_id, 'attached-mac': mac, 'vm-uuid': instance_id} self._check_parameter('Interface', port_name, 'external_ids', expected_external_ids) self._check_parameter('Interface', port_name, 'type', interface_type) self._check_parameter('Port', port_name, 'tag', 2000) # now we check that the port has a qos policy attached qos_uuid = self.ovs.get_qos( port_name, qos_type )[0]['_uuid'] self._check_parameter('Port', port_name, 'qos', qos_uuid) # finally we check that the qos policy has the correct parameters self._check_parameter( 'QoS', str(qos_uuid), 'type', qos_type ) def test_delete_qos_if_exists(self): port_name = 'del-qos-port-' + self.interface iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = 'internal' qos_type = CONF.os_vif_ovs.default_qos_type # setup test by creating a bridge and port, and register # cleanup funcitons to avoid leaking them. self.addCleanup(self._del_bridge, self.brname) self._add_bridge(self.brname) self.addCleanup( self.ovs.delete_ovs_vif_port, self.brname, port_name, delete_netdev=False, qos_type=qos_type ) self.ovs.create_ovs_vif_port( self.brname, port_name, iface_id, mac, instance_id, interface_type=interface_type, qos_type=qos_type ) # now we check that the port has a qos policy attached qos_uuid = self.ovs.get_qos( port_name, CONF.os_vif_ovs.default_qos_type )[0]['_uuid'] self._check_parameter('Port', port_name, 'qos', qos_uuid) # finally we check that the qos policy has the correct parameters self._check_parameter( 'QoS', str(qos_uuid), 'type', qos_type ) # we need to delete the port directly in the db to remove # any references to the qos policy self.ovs.ovsdb.del_port( port_name, bridge=self.brname, if_exists=True).execute() # then we can delete the qos policy self.ovs.delete_qos_if_exists(port_name, qos_type) self._check_parameter( 'QoS', str(qos_uuid), 'type', None ) # invoking the delete when the policy does not exist # should not result in an error self.ovs.delete_qos_if_exists(port_name, qos_type) self._check_parameter( 'QoS', str(qos_uuid), 'type', None ) def test_get_qos(self): port_name = 'get-qos-' + self.interface iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = 'internal' qos_type = CONF.os_vif_ovs.default_qos_type # initally no qos policy should exist self.assertEqual(0, len(self.ovs.get_qos(port_name, qos_type))) # if we create a port with a qos policy get_qos should # return the policy self.addCleanup(self._del_bridge, self.brname) self._add_bridge(self.brname) self.addCleanup( self.ovs.delete_ovs_vif_port, self.brname, port_name, delete_netdev=False, qos_type=qos_type ) self.ovs.create_ovs_vif_port( self.brname, port_name, iface_id, mac, instance_id, interface_type=interface_type, qos_type=qos_type ) # result should be a list of lenght 1 containing the # qos policy created for the port we defied. result = self.ovs.get_qos(port_name, qos_type) self.assertEqual(1, len(result)) self.assertIn('_uuid', result[0]) self._check_parameter( 'Port', port_name, 'qos', result[0]['_uuid'] ) # if we delete the port and its qos policy get_qos should # not return it. self.ovs.delete_ovs_vif_port( self.brname, port_name, delete_netdev=False, qos_type=qos_type ) self.assertEqual(0, len(self.ovs.get_qos(port_name, qos_type))) def test_port_exists(self): port_name = 'port-exists-' + self.interface iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = 'internal' self.assertFalse(self.ovs.port_exists(port_name, self.brname)) self.addCleanup(self._del_bridge, self.brname) self._add_bridge(self.brname) self.addCleanup( self.ovs.delete_ovs_vif_port, self.brname, port_name, delete_netdev=False, ) self.ovs.create_ovs_vif_port( self.brname, port_name, iface_id, mac, instance_id, interface_type=interface_type, ) self.assertTrue(self.ovs.port_exists(port_name, self.brname)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/functional/test_plugin.py0000664000175000017500000003133215140131732022541 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 testscenarios import time from unittest import mock import uuid from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import uuidutils from os_vif import objects from vif_plug_ovs import constants from vif_plug_ovs import ovs from vif_plug_ovs.ovsdb import ovsdb_lib from vif_plug_ovs import privsep from vif_plug_ovs.tests.functional import base CONF = cfg.CONF @privsep.vif_plug.entrypoint def run_privileged(*full_args): return processutils.execute(*full_args)[0].rstrip() # derived from test_impl_pyroute2 def exist_device(device): try: run_privileged('ip', 'link', 'show', device) return True except processutils.ProcessExecutionError as e: if e.exit_code == 1: return False raise def add_device(device, dev_type, peer=None, link=None, vlan_id=None): if 'vlan' == dev_type: run_privileged('ip', 'link', 'add', 'link', link, 'name', device, 'type', dev_type, 'vlan', 'id', vlan_id) elif 'veth' == dev_type: run_privileged('ip', 'link', 'add', device, 'type', dev_type, 'peer', 'name', peer) elif 'dummy' == dev_type: run_privileged('ip', 'link', 'add', device, 'type', dev_type) # ensure that the device exists to prevent racing with other ip commands for _ in range(10): if exist_device(device): return time.sleep(0.1) def del_device(device): if exist_device(device): run_privileged('ip', 'link', 'del', device) class TestOVSPlugin(testscenarios.WithScenarios, base.VifPlugOvsBaseFunctionalTestCase): scenarios = [ ('native', {'interface': 'native'}), ('vsctl', {'interface': 'vsctl'}) ] def setUp(self): super(TestOVSPlugin, self).setUp() run_privileged('ovs-vsctl', 'set-manager', 'ptcp:6640') self.plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) self.flags(ovsdb_interface=self.interface, group='os_vif_ovs') self.ovs = ovsdb_lib.BaseOVS(CONF.os_vif_ovs) self._ovsdb = self.ovs.ovsdb self.profile_ovs = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='netdev') self.subnet_bridge_4 = objects.subnet.Subnet( cidr='101.168.1.0/24', dns=['8.8.8.8'], gateway='101.168.1.1', dhcp_server='191.168.1.1') self.subnet_bridge_6 = objects.subnet.Subnet( cidr='101:1db9::/64', gateway='101:1db9::1') self.subnets = objects.subnet.SubnetList( objects=[self.subnet_bridge_4, self.subnet_bridge_6]) self.network_ovs_trunk = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='%s01' % constants.TRUNK_BR_PREFIX, subnets=self.subnets, vlan=99) self.vif_vhostuser_trunk = objects.vif.VIFVHostUser( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs_trunk, path='/var/run/openvswitch/vhub679325f-ca', mode='client', port_profile=self.profile_ovs) self.profile_ovs_system = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='system', create_port=True) self.network_ovs = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br-qos-' + self.interface, subnets=self.subnets, vlan=99) self.vif_ovs_port = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, port_profile=self.profile_ovs_system, vif_name="qos-port-" + self.interface) self.instance = objects.instance_info.InstanceInfo( name='demo', uuid='f0000000-0000-0000-0000-000000000001') def test_plug_unplug_ovs_vhostuser_trunk(self): trunk_bridge = '%s01' % constants.TRUNK_BR_PREFIX self.plugin.plug(self.vif_vhostuser_trunk, self.instance) self.addCleanup(self._del_bridge, trunk_bridge) self.assertTrue(self._check_bridge(trunk_bridge)) other_bridge = 'br-%s' % uuidutils.generate_uuid() self._add_bridge(other_bridge) self.addCleanup(self._del_bridge, other_bridge) self.plugin.unplug(self.vif_vhostuser_trunk, self.instance) self.assertTrue(self._check_bridge(other_bridge)) self.assertFalse(self._check_bridge(trunk_bridge)) def test_plug_unplug_ovs_port_with_qos(self): bridge = 'br-qos-' + self.interface vif_name = "qos-port-" + self.interface qos_type = CONF.os_vif_ovs.default_qos_type self.addCleanup(self._del_bridge, bridge) self.addCleanup( self.ovs.delete_ovs_vif_port, bridge, vif_name, delete_netdev=False, qos_type=qos_type ) self.addCleanup(del_device, vif_name) add_device(vif_name, 'dummy') # pluging a vif will create the port and bridge # if either does not exist self.plugin.plug(self.vif_ovs_port, self.instance) self.assertTrue(self._check_bridge(bridge)) self.assertTrue(self._check_port(vif_name, bridge)) qos_uuid = self.ovs.get_qos( vif_name, qos_type )[0]['_uuid'] self._check_parameter('Port', vif_name, 'qos', qos_uuid) self._check_parameter( 'QoS', str(qos_uuid), 'type', qos_type ) # unpluging a port will not delete the bridge. self.plugin.unplug(self.vif_ovs_port, self.instance) self.assertTrue(self._check_bridge(bridge)) self.assertFalse(self._check_port(vif_name, bridge)) self._check_parameter( 'QoS', str(qos_uuid), 'type', None ) def test_plug_unplug_ovs_port_with_qos_per_port_bridge(self): with mock.patch.object(self.plugin.config, 'per_port_bridge', True): bridge = 'br-ppb-' + self.interface vif_name = 'port-ppb-' + self.interface qos_type = CONF.os_vif_ovs.default_qos_type network = objects.network.Network( id='6977aa43-b7c3-484a-8bcb-09d77374981b', bridge=bridge, subnets=self.subnets, vlan=99) vif = objects.vif.VIFOpenVSwitch( id='e5cf7112-a72f-43a4-aaa3-48a5cfbdeaca', address='ca:fe:de:ad:be:ef', network=network, port_profile=self.profile_ovs_system, vif_name=vif_name) port_bridge_name = self.plugin.gen_port_name('pb', vif.id) self.addCleanup(self._del_bridge, bridge) self.addCleanup(self._del_bridge, port_bridge_name) self.addCleanup( self.ovs.delete_ovs_vif_port, port_bridge_name, vif_name, delete_netdev=False, qos_type=qos_type ) self.addCleanup(del_device, vif_name) add_device(vif_name, 'dummy') # plugging a vif will create the port and bridges # if they don't exist self.plugin.plug(vif, self.instance) self.assertTrue(self._check_bridge(bridge)) self.assertTrue(self._check_bridge(port_bridge_name)) self.assertTrue(self._check_port(vif_name, port_bridge_name)) # Plugging a second time should succeed self.plugin.plug(vif, self.instance) # Check that the 2nd plug did not create a 2nd qos row, # which happened in https://bugs.launchpad.net/os-vif/+bug/2133225 qos = self.ovs.get_qos(vif_name, qos_type) self.assertEqual(1, len(qos)) qos_uuid = qos[0]['_uuid'] self._check_parameter('Port', vif_name, 'qos', qos_uuid) self._check_parameter( 'QoS', str(qos_uuid), 'type', qos_type ) # unplugging a port will not delete the int bridge, # only the per-port bridge. self.plugin.unplug(vif, self.instance) self.assertTrue(self._check_bridge(bridge)) self.assertFalse(self._check_bridge(port_bridge_name)) self.assertFalse(self._check_port(vif_name, bridge)) self._check_parameter( 'QoS', str(qos_uuid), 'type', None ) def test_plug_br_int_isolate_vif_dead_vlan(self): with mock.patch.object(self.plugin.config, 'isolate_vif', True): network = objects.network.Network( id='5449523c-3a08-11ef-86d6-17149687aa4d', bridge='br-5449523c', subnets=self.subnets, vlan=99) vif = objects.vif.VIFOpenVSwitch( id='85cb9bc6-3a08-11ef-b2d4-9b7c38edd677', address='ca:fe:de:ad:be:ef', network=network, port_profile=self.profile_ovs_system, vif_name="port-85cb9bc6") self.plugin.plug(vif, self.instance) self.addCleanup(self._del_bridge, 'br-5449523c') self._check_parameter('Port', vif.vif_name, 'tag', 4095) def test_plug_trunk_bridge_ignores_isolate_vif(self): with mock.patch.object(self.plugin.config, 'isolate_vif', True): network = objects.network.Network( id='ef98b384-3a0f-11ef-9009-47345fca266f', bridge='tbr-ef98b384', subnets=self.subnets, vlan=99) vif = objects.vif.VIFOpenVSwitch( id='631f52bc-3a07-11ef-a006-1319ef9d6edd', address='ca:fe:de:ad:be:ef', network=network, port_profile=self.profile_ovs_system, vif_name='port-631f52bc') self.plugin.plug(vif, self.instance) self.addCleanup(self._del_bridge, 'tbr-ef98b384') self._check_parameter('Port', vif.vif_name, 'tag', []) def test_plug_trunk_bridge_fills_bridge_name(self): mac = 'ca:fe:de:ad:be:ef' iface_id = str(uuid.uuid4()) vif_name = 'port-%s' % iface_id[:8] trunk_id = str(uuid.uuid4()) bridge_name = 'tbr-%s' % trunk_id[:8] network = objects.network.Network( id=trunk_id, bridge=bridge_name, subnets=self.subnets, vlan=99) vif = objects.vif.VIFOpenVSwitch( id=iface_id, address=mac, network=network, port_profile=self.profile_ovs_system, vif_name=vif_name) self.plugin.plug(vif, self.instance) self.addCleanup(self._del_bridge, bridge_name) expected_external_ids = { 'attached-mac': mac, 'bridge_name': bridge_name, 'iface-id': self.profile_ovs.interface_id, 'iface-status': 'active', 'vm-uuid': self.instance.uuid, } self._check_parameter('Interface', vif.vif_name, 'external_ids', expected_external_ids) def test_plug_non_trunk_leave_bridge_name_empty(self): mac = 'ca:fe:de:ad:be:ef' iface_id = str(uuid.uuid4()) vif_name = 'port-%s' % iface_id[:8] bridge_name = 'br-something' network = objects.network.Network( id=str(uuid.uuid4()), bridge=bridge_name, subnets=self.subnets, vlan=99) vif = objects.vif.VIFOpenVSwitch( id=iface_id, address=mac, network=network, port_profile=self.profile_ovs_system, vif_name=vif_name) self.plugin.plug(vif, self.instance) self.addCleanup(self._del_bridge, bridge_name) # bridge_name is filled only in case of trunk plug expected_external_ids = { 'attached-mac': mac, 'iface-id': self.profile_ovs.interface_id, 'iface-status': 'active', 'vm-uuid': self.instance.uuid, } self._check_parameter('Interface', vif.vif_name, 'external_ids', expected_external_ids) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0525873 os_vif-4.3.0/vif_plug_ovs/tests/unit/0000775000175000017500000000000015140132020016434 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/unit/__init__.py0000664000175000017500000000000015140131732020544 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1770042384.0525873 os_vif-4.3.0/vif_plug_ovs/tests/unit/ovsdb/0000775000175000017500000000000015140132020017551 5ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/unit/ovsdb/__init__.py0000664000175000017500000000000015140131732021661 0ustar00zuulzuul././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py0000664000175000017500000002467715140131732023156 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import testtools from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import uuidutils from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs.ovsdb import ovsdb_lib CONF = cfg.CONF class BaseOVSTest(testtools.TestCase): def setUp(self): super(BaseOVSTest, self).setUp() test_vif_plug_ovs_group = cfg.OptGroup('test_vif_plug_ovs') CONF.register_group(test_vif_plug_ovs_group) CONF.register_opt(cfg.IntOpt('ovs_vsctl_timeout', default=1500), test_vif_plug_ovs_group) CONF.register_opt(cfg.StrOpt('ovsdb_interface', default='vsctl'), test_vif_plug_ovs_group) CONF.register_opt(cfg.StrOpt('ovsdb_connection', default=None), test_vif_plug_ovs_group) self.br = ovsdb_lib.BaseOVS(cfg.CONF.test_vif_plug_ovs) self.mock_db_set = mock.patch.object(self.br.ovsdb, 'db_set').start() self.mock_del_port = mock.patch.object(self.br.ovsdb, 'del_port').start() self.mock_add_port = mock.patch.object(self.br.ovsdb, 'add_port').start() self.mock_add_br = mock.patch.object(self.br.ovsdb, 'add_br').start() self.mock_transaction = mock.patch.object(self.br.ovsdb, 'transaction').start() def test__set_mtu_request(self): self.br._set_mtu_request(self.mock_transaction, 'device', 1500) calls = [mock.call('Interface', 'device', ('mtu_request', 1500))] self.mock_db_set.assert_has_calls(calls) @mock.patch.object(linux_net, 'set_device_mtu') def test__update_device_mtu_interface_not_vhostuser(self, mock_set_device_mtu): self.br.update_device_mtu( self.mock_transaction, 'device', 1500, 'not_vhost' ) mock_set_device_mtu.assert_has_calls([mock.call('device', 1500)]) def test__update_device_mtu_interface_vhostuser_supports_mtu_req(self): with mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=True), \ mock.patch.object(self.br, '_set_mtu_request') as \ mock_set_mtu_request: self.br.update_device_mtu( self.mock_transaction, 'device', 1500, constants.OVS_VHOSTUSER_INTERFACE_TYPE ) mock_set_mtu_request.assert_has_calls( [ mock.call( self.mock_transaction, 'device', 1500 ) ] ) def test__update_device_mtu_interface_vhostuser_not_supports_mtu_req(self): with mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=False), \ mock.patch.object(self.br, '_set_mtu_request') as \ mock_set_mtu_request: self.br.update_device_mtu( self.mock_transaction, 'device', 1500, constants.OVS_VHOSTUSER_INTERFACE_TYPE ) mock_set_mtu_request.assert_not_called() def _test_create_ovs_vif_port(self, bridge_name='bridge', check_br_name=False): iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = constants.OVS_VHOSTUSER_INTERFACE_TYPE vhost_server_path = '/fake/path' device = 'device' bridge = bridge_name mtu = 1500 external_ids = {'iface-id': iface_id, 'iface-status': 'active', 'attached-mac': mac, 'vm-uuid': instance_id} if check_br_name: external_ids['bridge_name'] = bridge_name values = [('external_ids', external_ids), ('type', interface_type), ('options', {'vhost-server-path': vhost_server_path}) ] with mock.patch.object(self.br, 'update_device_mtu', return_value=True) as mock_update_device_mtu, \ mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=True): self.br.create_ovs_vif_port(bridge, device, iface_id, mac, instance_id, mtu=mtu, interface_type=interface_type, vhost_server_path=vhost_server_path, tag=4000) self.mock_add_port.assert_has_calls([mock.call(bridge, device)]) self.mock_db_set.assert_has_calls( [mock.call('Port', device, ('tag', 4000)), mock.call('Interface', device, *values)]) with self.br._ovsdb.transaction() as txn: mock_update_device_mtu.assert_has_calls( [ mock.call( txn, device, mtu, interface_type=interface_type ) ] ) def test_create_ovs_vif_port(self): self._test_create_ovs_vif_port() def test_create_ovs_vif_port_for_trunk(self): self._test_create_ovs_vif_port(bridge_name='tbr-12345', check_br_name=True) def test_create_ovs_vif_port_type_dpdk(self): iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = constants.OVS_DPDK_INTERFACE_TYPE device = 'device' bridge = 'bridge' mtu = 1500 pf_pci = '0000:02:00.1' vf_num = '0' external_ids = {'iface-id': iface_id, 'iface-status': 'active', 'attached-mac': mac, 'vm-uuid': instance_id} values = [('external_ids', external_ids), ('type', interface_type), ('options', {'dpdk-devargs': '0000:02:00.1,representor=[0]'})] with mock.patch.object(self.br, 'update_device_mtu', return_value=True) as mock_update_device_mtu, \ mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=True): self.br.create_ovs_vif_port(bridge, device, iface_id, mac, instance_id, mtu=mtu, interface_type=interface_type, pf_pci=pf_pci, vf_num=vf_num) self.mock_add_port.assert_has_calls([mock.call(bridge, device)]) self.mock_db_set.assert_has_calls( [mock.call('Interface', device, *values)]) with self.br._ovsdb.transaction() as txn: mock_update_device_mtu.assert_has_calls( [ mock.call( txn, device, mtu, interface_type=interface_type ) ] ) def test_update_ovs_vif_port(self): with mock.patch.object(self.br, 'update_device_mtu') as \ mock_update_device_mtu: self.br.update_ovs_vif_port('device', mtu=1500, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) with self.br._ovsdb.transaction() as txn: mock_update_device_mtu.assert_has_calls([mock.call( txn, 'device', 1500, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE)]) @mock.patch.object(linux_net, 'delete_net_dev') def test_delete_ovs_vif_port(self, mock_delete_net_dev): self.br.delete_ovs_vif_port('bridge', 'device') self.mock_del_port.assert_has_calls( [mock.call('device', bridge='bridge', if_exists=True)]) mock_delete_net_dev.assert_has_calls([mock.call('device')]) @mock.patch.object(linux_net, 'delete_net_dev') def test_delete_ovs_vif_port_no_delete_netdev(self, mock_delete_net_dev): self.br.delete_ovs_vif_port('bridge', 'device', delete_netdev=False) self.mock_del_port.assert_has_calls( [mock.call('device', bridge='bridge', if_exists=True)]) mock_delete_net_dev.assert_not_called() def test_ensure_ovs_bridge(self): self.br.ensure_ovs_bridge('bridge', constants.OVS_DATAPATH_SYSTEM) self.mock_add_br('bridge', may_exist=True, datapath_type=constants.OVS_DATAPATH_SYSTEM) def test__ovs_supports_mtu_requests(self): with mock.patch.object(self.br.ovsdb, 'db_list') as mock_db_list: self.assertTrue(self.br._ovs_supports_mtu_requests()) mock_db_list.assert_called_once_with('Interface', columns=['mtu_request']) def test__ovs_supports_mtu_requests_not_supported(self): with mock.patch.object(self.br.ovsdb, 'db_list') as mock_db_list: mock_db_list.side_effect = processutils.ProcessExecutionError( stderr='ovs-vsctl: Interface does not contain a column whose ' 'name matches "mtu_request"') self.assertFalse(self.br._ovs_supports_mtu_requests()) mock_db_list.assert_called_once_with('Interface', columns=['mtu_request']) def test__ovs_supports_mtu_requests_other_error(self): with mock.patch.object(self.br.ovsdb, 'db_list') as mock_db_list: mock_db_list.side_effect = processutils.ProcessExecutionError( stderr='other error') self.assertRaises(processutils.ProcessExecutionError, self.br._ovs_supports_mtu_requests) mock_db_list.assert_called_once_with('Interface', columns=['mtu_request']) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/unit/test_linux_net.py0000664000175000017500000004636715140131732022103 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 glob import os.path from unittest import mock import testtools from os_vif.internal.ip.api import ip as ip_lib from vif_plug_ovs import exception from vif_plug_ovs import linux_net from vif_plug_ovs import privsep class LinuxNetTest(testtools.TestCase): def setUp(self): super(LinuxNetTest, self).setUp() privsep.vif_plug.set_client_mode(False) @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "exists", return_value=False) def test_ensure_bridge(self, mock_dev_exists, mock_add, mock_disable_ipv6, mock_set_state, mock_arp_filtering): linux_net.ensure_bridge("br0") mock_dev_exists.assert_called_once_with("br0") mock_add.assert_called_once_with("br0", "bridge", ageing=0) mock_disable_ipv6.assert_called_once_with("br0") mock_set_state.assert_called_once_with("br0", "up") mock_arp_filtering.assert_called_once_with("br0") @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "exists", return_value=True) def test_ensure_bridge_exists(self, mock_dev_exists, mock_add, mock_disable_ipv6, mock_set_state, mock_arp_filtering): linux_net.ensure_bridge("br0") mock_dev_exists.assert_called_once_with("br0") mock_add.assert_not_called() mock_disable_ipv6.assert_called_once_with("br0") mock_set_state.assert_called_once_with("br0", "up") mock_arp_filtering.assert_called_once_with("br0") @mock.patch('builtins.open') @mock.patch("os.path.exists") def test__disable_ipv6(self, mock_exists, mock_open): exists_path = "/proc/sys/net/ipv6/conf/br0/disable_ipv6" mock_exists.return_value = False linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_not_called() mock_exists.reset_mock() mock_exists.return_value = True linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_called_once_with(exists_path, 'w') @mock.patch.object(os.path, 'exists', return_value=True) @mock.patch('builtins.open') def test__arp_filtering(self, mock_open, *args): mock_open.side_effect = mock.mock_open() linux_net._arp_filtering("br0") mock_open.assert_has_calls([ mock.call('/proc/sys/net/ipv4/conf/br0/arp_ignore', 'w'), mock.call('/proc/sys/net/ipv4/conf/br0/arp_announce', 'w')]) mock_open.side_effect.return_value.write.assert_has_calls([ mock.call('1'), mock.call('2')]) @mock.patch.object(ip_lib, "delete") @mock.patch.object(ip_lib, "exists", return_value=False) def test_delete_bridge_none(self, mock_dev_exists, mock_delete): linux_net.delete_bridge("br0", "vnet1") mock_delete.assert_not_called() mock_dev_exists.assert_called_once_with("br0") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(ip_lib, "delete") @mock.patch.object(ip_lib, "exists", return_value=True) def test_delete_bridge_exists(self, mock_dev_exists, mock_delete, mock_set_state): linux_net.delete_bridge("br0", "vnet1") mock_dev_exists.assert_has_calls([mock.call("br0"), mock.call("vnet1")]) mock_delete.assert_called_once_with("br0", check_exit_code=[0, 2, 254]) mock_set_state.assert_called_once_with("vnet1", "down") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(ip_lib, "delete") @mock.patch.object(ip_lib, "exists") def test_delete_interface_not_present(self, mock_dev_exists, mock_delete, mock_set_state): mock_dev_exists.return_value = next(lambda: (yield True), (yield False)) linux_net.delete_bridge("br0", "vnet1") mock_dev_exists.assert_has_calls([mock.call("br0"), mock.call("vnet1")]) mock_delete.assert_called_once_with("br0", check_exit_code=[0, 2, 254]) mock_set_state.assert_not_called() @mock.patch.object(ip_lib, "set") def test_add_bridge_port(self, mock_set): linux_net.add_bridge_port("br0", "vnet1") mock_set.assert_called_once_with("vnet1", master="br0") @mock.patch.object(linux_net, '_get_phys_switch_id') def test_is_switchdev_ioerror(self, mock__get_phys_switch_id): mock__get_phys_switch_id.side_effect = ([IOError()]) test_switchdev = linux_net._is_switchdev('pf_ifname') self.assertEqual(test_switchdev, False) @mock.patch.object(linux_net, '_get_phys_switch_id') def test_is_switchdev_empty(self, mock__get_phys_switch_id): mock__get_phys_switch_id.return_value = '' test_switchdev = linux_net._is_switchdev('pf_ifname') self.assertEqual(test_switchdev, False) @mock.patch.object(linux_net, '_get_phys_switch_id') def test_is_switchdev_positive(self, mock__get_phys_switch_id): mock__get_phys_switch_id.return_value = 'pf_sw_id' test_switchdev = linux_net._is_switchdev('pf_ifname') self.assertEqual(test_switchdev, True) def test_parse_vf_number(self): self.assertEqual(linux_net._parse_vf_number("0"), "0") self.assertEqual(linux_net._parse_vf_number("pf13vf42"), "42") self.assertEqual(linux_net._parse_vf_number("VF19@PF13"), "19") self.assertIsNone(linux_net._parse_vf_number("p7")) self.assertIsNone(linux_net._parse_vf_number("pf31")) self.assertIsNone(linux_net._parse_vf_number("g4rbl3d")) def test_parse_pf_number(self): self.assertIsNone(linux_net._parse_pf_number("0")) self.assertEqual(linux_net._parse_pf_number("pf13vf42"), "13") self.assertEqual(linux_net._parse_pf_number("VF19@PF13"), "13") self.assertIsNone(linux_net._parse_pf_number("p7")) self.assertEqual(linux_net._parse_pf_number("pf31"), "31") self.assertIsNone(linux_net._parse_pf_number("g4rbl3d")) @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_pf_func") @mock.patch.object(linux_net, "_get_phys_port_name") @mock.patch.object(linux_net, '_get_phys_switch_id') def test_get_representor_port(self, mock__get_phys_switch_id, mock__get_phys_port_name, mock__get_pf_func, mock_listdir): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock__get_phys_switch_id.return_value = 'pf_sw_id' mock__get_pf_func.return_value = "0" mock__get_phys_port_name.side_effect = (['1', "pf0vf1", "pf0vf2"]) ifname = linux_net.get_representor_port('pf_ifname', '2') self.assertEqual('rep_vf_2', ifname) @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_pf_func") @mock.patch.object(linux_net, "_get_phys_port_name") @mock.patch.object(linux_net, "_get_phys_switch_id") def test_get_representor_port_2_pfs( self, mock__get_phys_switch_id, mock__get_phys_port_name, mock__get_pf_func, mock_listdir): mock_listdir.return_value = [ 'pf_ifname1', 'pf_ifname2', 'rep_pf1_vf_1', 'rep_pf1_vf_2', 'rep_pf2_vf_1', 'rep_pf2_vf_2', ] mock__get_phys_switch_id.return_value = 'pf_sw_id' mock__get_pf_func.return_value = "2" mock__get_phys_port_name.side_effect = ( ["p1", "p2", "VF1@PF1", "pf2vf1", "vf2@pf1", "pf2vf2"]) ifname = linux_net.get_representor_port('pf_ifname2', '2') self.assertEqual('rep_pf2_vf_2', ifname) @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_pf_func") @mock.patch.object(linux_net, "_get_phys_switch_id") @mock.patch.object(linux_net, "_get_phys_port_name") def test_get_representor_port_not_found( self, mock__get_phys_port_name, mock__get_phys_switch_id, mock__get_pf_func, mock_listdir): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock__get_phys_switch_id.return_value = 'pf_sw_id' mock__get_pf_func.return_value = "0" mock__get_phys_port_name.side_effect = ( ["p0", "1", "2"]) self.assertRaises( exception.RepresentorNotFound, linux_net.get_representor_port, 'pf_ifname', '3'), @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_pf_func") @mock.patch.object(linux_net, "_get_phys_port_name") @mock.patch.object(linux_net, "_get_phys_switch_id") def test_get_representor_port_exception_io_error( self, mock__get_phys_switch_id, mock__get_phys_port_name, mock__get_pf_func, mock_listdir): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock__get_phys_switch_id.side_effect = ( ['pf_sw_id', 'pf_sw_id', IOError(), 'pf_sw_id', '2']) mock__get_pf_func.return_value = "0" mock__get_phys_port_name.side_effect = ( ["p0", "pf0vf0", "pf0vf1"]) self.assertRaises( exception.RepresentorNotFound, linux_net.get_representor_port, 'pf_ifname', '3') @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_pf_func") @mock.patch.object(linux_net, "_get_phys_port_name") @mock.patch.object(linux_net, "_get_phys_switch_id") def test_get_representor_port_exception_value_error( self, mock__get_phys_switch_id, mock__get_phys_port_name, mock__get_pf_func, mock_listdir): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock__get_phys_switch_id.return_value = 'pf_sw_id' mock__get_phys_port_name.side_effect = (['p0', '1', 'a']) mock__get_pf_func.return_value = "0" self.assertRaises( exception.RepresentorNotFound, linux_net.get_representor_port, 'pf_ifname', '3') @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, '_get_phys_switch_id') @mock.patch.object(linux_net, "_get_phys_port_name") def test_physical_function_interface_name( self, mock__get_phys_port_name, mock__get_phys_switch_id, mock_listdir): mock_listdir.return_value = ['foo', 'bar'] mock__get_phys_switch_id.side_effect = ( ['', 'valid_switch']) mock__get_phys_port_name.side_effect = (["p1"]) ifname = linux_net.get_ifname_by_pci_address( '0000:00:00.1', pf_interface=True, switchdev=False) self.assertEqual(ifname, 'foo') @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_phys_switch_id") @mock.patch.object(linux_net, "_get_phys_port_name") def test_physical_function_interface_name_with_switchdev( self, mock__get_phys_port_name, mock__get_phys_switch_id, mock_listdir): mock_listdir.return_value = ['foo', 'bar'] mock__get_phys_switch_id.side_effect = ( ['', 'valid_switch']) mock__get_phys_port_name.side_effect = (["p1s0"]) ifname = linux_net.get_ifname_by_pci_address( '0000:00:00.1', pf_interface=True, switchdev=True) self.assertEqual(ifname, 'bar') @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_phys_switch_id") @mock.patch.object(linux_net, "_get_phys_port_name") def test_physical_function_interface_name_with_representors( self, mock__get_phys_port_name, mock__get_phys_switch_id, mock_listdir): # Get the PF that matches the phys_port_name regex mock_listdir.return_value = ['enp2s0f0_0', 'enp2s0f0_1', 'enp2s0f0'] mock__get_phys_switch_id.side_effect = ( ['valid_switch', 'valid_switch', 'valid_switch']) mock__get_phys_port_name.side_effect = (["pf0vf0", "pf0vf1", "p0"]) ifname = linux_net.get_ifname_by_pci_address( '0000:00:00.1', pf_interface=True, switchdev=True) self.assertEqual(ifname, 'enp2s0f0') @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "_get_phys_switch_id") @mock.patch.object(linux_net, "_get_phys_port_name") def test_physical_function_interface_name_with_fallback_To_first_netdev( self, mock__get_phys_port_name, mock__get_phys_switch_id, mock_listdir): # Try with switchdev mode to get PF but fail because there is no match # for the phys_port_name then fallback to first interface found mock_listdir.return_value = ['enp2s0f0_0', 'enp2s0f0_1', 'enp2s0f0'] mock__get_phys_switch_id.side_effect = (['valid_switch', 'valid_switch', 'valid_switch']) mock__get_phys_port_name.side_effect = (["pf0vf0", "pf0vf1", "pf0vf2"]) ifname = linux_net.get_ifname_by_pci_address( '0000:00:00.1', pf_interface=True, switchdev=True) self.assertEqual(ifname, 'enp2s0f0_0') @mock.patch.object(os, 'listdir') def test_get_ifname_by_pci_address_exception(self, mock_listdir): mock_listdir.side_effect = OSError('No such file or directory') self.assertRaises( exception.PciDeviceNotFoundById, linux_net.get_ifname_by_pci_address, '0000:00:00.1' ) @mock.patch.object(os, 'readlink') @mock.patch.object(glob, 'iglob') def test_vf_number_found(self, mock_iglob, mock_readlink): mock_iglob.return_value = [ '/sys/bus/pci/devices/0000:00:00.1/physfn/virtfn3', ] mock_readlink.return_value = '../../0000:00:00.1' vf_num = linux_net.get_vf_num_by_pci_address('0000:00:00.1') self.assertEqual(vf_num, '3') @mock.patch.object(os, 'readlink') @mock.patch.object(glob, 'iglob') def test_vf_number_not_found(self, mock_iglob, mock_readlink): mock_iglob.return_value = [ '/sys/bus/pci/devices/0000:00:00.1/physfn/virtfn3', ] mock_readlink.return_value = '../../0000:00:00.2' self.assertRaises( exception.PciDeviceNotFoundById, linux_net.get_vf_num_by_pci_address, '0000:00:00.1' ) @mock.patch.object(os, 'readlink') @mock.patch.object(glob, 'iglob') def test_get_vf_num_by_pci_address_exception( self, mock_iglob, mock_readlink): mock_iglob.return_value = [ '/sys/bus/pci/devices/0000:00:00.1/physfn/virtfn3', ] mock_readlink.side_effect = OSError('No such file or directory') self.assertRaises( exception.PciDeviceNotFoundById, linux_net.get_vf_num_by_pci_address, '0000:00:00.1' ) @mock.patch('builtins.open') @mock.patch.object(os.path, 'isfile') def test__get_phys_port_name(self, mock_isfile, mock_open): mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.return_value = 'pf0vf0' mock_isfile.return_value = True phys_port_name = linux_net._get_phys_port_name("vf_ifname") self.assertEqual(phys_port_name, 'pf0vf0') @mock.patch.object(os.path, 'isfile') def test__get_phys_port_name_not_found(self, mock_isfile): mock_isfile.return_value = False phys_port_name = linux_net._get_phys_port_name("vf_ifname") self.assertIsNone(phys_port_name) @mock.patch('builtins.open') @mock.patch.object(os.path, 'isfile') def test__get_phys_switch_id(self, mock_isfile, mock_open): mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.return_value = '66e40000039b0398' mock_isfile.return_value = True phys_port_name = linux_net._get_phys_switch_id("ifname") self.assertEqual(phys_port_name, '66e40000039b0398') @mock.patch.object(os.path, 'isfile') def test__get_phys_switch_id_not_found(self, mock_isfile): mock_isfile.return_value = False phys_port_name = linux_net._get_phys_switch_id("ifname") self.assertIsNone(phys_port_name) @mock.patch.object(linux_net, "_update_device_mtu") @mock.patch.object(ip_lib, "set") @mock.patch.object(ip_lib, "add") def test_create_tap(self, mock_add, mock_set, mock_update_mtu): """Test basic tap device creation.""" linux_net.create_tap("tap0", 1500, "aa:bb:cc:dd:ee:ff", multiqueue=False) mock_add.assert_called_once_with("tap0", "tuntap", mode="tap", multiqueue=False, check_exit_code=[0, 17]) mock_set.assert_called_once_with("tap0", state="up", address="aa:bb:cc:dd:ee:ff", check_exit_code=[0, 2, 254]) mock_update_mtu.assert_called_once_with("tap0", 1500) @mock.patch.object(linux_net, "_update_device_mtu") @mock.patch.object(ip_lib, "set") @mock.patch.object(ip_lib, "add") def test_create_tap_with_multiqueue(self, mock_add, mock_set, mock_update_mtu): """Test tap device creation with multiqueue enabled.""" linux_net.create_tap("tap0", 1500, "aa:bb:cc:dd:ee:ff", multiqueue=True) mock_add.assert_called_once_with("tap0", "tuntap", mode="tap", multiqueue=True, check_exit_code=[0, 17]) mock_set.assert_called_once_with("tap0", state="up", address="aa:bb:cc:dd:ee:ff", check_exit_code=[0, 2, 254]) mock_update_mtu.assert_called_once_with("tap0", 1500) @mock.patch.object(linux_net, "_update_device_mtu") @mock.patch.object(ip_lib, "set") @mock.patch.object(ip_lib, "add") def test_create_tap_no_mtu(self, mock_add, mock_set, mock_update_mtu): """Test tap device creation without MTU.""" linux_net.create_tap("tap0", None, "aa:bb:cc:dd:ee:ff") mock_add.assert_called_once() mock_set.assert_called_once() mock_update_mtu.assert_called_once_with("tap0", None) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1770042330.0 os_vif-4.3.0/vif_plug_ovs/tests/unit/test_plugin.py0000664000175000017500000012476215140131732021370 0ustar00zuulzuul# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import testtools from os_vif.internal.ip.api import ip as ip_lib from os_vif import objects from os_vif.objects import fields from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs import linux_net from vif_plug_ovs import ovs from vif_plug_ovs.ovsdb import ovsdb_lib class PluginTest(testtools.TestCase): def __init__(self, *args, **kwargs): super(PluginTest, self).__init__(*args, **kwargs) objects.register_all() self.subnet_bridge_4 = objects.subnet.Subnet( cidr='101.168.1.0/24', dns=['8.8.8.8'], gateway='101.168.1.1', dhcp_server='191.168.1.1') self.subnet_bridge_6 = objects.subnet.Subnet( cidr='101:1db9::/64', gateway='101:1db9::1') self.subnets = objects.subnet.SubnetList( objects=[self.subnet_bridge_4, self.subnet_bridge_6]) self.network_ovs = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br0', subnets=self.subnets, vlan=99) self.network_ovs_trunk = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='%s01' % constants.TRUNK_BR_PREFIX, subnets=self.subnets, vlan=99) self.network_ovs_mtu = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br0', subnets=self.subnets, vlan=99, mtu=1234) self.profile_ovs = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='netdev') self.profile_ovs_system = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='system') # This is used for ironic with vif_type=smartnic self.profile_ovs_smart_nic = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_port=True) self.profile_ovs_no_datatype = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='') self.vif_ovs_hybrid = objects.vif.VIFBridge( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', bridge_name="qbrvif-xxx-yyy", port_profile=self.profile_ovs_no_datatype) self.vif_ovs = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', port_profile=self.profile_ovs) self.vif_ovs_system = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', port_profile=self.profile_ovs_system) # This is used for ironic with vif_type=smartnic self.vif_ovs_smart_nic = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='rep0-0', port_profile=self.profile_ovs_smart_nic) self.vif_vhostuser = objects.vif.VIFVHostUser( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, path='/var/run/openvswitch/vhub679325f-ca', mode='client', port_profile=self.profile_ovs) self.vif_vhostuser_trunk = objects.vif.VIFVHostUser( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs_trunk, path='/var/run/openvswitch/vhub679325f-ca', mode='client', port_profile=self.profile_ovs) self.vif_vhostuser_client = objects.vif.VIFVHostUser( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, path='/var/run/openvswitch/vhub679325f-ca', mode='server', # qemu server mode <=> ovs client mode port_profile=self.profile_ovs) self.vif_ovs_vf_passthrough = objects.vif.VIFHostDevice( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, dev_type=fields.VIFHostDeviceDevType.ETHERNET, dev_address='0002:24:12.3', bridge_name='br-int', port_profile=self.profile_ovs_system) self.vif_ovs_vf_dpdk = objects.vif.VIFHostDevice( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, dev_type=fields.VIFHostDeviceDevType.ETHERNET, dev_address='0002:24:12.3', port_profile=self.profile_ovs) self.instance = objects.instance_info.InstanceInfo( name='demo', uuid='f0000000-0000-0000-0000-000000000001') def test__get_vif_datapath_type(self): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) dp_type = plugin._get_vif_datapath_type( self.vif_ovs, datapath=constants.OVS_DATAPATH_SYSTEM) self.assertEqual(self.profile_ovs.datapath_type, dp_type) dp_type = plugin._get_vif_datapath_type( self.vif_ovs_hybrid, datapath=constants.OVS_DATAPATH_SYSTEM) self.assertEqual(constants.OVS_DATAPATH_SYSTEM, dp_type) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_create_vif_port(self, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, mtu=plugin.config.network_device_mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_create_vif_port_mtu_in_model(self, mock_create_ovs_vif_port): self.vif_ovs.network = self.network_ovs_mtu plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, mtu=self.network_ovs_mtu.mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_isolate_port_no_isolate_vif_no_port( self, mock_port_exists, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False with mock.patch.object(plugin.config, 'isolate_vif', False): plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, mtu=plugin.config.network_device_mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_isolate_port_isolate_vif_no_port( self, mock_port_exists, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False with mock.patch.object(plugin.config, 'isolate_vif', True): plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, mtu=plugin.config.network_device_mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE, tag=constants.DEAD_VLAN, vlan_mode='trunk', trunks=constants.DEAD_VLAN ) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_isolate_port_isolate_vif_port_exists( self, mock_port_exists, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = True with mock.patch.object(plugin.config, 'isolate_vif', True): plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, mtu=plugin.config.network_device_mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_qos_port_bridge_true_port_new( self, mock_port_exists, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False port_bridge_name = "port-bridge-xxx" # _create_vif_port as from _plug_port_bridge plugin._create_vif_port( self.vif_ovs_system, mock.sentinel.vif_name, self.instance, bridge=port_bridge_name, set_ids=False) # port existence should be checked on the per-port bridge mock_port_exists.assert_called_once_with( mock.sentinel.vif_name, port_bridge_name) # qos_type should be set for the new port mock_create_ovs_vif_port.assert_called_once_with( port_bridge_name, mock.sentinel.vif_name, self.vif_ovs_system.port_profile.interface_id, self.vif_ovs_system.address, self.instance.uuid, mtu=plugin.config.network_device_mtu, set_ids=False, qos_type="linux-noop") @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_qos_port_bridge_true_port_exists( self, mock_port_exists, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = True port_bridge_name = "port-bridge-xxx" # _create_vif_port as from _plug_port_bridge plugin._create_vif_port( self.vif_ovs_system, mock.sentinel.vif_name, self.instance, bridge=port_bridge_name, set_ids=False) # port existence should be checked on the per-port bridge mock_port_exists.assert_called_once_with( mock.sentinel.vif_name, port_bridge_name) # qos_type should not be set for the existing port mock_create_ovs_vif_port.assert_called_once_with( port_bridge_name, mock.sentinel.vif_name, self.vif_ovs_system.port_profile.interface_id, self.vif_ovs_system.address, self.instance.uuid, mtu=plugin.config.network_device_mtu, set_ids=False) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_qos_port_bridge_false_port_new( self, mock_port_exists, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False # _create_vif_port as from _plug_vif_generic plugin._create_vif_port( self.vif_ovs_system, mock.sentinel.vif_name, self.instance) mock_port_exists.assert_called_once_with( mock.sentinel.vif_name, self.vif_ovs_system.network.bridge) # qos_type should be set for the new port mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs_system.network.bridge, mock.sentinel.vif_name, self.vif_ovs_system.port_profile.interface_id, self.vif_ovs_system.address, self.instance.uuid, mtu=plugin.config.network_device_mtu, qos_type="linux-noop") @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_qos_port_bridge_false_port_exists( self, mock_port_exists, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = True # _create_vif_port as from _plug_vif_generic plugin._create_vif_port( self.vif_ovs_system, mock.sentinel.vif_name, self.instance) mock_port_exists.assert_called_once_with( mock.sentinel.vif_name, self.vif_ovs_system.network.bridge) # qos_type should not be set for the existing port mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs_system.network.bridge, mock.sentinel.vif_name, self.vif_ovs_system.port_profile.interface_id, self.vif_ovs_system.address, self.instance.uuid, mtu=plugin.config.network_device_mtu) @mock.patch.object(ovs.OvsPlugin, '_plug_vif_generic') def test_plug_ovs_port_bridge_false(self, plug_vif_generic): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) with mock.patch.object(plugin.config, 'per_port_bridge', False): plugin.plug(self.vif_ovs, self.instance) plug_vif_generic.assert_called_once_with( self.vif_ovs, self.instance) @mock.patch.object(ovs.OvsPlugin, '_plug_port_bridge') def test_plug_ovs_port_bridge_true(self, plug_vif): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) with mock.patch.object(plugin.config, 'per_port_bridge', True): plugin.plug(self.vif_ovs, self.instance) plug_vif.assert_called_once_with(self.vif_ovs, self.instance) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, "_create_vif_port") def test_plug_vif_generic(self, create_port, ensure_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._plug_vif_generic(self.vif_ovs, self.instance) ensure_bridge.assert_called_once() # NOTE(sean-k-mooney): the interface will be plugged # by libvirt so we assert _create_vif_port is not called. create_port.assert_not_called() @mock.patch.object(linux_net, 'set_interface_state') @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_update_vif_port') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') @mock.patch.object(linux_net, 'add_bridge_port') @mock.patch.object(linux_net, 'update_veth_pair') @mock.patch.object(linux_net, 'create_veth_pair') @mock.patch.object(ip_lib, 'exists') @mock.patch.object(linux_net, 'ensure_bridge') def test_plug_ovs_bridge(self, ensure_bridge, device_exists, create_veth_pair, update_veth_pair, add_bridge_port, _create_vif_port, _update_vif_port, ensure_ovs_bridge, set_interface_state): dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_ovs_hybrid) calls = { 'device_exists': [mock.call('qvob679325f-ca')], 'create_veth_pair': [mock.call('qvbb679325f-ca', 'qvob679325f-ca', 1500)], 'update_veth_pair': [mock.call('qvbb679325f-ca', 'qvob679325f-ca', 1500)], 'ensure_bridge': [mock.call('qbrvif-xxx-yyy')], 'set_interface_state': [mock.call('qbrvif-xxx-yyy', 'up')], 'add_bridge_port': [mock.call('qbrvif-xxx-yyy', 'qvbb679325f-ca')], '_update_vif_port': [mock.call(self.vif_ovs_hybrid, 'qvob679325f-ca')], '_create_vif_port': [mock.call(self.vif_ovs_hybrid, 'qvob679325f-ca', self.instance)], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } # plugging new devices should result in devices being created device_exists.return_value = False plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs_hybrid, self.instance) ensure_bridge.assert_has_calls(calls['ensure_bridge']) device_exists.assert_has_calls(calls['device_exists']) create_veth_pair.assert_has_calls(calls['create_veth_pair']) update_veth_pair.assert_not_called() _update_vif_port.assert_not_called() add_bridge_port.assert_has_calls(calls['add_bridge_port']) _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) # reset call stacks create_veth_pair.reset_mock() _create_vif_port.reset_mock() # plugging existing devices should result in devices being updated device_exists.return_value = True plugin.plug(self.vif_ovs_hybrid, self.instance) create_veth_pair.assert_not_called() _create_vif_port.assert_not_called() update_veth_pair.assert_has_calls(calls['update_veth_pair']) _update_vif_port.assert_has_calls(calls['_update_vif_port']) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_ovs_port_bridge_false(self, unplug, delete_ovs_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) with mock.patch.object(plugin.config, 'per_port_bridge', False): plugin.unplug(self.vif_ovs, self.instance) unplug.assert_called_once_with(self.vif_ovs, self.instance) delete_ovs_bridge.assert_not_called() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_unplug_port_bridge') def test_unplug_ovs_port_bridge_true(self, unplug, delete_ovs_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) with mock.patch.object(plugin.config, 'per_port_bridge', True): plugin.unplug(self.vif_ovs, self.instance) unplug.assert_called_once_with(self.vif_ovs, self.instance) delete_ovs_bridge.assert_not_called() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_vif_generic(self, delete_port, delete_ovs_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_vif_generic(self.vif_ovs, self.instance) delete_port.assert_called_once() delete_ovs_bridge.assert_not_called() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(linux_net, 'delete_bridge') def test_unplug_ovs_bridge(self, delete_bridge, delete_ovs_vif_port, delete_ovs_bridge): calls = { 'delete_bridge': [mock.call('qbrvif-xxx-yyy', 'qvbb679325f-ca')], 'delete_ovs_vif_port': [mock.call( 'br0', 'qvob679325f-ca', qos_type='linux-noop' )] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs_hybrid, self.instance) delete_bridge.assert_has_calls(calls['delete_bridge']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) delete_ovs_bridge.assert_not_called() @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vhostuser(self, _create_vif_port): dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_vhostuser) calls = [mock.call( self.vif_vhostuser, 'vhub679325f-ca', self.instance, interface_type='dpdkvhostuser', datapath_type=dp_type)] plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_vhostuser, self.instance) _create_vif_port.assert_has_calls(calls) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_plug_ovs_vhostuser_client(self, create_ovs_vif_port): dp_type = ovs.OvsPlugin._get_vif_datapath_type( self.vif_vhostuser_client) calls = [ mock.call( 'br0', 'vhub679325f-ca', 'e65867e0-9340-4a7f-a256-09af6eb7a3aa', 'ca:fe:de:ad:be:ef', 'f0000000-0000-0000-0000-000000000001', mtu=1500, interface_type='dpdkvhostuserclient', vhost_server_path='/var/run/openvswitch/vhub679325f-ca', datapath_type=dp_type )] plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_vhostuser_client, self.instance) create_ovs_vif_port.assert_has_calls(calls) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vhostuser(self, delete_ovs_vif_port, delete_ovs_bridge): calls = { 'delete_ovs_vif_port': [mock.call('br0', 'vhub679325f-ca')] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_vhostuser, self.instance) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) delete_ovs_bridge.assert_not_called() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vhostuser_trunk(self, delete_ovs_vif_port, delete_ovs_bridge): bridge_name = '%s01' % constants.TRUNK_BR_PREFIX calls = { 'delete_ovs_vif_port': [mock.call(bridge_name, 'vhub679325f-ca')] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_vhostuser_trunk, self.instance) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) delete_ovs_bridge.assert_called_once_with(bridge_name) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(linux_net, 'get_ifname_by_pci_address') @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_representor_port') @mock.patch.object(linux_net, 'set_interface_state') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vf_passthrough(self, _create_vif_port, set_interface_state, get_representor_port, get_vf_num_by_pci_address, get_ifname_by_pci_address, ensure_ovs_bridge): get_ifname_by_pci_address.return_value = 'eth0' get_vf_num_by_pci_address.return_value = '2' get_representor_port.return_value = 'eth0_2' calls = { 'ensure_ovs_bridge': [mock.call('br0', constants.OVS_DATAPATH_SYSTEM)], 'get_ifname_by_pci_address': [mock.call('0002:24:12.3', pf_interface=True, switchdev=True)], 'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')], 'get_representor_port': [mock.call('eth0', '2')], 'set_interface_state': [mock.call('eth0_2', 'up')], '_create_vif_port': [mock.call( self.vif_ovs_vf_passthrough, 'eth0_2', self.instance)] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs_vf_passthrough, self.instance) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) get_ifname_by_pci_address.assert_has_calls( calls['get_ifname_by_pci_address']) get_vf_num_by_pci_address.assert_has_calls( calls['get_vf_num_by_pci_address']) get_representor_port.assert_has_calls( calls['get_representor_port']) set_interface_state.assert_has_calls(calls['set_interface_state']) _create_vif_port.assert_has_calls(calls['_create_vif_port']) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(linux_net, 'get_ifname_by_pci_address') @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_representor_port') @mock.patch.object(linux_net, 'set_interface_state') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vf_passthrough(self, delete_ovs_vif_port, set_interface_state, get_representor_port, get_vf_num_by_pci_address, get_ifname_by_pci_address, delete_ovs_bridge): calls = { 'get_ifname_by_pci_address': [mock.call('0002:24:12.3', pf_interface=True, switchdev=True)], 'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')], 'get_representor_port': [mock.call('eth0', '2')], 'set_interface_state': [mock.call('eth0_2', 'down')], 'delete_ovs_vif_port': [ mock.call( 'br0', 'eth0_2', delete_netdev=False, qos_type='linux-noop' ) ] } get_ifname_by_pci_address.return_value = 'eth0' get_vf_num_by_pci_address.return_value = '2' get_representor_port.return_value = 'eth0_2' plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs_vf_passthrough, self.instance) get_ifname_by_pci_address.assert_has_calls( calls['get_ifname_by_pci_address']) get_vf_num_by_pci_address.assert_has_calls( calls['get_vf_num_by_pci_address']) get_representor_port.assert_has_calls( calls['get_representor_port']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) set_interface_state.assert_has_calls(calls['set_interface_state']) delete_ovs_bridge.assert_not_called() @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, "_create_vif_port") def test_plug_vif_ovs_ironic_smart_nic(self, create_port, ensure_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) with mock.patch.object(plugin.config, 'per_port_bridge', False): plugin.plug(self.vif_ovs_smart_nic, self.instance) ensure_bridge.assert_called_once() create_port.assert_called_once() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_vif_ovs_smart_nic(self, delete_port, delete_ovs_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) with mock.patch.object(plugin.config, 'per_port_bridge', False): plugin.unplug(self.vif_ovs_smart_nic, self.instance) delete_port.assert_called_once() delete_ovs_bridge.assert_not_called() @mock.patch.object(linux_net, 'get_dpdk_representor_port_name') @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_pf_pci_from_vf') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vf_dpdk(self, _create_vif_port, get_pf_pci_from_vf, get_vf_num_by_pci_address, ensure_ovs_bridge, get_dpdk_representor_port_name): pf_pci = self.vif_ovs_vf_dpdk.dev_address devname = 'vfrb679325f-ca' get_vf_num_by_pci_address.return_value = '2' get_pf_pci_from_vf.return_value = pf_pci get_dpdk_representor_port_name.return_value = devname calls = { 'ensure_ovs_bridge': [mock.call('br0', constants.OVS_DATAPATH_NETDEV)], 'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')], 'get_pf_pci_from_vf': [mock.call(pf_pci)], 'get_dpdk_representor_port_name': [mock.call( self.vif_ovs_vf_dpdk.id)], '_create_vif_port': [mock.call( self.vif_ovs_vf_dpdk, devname, self.instance, interface_type='dpdk', pf_pci=pf_pci, vf_num='2')]} plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs_vf_dpdk, self.instance) ensure_ovs_bridge.assert_has_calls( calls['ensure_ovs_bridge']) get_vf_num_by_pci_address.assert_has_calls( calls['get_vf_num_by_pci_address']) get_pf_pci_from_vf.assert_has_calls( calls['get_pf_pci_from_vf']) get_dpdk_representor_port_name.assert_has_calls( calls['get_dpdk_representor_port_name']) _create_vif_port.assert_has_calls( calls['_create_vif_port']) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') @mock.patch.object(linux_net, 'get_dpdk_representor_port_name') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vf_dpdk(self, delete_ovs_vif_port, get_dpdk_representor_port_name, delete_ovs_bridge): devname = 'vfrb679325f-ca' get_dpdk_representor_port_name.return_value = devname calls = { 'get_dpdk_representor_port_name': [mock.call( self.vif_ovs_vf_dpdk.id)], 'delete_ovs_vif_port': [ mock.call( 'br0', devname, delete_netdev=False, qos_type=None ) ] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs_vf_dpdk, self.instance) get_dpdk_representor_port_name.assert_has_calls( calls['get_dpdk_representor_port_name']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) delete_ovs_bridge.assert_not_called() @mock.patch.object(ovsdb_lib.BaseOVS, 'create_patch_port_pair') @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, "_create_vif_port") def test_plug_port_bridge( self, create_port, ensure_bridge, create_patch_port_pair): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._plug_port_bridge(self.vif_ovs, self.instance) calls = [ mock.call('br0', 'netdev'), mock.call('pbb679325f-ca8', 'netdev') ] ensure_bridge.assert_has_calls(calls) create_port.assert_called_once() create_patch_port_pair.assert_called_once() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') def test_unplug_port_bridge( self, delete_ovs_bridge, delete_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_port_bridge(self.vif_ovs, self.instance) calls = [ mock.call('br0', 'ibpb679325f-ca89-4ee0-a8be-6db1409b69ea'), mock.call( 'pbb679325f-ca8', 'pbpb679325f-ca89-4ee0-a8be-6db1409b69ea'), mock.call('pbb679325f-ca8', 'tap-xxx-yyy-zzz', qos_type=None) ] delete_ovs_vif_port.assert_has_calls(calls) delete_ovs_bridge.assert_called_once_with('pbb679325f-ca8') @mock.patch.object(ip_lib, 'exists', return_value=True) @mock.patch.object(ovs.OvsPlugin, '_unplug_bridge') def test_unplug_hybrid_bridge(self, m_unplug_bridge, m_ip_lib_exists): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs, self.instance) m_unplug_bridge.assert_called_once() @mock.patch.object(ip_lib, 'exists', return_value=False) @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_ovs(self, m_unplug_generic, m_ip_lib_exists): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs, self.instance) m_unplug_generic.assert_called_once() @mock.patch.object(linux_net, 'create_tap') @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_with_tap_creation( self, mock_port_exists, mock_create_ovs_vif_port, mock_create_tap): """Test that create_tap is called when create_tap flag is set.""" # Create a profile with create_tap=True profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True) vif_with_tap = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', port_profile=profile_with_tap) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False plugin._create_vif_port( vif_with_tap, 'tap-xxx-yyy-zzz', self.instance) # Verify create_tap was called with correct parameters mock_create_tap.assert_called_once_with( 'tap-xxx-yyy-zzz', plugin.config.network_device_mtu, 'ca:fe:de:ad:be:ef', multiqueue=False) @mock.patch.object(linux_net, 'create_tap') @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_with_tap_and_multiqueue( self, mock_port_exists, mock_create_ovs_vif_port, mock_create_tap): """Test that create_tap is called with multiqueue when both are set.""" # Create a profile with create_tap=True and multiqueue=True profile_with_tap_mq = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True, multiqueue=True) vif_with_tap_mq = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', port_profile=profile_with_tap_mq) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False plugin._create_vif_port( vif_with_tap_mq, 'tap-xxx-yyy-zzz', self.instance) # Verify create_tap was called with multiqueue=True mock_create_tap.assert_called_once_with( 'tap-xxx-yyy-zzz', plugin.config.network_device_mtu, 'ca:fe:de:ad:be:ef', multiqueue=True) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_tap_not_supported_vhostuser( self, mock_port_exists, mock_create_ovs_vif_port): """Test that TapCreationNotSupported is raised for VIFVHostUser.""" # Create a VIFVHostUser with create_tap=True profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True) vif_vhostuser_with_tap = objects.vif.VIFVHostUser( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, path='/var/run/openvswitch/vhub679325f-ca', mode='client', port_profile=profile_with_tap) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False # Verify exception is raised self.assertRaises( exception.TapCreationNotSupported, plugin._create_vif_port, vif_vhostuser_with_tap, 'vhub679325f-ca', self.instance) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists') def test_create_vif_port_tap_not_supported_hostdevice( self, mock_port_exists, mock_create_ovs_vif_port): """Test that TapCreationNotSupported is raised for VIFHostDevice.""" # Create a VIFHostDevice with create_tap=True profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True) vif_hostdevice_with_tap = objects.vif.VIFHostDevice( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, dev_type=fields.VIFHostDeviceDevType.ETHERNET, dev_address='0002:24:12.3', port_profile=profile_with_tap) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) mock_port_exists.return_value = False # Verify exception is raised self.assertRaises( exception.TapCreationNotSupported, plugin._create_vif_port, vif_hostdevice_with_tap, 'tap-xxx-yyy-zzz', self.instance) @mock.patch.object(ip_lib, 'exists', return_value=True) @mock.patch.object(linux_net, 'delete_net_dev') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_vif_generic_deletes_tap( self, mock_delete_ovs_vif_port, mock_delete_net_dev, mock_exists): """Test that tap device is deleted when unplugging with create_tap=True. """ # Create a VIF with create_tap=True profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True) vif_with_tap = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', port_profile=profile_with_tap) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_vif_generic(vif_with_tap, self.instance) # Verify delete_net_dev was called with vif_name mock_delete_net_dev.assert_called_once_with('tap-xxx-yyy-zzz') @mock.patch.object(linux_net, 'delete_net_dev') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_vif_generic_no_tap_deletion_when_not_created( self, mock_delete_ovs_vif_port, mock_delete_net_dev): """Test that tap device is not deleted when create_tap=False.""" # Use default vif_ovs which has create_tap=False (or unset) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_vif_generic(self.vif_ovs, self.instance) # Verify delete_net_dev was not called mock_delete_net_dev.assert_not_called() @mock.patch.object(ip_lib, 'exists', return_value=True) @mock.patch.object(linux_net, 'delete_net_dev') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge') def test_unplug_port_bridge_deletes_tap( self, mock_delete_ovs_bridge, mock_delete_ovs_vif_port, mock_delete_net_dev, mock_exists): """Test that tap device is deleted when unplugging port bridge with create_tap=True. """ # Create a VIF with create_tap=True profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_tap=True) vif_with_tap = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', port_profile=profile_with_tap) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_port_bridge(vif_with_tap, self.instance) # Verify delete_net_dev was called with vif_name mock_delete_net_dev.assert_called_once_with('tap-xxx-yyy-zzz')