././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/0000775000175000017500000000000000000000000013031 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/.coveragerc0000664000175000017500000000011500000000000015147 0ustar00zuulzuul00000000000000[run] branch = True source = os_vif omit = os_vif/tests/*,os_vif/openstack/* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/.mailmap0000664000175000017500000000013100000000000014445 0ustar00zuulzuul00000000000000# Format is: # # ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/.stestr.conf0000664000175000017500000000006200000000000015300 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-.} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/.zuul.yaml0000664000175000017500000001213100000000000014770 0ustar00zuulzuul00000000000000- 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 tox_envlist: full devstack_plugins: neutron: https://opendev.org/openstack/neutron.git devstack_localrc: USE_PYTHON3: true 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 - job: name: os-vif-linuxbridge parent: os-vif-tempest-base description: | os-vif linux bridge job derived from neutron-tempest-linuxbridge 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: linuxbridge Q_ML2_PLUGIN_MECHANISM_DRIVERS: linuxbridge Q_ML2_TENANT_NETWORK_TYPE: vxlan devstack_local_conf: post-config: "/$NEUTRON_CORE_PLUGIN_CONF": ml2: type_drivers: flat,vlan,local,vxlan ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: tunnel_types: vxlan securitygroup: firewall_driver: iptables - project: templates: - check-requirements - openstack-lower-constraints-jobs - openstack-python3-yoga-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 - openstack-cover-jobs check: jobs: - kuryr-kubernetes-tempest: voting: false - openstack-tox-functional-ovs-with-sudo - os-vif-ovn - os-vif-ovs-iptables - os-vif-linuxbridge gate: jobs: - openstack-tox-functional-ovs-with-sudo - os-vif-ovn - os-vif-ovs-iptables - os-vif-linuxbridge ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/AUTHORS0000664000175000017500000000561000000000000014103 0ustar00zuulzuul00000000000000Adrian Chiris Alin Balutoiu Andreas Jaeger Brian Haley Cao Xuan Hoang Carlos Goncalves ChangBo Guo(gcb) Charles Short Claudiu Belu Corey Bryant 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 Jan Gutter Janonymous Jay Pipes Kevin Benton Lucian Petrut Mamduh Mamduh Alassi Maria Malyarova Masayuki Igawa Matt Riedemann Michał Dulko Moshe Levi OpenStack Release Bot Przemyslaw Lal 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 Sergey Belous Spencer Yu Sriharsha Basavapatna Stephen Finucane Swapnil Kulkarni (coolsvap) Takashi NATSUME Takashi Natsume Thomas Bechtold Tony Breeds Tony Xu Vieri <15050873171@163.com> Vu Cong Tuan XinxinShen YAMAMOTO Takashi ZhijunWei blue55 caoyuan ericxiett gecong1973 jacky06 kavithahr lingyongxu loooosy melanie witt melissaml pengyuesheng pranabjb qingszhao shaleijie sunjia vagrant wu.shiming ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/CONTRIBUTING.rst0000664000175000017500000000112700000000000015473 0ustar00zuulzuul00000000000000The 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/ChangeLog0000664000175000017500000003026100000000000014605 0ustar00zuulzuul00000000000000CHANGES ======= 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/HACKING.rst0000664000175000017500000000020600000000000014625 0ustar00zuulzuul00000000000000os_vif Style Commandments ========================= Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/LICENSE0000664000175000017500000002363700000000000014051 0ustar00zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/PKG-INFO0000664000175000017500000000403600000000000014131 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: os_vif Version: 2.7.1 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 License: UNKNOWN Description: ======================== 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 Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/README.rst0000664000175000017500000000151300000000000014520 0ustar00zuulzuul00000000000000======================== 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/bindep.txt0000664000175000017500000000053600000000000015037 0ustar00zuulzuul00000000000000# This is a cross-platform list tracking distribution packages needed for install and tests; # see https://docs.openstack.org/infra/bindep/ for additional information. 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] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9785383 os_vif-2.7.1/doc/0000775000175000017500000000000000000000000013576 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/requirements.txt0000664000175000017500000000013400000000000017060 0ustar00zuulzuul00000000000000sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9785383 os_vif-2.7.1/doc/source/0000775000175000017500000000000000000000000015076 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/conf.py0000664000175000017500000000334100000000000016376 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # -- 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 = u'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' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9785383 os_vif-2.7.1/doc/source/contributor/0000775000175000017500000000000000000000000017450 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/contributor/contributing.rst0000664000175000017500000000406400000000000022715 0ustar00zuulzuul00000000000000============================ 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/index.rst0000664000175000017500000000177600000000000016752 0ustar00zuulzuul00000000000000====== 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9785383 os_vif-2.7.1/doc/source/reference/0000775000175000017500000000000000000000000017034 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/reference/glossary.rst0000664000175000017500000001564600000000000021445 0ustar00zuulzuul00000000000000======== 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/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9785383 os_vif-2.7.1/doc/source/user/0000775000175000017500000000000000000000000016054 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/user/host-info.rst0000664000175000017500000000536300000000000020523 0ustar00zuulzuul00000000000000================ 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. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9825382 os_vif-2.7.1/doc/source/user/plugins/0000775000175000017500000000000000000000000017535 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/user/plugins/linux-bridge.rst0000664000175000017500000000126200000000000022661 0ustar00zuulzuul00000000000000============ 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/user/plugins/noop.rst0000664000175000017500000000132400000000000021242 0ustar00zuulzuul00000000000000===== 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/user/plugins/ovs.rst0000664000175000017500000000356300000000000021105 0ustar00zuulzuul00000000000000============ 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/user/usage.rst0000664000175000017500000000446000000000000017716 0ustar00zuulzuul00000000000000===== 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... ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/doc/source/user/vif-types.rst0000664000175000017500000000416400000000000020541 0ustar00zuulzuul00000000000000========= 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! ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/lower-constraints.txt0000664000175000017500000000057700000000000017300 0ustar00zuulzuul00000000000000# from requirements.txt pbr==2.0.0 netaddr==0.7.18 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.versionedobjects==1.28.0 ovsdbapp==0.12.1 pyroute2==0.5.2;sys_platform!='win32' stevedore==1.20.0 debtcollector==1.19.0 # from test-requirements.txt coverage==4.0 oslotest==1.10.0 ovs==2.9.2 stestr==1.0.0 testscenarios==0.4 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9825382 os_vif-2.7.1/os_vif/0000775000175000017500000000000000000000000014316 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/__init__.py0000664000175000017500000001255400000000000016436 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/exception.py0000664000175000017500000000641400000000000016673 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from 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") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/i18n.py0000664000175000017500000000202000000000000015441 0ustar00zuulzuul00000000000000# 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9865384 os_vif-2.7.1/os_vif/internal/0000775000175000017500000000000000000000000016132 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/__init__.py0000664000175000017500000000170700000000000020250 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import 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() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9865384 os_vif-2.7.1/os_vif/internal/ip/0000775000175000017500000000000000000000000016542 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/ip/__init__.py0000664000175000017500000000000000000000000020641 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/ip/api.py0000664000175000017500000000157000000000000017670 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from oslo_log import log as logging if os.name == 'nt': from os_vif.internal.ip.windows.impl_netifaces import \ Netifaces as ip_lib_class else: from os_vif.internal.ip.linux.impl_pyroute2 import \ PyRoute2 as ip_lib_class LOG = logging.getLogger(__name__) ip = ip_lib_class() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/ip/ip_command.py0000664000175000017500000000534300000000000021227 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc class IpCommand(metaclass=abc.ABCMeta): TYPE_VETH = 'veth' TYPE_VLAN = 'vlan' TYPE_BRIDGE = 'bridge' @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): """Method to add an interface. :param device: A network device (string) :param dev_type: String network device type (TYPE_VETH, TYPE_VLAN) :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. :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 """ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9865384 os_vif-2.7.1/os_vif/internal/ip/linux/0000775000175000017500000000000000000000000017701 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/ip/linux/__init__.py0000664000175000017500000000000000000000000022000 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/ip/linux/impl_pyroute2.py0000664000175000017500000001260700000000000023073 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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): 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 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9865384 os_vif-2.7.1/os_vif/internal/ip/windows/0000775000175000017500000000000000000000000020234 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/ip/windows/__init__.py0000664000175000017500000000000000000000000022333 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/internal/ip/windows/impl_netifaces.py0000664000175000017500000000332100000000000023567 0ustar00zuulzuul00000000000000# Derived from: neutron/agent/windows/ip_lib.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 netifaces from oslo_log import log as logging from os_vif import exception from os_vif.internal.ip import ip_command LOG = logging.getLogger(__name__) class Netifaces(ip_command.IpCommand): def exists(self, device): """Return True if the device exists in the namespace.""" try: return bool(netifaces.ifaddresses(device)) except ValueError: LOG.warning("The device does not exist on the system: %s", device) return False except OSError: LOG.error("Failed to get interface addresses: %s", device) return False def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None, master=None): exception.NotImplementedForOS(function='ip.set', os='Windows') def add(self, device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None): exception.NotImplementedForOS(function='ip.add', os='Windows') def delete(self, device, check_exit_code=None): exception.NotImplementedForOS(function='ip.delete', os='Windows') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9905384 os_vif-2.7.1/os_vif/objects/0000775000175000017500000000000000000000000015747 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/__init__.py0000664000175000017500000000156300000000000020065 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. 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') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/base.py0000664000175000017500000000216300000000000017235 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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__() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/fields.py0000664000175000017500000000334100000000000017570 0ustar00zuulzuul00000000000000# 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/fixed_ip.py0000664000175000017500000000240200000000000020106 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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'), } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/host_info.py0000664000175000017500000001505200000000000020314 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/instance_info.py0000664000175000017500000000231200000000000021136 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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(), } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/network.py0000664000175000017500000000412600000000000020015 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/route.py0000664000175000017500000000251000000000000017455 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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'), } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/subnet.py0000664000175000017500000000266200000000000017627 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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'), } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/objects/vif.py0000664000175000017500000005224400000000000017114 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from 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.3' 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), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) 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.3' 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') else: super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.3') @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.3' 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') else: super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.3') @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') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/opts.py0000664000175000017500000000164700000000000015665 0ustar00zuulzuul00000000000000# 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] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/plugin.py0000664000175000017500000000573000000000000016173 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc from 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9905384 os_vif-2.7.1/os_vif/tests/0000775000175000017500000000000000000000000015460 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/__init__.py0000664000175000017500000000000000000000000017557 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9905384 os_vif-2.7.1/os_vif/tests/functional/0000775000175000017500000000000000000000000017622 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/functional/__init__.py0000664000175000017500000000000000000000000021721 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/functional/base.py0000664000175000017500000001161500000000000021112 0ustar00zuulzuul00000000000000# 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9905384 os_vif-2.7.1/os_vif/tests/functional/internal/0000775000175000017500000000000000000000000021436 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/functional/internal/__init__.py0000664000175000017500000000000000000000000023535 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9905384 os_vif-2.7.1/os_vif/tests/functional/internal/command/0000775000175000017500000000000000000000000023054 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/functional/internal/command/__init__.py0000664000175000017500000000000000000000000025153 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9905384 os_vif-2.7.1/os_vif/tests/functional/internal/command/ip/0000775000175000017500000000000000000000000023464 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/functional/internal/command/ip/__init__.py0000664000175000017500000000000000000000000025563 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py0000664000175000017500000002303600000000000027713 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import re 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) 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 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)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/functional/privsep.py0000664000175000017500000000147000000000000021666 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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], ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9945385 os_vif-2.7.1/os_vif/tests/unit/0000775000175000017500000000000000000000000016437 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/__init__.py0000664000175000017500000000000000000000000020536 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/base.py0000664000175000017500000000143300000000000017724 0ustar00zuulzuul00000000000000# -*- 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9945385 os_vif-2.7.1/os_vif/tests/unit/internal/0000775000175000017500000000000000000000000020253 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/internal/__init__.py0000664000175000017500000000000000000000000022352 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9945385 os_vif-2.7.1/os_vif/tests/unit/internal/ip/0000775000175000017500000000000000000000000020663 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/internal/ip/__init__.py0000664000175000017500000000000000000000000022762 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9945385 os_vif-2.7.1/os_vif/tests/unit/internal/ip/linux/0000775000175000017500000000000000000000000022022 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/internal/ip/linux/__init__.py0000664000175000017500000000000000000000000024121 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/internal/ip/linux/test_impl_pyroute2.py0000664000175000017500000001630400000000000026251 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from 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') self.ip_link = self.ip_link_p.start() def test_set(self): with mock.patch.object(iproute.IPRoute, 'link_lookup', return_value=[1]) 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]) 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=[]) 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]) 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=[]) 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]) 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=[]) 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]) 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]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/internal/ip/test_api.py0000664000175000017500000000260700000000000023052 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import importlib from unittest import mock from os_vif.internal.ip import api from os_vif.tests.unit import base class TestIpApi(base.TestCase): @staticmethod def _reload_original_os_module(): importlib.reload(api) def test_get_impl_windows(self): self.addCleanup(self._reload_original_os_module) with mock.patch('os.name', 'nt'): importlib.reload(api) from os_vif.internal.ip.windows import impl_netifaces self.assertIsInstance(api.ip, impl_netifaces.Netifaces) def test_get_impl_linux(self): self.addCleanup(self._reload_original_os_module) with mock.patch('os.name', 'posix'): importlib.reload(api) from os_vif.internal.ip.linux import impl_pyroute2 self.assertIsInstance(api.ip, impl_pyroute2.PyRoute2) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9945385 os_vif-2.7.1/os_vif/tests/unit/internal/ip/windows/0000775000175000017500000000000000000000000022355 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/internal/ip/windows/__init__.py0000664000175000017500000000000000000000000024454 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/internal/ip/windows/test_impl_netifaces.py0000664000175000017500000000371000000000000026751 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import netifaces from os_vif.internal.ip.windows import impl_netifaces as ip_lib from os_vif.tests.unit import base class TestIPDevice(base.TestCase): def setUp(self): super(TestIPDevice, self).setUp() self.device_name = 'test_device' self.mock_log = mock.patch.object(ip_lib, "LOG").start() self.ip_lib = ip_lib.Netifaces() @mock.patch.object(netifaces, 'ifaddresses', return_value=True) def test_exists(self, mock_ifaddresses): self.assertTrue(self.ip_lib.exists(self.device_name)) mock_ifaddresses.assert_called_once_with(self.device_name) @mock.patch.object(netifaces, 'ifaddresses', side_effect=ValueError()) def test_exists_not_found(self, mock_ifaddresses): self.assertFalse(self.ip_lib.exists(self.device_name)) mock_ifaddresses.assert_called_once_with(self.device_name) self.mock_log.warning.assert_called_once_with( "The device does not exist on the system: %s", self.device_name) @mock.patch.object(netifaces, 'ifaddresses', side_effect=OSError()) def test_exists_os_error_exception(self, mock_ifaddresses): self.assertFalse(self.ip_lib.exists(self.device_name)) mock_ifaddresses.assert_called_once_with(self.device_name) self.mock_log.error.assert_called_once_with( "Failed to get interface addresses: %s", self.device_name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/test_base.py0000664000175000017500000000610100000000000020760 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from 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())) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/test_exception.py0000664000175000017500000000246700000000000022057 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from 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') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/test_host_info.py0000664000175000017500000001663000000000000022046 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from 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") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/test_objects.py0000664000175000017500000000717500000000000021513 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_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.3-1ad9a350a9cae19c977d21fcce7c8c7f', 'VIFPortProfileFPOpenVSwitch': '1.3-06c425743430e7702ef112e09b987346', 'VIFPortProfileFPBridge': '1.1-49f1952bf50bab7a95112c908534751f', 'VIFPortProfileFPTap': '1.1-fd178229477604dfb65de5ce929488e5', 'VIFVHostUser': '1.1-1f95b43be1f884f090ca1f4d79adfd35', 'VIFPortProfileOVSRepresentor': '1.3-f625e17143473b93d6c7f97ded9f785a', '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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/test_os_vif.py0000664000175000017500000001472100000000000021342 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/tests/unit/test_vif.py0000664000175000017500000004312000000000000020634 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/utils.py0000664000175000017500000000131100000000000016024 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def set_mask(data, mask): return data | mask def unset_mask(data, mask, bit_size=32): return data & ((2 ** bit_size - 1) ^ mask) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/os_vif/version.py0000664000175000017500000000131100000000000016351 0ustar00zuulzuul00000000000000# All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import pbr.version version_info = pbr.version.VersionInfo('os-vif') __version__ = version_info.version_string() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9865384 os_vif-2.7.1/os_vif.egg-info/0000775000175000017500000000000000000000000016010 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/PKG-INFO0000664000175000017500000000403600000000000017110 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: os-vif Version: 2.7.1 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 License: UNKNOWN Description: ======================== 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 Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/SOURCES.txt0000664000175000017500000001432600000000000017702 0ustar00zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst bindep.txt lower-constraints.txt 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/internal/ip/windows/__init__.py os_vif/internal/ip/windows/impl_netifaces.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 os_vif/tests/unit/internal/ip/windows/__init__.py os_vif/tests/unit/internal/ip/windows/test_impl_netifaces.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/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/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-to-native-ovsdb-driver-112fb5adf6e19a30.yaml releasenotes/notes/do-not-force-mac-ageing-c6e8d750130c5740.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-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_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/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/_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/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././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/dependency_links.txt0000664000175000017500000000000100000000000022056 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/entry_points.txt0000664000175000017500000000031700000000000021307 0ustar00zuulzuul00000000000000[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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/not-zip-safe0000664000175000017500000000000100000000000020236 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/pbr.json0000664000175000017500000000005600000000000017467 0ustar00zuulzuul00000000000000{"git_version": "7f9e9b8", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/requires.txt0000664000175000017500000000041200000000000020405 0ustar00zuulzuul00000000000000debtcollector>=1.19.0 netaddr>=0.7.18 oslo.concurrency>=3.20.0 oslo.config>=5.1.0 oslo.i18n>=3.15.3 oslo.log>=3.30.0 oslo.privsep>=1.23.0 oslo.versionedobjects>=1.28.0 ovsdbapp>=0.12.1 pbr!=2.1.0,>=2.0.0 stevedore>=1.20.0 [:(sys_platform!='win32')] pyroute2>=0.5.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198866.0 os_vif-2.7.1/os_vif.egg-info/top_level.txt0000664000175000017500000000007000000000000020537 0ustar00zuulzuul00000000000000os_vif vif_plug_linux_bridge vif_plug_noop vif_plug_ovs ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9705381 os_vif-2.7.1/playbooks/0000775000175000017500000000000000000000000015034 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9945385 os_vif-2.7.1/playbooks/openstack-tox-functional-ovs-with-sudo/0000775000175000017500000000000000000000000024521 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/playbooks/openstack-tox-functional-ovs-with-sudo/Debian.yaml0000664000175000017500000000011000000000000026557 0ustar00zuulzuul00000000000000--- ovs_package: "openvswitch-switch" ovs_service: "openvswitch-switch" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/playbooks/openstack-tox-functional-ovs-with-sudo/Gentoo.yaml0000664000175000017500000000010400000000000026633 0ustar00zuulzuul00000000000000--- ovs_package: "net-misc/openvswitch" ovs_service: "ovs-vswitchd" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/playbooks/openstack-tox-functional-ovs-with-sudo/RedHat.yaml0000664000175000017500000000007200000000000026553 0ustar00zuulzuul00000000000000--- ovs_package: "openvswitch" ovs_service: "openvswitch" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/playbooks/openstack-tox-functional-ovs-with-sudo/Suse.yaml0000664000175000017500000000007200000000000026323 0ustar00zuulzuul00000000000000--- ovs_package: "openvswitch" ovs_service: "openvswitch" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/playbooks/openstack-tox-functional-ovs-with-sudo/pre.yaml0000664000175000017500000000106600000000000026176 0ustar00zuulzuul00000000000000- 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198866.9705381 os_vif-2.7.1/releasenotes/0000775000175000017500000000000000000000000015522 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0025387 os_vif-2.7.1/releasenotes/notes/0000775000175000017500000000000000000000000016652 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml0000664000175000017500000000057100000000000026264 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/add-fast-path-vhostuser-support-fe87e558326909b6.yaml0000664000175000017500000000043000000000000030025 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/add-no-op-plugin-763a6703e7328a24.yaml0000664000175000017500000000045400000000000024611 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/add-ovs-representor-portprofile-5f8290e5a40bf0a4.yaml0000664000175000017500000000044500000000000030216 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml0000664000175000017500000000035200000000000027126 0ustar00zuulzuul00000000000000--- 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/add-ovsdb-native-322fffb49c91503d.yaml0000664000175000017500000000104600000000000025104 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/always-plug-vifs-for-ovs-1d033fc49a9c6c4e.yaml0000664000175000017500000000403700000000000026642 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/brctl-removal-a5b0e69b865afa57.yaml0000664000175000017500000000102600000000000024610 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/bug-1892132-812e6d5ce0588ebb.yaml0000664000175000017500000000061500000000000023446 0ustar00zuulzuul00000000000000--- 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/contextlib-and-nested-with-statements-2747a9ebb9a5bfd7.yaml0000664000175000017500000000051100000000000031452 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/default-to-native-ovsdb-driver-112fb5adf6e19a30.yaml0000664000175000017500000000130700000000000027755 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/do-not-force-mac-ageing-c6e8d750130c5740.yaml0000664000175000017500000000121000000000000026064 0ustar00zuulzuul00000000000000--- 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././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/drop-python2-support-7a4bc7d31253c1e5.yaml0000664000175000017500000000011600000000000026020 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2 is no longer supported. Python 3 is required. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/ensure-ovs-bridge-a0c1b51f469c92d0.yaml0000664000175000017500000000036000000000000025302 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/extend-vhostuser-object-fada14a1457d4e56.yaml0000664000175000017500000000054200000000000026622 0ustar00zuulzuul00000000000000--- 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). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/fix-ovs-plugin-describe-049750609559f1ba.yaml0000664000175000017500000000034700000000000026211 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/fix-stevedore-entrypoints-8002ec7a5166c977.yaml0000664000175000017500000000040200000000000026775 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/fix-vif-openvswitch-fa0d19be9dd668e1.yaml0000664000175000017500000000031000000000000026033 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/generic-datapath-offloads-41cabb6842b41533.yaml0000664000175000017500000000107600000000000026655 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/initial-release-2c71d6bbf55f763b.yaml0000664000175000017500000000076000000000000025110 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/oslo-config-opts-entrypoints-e83f907b686d774a.yaml0000664000175000017500000000026300000000000027515 0ustar00zuulzuul00000000000000--- features: - | We now create entrypoints for oslo.config options to allow automated documentation and validation of the configurable options for all the plugins. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml0000664000175000017500000000317300000000000024673 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/port-profile-info-linux-bridge-4800f5a0b7328615.yaml0000664000175000017500000000052200000000000027466 0ustar00zuulzuul00000000000000--- 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``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/port-profile-info-ovs-63b46a3eafc11de2.yaml0000664000175000017500000000057700000000000026271 0ustar00zuulzuul00000000000000--- 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``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/prevent-lb-reply-arp-6459133bfb056069.yaml0000664000175000017500000000053600000000000025531 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/remove_iptools_implementation-2eb866573a680e61.yaml0000664000175000017500000000045200000000000027776 0ustar00zuulzuul00000000000000--- 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/ ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=os_vif-2.7.1/releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.yaml 22 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.y0000664000175000017500000000172500000000000032724 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/vhost-user-mtu-support-cbc7d02a6665fab1.yaml0000664000175000017500000000055200000000000026540 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml0000664000175000017500000000117600000000000026140 0ustar00zuulzuul00000000000000--- 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. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0065386 os_vif-2.7.1/releasenotes/source/0000775000175000017500000000000000000000000017022 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0065386 os_vif-2.7.1/releasenotes/source/_static/0000775000175000017500000000000000000000000020450 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000022721 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0065386 os_vif-2.7.1/releasenotes/source/_templates/0000775000175000017500000000000000000000000021157 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000023430 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/conf.py0000664000175000017500000000346400000000000020330 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # # 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 = u'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'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/index.rst0000664000175000017500000000030000000000000020654 0ustar00zuulzuul00000000000000============= Release Notes ============= .. toctree:: :maxdepth: 1 unreleased xena wallaby victoria ussuri train stein rocky queens pike ocata newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/newton.rst0000664000175000017500000000023200000000000021063 0ustar00zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/ocata.rst0000664000175000017500000000023000000000000020636 0ustar00zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/pike.rst0000664000175000017500000000017200000000000020504 0ustar00zuulzuul00000000000000========================= Pike Series Release Notes ========================= .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/queens.rst0000664000175000017500000000020200000000000021046 0ustar00zuulzuul00000000000000=========================== Queens Series Release Notes =========================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000020676 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/stein.rst0000664000175000017500000000017600000000000020702 0ustar00zuulzuul00000000000000========================== Stein Series Release Notes ========================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/train.rst0000664000175000017500000000017600000000000020675 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/unreleased.rst0000664000175000017500000000015300000000000021702 0ustar00zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000021100 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/victoria.rst0000664000175000017500000000021200000000000021367 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: stable/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/wallaby.rst0000664000175000017500000000020600000000000021205 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: stable/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/releasenotes/source/xena.rst0000664000175000017500000000017200000000000020507 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: stable/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/requirements.txt0000664000175000017500000000120300000000000016311 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 netaddr>=0.7.18 # BSD 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.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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0185387 os_vif-2.7.1/setup.cfg0000664000175000017500000000223500000000000014654 0ustar00zuulzuul00000000000000[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.6 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.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/setup.py0000664000175000017500000000200600000000000014541 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/test-requirements.txt0000664000175000017500000000053400000000000017274 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. coverage!=4.4,>=4.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 ovs>=2.9.2 stestr>=1.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/tox.ini0000664000175000017500000000533100000000000014346 0ustar00zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py3,pep8,docs,releasenotes,cover,lower-constraints skipsdist = True ignore_basepython_conflict = True [testenv] basepython = python3 usedevelop = True setenv = 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 --black-regex ".tests.functional" {posargs} [testenv:functional] envdir = {toxworkdir}/shared setenv = {[testenv]setenv} commands = stestr run --black-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] envdir = {toxworkdir}/docs deps = {[testenv:docs]deps} whitelist_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] envdir = {toxworkdir}/docs deps = {[testenv:docs]deps} commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/doc/requirements.txt commands = stestr run --black-regex ".tests.functional" {posargs} sphinx-build -W doc/source doc/build [testenv:venv] commands = {posargs} deps = {[testenv]deps} -r{toxinidir}/doc/requirements.txt [testenv:cover] envdir = {toxworkdir}/shared setenv = {[testenv]setenv} PYTHON=coverage run --source os_vif,vif_plug_linux_bridge,vif_plug_ovs,vif_plug_noop --parallel-mode commands = stestr run --black-regex ".tests.functional" {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:pep8] envdir = {toxworkdir}/shared deps = hacking>=3.0.1,<3.1.0 commands = flake8 [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. deps = bindep commands = bindep test ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0065386 os_vif-2.7.1/vif_plug_linux_bridge/0000775000175000017500000000000000000000000017377 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/__init__.py0000664000175000017500000000000000000000000021476 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/constants.py0000664000175000017500000000113200000000000021762 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. PLUGIN_NAME = 'linux_bridge' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/iptables.py0000664000175000017500000005024100000000000021556 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/linux_bridge.py0000664000175000017500000001152500000000000022430 0ustar00zuulzuul00000000000000# 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 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=[]), ]) 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) def unplug(self, vif, instance_info): # Nothing required to unplug a port for a VIF using standard # Linux bridge device... pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/linux_net.py0000664000175000017500000002241100000000000021756 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/privsep.py0000664000175000017500000000156400000000000021447 0ustar00zuulzuul00000000000000# # 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], ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0065386 os_vif-2.7.1/vif_plug_linux_bridge/tests/0000775000175000017500000000000000000000000020541 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/tests/__init__.py0000664000175000017500000000000000000000000022640 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0105388 os_vif-2.7.1/vif_plug_linux_bridge/tests/unit/0000775000175000017500000000000000000000000021520 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/tests/unit/__init__.py0000664000175000017500000000000000000000000023617 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/tests/unit/test_linux_net.py0000664000175000017500000001512400000000000025141 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock 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') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_linux_bridge/tests/unit/test_plugin.py0000664000175000017500000001130300000000000024425 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0105388 os_vif-2.7.1/vif_plug_noop/0000775000175000017500000000000000000000000015677 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_noop/__init__.py0000664000175000017500000000000000000000000017776 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_noop/noop.py0000664000175000017500000000313500000000000017226 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0105388 os_vif-2.7.1/vif_plug_noop/tests/0000775000175000017500000000000000000000000017041 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_noop/tests/__init__.py0000664000175000017500000000000000000000000021140 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0105388 os_vif-2.7.1/vif_plug_noop/tests/unit/0000775000175000017500000000000000000000000020020 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_noop/tests/unit/__init__.py0000664000175000017500000000000000000000000022117 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_noop/tests/unit/test_plugin.py0000664000175000017500000000232400000000000022730 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0105388 os_vif-2.7.1/vif_plug_ovs/0000775000175000017500000000000000000000000015533 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/__init__.py0000664000175000017500000000000000000000000017632 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/constants.py0000664000175000017500000000162600000000000020126 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. 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' PLATFORM_LINUX = 'linux2' PLATFORM_WIN32 = 'win32' OVS_DPDK_INTERFACE_TYPE = 'dpdk' # Neutron dead VLAN. DEAD_VLAN = 4095 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/exception.py0000664000175000017500000000246000000000000020105 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from 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") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/linux_net.py0000664000175000017500000003372100000000000020120 0ustar00zuulzuul00000000000000# 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 import sys 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 constants 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 if sys.platform != constants.PLATFORM_WIN32: # Hyper-V with OVS does not support external programming of # virtual interface MTUs via netsh or other Windows tools. # When plugging an interface on Windows, we therefore skip # programming the MTU and fallback to DHCP advertisement. 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) 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/ovs.py0000664000175000017500000004776400000000000016736 0ustar00zuulzuul00000000000000# 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 sys 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__) 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.') ) 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 _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. if self.config.isolate_vif: kwargs['tag'] = constants.DEAD_VLAN bridge = kwargs.pop('bridge', vif.network.bridge) self.ovsdb.create_ovs_vif_port( bridge, vif_name, vif.port_profile.interface_id, vif.address, instance_info.uuid, mtu=mtu, **kwargs) def _update_vif_port(self, vif, vif_name): mtu = self._get_mtu(vif) self.ovsdb.update_ovs_vif_port(vif_name, mtu) @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): self.ovsdb.ensure_ovs_bridge( vif.network.bridge, self._get_vif_datapath_type( vif, datapath=constants.OVS_DATAPATH_NETDEV)) vif_name = OvsPlugin.gen_port_name( constants.OVS_VHOSTUSER_PREFIX, vif.id) args = {} 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_vif_windows(self, vif, instance_info): """Create a per-VIF OVS port.""" if not ip_lib.exists(vif.id): self.ovsdb.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) self._create_vif_port(vif, vif.id, instance_info) 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.config.isolate_vif 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 sys.platform == constants.PLATFORM_WIN32: if type(vif) not in ( objects.vif.VIFOpenVSwitch, objects.vif.VIFBridge ): raise osv_exception.PlugException( vif=vif, err="This vif type is not supported on Windows") self._plug_vif_windows(vif, instance_info) elif 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 _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)) def _unplug_bridge(self, vif, instance_info): """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(vif.bridge_name, v1_name) self.ovsdb.delete_ovs_vif_port(vif.network.bridge, v2_name) def _unplug_vif_windows(self, vif, instance_info): """Remove port from OVS.""" self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.id, delete_netdev=False) def _unplug_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 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) self.ovsdb.delete_ovs_vif_port(port_bridge_name, vif.vif_name) self.ovsdb.delete_ovs_bridge(port_bridge_name) def _unplug_vif_generic(self, vif, instance_info): """Remove port from OVS.""" # NOTE(sean-k-mooney): even with the partial revert of change # Iaf15fa7a678ec2624f7c12f634269c465fbad930 this should be correct # so this is not removed. self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.vif_name) 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 self.ovsdb.delete_ovs_vif_port( vif.network.bridge, representor, delete_netdev=False) if datapath == constants.OVS_DATAPATH_SYSTEM: linux_net.set_interface_state(representor, 'down') 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 sys.platform == constants.PLATFORM_WIN32: if type(vif) not in ( objects.vif.VIFOpenVSwitch, objects.vif.VIFBridge ): raise osv_exception.UnplugException( vif=vif, err="This vif type is not supported on windows.") self._unplug_vif_windows(vif, instance_info) elif isinstance(vif, objects.vif.VIFOpenVSwitch): if self.config.per_port_bridge: self._unplug_port_bridge(vif, instance_info) else: self._unplug_vif_generic(vif, instance_info) elif isinstance(vif, objects.vif.VIFBridge): self._unplug_bridge(vif, instance_info) 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") ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/vif_plug_ovs/ovsdb/0000775000175000017500000000000000000000000016650 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/ovsdb/__init__.py0000664000175000017500000000000000000000000020747 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/ovsdb/api.py0000664000175000017500000000241500000000000017775 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc from 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. """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/ovsdb/impl_idl.py0000664000175000017500000000571400000000000021022 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import 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') 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/ovsdb/impl_vsctl.py0000664000175000017500000003367200000000000021411 0ustar00zuulzuul00000000000000# 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 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/ovsdb/ovsdb_lib.py0000664000175000017500000001767500000000000021205 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys from oslo_log import log as logging from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs.ovsdb import api as ovsdb_api LOG = logging.getLogger(__name__) 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, dev, mtu): self.ovsdb.db_set('Interface', dev, ('mtu_request', mtu)).execute() def update_device_mtu(self, 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]: if sys.platform != constants.PLATFORM_WIN32: # Hyper-V with OVS does not support external programming of # virtual interface MTUs via netsh or other Windows tools. # When plugging an interface on Windows, we therefore skip # programming the MTU and fallback to DHCP advertisement. linux_net.set_device_mtu(dev, mtu) elif self._ovs_supports_mtu_requests(): self._set_mtu_request(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 ): """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). .. 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} 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})) with self.ovsdb.transaction() as txn: txn.add(self.ovsdb.add_port(bridge, dev)) if tag: txn.add(self.ovsdb.db_set('Port', dev, ('tag', tag))) if col_values: txn.add(self.ovsdb.db_set('Interface', dev, *col_values)) self.update_device_mtu(dev, mtu, interface_type=interface_type) def update_ovs_vif_port(self, dev, mtu=None, interface_type=None): self.update_device_mtu(dev, mtu, interface_type=interface_type) def delete_ovs_vif_port(self, bridge, dev, delete_netdev=True): self.ovsdb.del_port(dev, bridge=bridge, if_exists=True).execute() if delete_netdev: linux_net.delete_net_dev(dev) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/privsep.py0000664000175000017500000000154200000000000017577 0ustar00zuulzuul00000000000000# # 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], ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/vif_plug_ovs/tests/0000775000175000017500000000000000000000000016675 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/__init__.py0000664000175000017500000000000000000000000020774 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/vif_plug_ovs/tests/functional/0000775000175000017500000000000000000000000021037 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/functional/__init__.py0000664000175000017500000000000000000000000023136 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/functional/base.py0000664000175000017500000000160100000000000022321 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from 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' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/vif_plug_ovs/tests/functional/ovsdb/0000775000175000017500000000000000000000000022154 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/functional/ovsdb/__init__.py0000664000175000017500000000000000000000000024253 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py0000664000175000017500000001746400000000000025544 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import random from unittest import mock import testscenarios from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import uuidutils from ovsdbapp.schema.open_vswitch import impl_idl 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.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 mock.patch.object( impl_idl.OvsVsctlTransaction, 'post_commit', side_effect=impl_idl.OvsVsctlTransaction.do_post_commit).start() def _check_parameter(self, table, port, parameter, expected_value): def check_value(): return (self._ovsdb.db_get( table, port, parameter).execute() == expected_value) self.assertTrue(base.wait_until_true(check_value, timeout=2, sleep=0.5)) 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 _check_bridge(self, name): return self._ovsdb.br_exists(name).execute() 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() 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(): self.ovs._set_mtu_request(port_name, 1000) self._check_parameter('Interface', port_name, 'mtu', 1000) self.ovs._set_mtu_request(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('Interface', port_name, 'options', expected_vhost_server_path) self._check_parameter('Port', port_name, 'tag', 2000) @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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/vif_plug_ovs/tests/unit/0000775000175000017500000000000000000000000017654 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/unit/__init__.py0000664000175000017500000000000000000000000021753 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645198867.0145388 os_vif-2.7.1/vif_plug_ovs/tests/unit/ovsdb/0000775000175000017500000000000000000000000020771 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/unit/ovsdb/__init__.py0000664000175000017500000000000000000000000023070 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py0000664000175000017500000002337500000000000024357 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock 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('device', 1500) calls = [mock.call('Interface', 'device', ('mtu_request', 1500))] self.mock_db_set.assert_has_calls(calls) @mock.patch('sys.platform', constants.PLATFORM_LINUX) @mock.patch.object(linux_net, 'set_device_mtu') def test__update_device_mtu_interface_not_vhostuser_linux(self, mock_set_device_mtu): self.br.update_device_mtu('device', 1500, 'not_vhost') mock_set_device_mtu.assert_has_calls([mock.call('device', 1500)]) @mock.patch('sys.platform', constants.PLATFORM_WIN32) @mock.patch.object(linux_net, 'set_device_mtu') def test__update_device_mtu_interface_not_vhostuser_windows(self, mock_set_device_mtu): self.br.update_device_mtu('device', 1500, 'not_vhost') mock_set_device_mtu.assert_not_called() 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('device', 1500, constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_set_mtu_request.assert_has_calls([mock.call('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('device', 1500, constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_set_mtu_request.assert_not_called() def test_create_ovs_vif_port(self): 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' mtu = 1500 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', {'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)]) mock_update_device_mtu.assert_has_calls( [mock.call(device, mtu, interface_type=interface_type)]) 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)]) mock_update_device_mtu.assert_has_calls( [mock.call(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) mock_update_device_mtu.assert_has_calls( [mock.call( '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']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/unit/test_linux_net.py0000664000175000017500000004217400000000000023302 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645198832.0 os_vif-2.7.1/vif_plug_ovs/tests/unit/test_plugin.py0000664000175000017500000006702700000000000022577 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock 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 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_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) # 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_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') def test_create_vif_port_isolate(self, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) 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) @mock.patch.object(ovs, 'sys') @mock.patch.object(ovs.OvsPlugin, '_plug_vif_generic') def test_plug_ovs_port_bridge_false(self, plug_vif_generic, mock_sys): mock_sys.platform = 'linux' 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, 'sys') @mock.patch.object(ovs.OvsPlugin, '_plug_port_bridge') def test_plug_ovs_port_bridge_true(self, plug_vif, mock_sys): mock_sys.platform = 'linux' 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') @mock.patch.object(ovs, 'sys') def test_plug_ovs_bridge(self, mock_sys, 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 mock_sys.platform = 'linux' 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, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') @mock.patch.object(ip_lib, 'exists', return_value=False) @mock.patch.object(ovs, 'sys') def _check_plug_ovs_windows(self, vif, mock_sys, mock_exists, _create_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type(vif) calls = { 'exists': [mock.call(vif.id)], '_create_vif_port': [mock.call(vif, vif.id, self.instance)], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } mock_sys.platform = constants.PLATFORM_WIN32 plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(vif, self.instance) mock_exists.assert_has_calls(calls['exists']) _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) def test_plug_ovs_windows(self): self._check_plug_ovs_windows(self.vif_ovs) def test_plug_ovs_bridge_windows(self): self._check_plug_ovs_windows(self.vif_ovs_hybrid) @mock.patch.object(ovs, 'sys') @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_ovs_port_bridge_false(self, unplug, mock_sys): mock_sys.platform = 'linux' 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) @mock.patch.object(ovs, 'sys') @mock.patch.object(ovs.OvsPlugin, '_unplug_port_bridge') def test_unplug_ovs_port_bridge_true(self, unplug, mock_sys): mock_sys.platform = 'linux' 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) @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_vif_generic(self, delete_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_vif_generic(self.vif_ovs, self.instance) delete_port.assert_called_once() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(linux_net, 'delete_bridge') @mock.patch.object(ovs, 'sys') def test_unplug_ovs_bridge(self, mock_sys, delete_bridge, delete_ovs_vif_port): calls = { 'delete_bridge': [mock.call('qbrvif-xxx-yyy', 'qvbb679325f-ca')], 'delete_ovs_vif_port': [mock.call('br0', 'qvob679325f-ca')] } mock_sys.platform = 'linux' 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']) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(ovs, 'sys') def _check_unplug_ovs_windows(self, vif, mock_sys, delete_ovs_vif_port): mock_sys.platform = constants.PLATFORM_WIN32 plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(vif, self.instance) delete_ovs_vif_port.assert_called_once_with('br0', vif.id, delete_netdev=False) def test_unplug_ovs_windows(self): self._check_unplug_ovs_windows(self.vif_ovs) def test_unplug_ovs_bridge_windows(self): self._check_unplug_ovs_windows(self.vif_ovs_hybrid) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vhostuser(self, _create_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_vhostuser) calls = { '_create_vif_port': [mock.call( self.vif_vhostuser, 'vhub679325f-ca', self.instance, interface_type='dpdkvhostuser')], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_vhostuser, self.instance) _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_plug_ovs_vhostuser_client(self, create_ovs_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type( self.vif_vhostuser_client) calls = { 'create_ovs_vif_port': [ 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' )], 'ensure_ovs_bridge': [mock.call('br0', 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['create_ovs_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vhostuser(self, delete_ovs_vif_port): 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']) @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(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): 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)] } 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']) @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(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_vif_ovs_smart_nic(self, delete_port): 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() @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(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): 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)]} 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']) @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') ] delete_ovs_vif_port.assert_has_calls(calls) delete_ovs_bridge.assert_called_once_with('pbb679325f-ca8')