pax_global_header00006660000000000000000000000064132276571230014522gustar00rootroot0000000000000052 comment=87e0edde4d190549867e4a0be0c544c5f080213d os-vif-1.9.0/000077500000000000000000000000001322765712300127345ustar00rootroot00000000000000os-vif-1.9.0/.coveragerc000066400000000000000000000001151322765712300150520ustar00rootroot00000000000000[run] branch = True source = os_vif omit = os_vif/tests/*,os_vif/openstack/* os-vif-1.9.0/.gitignore000066400000000000000000000010021322765712300147150ustar00rootroot00000000000000covhtml *.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml .testrepository .venv .stestr # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Complexity output/*.html output/*/index.html # Sphinx doc/build # Release notes releasenotes/build/ # pbr generates these AUTHORS ChangeLog # Editors *~ .*.swp .*sw? os-vif-1.9.0/.gitreview000066400000000000000000000001131322765712300147350ustar00rootroot00000000000000[gerrit] host=review.openstack.org port=29418 project=openstack/os-vif.git os-vif-1.9.0/.mailmap000066400000000000000000000001311322765712300143500ustar00rootroot00000000000000# Format is: # # os-vif-1.9.0/.stestr.conf000066400000000000000000000000621322765712300152030ustar00rootroot00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-.} top_dir=./ os-vif-1.9.0/.testr.conf000066400000000000000000000004771322765712300150320ustar00rootroot00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list os-vif-1.9.0/CONTRIBUTING.rst000066400000000000000000000010311322765712300153700ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/os-vif os-vif-1.9.0/HACKING.rst000066400000000000000000000002061322765712300145300ustar00rootroot00000000000000os_vif Style Commandments ========================= Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ os-vif-1.9.0/LICENSE000066400000000000000000000236371322765712300137540ustar00rootroot00000000000000 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. os-vif-1.9.0/README.rst000066400000000000000000000014261322765712300144260ustar00rootroot00000000000000======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/os-vif.svg :target: http://governance.openstack.org/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.python.org/pypi/os-vif/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/os-vif.svg :target: https://pypi.python.org/pypi/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://git.openstack.org/cgit/openstack/os-vif * Bugs: https://bugs.launchpad.net/os-vif os-vif-1.9.0/babel.cfg000066400000000000000000000000211322765712300144530ustar00rootroot00000000000000[python: **.py] os-vif-1.9.0/doc/000077500000000000000000000000001322765712300135015ustar00rootroot00000000000000os-vif-1.9.0/doc/source/000077500000000000000000000000001322765712300150015ustar00rootroot00000000000000os-vif-1.9.0/doc/source/conf.py000066400000000000000000000033431322765712300163030ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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.autodoc', 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/os-vif' bug_project = 'os-vif' bug_tag = '' html_last_updated_fmt = '%Y-%m-%d %H:%M' # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'os-vif' 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 = 'sphinx' # -- 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' os-vif-1.9.0/doc/source/index.rst000066400000000000000000000014671322765712300166520ustar00rootroot00000000000000====== 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/ovs user/plugins/linux-bridge Reference --------- .. toctree:: :maxdepth: 2 reference/glossary os-vif-1.9.0/doc/source/reference/000077500000000000000000000000001322765712300167375ustar00rootroot00000000000000os-vif-1.9.0/doc/source/reference/glossary.rst000066400000000000000000000143561322765712300213450ustar00rootroot00000000000000======== 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 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 a userspace process to share *virtqueues* directly with the kernel, preventing the QEMU process from becoming a bottleneck. vhost-user A variation of :term:`vhost` that operates entirely in userspace. This allows processes operating in userspace, such as virtual switches, to avoid the kernel entirely and maximize performance. 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/os-vif-1.9.0/doc/source/user/000077500000000000000000000000001322765712300157575ustar00rootroot00000000000000os-vif-1.9.0/doc/source/user/host-info.rst000066400000000000000000000053641322765712300204270ustar00rootroot00000000000000================ 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. os-vif-1.9.0/doc/source/user/plugins/000077500000000000000000000000001322765712300174405ustar00rootroot00000000000000os-vif-1.9.0/doc/source/user/plugins/linux-bridge.rst000066400000000000000000000012771322765712300225720ustar00rootroot00000000000000============ 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 two plugins provided as part of `os-vif` itself, the other being :doc:`ovs`. Supported VIF Types =================== The Linux Bridge plugin provides support for the following VIF types: `VIFBridge` Configuration where a guest is connected to a Linux bridge via a TAP device. This is the only supported configuration for this plugin. Refer to :ref:`vif-bridge` for more information. 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. os-vif-1.9.0/doc/source/user/plugins/ovs.rst000066400000000000000000000037411322765712300210060ustar00rootroot00000000000000============ 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 two plugins provided as part of `os-vif` itself, the other being :doc:`linux-bridge`. Supported VIF Types ------------------- The Open vSwitch plugin provides support for the following VIF types: `VIFOpenVSwitch` Configuration where a guest is directly connected an Open vSwitch bridge. Refer to :ref:`vif-openvswitch` for more information. `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. Refer to :ref:`vif-bridge` for more information. `VIFVHostUser` Configuration where a guest exposes a UNIX socket for its control plane. This configuration is used with the `DPDK datapath of Open vSwitch`__. Refer to :ref:`vif-vhostuser` for more information. `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 ``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. Refer to :ref:`vif-hostdevice` for more information. .. 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. __ http://docs.openvswitch.org/en/latest/howto/dpdk/ os-vif-1.9.0/doc/source/user/usage.rst000066400000000000000000000041731322765712300176220ustar00rootroot00000000000000===== 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 a single argument of type `os_vif.objects.VIF`: .. code-block:: python import uuid from nova import objects as nova_objects from os_vif import exception as vif_exc from os_vif import objects as vif_objects from os_vif import vnic_types instance_uuid = 'd7a730ca-3c28-49c3-8f26-4662b909fe8a' instance = nova_objects.Instance.get_by_uuid(instance_uuid) instance_info = vif_objects.InstanceInfo( uuid=instance.uuid, name=instance.name, project_id=instance.project_id) subnet = vif_objects.Subnet(cidr='192.168.1.0/24') subnets = vif_objects.SubnetList([subnet]) network = vif_objects.Network(label='tenantnet', subnets=subnets, multi_host=False, should_provide_vlan=False, should_provide_bridge=False) vif_uuid = uuid.uuid4() vif = vif_objects.VIFVHostUser(id=vif_uuid, address=None, network=network, plugin='vhostuser', path='/path/to/socket', mode=vif_objects.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... os-vif-1.9.0/doc/source/user/vif-types.rst000066400000000000000000000106101322765712300204350ustar00rootroot00000000000000========= 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. VIF objects =========== Each distinct type of VIF configuration is represented by a versioned object in os-vif, subclassed from `os_vif.objects.VIFBase`. The `VIFBase` class defines some fields that are common to all types of VIF, and provides an association to a versioned object describing the network the VIF is plugged into. .. _vif-generic: VIFGeneric ---------- This class provides a totally generic type of configuration, where the guest is simply associated with an arbitrary TAP device (or equivalent). The way the TAP device is connected to the host network stack is explicitly left undefined and entirely up to the plugin to decide. .. _vif-bridge: VIFBridge --------- This class provides a configuration where the guest is connected directly to an explicit host bridge device. This provides ethernet layer bridging, typically to the LAN. .. _vif-openvswitch: VIFOpenVSwitch -------------- This class provides a configuration where the guest is connected to an Open vSwitch port. .. _vif-direct: VIFDirect --------- This class provides a configuration where the guest is connected to a physical network device. 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, the `VIFHostDevice` should be used instead. .. _vif-vhostuser: VIFVHostUser ------------ This class provides another totally generic type of configuration, where the guest is exposing a UNIX socket for its control plane, allowing an external userspace service to provide the backend data plane via a mapped memory region. The process must implement the :term:`virtio-net` vhost protocol on this socket in whatever means is most suitable. .. _vif-hostdevice: VIFHostDevice ------------- This class provides a way to pass a physical device to the guest. Either an entire physical device, or an SR-IOV PCI device virtual function, are permitted. 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 `VIFPortProfileBase`. VIFPortProfileOpenVSwitch ------------------------- This profile provides the metadata required to associate a VIF with an Open vSwitch host port. VIFPortProfile8021Qbg --------------------- This profile provides the metadata required to associate a VIF with a VEPA host device supporting the :term:`802.1Qbg` spec. VIFPortProfile8021Qbh --------------------- This profile provides the metadata required to associate a VIF with a VEPA host device supporting the :term:`802.1Qbh` spec. VIFPortProfileFPOpenVSwitch --------------------------- This profile provides the metadata required to associate a fast path :term:`vhost-user` VIF with an :term:`Open vSwitch` port. VIFPortProfileOVSRepresentor ---------------------------- 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. VIFPortProfileFPBridge ---------------------- This profile provides the metadata required to associate a fast path vhost-user VIF with a :term:`Linux bridge` port. VIFPortProfileFPTap ------------------- This profile provides the metadata required to associate a fast path vhost-user VIF with a Calico port. 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 :-( os-vif-1.9.0/os_vif/000077500000000000000000000000001322765712300142215ustar00rootroot00000000000000os-vif-1.9.0/os_vif/__init__.py000066400000000000000000000124141322765712300163340ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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: `os_vif.objects.VIF` object. :param instance_info: `os_vif.objects.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: `os_vif.objects.VIF` object. :param instance_info: `os_vif.objects.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 os-vif-1.9.0/os_vif/exception.py000066400000000000000000000062331322765712300165750ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.") os-vif-1.9.0/os_vif/i18n.py000066400000000000000000000020201322765712300153440ustar00rootroot00000000000000# 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) os-vif-1.9.0/os_vif/internal/000077500000000000000000000000001322765712300160355ustar00rootroot00000000000000os-vif-1.9.0/os_vif/internal/__init__.py000066400000000000000000000017071322765712300201530ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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() os-vif-1.9.0/os_vif/internal/command/000077500000000000000000000000001322765712300174535ustar00rootroot00000000000000os-vif-1.9.0/os_vif/internal/command/__init__.py000066400000000000000000000000001322765712300215520ustar00rootroot00000000000000os-vif-1.9.0/os_vif/internal/command/ip/000077500000000000000000000000001322765712300200635ustar00rootroot00000000000000os-vif-1.9.0/os_vif/internal/command/ip/__init__.py000066400000000000000000000026321322765712300221770ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from os_vif.internal.command.ip import api def set(device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None): """Method to set a parameter in an interface.""" return api._get_impl().set(device, check_exit_code=check_exit_code, state=state, mtu=mtu, address=address, promisc=promisc) def add(device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None): """Method to add an interface.""" return api._get_impl().add(device, dev_type, check_exit_code=check_exit_code, peer=peer, link=link, vlan_id=vlan_id) def delete(device, check_exit_code=None): """Method to delete an interface.""" return api._get_impl().delete(device, check_exit_code=check_exit_code) os-vif-1.9.0/os_vif/internal/command/ip/api.py000066400000000000000000000056631322765712300212200ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 six from oslo_log import log as logging from oslo_utils import importutils LOG = logging.getLogger(__name__) impl_map = { 'pyroute2': 'os_vif.internal.command.ip.impl_pyroute2.PyRoute2', 'IPTools': 'os_vif.internal.command.ip.impl_shell.IPTools', } def _get_impl(): # NOTE(sean-k-mooney): currently pyroute2 has a file handle leak. An # iptools driver has been added as a workaround but No config options are # exposed to the user. The iptools driver is considered deprecated and # will be removed when a new release of pyroute2 is available. driver = 'IPTools' return importutils.import_object(impl_map[driver]) @six.add_metaclass(abc.ABCMeta) class IpCommand(object): TYPE_VETH = 'veth' TYPE_VLAN = 'vlan' @abc.abstractmethod def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=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 :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): """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 :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 """ os-vif-1.9.0/os_vif/internal/command/ip/impl_pyroute2.py000066400000000000000000000070221322765712300232500ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.command.ip import api from os_vif import utils LOG = logging.getLogger(__name__) class PyRoute2(api.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): check_exit_code = check_exit_code or [] ip = iproute.IPRoute() idx = ip.link_lookup(ifname=device) if not idx: raise exception.NetworkInterfaceNotFound(interface=device) idx = idx[0] 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 isinstance(check_exit_code, int): check_exit_code = [check_exit_code] return self._ip_link(ip, 'set', check_exit_code, **args) def add(self, device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None): check_exit_code = check_exit_code or [] ip = iproute.IPRoute() args = {'ifname': device, 'kind': dev_type} if self.TYPE_VLAN == dev_type: args['vlan_id'] = vlan_id idx = ip.link_lookup(ifname=link) if 0 == len(idx): raise exception.NetworkInterfaceNotFound(interface=link) args['link'] = idx[0] elif self.TYPE_VETH == dev_type: args['peer'] = peer 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 [] ip = iproute.IPRoute() idx = ip.link_lookup(ifname=device) if len(idx) == 0: raise exception.NetworkInterfaceNotFound(interface=device) idx = idx[0] return self._ip_link(ip, 'del', check_exit_code, **{'index': idx}) os-vif-1.9.0/os_vif/internal/command/ip/impl_shell.py000066400000000000000000000111421322765712300225640ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from oslo_concurrency import processutils from oslo_utils import excutils from os_vif.internal.command.ip import api def _execute_command(*args): return processutils.execute(*args) class ShellIpCommands(object): def __init__(self, cmd=_execute_command): self._execute_command = cmd def add_device(self, device, dev_type, peer=None, link=None, vlan_id=None): ret = None if 'vlan' == dev_type: ret = self._execute_command('ip', 'link', 'add', 'link', link, 'name', device, 'type', dev_type, 'id', vlan_id) elif 'veth' == dev_type: ret = self._execute_command('ip', 'link', 'add', device, 'type', dev_type, 'peer', 'name', peer) elif 'dummy' == dev_type: ret = self._execute_command('ip', 'link', 'add', device, 'type', dev_type) return ret def del_device(self, device): ret = None if self.exist_device(device): ret = self._execute_command('ip', 'link', 'del', device) return ret def set(self, device, status=None, **kwargs): args = ['ip', 'link', 'set', device] if status is not None: args.append(status) temp = [x for x in kwargs.items()] for x in temp: args += x self._execute_command(*args) def set_status_up(self, device): self.set(device, status='up') def set_status_down(self, device): self.set(device, status='down') def set_device_mtu(self, device, mtu): args = {'mtu': mtu} self.set(device, args) def show_device(self, device): val, err = _execute_command('ip', 'link', 'show', device) return val.splitlines() def exist_device(self, device): try: self._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')) class IPTools(api.IpCommand): def __init__(self, cmd=_execute_command): self.ip = ShellIpCommands(cmd=cmd) def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None): args = {} if mtu is not None: args['mtu'] = mtu if address is not None: args['address'] = address if promisc is not None: args['promisc'] = 'on' if promisc else 'off' if isinstance(check_exit_code, int): check_exit_code = [check_exit_code] return self.ip.set(device, status=state, **args) def add(self, device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None): return self.ip.add_device(device, dev_type, peer=peer, link=link, vlan_id=vlan_id) def delete(self, device, check_exit_code=None): return self.ip.del_device(device) os-vif-1.9.0/os_vif/objects/000077500000000000000000000000001322765712300156525ustar00rootroot00000000000000os-vif-1.9.0/os_vif/objects/__init__.py000066400000000000000000000015631322765712300177700ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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') os-vif-1.9.0/os_vif/objects/base.py000066400000000000000000000021631322765712300171400ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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__() os-vif-1.9.0/os_vif/objects/fields.py000066400000000000000000000033411322765712300174730ustar00rootroot00000000000000# 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() os-vif-1.9.0/os_vif/objects/fixed_ip.py000066400000000000000000000024021322765712300200110ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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'), } os-vif-1.9.0/os_vif/objects/host_info.py000066400000000000000000000151511322765712300202170ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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): super(HostVIFInfo, self).obj_make_compatible(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'] 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 os-vif-1.9.0/os_vif/objects/instance_info.py000066400000000000000000000023121322765712300210410ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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(), } os-vif-1.9.0/os_vif/objects/network.py000066400000000000000000000040021322765712300177110ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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): primitive.pop('mtu', None) os-vif-1.9.0/os_vif/objects/route.py000066400000000000000000000025101322765712300173600ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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'), } os-vif-1.9.0/os_vif/objects/subnet.py000066400000000000000000000026621322765712300175320ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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'), } os-vif-1.9.0/os_vif/objects/vif.py000066400000000000000000000210001322765712300170010ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.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.""" # Version 1.0: Initial version 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): # For libvirt drivers, this maps to type="ethernet" which # just implies a bare TAP device, all setup delegated to # the plugin VERSION = '1.0' fields = { # Name of the device to create 'vif_name': fields.StringField() } @base.VersionedObjectRegistry.register class VIFBridge(VIFBase): # For libvirt drivers, this maps to type='bridge' VERSION = '1.0' fields = { # Name of the virtual device to create 'vif_name': fields.StringField(), # Name of the physical device to connect to (eg br0) 'bridge_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFOpenVSwitch(VIFBase): # For libvirt drivers, this also maps to type='bridge' VERSION = '1.0' fields = { # Name of the virtual device to create 'vif_name': fields.StringField(), # Name of the physical device to connect to (eg br0) 'bridge_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFDirect(VIFBase): # For libvirt drivers, this maps to type='direct' 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): # For libvirt drivers, this maps to type='vhostuser' 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): super(VIFVHostUser, self).obj_make_compatible(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'] @base.VersionedObjectRegistry.register class VIFHostDevice(VIFBase): # For libvirt drivers, this maps to type='hostdev' 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 VIFPortProfileBase(osv_base.VersionedObject, base.ComparableVersionedObject): # Base class for all types of port profile VERSION = '1.0' @base.VersionedObjectRegistry.register class VIFPortProfileOpenVSwitch(VIFPortProfileBase): # Port profile info for OpenVSwitch networks # Version 1.0: Initial release # Version 1.1: Added 'datapath_type' VERSION = '1.1' fields = { 'interface_id': fields.UUIDField(), 'profile_id': fields.StringField(), # Datapath type of the bridge 'datapath_type': fields.StringField(nullable=True), } def obj_make_compatible(self, primitive, target_version): super(VIFPortProfileOpenVSwitch, self).obj_make_compatible( primitive, target_version) target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'datapath_type' in primitive: del primitive['datapath_type'] @base.VersionedObjectRegistry.register class VIFPortProfileFPOpenVSwitch(VIFPortProfileOpenVSwitch): # Port profile info for OpenVSwitch networks using fastpath # Version 1.0: Initial release # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 VERSION = '1.1' 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") @base.VersionedObjectRegistry.register class VIFPortProfileOVSRepresentor(VIFPortProfileOpenVSwitch): # Port profile info for OpenVSwitch networks using a representor # Version 1.0: Initial release # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 VERSION = '1.1' 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") @base.VersionedObjectRegistry.register class VIFPortProfileFPBridge(VIFPortProfileBase): # Port profile info for LinuxBridge networks using fastpath VERSION = '1.0' fields = { # Name of the bridge (managed by fast path) to connect to 'bridge_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFPortProfileFPTap(VIFPortProfileBase): # Port profile info for Calico networks using fastpath VERSION = '1.0' fields = { # The mac address of the host vhostuser port 'mac_address': fields.MACAddressField(nullable=True), } @base.VersionedObjectRegistry.register class VIFPortProfile8021Qbg(VIFPortProfileBase): # Port profile info for VEPA 802.1qbg networks VERSION = '1.0' fields = { 'manager_id': fields.IntegerField(), 'type_id': fields.IntegerField(), 'type_id_version': fields.IntegerField(), 'instance_id': fields.UUIDField(), } @base.VersionedObjectRegistry.register class VIFPortProfile8021Qbh(VIFPortProfileBase): # Port profile info for VEPA 802.1qbh networks VERSION = '1.0' fields = { 'profile_id': fields.StringField() } os-vif-1.9.0/os_vif/plugin.py000066400000000000000000000057421322765712300161010ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import six CONF = cfg.CONF @six.add_metaclass(abc.ABCMeta) class PluginBase(object): """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) os-vif-1.9.0/os_vif/tests/000077500000000000000000000000001322765712300153635ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/__init__.py000066400000000000000000000000001322765712300174620ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/000077500000000000000000000000001322765712300175255ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/__init__.py000066400000000000000000000000001322765712300216240ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/base.py000066400000000000000000000074061322765712300210200ustar00rootroot00000000000000# 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 six import string 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 _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 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, _catch_timeout(method)) def setup_logging(): """Sets up the logging options for a log with supplied name.""" product_name = "os_vif" logging.setup(cfg.CONF, product_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""" replace_map = string.maketrans(' ()', '-__') return path.translate(replace_map) # 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). @six.add_metaclass(_CatchTimeoutMetaclass) class BaseFunctionalTestCase(base.BaseTestCase): """Base class for functional tests.""" def setUp(self): super(BaseFunctionalTestCase, self).setUp() logging.register_options(CONF) setup_logging() fileutils.ensure_tree(DEFAULT_LOG_DIR, mode=0o755) log_file = sanitize_log_path( os.path.join(DEFAULT_LOG_DIR, "%s.txt" % self.id())) self.config(log_file=log_file) def config(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) os-vif-1.9.0/os_vif/tests/functional/internal/000077500000000000000000000000001322765712300213415ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/internal/__init__.py000066400000000000000000000000001322765712300234400ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/internal/command/000077500000000000000000000000001322765712300227575ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/internal/command/__init__.py000066400000000000000000000000001322765712300250560ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/internal/command/ip/000077500000000000000000000000001322765712300233675ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/internal/command/ip/__init__.py000066400000000000000000000000001322765712300254660ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/functional/internal/command/ip/test_impl_iptools.py000066400000000000000000000144461322765712300275230ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from oslo_concurrency import processutils from oslo_utils import excutils from os_vif.internal.command.ip import impl_shell 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')) def _ip_cmd_set(*args, **kwargs): impl_shell.IPTools(cmd=_execute_command).set(*args, **kwargs) def _ip_cmd_add(*args, **kwargs): impl_shell.IPTools(cmd=_execute_command).add(*args, **kwargs) def _ip_cmd_delete(*args, **kwargs): impl_shell.IPTools(cmd=_execute_command).delete(*args, **kwargs) class TestIpCommand(ShellIpCommands, base.BaseFunctionalTestCase): def setUp(self): super(TestIpCommand, self).setUp() def test_set_state(self): device1 = "iptools_dev_1" device2 = "iptools_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 = "iptools_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 = "iptools_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 = "iptools_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 = "iptools_dev_6" link = "iptools_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 = "iptools_dev_7" peer = "iptools_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 = "iptools_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)) os-vif-1.9.0/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py000066400000000000000000000144761322765712300276260ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from oslo_concurrency import processutils from oslo_utils import excutils from os_vif.internal.command.ip import impl_pyroute2 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): impl_pyroute2.PyRoute2().set(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_add(*args, **kwargs): impl_pyroute2.PyRoute2().add(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_delete(*args, **kwargs): impl_pyroute2.PyRoute2().delete(*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)) os-vif-1.9.0/os_vif/tests/functional/privsep.py000066400000000000000000000014701322765712300215710ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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], ) os-vif-1.9.0/os_vif/tests/unit/000077500000000000000000000000001322765712300163425ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/__init__.py000066400000000000000000000000001322765712300204410ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/base.py000066400000000000000000000014331322765712300176270ustar00rootroot00000000000000# -*- 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 os-vif-1.9.0/os_vif/tests/unit/internal/000077500000000000000000000000001322765712300201565ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/internal/__init__.py000066400000000000000000000000001322765712300222550ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/internal/command/000077500000000000000000000000001322765712300215745ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/internal/command/__init__.py000066400000000000000000000000001322765712300236730ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/internal/command/ip/000077500000000000000000000000001322765712300222045ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/internal/command/ip/__init__.py000066400000000000000000000000001322765712300243030ustar00rootroot00000000000000os-vif-1.9.0/os_vif/tests/unit/internal/command/ip/test_impl_pyroute2.py000066400000000000000000000146151322765712300264360ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 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.command.ip 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' 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_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(ipexc.NetlinkError, 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]) os-vif-1.9.0/os_vif/tests/unit/test_base.py000066400000000000000000000061141322765712300206670ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import mock import six 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': six.text_type("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())) os-vif-1.9.0/os_vif/tests/unit/test_exception.py000066400000000000000000000025411322765712300217530ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 six 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(six.text_type(exc), 'default message') def test_error_msg(self): self.assertEqual(six.text_type(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(six.text_type(exc), 'default message: bar') os-vif-1.9.0/os_vif/tests/unit/test_host_info.py000066400000000000000000000151701322765712300217470ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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" ), ]) ]) def test_serialization(self): json = self.host_info.obj_to_primitive() host_info = objects.host_info.HostInfo.obj_from_primitive(json) 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") os-vif-1.9.0/os_vif/tests/unit/test_objects.py000066400000000000000000000065561322765712300214200ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.0-167f305f6e982b9368cc38763815d429', 'VIFPortProfile8021Qbh': '1.0-4b945f07d2666ab00a48d1dc225669b1', 'VIFPortProfileBase': '1.0-77509ea1ea0dd750d5864b9bd87d3f9d', 'VIFPortProfileOpenVSwitch': '1.1-70d36e09c8d800345ce71177265212df', 'VIFPortProfileFPOpenVSwitch': '1.1-74e77f46aa5806930df6f37a0b76ff8b', 'VIFPortProfileFPBridge': '1.0-d50872b3cddd245ffebef6053dfbe27a', 'VIFPortProfileFPTap': '1.0-11670d8dbabd772ff0da26961adadc5a', 'VIFVHostUser': '1.1-1f95b43be1f884f090ca1f4d79adfd35', 'VIFPortProfileOVSRepresentor': '1.1-30e555981003a109b133da5b43ded5df', } 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) os-vif-1.9.0/os_vif/tests/unit/test_os_vif.py000066400000000000000000000136371322765712300212520ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 mock from oslo_config import cfg from stevedore import extension from vif_plug_linux_bridge import constants as lb_constants import os_vif from os_vif import exception from os_vif import objects 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() self.assertEqual(len(info.plugin_info), 2) self.assertEqual(info.plugin_info[0].plugin_name, lb_constants.PLUGIN_NAME) vif_info = info.plugin_info[0].vif_info self.assertEqual(len(vif_info), 1) self.assertEqual(vif_info[0].vif_object_name, "VIFBridge") self.assertEqual(info.plugin_info[1].plugin_name, "ovs") vif_info = info.plugin_info[1].vif_info self.assertEqual(len(vif_info), 4) self.assertEqual(vif_info[0].vif_object_name, "VIFBridge") self.assertEqual(vif_info[1].vif_object_name, "VIFOpenVSwitch") self.assertEqual(vif_info[2].vif_object_name, "VIFVHostUser") self.assertEqual(vif_info[3].vif_object_name, "VIFHostDevice") 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") os-vif-1.9.0/os_vif/tests/unit/test_vif.py000066400000000000000000000177321322765712300205510ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_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_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_vif_bridge_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_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_vif_vhost_user_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_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_vif_vhost_user_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_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) os-vif-1.9.0/os_vif/utils.py000066400000000000000000000013111322765712300157270ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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) os-vif-1.9.0/os_vif/version.py000066400000000000000000000013111322765712300162540ustar00rootroot00000000000000# 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() os-vif-1.9.0/releasenotes/000077500000000000000000000000001322765712300154255ustar00rootroot00000000000000os-vif-1.9.0/releasenotes/notes/000077500000000000000000000000001322765712300165555ustar00rootroot00000000000000os-vif-1.9.0/releasenotes/notes/add-fast-path-vhostuser-support-fe87e558326909b6.yaml000066400000000000000000000004301322765712300277300ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/add-ovs-representor-portprofile-5f8290e5a40bf0a4.yaml000066400000000000000000000004451322765712300301210ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml000066400000000000000000000003521322765712300270310ustar00rootroot00000000000000--- 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 os-vif-1.9.0/releasenotes/notes/contextlib-and-nested-with-statements-2747a9ebb9a5bfd7.yaml000066400000000000000000000005111322765712300313550ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/ensure-ovs-bridge-a0c1b51f469c92d0.yaml000066400000000000000000000003601322765712300252050ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/extend-vhostuser-object-fada14a1457d4e56.yaml000066400000000000000000000005421322765712300265250ustar00rootroot00000000000000--- 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). os-vif-1.9.0/releasenotes/notes/fix-ovs-plugin-describe-049750609559f1ba.yaml000066400000000000000000000003471322765712300261140ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/fix-stevedore-entrypoints-8002ec7a5166c977.yaml000066400000000000000000000004021322765712300267000ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/fix-vif-openvswitch-fa0d19be9dd668e1.yaml000066400000000000000000000003101322765712300257360ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/initial-release-2c71d6bbf55f763b.yaml000066400000000000000000000007601322765712300250130ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/port-profile-info-linux-bridge-4800f5a0b7328615.yaml000066400000000000000000000005221322765712300273710ustar00rootroot00000000000000--- 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``. os-vif-1.9.0/releasenotes/notes/port-profile-info-ovs-63b46a3eafc11de2.yaml000066400000000000000000000005771322765712300261740ustar00rootroot00000000000000--- 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``. os-vif-1.9.0/releasenotes/notes/vhost-user-mtu-support-cbc7d02a6665fab1.yaml000066400000000000000000000005521322765712300264430ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml000066400000000000000000000011761322765712300260430ustar00rootroot00000000000000--- 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. os-vif-1.9.0/releasenotes/source/000077500000000000000000000000001322765712300167255ustar00rootroot00000000000000os-vif-1.9.0/releasenotes/source/_static/000077500000000000000000000000001322765712300203535ustar00rootroot00000000000000os-vif-1.9.0/releasenotes/source/_static/.placeholder000066400000000000000000000000001322765712300226240ustar00rootroot00000000000000os-vif-1.9.0/releasenotes/source/_templates/000077500000000000000000000000001322765712300210625ustar00rootroot00000000000000os-vif-1.9.0/releasenotes/source/_templates/.placeholder000066400000000000000000000000001322765712300233330ustar00rootroot00000000000000os-vif-1.9.0/releasenotes/source/conf.py000066400000000000000000000035261322765712300202320ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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. project = u'os-vif release Notes' 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 = 'sphinx' # -- 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'] os-vif-1.9.0/releasenotes/source/index.rst000066400000000000000000000001621322765712300205650ustar00rootroot00000000000000============= Release Notes ============= .. toctree:: :maxdepth: 1 unreleased pike ocata newton os-vif-1.9.0/releasenotes/source/newton.rst000066400000000000000000000002321322765712300207660ustar00rootroot00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton os-vif-1.9.0/releasenotes/source/ocata.rst000066400000000000000000000002301322765712300205410ustar00rootroot00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata os-vif-1.9.0/releasenotes/source/pike.rst000066400000000000000000000002171322765712300204070ustar00rootroot00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike os-vif-1.9.0/releasenotes/source/unreleased.rst000066400000000000000000000001601322765712300216030ustar00rootroot00000000000000============================== Current Series Release Notes ============================== .. release-notes:: os-vif-1.9.0/requirements.txt000066400000000000000000000011251322765712300162170ustar00rootroot00000000000000# 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 pyroute2>=0.4.21;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 os-vif-1.9.0/setup.cfg000066400000000000000000000026531322765712300145630ustar00rootroot00000000000000[metadata] name = os_vif summary = A library for plugging and unplugging virtual interfaces in OpenStack. description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/os-vif/latest/ 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 :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [global] setup-hooks = pbr.hooks.setup_hook [files] packages = os_vif vif_plug_linux_bridge vif_plug_ovs [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [build_sphinx] builders = html all-files = 1 warning-is-error = 1 source-dir = doc/source build-dir = doc/build [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = os_vif/locale domain = os_vif [update_catalog] domain = os_vif output_dir = os_vif/locale input_file = os_vif/locale/os-vif.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = os_vif/locale/os-vif.pot [entry_points] os_vif = linux_bridge = vif_plug_linux_bridge.linux_bridge:LinuxBridgePlugin ovs = vif_plug_ovs.ovs:OvsPlugin os-vif-1.9.0/setup.py000066400000000000000000000020061322765712300144440ustar00rootroot00000000000000# 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) os-vif-1.9.0/test-requirements.txt000066400000000000000000000010421322765712300171720ustar00rootroot00000000000000# 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. hacking<0.11,>=0.10.2 coverage!=4.4,>=4.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD reno>=2.5.0 # Apache-2.0 sphinx>=1.6.2 # BSD openstackdocstheme>=1.17.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT os-vif-1.9.0/tox.ini000066400000000000000000000040411322765712300142460ustar00rootroot00000000000000[tox] minversion = 1.6 envlist = py35,py27,pep8 skipsdist = True [testenv] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt whitelist_externals = bash [tox:jenkins] downloadcache = ~/cache/pip [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:py27] commands = stestr run --black-regex ".tests.functional" --test-path="os_vif/tests" '{posargs}' [testenv:py35] commands = stestr run --black-regex ".tests.functional" '{posargs}' [testenv:functional] basepython = python2.7 setenv = {[testenv]setenv} commands = stestr run --black-regex ".tests.unit" '{posargs}' [testenv:cover] commands = coverage erase python setup.py testr --coverage --testr-args='--concurrency=1 {posargs}' coverage combine coverage html --include='os_vif/*' -d covhtml -i [testenv:docs] commands = python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] # E123, E125 skipped as they are invalid PEP-8. # Following checks are ignored on purpose. # # E251 unexpected spaces around keyword / parameter equals # reason: no improvement in readability # # E265 block comment should start with '# ' # reason: no improvement in readability # # H904 wrap long lines in parentheses instead of a backslash # reason: removed in hacking (https://review.openstack.org/#/c/101701/) # # H404 skipped on purpose per jay pipes discussion. # # Due to the upgrade to hacking 0.9.2 the following checking are # ignored on purpose for the moment and should be re-enabled. # H106: Don’t put vim configuration in source files # H203: Use assertIs(Not)None to check for None show-source = True ignore = E123,E125,E126,E127,E128,E251,E265,H302,H405,H904,H404 enable-extensions=H106,H203 builtins = _ exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build max-complexity=30 [hacking] import_exceptions = os_vif.i18n os-vif-1.9.0/vif_plug_linux_bridge/000077500000000000000000000000001322765712300173025ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_linux_bridge/__init__.py000066400000000000000000000000001322765712300214010ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_linux_bridge/constants.py000066400000000000000000000011321322765712300216650ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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' os-vif-1.9.0/vif_plug_linux_bridge/iptables.py000066400000000000000000000503251322765712300214640ustar00rootroot00000000000000# 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 import six # 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, six.string_types): 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 six.iteritems(builtin_chains[ip_version]): 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 six.itervalues(self.ipv4): if table.dirty: return True if self.use_ipv6: for table in six.itervalues(self.ipv6): 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 six.iteritems(tables): 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 os-vif-1.9.0/vif_plug_linux_bridge/linux_bridge.py000066400000000000000000000115251322765712300223330ustar00rootroot00000000000000# 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 os-vif-1.9.0/vif_plug_linux_bridge/linux_net.py000066400000000000000000000217141322765712300216660ustar00rootroot00000000000000# 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.command import ip as ip_lib from oslo_concurrency import lockutils from oslo_concurrency import processutils from oslo_log import log as logging from oslo_utils import excutils from vif_plug_linux_bridge import privsep LOG = logging.getLogger(__name__) _IPTABLES_MANAGER = None def device_exists(device): """Check if ethernet device exists.""" return os.path.exists('/sys/class/net/%s' % device) 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 device_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 # propogated _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) @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 device_exists(bridge): LOG.debug('Starting Bridge %s', bridge) try: processutils.execute('brctl', 'addbr', bridge) except Exception: with excutils.save_and_reraise_exception() as ectx: ectx.reraise = not device_exists(bridge) processutils.execute('brctl', 'setfd', bridge, 0) # processutils.execute('brctl setageing %s 10' % bridge) processutils.execute('brctl', 'stp', bridge, 'off') disv6 = ('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % bridge) if os.path.exists(disv6): processutils.execute('tee', disv6, process_input='1', check_exit_code=[0, 1]) # (danwent) bridge device MAC address can't be set directly. # instead it inherits the MAC address of the first device on the # bridge, which will either be the vlan interface, or a # physical NIC. ip_lib.set(bridge, state='up') if interface: LOG.debug('Adding interface %(interface)s to bridge %(bridge)s', {'interface': interface, 'bridge': bridge}) out, err = processutils.execute('brctl', 'addif', bridge, interface, check_exit_code=False) if (err and err != "device %s is already a member of a bridge; " "can't enslave it to bridge %s.\n" % (interface, bridge)): msg = _('Failed to add interface: %s') % err raise Exception(msg) ip_lib.set(interface, state='up') _set_device_mtu(interface, mtu) # 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, err = 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, err = 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) # NOTE(sean-k-mooney): # The bridge mtu cannont 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 os-vif-1.9.0/vif_plug_linux_bridge/privsep.py000066400000000000000000000015641322765712300213520ustar00rootroot00000000000000# # 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], ) os-vif-1.9.0/vif_plug_linux_bridge/tests/000077500000000000000000000000001322765712300204445ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_linux_bridge/tests/__init__.py000066400000000000000000000000001322765712300225430ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_linux_bridge/tests/unit/000077500000000000000000000000001322765712300214235ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_linux_bridge/tests/unit/__init__.py000066400000000000000000000000001322765712300235220ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_linux_bridge/tests/unit/test_linux_net.py000066400000000000000000000175221322765712300250500ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 mock import os.path import testtools import fixtures from os_vif.internal.command import ip as ip_lib 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 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(linux_net, "device_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(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=True) def test_ensure_bridge_exists(self, mock_dev_exists, mock_exec): linux_net.ensure_bridge("br0", None, filtering=False) mock_exec.assert_not_called() mock_dev_exists.assert_called_once_with("br0") @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=False) def test_ensure_bridge_addbr_exception(self, mock_dev_exists, mock_exec): mock_exec.side_effect = ValueError() with testtools.ExpectedException(ValueError): linux_net.ensure_bridge("br0", None, filtering=False) @mock.patch.object(ip_lib, "set") @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", side_effect=[False, True]) def test_ensure_bridge_concurrent_add(self, mock_dev_exists, mock_exec, mock_ip_set): mock_exec.side_effect = [ValueError(), 0, 0, 0] linux_net.ensure_bridge("br0", None, filtering=False) calls = [mock.call('brctl', 'addbr', 'br0'), mock.call('brctl', 'setfd', 'br0', 0), mock.call('brctl', 'stp', 'br0', "off")] mock_exec.assert_has_calls(calls) mock_dev_exists.assert_has_calls([mock.call("br0"), mock.call("br0")]) mock_ip_set.assert_called_once_with('br0', state='up') @mock.patch.object(ip_lib, "set") @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=False) def test_ensure_bridge_mtu_not_called(self, mock_dev_exists, mock_exec, mock_path_exists, mock_set_mtu, mock_ip_set): """This test validates that mtus are updated only if an interface is added to the bridge """ 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, "set") @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute", return_value=("", "")) @mock.patch.object(linux_net, "device_exists", return_value=False) def test_ensure_bridge_mtu_order(self, mock_dev_exists, mock_exec, mock_path_exists, mock_set_mtu, mock_ip_set): """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 """ 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', state='up')] mock_ip_set.assert_has_calls(calls) @mock.patch.object(ip_lib, "set") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=False) def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_exec, mock_path_exists, mock_ip_set): linux_net.ensure_bridge("br0", None, filtering=False) calls = [mock.call('brctl', 'addbr', 'br0'), mock.call('brctl', 'setfd', 'br0', 0), mock.call('brctl', 'stp', 'br0', "off")] mock_exec.assert_has_calls(calls) mock_dev_exists.assert_called_once_with("br0") mock_ip_set.assert_called_once_with('br0', state='up') @mock.patch.object(ip_lib, "set") @mock.patch.object(os.path, "exists", return_value=True) @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=False) def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_exec, mock_path_exists, mock_ip_set): linux_net.ensure_bridge("br0", None, filtering=False) calls = [mock.call('brctl', 'addbr', 'br0'), mock.call('brctl', 'setfd', 'br0', 0), mock.call('brctl', 'stp', 'br0', "off"), mock.call('tee', '/proc/sys/net/ipv6/conf/br0/disable_ipv6', check_exit_code=[0, 1], process_input='1')] mock_exec.assert_has_calls(calls) mock_dev_exists.assert_called_once_with("br0") mock_ip_set.assert_called_once_with('br0', state='up') os-vif-1.9.0/vif_plug_linux_bridge/tests/unit/test_plugin.py000066400000000000000000000112641322765712300243360ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 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) os-vif-1.9.0/vif_plug_ovs/000077500000000000000000000000001322765712300154365ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_ovs/__init__.py000066400000000000000000000000001322765712300175350ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_ovs/constants.py000066400000000000000000000014631322765712300200300ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_WIN32 = 'win32' os-vif-1.9.0/vif_plug_ovs/exception.py000066400000000000000000000024601322765712300200100ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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") os-vif-1.9.0/vif_plug_ovs/i18n.py000066400000000000000000000022251322765712300165700ustar00rootroot00000000000000# 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 # Normally this would be the plugin specific name # eg 'vif_plug_ovs', but since the OVS plugin is # in-tree, this is a special case 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) os-vif-1.9.0/vif_plug_ovs/linux_net.py000066400000000000000000000331641322765712300200240ustar00rootroot00000000000000# 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.command 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("virtfn(\d+)") # phys_port_name only contains the VF number INT_RE = re.compile("^(\d+)$") # phys_port_name contains VF## or vf## VF_RE = re.compile("vf(\d+)", re.IGNORECASE) # phys_port_name contains PF## or pf## PF_RE = re.compile("pf(\d+)", re.IGNORECASE) def _ovs_vsctl(args, timeout=None): full_args = ['ovs-vsctl'] if timeout is not None: full_args += ['--timeout=%s' % timeout] full_args += args try: return processutils.execute(*full_args) except Exception as e: LOG.error("Unable to execute %(cmd)s. Exception: %(exception)s", {'cmd': full_args, 'exception': e}) raise exception.AgentError(method=full_args) def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, instance_id, interface_type=None, vhost_server_path=None): cmd = ['--', '--if-exists', 'del-port', dev, '--', 'add-port', bridge, dev, '--', 'set', 'Interface', dev, 'external-ids:iface-id=%s' % iface_id, 'external-ids:iface-status=active', 'external-ids:attached-mac=%s' % mac, 'external-ids:vm-uuid=%s' % instance_id] if interface_type: cmd += ['type=%s' % interface_type] if vhost_server_path: cmd += ['options:vhost-server-path=%s' % vhost_server_path] return cmd def _create_ovs_bridge_cmd(bridge, datapath_type): return ['--', '--may-exist', 'add-br', bridge, '--', 'set', 'Bridge', bridge, 'datapath_type=%s' % datapath_type] @privsep.vif_plug.entrypoint def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, mtu=None, interface_type=None, timeout=None, vhost_server_path=None): _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id, mac, instance_id, interface_type, vhost_server_path), timeout=timeout) _update_device_mtu(dev, mtu, interface_type, timeout=timeout) @privsep.vif_plug.entrypoint def update_ovs_vif_port(dev, mtu=None, interface_type=None, timeout=None): _update_device_mtu(dev, mtu, interface_type, timeout=timeout) @privsep.vif_plug.entrypoint def delete_ovs_vif_port(bridge, dev, timeout=None, delete_netdev=True): _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev], timeout=timeout) if delete_netdev: _delete_net_dev(dev) def device_exists(device): """Check if ethernet device exists.""" return os.path.exists('/sys/class/net/%s' % device) def interface_in_bridge(bridge, device): """Check if an ethernet device belongs to a Linux Bridge.""" return os.path.exists('/sys/class/net/%(bridge)s/brif/%(device)s' % {'bridge': bridge, 'device': device}) def _delete_net_dev(dev): """Delete a network device only if it exists.""" if device_exists(dev): try: ip_lib.delete(dev, check_exit_code=[0, 2, 254]) LOG.debug("Net device removed: '%s'", dev) except processutils.ProcessExecutionError: with excutils.save_and_reraise_exception(): LOG.error("Failed removing net device: '%s'", dev) @privsep.vif_plug.entrypoint def create_veth_pair(dev1_name, dev2_name, mtu): """Create a pair of veth devices with the specified names, deleting any previous devices with those names. """ for dev in [dev1_name, dev2_name]: _delete_net_dev(dev) ip_lib.add(dev1_name, 'veth', peer=dev2_name) for dev in [dev1_name, dev2_name]: ip_lib.set(dev, state='up') ip_lib.set(dev, promisc='on') _update_device_mtu(dev, mtu) @privsep.vif_plug.entrypoint def update_veth_pair(dev1_name, dev2_name, mtu): """Update a pair of veth devices with new configuration.""" for dev in [dev1_name, dev2_name]: _update_device_mtu(dev, mtu) @privsep.vif_plug.entrypoint def ensure_ovs_bridge(bridge, datapath_type): _ovs_vsctl(_create_ovs_bridge_cmd(bridge, datapath_type)) @privsep.vif_plug.entrypoint def ensure_bridge(bridge): if not device_exists(bridge): processutils.execute('brctl', 'addbr', bridge) processutils.execute('brctl', 'setfd', bridge, 0) processutils.execute('brctl', 'stp', bridge, 'off') processutils.execute('brctl', 'setageing', bridge, 0) syspath = '/sys/class/net/%s/bridge/multicast_snooping' syspath = syspath % bridge processutils.execute('tee', syspath, process_input='0', check_exit_code=[0, 1]) disv6 = ('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % bridge) if os.path.exists(disv6): processutils.execute('tee', disv6, process_input='1', check_exit_code=[0, 1]) # 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 device_exists(bridge): if interface_in_bridge(bridge, dev): processutils.execute('brctl', 'delif', bridge, dev) ip_lib.set(bridge, state='down') processutils.execute('brctl', 'delbr', bridge) @privsep.vif_plug.entrypoint def add_bridge_port(bridge, dev): processutils.execute('brctl', 'addif', bridge, dev) def _update_device_mtu(dev, mtu, interface_type=None, timeout=120): 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. _set_device_mtu(dev, mtu) elif _ovs_supports_mtu_requests(timeout=timeout): _set_mtu_request(dev, mtu, timeout=timeout) else: LOG.debug("MTU not set on %(interface_name)s interface " "of type %(interface_type)s.", {'interface_name': dev, 'interface_type': interface_type}) @privsep.vif_plug.entrypoint def _set_device_mtu(dev, mtu): """Set the device MTU.""" 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]) @privsep.vif_plug.entrypoint def _set_mtu_request(dev, mtu, timeout=None): args = ['--', 'set', 'interface', dev, 'mtu_request=%s' % mtu] _ovs_vsctl(args, timeout=timeout) @privsep.vif_plug.entrypoint def _ovs_supports_mtu_requests(timeout=None): args = ['--columns=mtu_request', 'list', 'interface'] _, error = _ovs_vsctl(args, timeout=timeout) if (error == 'ovs-vsctl: Interface does not contain' + ' a column whose name matches "mtu_request"'): return False return True 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 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_path = "/sys/class/net/%s" % pf_ifname pf_sw_id_file = os.path.join(pf_path, "phys_switch_id") pf_sw_id = None try: with open(pf_sw_id_file, 'r') as fd: pf_sw_id = fd.readline().rstrip() except (OSError, IOError): raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num) pf_subsystem_file = os.path.join(pf_path, "subsystem") try: devices = os.listdir(pf_subsystem_file) except (OSError, IOError): raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num) for device in devices: if device == pf_ifname: continue device_path = "/sys/class/net/%s" % device device_sw_id_file = os.path.join(device_path, "phys_switch_id") try: with open(device_sw_id_file, 'r') as fd: device_sw_id = fd.readline().rstrip() except (OSError, IOError): continue if device_sw_id != pf_sw_id: continue device_port_name_file = ( os.path.join(device_path, 'phys_port_name')) if not os.path.isfile(device_port_name_file): continue try: with open(device_port_name_file, 'r') as fd: phys_port_name = fd.readline().rstrip() except (OSError, IOError): 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: sw_id_file = "/sys/class/net/%s/phys_switch_id" % netdev with open(sw_id_file, 'r') as fd: phys_switch_id = fd.readline().rstrip() 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) # make the if statement later more readable ignore_switchdev = not switchdev try: for netdev in os.listdir(dev_path): if ignore_switchdev or _is_switchdev(netdev): return 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 os-vif-1.9.0/vif_plug_ovs/ovs.py000066400000000000000000000262071322765712300166260ustar00rootroot00000000000000# 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 os_vif import objects from os_vif import plugin from oslo_config import cfg from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs import linux_net 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 traiffic. """ 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"), ) @staticmethod def gen_port_name(prefix, id): return ("%s%s" % (prefix, id))[:OvsPlugin.NIC_NAME_LEN] @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__, min_version="1.0", max_version="1.0", ) pp_ovs_representor = objects.host_info.HostPortProfileInfo( profile_object_name= objects.vif.VIFPortProfileOVSRepresentor.__name__, 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) linux_net.create_ovs_vif_port( vif.network.bridge, vif_name, vif.port_profile.interface_id, vif.address, instance_info.uuid, mtu, timeout=self.config.ovs_vsctl_timeout, **kwargs) def _update_vif_port(self, vif, vif_name): mtu = self._get_mtu(vif) linux_net.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): linux_net.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 linux_net.device_exists(v2_name): linux_net.create_veth_pair(v1_name, v2_name, mtu) linux_net.add_bridge_port(vif.bridge_name, v1_name) linux_net.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 linux_net.device_exists(vif.id): linux_net.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) self._create_vif_port(vif, vif.id, instance_info) def _plug_vf_passthrough(self, vif, instance_info): linux_net.ensure_ovs_bridge( vif.network.bridge, 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) linux_net.set_interface_state(representor, 'up') self._create_vif_port(vif, representor, instance_info) def plug(self, vif, instance_info): if not hasattr(vif, "port_profile"): raise exception.MissingPortProfile() if not isinstance(vif.port_profile, objects.vif.VIFPortProfileOpenVSwitch): raise exception.WrongPortProfile( profile=vif.port_profile.__class__.__name__) if isinstance(vif, objects.vif.VIFOpenVSwitch): if sys.platform != constants.PLATFORM_WIN32: linux_net.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) else: self._plug_vif_windows(vif, instance_info) elif isinstance(vif, objects.vif.VIFBridge): if sys.platform != constants.PLATFORM_WIN32: self._plug_bridge(vif, instance_info) else: self._plug_vif_windows(vif, instance_info) elif isinstance(vif, objects.vif.VIFVHostUser): self._plug_vhostuser(vif, instance_info) elif isinstance(vif, objects.vif.VIFHostDevice): self._plug_vf_passthrough(vif, instance_info) def _unplug_vhostuser(self, vif, instance_info): linux_net.delete_ovs_vif_port(vif.network.bridge, OvsPlugin.gen_port_name( constants.OVS_VHOSTUSER_PREFIX, vif.id), timeout=self.config.ovs_vsctl_timeout) 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) linux_net.delete_ovs_vif_port(vif.network.bridge, v2_name, timeout=self.config.ovs_vsctl_timeout) def _unplug_vif_windows(self, vif, instance_info): """Remove port from OVS.""" linux_net.delete_ovs_vif_port(vif.network.bridge, vif.id, timeout=self.config.ovs_vsctl_timeout) def _unplug_vf_passthrough(self, vif, instance_info): """Remove port from OVS.""" 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) # 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 linux_net.delete_ovs_vif_port( vif.network.bridge, representor, delete_netdev=False) 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 isinstance(vif, objects.vif.VIFOpenVSwitch): if sys.platform == constants.PLATFORM_WIN32: self._unplug_vif_windows(vif, instance_info) elif isinstance(vif, objects.vif.VIFBridge): if sys.platform != constants.PLATFORM_WIN32: self._unplug_bridge(vif, instance_info) else: self._unplug_vif_windows(vif, instance_info) elif isinstance(vif, objects.vif.VIFVHostUser): self._unplug_vhostuser(vif, instance_info) elif isinstance(vif, objects.vif.VIFHostDevice): self._unplug_vf_passthrough(vif, instance_info) os-vif-1.9.0/vif_plug_ovs/privsep.py000066400000000000000000000015421322765712300175020ustar00rootroot00000000000000# # 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], ) os-vif-1.9.0/vif_plug_ovs/tests/000077500000000000000000000000001322765712300166005ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_ovs/tests/__init__.py000066400000000000000000000000001322765712300206770ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_ovs/tests/unit/000077500000000000000000000000001322765712300175575ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_ovs/tests/unit/__init__.py000066400000000000000000000000001322765712300216560ustar00rootroot00000000000000os-vif-1.9.0/vif_plug_ovs/tests/unit/test_linux_net.py000066400000000000000000000625701322765712300232070ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 mock import os.path import testtools from os_vif.internal.command import ip as ip_lib from oslo_concurrency import processutils from vif_plug_ovs import constants 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(ip_lib, "set") @mock.patch.object(linux_net, "device_exists", return_value=True) def test_ensure_bridge_exists(self, mock_dev_exists, mock_ip_set): linux_net.ensure_bridge("br0") mock_ip_set.assert_called_once_with('br0', state='up', check_exit_code=[0, 2, 254]) mock_dev_exists.assert_has_calls([mock.call("br0")]) @mock.patch.object(ip_lib, "set") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=False) def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_execute, mock_path_exists, mock_ip_set): linux_net.ensure_bridge("br0") calls = [ mock.call('brctl', 'addbr', 'br0'), mock.call('brctl', 'setfd', 'br0', 0), mock.call('brctl', 'stp', 'br0', "off"), mock.call('brctl', 'setageing', 'br0', 0), mock.call('tee', '/sys/class/net/br0/bridge/multicast_snooping', check_exit_code=[0, 1], process_input='0'), ] mock_execute.assert_has_calls(calls) mock_dev_exists.assert_has_calls([mock.call("br0")]) mock_ip_set.assert_called_once_with('br0', state='up', check_exit_code=[0, 2, 254]) @mock.patch.object(ip_lib, "set") @mock.patch.object(os.path, "exists", return_value=True) @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=False) def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_execute, mock_path_exists, mock_ip_set): linux_net.ensure_bridge("br0") calls = [ mock.call('brctl', 'addbr', 'br0'), mock.call('brctl', 'setfd', 'br0', 0), mock.call('brctl', 'stp', 'br0', "off"), mock.call('brctl', 'setageing', 'br0', 0), mock.call('tee', '/sys/class/net/br0/bridge/multicast_snooping', check_exit_code=[0, 1], process_input='0'), mock.call('tee', '/proc/sys/net/ipv6/conf/br0/disable_ipv6', check_exit_code=[0, 1], process_input='1'), ] mock_execute.assert_has_calls(calls) mock_dev_exists.assert_has_calls([mock.call("br0")]) mock_ip_set.assert_called_once_with('br0', state='up', check_exit_code=[0, 2, 254]) @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=False) @mock.patch.object(linux_net, "interface_in_bridge", return_value=False) def test_delete_bridge_none(self, mock_interface_br, mock_dev_exists, mock_execute,): linux_net.delete_bridge("br0", "vnet1") mock_execute.assert_not_called() mock_dev_exists.assert_has_calls([mock.call("br0")]) mock_interface_br.assert_not_called() @mock.patch.object(ip_lib, "set") @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=True) @mock.patch.object(linux_net, "interface_in_bridge", return_value=True) def test_delete_bridge_exists(self, mock_interface_br, mock_dev_exists, mock_execute, mock_ip_set): linux_net.delete_bridge("br0", "vnet1") calls = [ mock.call('brctl', 'delif', 'br0', 'vnet1'), mock.call('brctl', 'delbr', 'br0')] mock_execute.assert_has_calls(calls) mock_dev_exists.assert_has_calls([mock.call("br0")]) mock_interface_br.assert_called_once_with("br0", "vnet1") mock_ip_set.assert_called_once_with('br0', state='down') @mock.patch.object(ip_lib, "set") @mock.patch.object(processutils, "execute") @mock.patch.object(linux_net, "device_exists", return_value=True) @mock.patch.object(linux_net, "interface_in_bridge", return_value=False) def test_delete_interface_not_present(self, mock_interface_br, mock_dev_exists, mock_execute, mock_ip_set): linux_net.delete_bridge("br0", "vnet1") mock_execute.assert_called_once_with('brctl', 'delbr', 'br0') mock_dev_exists.assert_has_calls([mock.call("br0")]) mock_interface_br.assert_called_once_with("br0", "vnet1") mock_ip_set.assert_called_once_with('br0', state='down') @mock.patch.object(processutils, "execute") def test_add_bridge_port(self, mock_execute): linux_net.add_bridge_port("br0", "vnet1") mock_execute.assert_has_calls([ mock.call('brctl', 'addif', 'br0', 'vnet1')]) def test_ovs_vif_port_cmd(self): expected = ['--', '--if-exists', 'del-port', 'fake-dev', '--', 'add-port', 'fake-bridge', 'fake-dev', '--', 'set', 'Interface', 'fake-dev', 'external-ids:iface-id=fake-iface-id', 'external-ids:iface-status=active', 'external-ids:attached-mac=fake-mac', 'external-ids:vm-uuid=fake-instance-uuid'] cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', 'fake-instance-uuid') self.assertEqual(expected, cmd) expected += ['type=fake-type'] cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', 'fake-instance-uuid', 'fake-type') self.assertEqual(expected, cmd) expected += ['options:vhost-server-path=/fake/path'] cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', 'fake-instance-uuid', 'fake-type', vhost_server_path='/fake/path') self.assertEqual(expected, cmd) @mock.patch.object(linux_net, '_create_ovs_bridge_cmd') @mock.patch.object(linux_net, '_ovs_vsctl') def test_ensure_ovs_bridge(self, mock_vsctl, mock_create_ovs_bridge): bridge = 'fake-bridge' dp_type = 'fake-type' linux_net.ensure_ovs_bridge(bridge, dp_type) mock_create_ovs_bridge.assert_called_once_with(bridge, dp_type) self.assertTrue(mock_vsctl.called) def test_create_ovs_bridge_cmd(self): bridge = 'fake-bridge' dp_type = 'fake-type' expected = ['--', '--may-exist', 'add-br', bridge, '--', 'set', 'Bridge', bridge, 'datapath_type=%s' % dp_type] actual = linux_net._create_ovs_bridge_cmd(bridge, dp_type) self.assertEqual(expected, actual) @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') @mock.patch.object(linux_net, '_ovs_vsctl') @mock.patch.object(linux_net, '_create_ovs_vif_cmd') @mock.patch.object(linux_net, '_set_device_mtu') def test_ovs_vif_port_with_type_vhostuser(self, mock_set_device_mtu, mock_create_cmd, mock_vsctl, mock_ovs_supports_mtu_requests): mock_ovs_supports_mtu_requests.return_value = True linux_net.create_ovs_vif_port( 'fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid", mtu=1500, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_cmd.assert_called_once_with('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid", constants.OVS_VHOSTUSER_INTERFACE_TYPE, None) self.assertFalse(mock_set_device_mtu.called) self.assertTrue(mock_vsctl.called) @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') @mock.patch.object(linux_net, '_ovs_vsctl') @mock.patch.object(linux_net, '_create_ovs_vif_cmd') @mock.patch.object(linux_net, '_set_device_mtu') def test_ovs_vif_port_with_type_vhostuserclient(self, mock_set_device_mtu, mock_create_cmd, mock_vsctl, mock_ovs_supports_mtu_requests): mock_ovs_supports_mtu_requests.return_value = True linux_net.create_ovs_vif_port( 'fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid", mtu=1500, interface_type=constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE, vhost_server_path="/fake/path") mock_create_cmd.assert_called_once_with('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid", constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE, "/fake/path") self.assertFalse(mock_set_device_mtu.called) self.assertTrue(mock_vsctl.called) @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') @mock.patch.object(linux_net, '_ovs_vsctl') @mock.patch.object(linux_net, '_create_ovs_vif_cmd') @mock.patch.object(linux_net, '_set_device_mtu') def test_ovs_vif_port_with_no_mtu(self, mock_set_device_mtu, mock_create_cmd, mock_vsctl, mock_ovs_supports_mtu_requests): mock_ovs_supports_mtu_requests.return_value = True linux_net.create_ovs_vif_port( 'fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid") mock_create_cmd.assert_called_once_with('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid", None, None) self.assertFalse(mock_set_device_mtu.called) self.assertTrue(mock_vsctl.called) @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') @mock.patch.object(linux_net, '_set_mtu_request') @mock.patch.object(linux_net, '_ovs_vsctl') @mock.patch.object(linux_net, '_create_ovs_vif_cmd', return_value='ovs_command') @mock.patch.object(linux_net, '_set_device_mtu') def test_ovs_vif_port_with_timeout(self, mock_set_device_mtu, mock_create_cmd, mock_vsctl, mock_set_mtu_request, mock_ovs_supports_mtu_requests): mock_ovs_supports_mtu_requests.return_value = True linux_net.create_ovs_vif_port( 'fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid", timeout=42) self.assertTrue(mock_create_cmd.called) self.assertFalse(mock_set_device_mtu.called) mock_vsctl.assert_called_with('ovs_command', timeout=42) @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') @mock.patch.object(linux_net, '_set_mtu_request') @mock.patch.object(linux_net, '_ovs_vsctl') @mock.patch.object(linux_net, '_create_ovs_vif_cmd', return_value='ovs_command') @mock.patch.object(linux_net, '_set_device_mtu') def test_ovs_vif_port_with_no_timeout(self, mock_set_device_mtu, mock_create_cmd, mock_vsctl, mock_set_mtu_request, mock_ovs_supports_mtu_requests): mock_ovs_supports_mtu_requests.return_value = True linux_net.create_ovs_vif_port( 'fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', "fake-instance-uuid") self.assertTrue(mock_create_cmd.called) self.assertFalse(mock_set_device_mtu.called) mock_vsctl.assert_called_with('ovs_command', timeout=None) @mock.patch.object(processutils, "execute") def test_ovs_vsctl(self, mock_execute): args = ['fake-args', 42] timeout = 42 linux_net._ovs_vsctl(args) linux_net._ovs_vsctl(args, timeout=timeout) mock_execute.assert_has_calls([ mock.call('ovs-vsctl', *args), mock.call('ovs-vsctl', '--timeout=%s' % timeout, *args)]) @mock.patch.object(linux_net, '_ovs_vsctl') def test_set_mtu_request(self, mock_vsctl): dev = 'fake-dev' mtu = 'fake-mtu' timeout = 120 linux_net._set_mtu_request(dev, mtu, timeout=timeout) args = ['--', 'set', 'interface', dev, 'mtu_request=%s' % mtu] mock_vsctl.assert_called_with(args, timeout=timeout) @mock.patch.object(linux_net, '_delete_net_dev') @mock.patch.object(linux_net, '_ovs_vsctl') def test_delete_ovs_vif_port_delete_netdev( self, mock_vsctl, mock_delete_net_dev): bridge = 'fake-bridge' dev = 'fake-dev' timeout = 120 linux_net.delete_ovs_vif_port(bridge, dev, timeout=timeout) args = ['--', '--if-exists', 'del-port', bridge, dev] mock_vsctl.assert_called_with(args, timeout=timeout) mock_delete_net_dev.assert_called() @mock.patch.object(linux_net, '_delete_net_dev') @mock.patch.object(linux_net, '_ovs_vsctl') def test_delete_ovs_vif_port(self, mock_vsctl, mock_delete_net_dev): bridge = 'fake-bridge' dev = 'fake-dev' timeout = 120 linux_net.delete_ovs_vif_port( bridge, dev, timeout=timeout, delete_netdev=False) args = ['--', '--if-exists', 'del-port', bridge, dev] mock_vsctl.assert_called_with(args, timeout=timeout) mock_delete_net_dev.assert_not_called() @mock.patch.object(linux_net, '_ovs_vsctl') def test_ovs_supports_mtu_requests(self, mock_vsctl): args = ['--columns=mtu_request', 'list', 'interface'] timeout = 120 msg = 'ovs-vsctl: Interface does not contain' + \ ' a column whose name matches "mtu_request"' mock_vsctl.return_value = (None, msg) result = linux_net._ovs_supports_mtu_requests(timeout=timeout) mock_vsctl.assert_called_with(args, timeout=timeout) self.assertFalse(result) mock_vsctl.return_value = (None, None) result = linux_net._ovs_supports_mtu_requests(timeout=timeout) mock_vsctl.assert_called_with(args, timeout=timeout) self.assertTrue(result) @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') def test_is_switchdev_ioerror(self, mock_isfile, mock_open): mock_isfile.side_effect = [True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( [IOError()]) test_switchdev = linux_net._is_switchdev('pf_ifname') self.assertEqual(test_switchdev, False) @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') def test_is_switchdev_empty(self, mock_isfile, mock_open): mock_isfile.side_effect = [True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['']) open_calls = ( [mock.call('/sys/class/net/pf_ifname/phys_switch_id', 'r'), mock.call().readline(), mock.call().__exit__(None, None, None)]) test_switchdev = linux_net._is_switchdev('pf_ifname') mock_open.assert_has_calls(open_calls) self.assertEqual(test_switchdev, False) @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') def test_is_switchdev_positive(self, mock_isfile, mock_open): mock_isfile.side_effect = [True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['pf_sw_id']) open_calls = ( [mock.call('/sys/class/net/pf_ifname/phys_switch_id', 'r'), mock.call().readline(), mock.call().__exit__(None, None, None)]) test_switchdev = linux_net._is_switchdev('pf_ifname') mock_open.assert_has_calls(open_calls) 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('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') def test_get_representor_port(self, mock_listdir, mock_isfile, mock_open): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock_isfile.side_effect = [True, True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['pf_sw_id', 'pf_sw_id', '1', 'pf_sw_id', 'pf0vf2']) open_calls = ( [mock.call('/sys/class/net/pf_ifname/phys_switch_id', 'r'), mock.call().readline(), mock.call().__exit__(None, None, None), mock.call('/sys/class/net/rep_vf_1/phys_switch_id', 'r'), mock.call().readline(), mock.call().__exit__(None, None, None), mock.call('/sys/class/net/rep_vf_1/phys_port_name', 'r'), mock.call().readline(), mock.call().__exit__(None, None, None), mock.call('/sys/class/net/rep_vf_2/phys_switch_id', 'r'), mock.call().readline(), mock.call().__exit__(None, None, None), mock.call('/sys/class/net/rep_vf_2/phys_port_name', 'r'), mock.call().readline(), mock.call().__exit__(None, None, None)]) ifname = linux_net.get_representor_port('pf_ifname', '2') mock_open.assert_has_calls(open_calls) self.assertEqual('rep_vf_2', ifname) @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') def test_get_representor_port_2_pfs( self, mock_listdir, mock_isfile, mock_open): 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_isfile.side_effect = [True, True, True, True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['pf1_sw_id', 'pf1_sw_id', 'pf2_sw_id', '1', 'pf1_sw_id', '2']) ifname = linux_net.get_representor_port('pf_ifname1', '2') self.assertEqual('rep_pf1_vf_2', ifname) @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') def test_get_representor_port_not_found( self, mock_listdir, mock_isfile, mock_open): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock_isfile.side_effect = [True, True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['pf_sw_id', 'pf_sw_id', '1', 'pf_sw_id', '2']) self.assertRaises( exception.RepresentorNotFound, linux_net.get_representor_port, 'pf_ifname', '3'), @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') def test_get_representor_port_exception_io_error( self, mock_listdir, mock_isfile, mock_open): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock_isfile.side_effect = [True, True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['pf_sw_id', 'pf_sw_id', IOError(), 'pf_sw_id', '2']) self.assertRaises( exception.RepresentorNotFound, linux_net.get_representor_port, 'pf_ifname', '3') @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') def test_get_representor_port_exception_value_error( self, mock_listdir, mock_isfile, mock_open): mock_listdir.return_value = [ 'pf_ifname', 'rep_vf_1', 'rep_vf_2' ] mock_isfile.side_effect = [True, True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['pf_sw_id', 'pf_sw_id', '1', 'pf_sw_id', 'a']) self.assertRaises( exception.RepresentorNotFound, linux_net.get_representor_port, 'pf_ifname', '3') @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') def test_physical_function_inferface_name( self, mock_listdir, mock_isfile, mock_open): mock_listdir.return_value = ['foo', 'bar'] mock_isfile.side_effect = [True, True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['', 'valid_switch']) ifname = linux_net.get_ifname_by_pci_address( '0000:00:00.1', pf_interface=True, switchdev=False) self.assertEqual(ifname, 'foo') @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') def test_physical_function_inferface_name_with_switchdev( self, mock_listdir, mock_isfile, mock_open): mock_listdir.return_value = ['foo', 'bar'] mock_isfile.side_effect = [True, True] mock_open.return_value.__enter__ = lambda s: s readline_mock = mock_open.return_value.readline readline_mock.side_effect = ( ['', 'valid_switch']) 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') 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' ) os-vif-1.9.0/vif_plug_ovs/tests/unit/test_plugin.py000066400000000000000000000454071322765712300225000ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 mock import testtools 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 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_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, dev_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, dev_name='tap-xxx-yyy-zzz', port_profile=self.profile_ovs) 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) 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(linux_net, '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, plugin.config.network_device_mtu, timeout=plugin.config.ovs_vsctl_timeout, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(linux_net, '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, self.network_ovs_mtu.mtu, timeout=plugin.config.ovs_vsctl_timeout, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovs, 'sys') @mock.patch.object(linux_net, 'ensure_ovs_bridge') def test_plug_ovs(self, ensure_ovs_bridge, mock_sys): mock_sys.platform = 'linux' plug_bridge_mock = mock.Mock() plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._plug_bridge = plug_bridge_mock plugin.plug(self.vif_ovs, self.instance) dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_ovs) ensure_ovs_bridge.assert_called_once_with(self.vif_ovs.network.bridge, dp_type) @mock.patch.object(linux_net, 'set_interface_state') @mock.patch.object(linux_net, '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(linux_net, 'device_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 self.assertTrue(linux_net.device_exists('test')) 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(linux_net, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') @mock.patch.object(linux_net, 'device_exists', return_value=False) @mock.patch.object(ovs, 'sys') def _check_plug_ovs_windows(self, vif, mock_sys, device_exists, _create_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type(vif) calls = { 'device_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) device_exists.assert_has_calls(calls['device_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) def test_unplug_ovs(self): unplug_bridge_mock = mock.Mock() plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_bridge = unplug_bridge_mock plugin.unplug(self.vif_ovs, self.instance) unplug_bridge_mock.assert_not_called() @mock.patch.object(linux_net, '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', timeout=120)] } 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(linux_net, '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, timeout=120) 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(linux_net, '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(linux_net, 'ensure_ovs_bridge') @mock.patch.object(linux_net, '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', 1500, interface_type='dpdkvhostuserclient', vhost_server_path='/var/run/openvswitch/vhub679325f-ca', timeout=120)], '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(linux_net, 'delete_ovs_vif_port') def test_unplug_ovs_vhostuser(self, delete_ovs_vif_port): calls = { 'delete_ovs_vif_port': [mock.call('br0', 'vhub679325f-ca', timeout=120)] } 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(linux_net, '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(linux_net, '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'])