os_vif-2.0.0/0000775000175000017500000000000013621620704013021 5ustar zuulzuul00000000000000os_vif-2.0.0/CONTRIBUTING.rst0000664000175000017500000000103313621620626015462 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/os-vif os_vif-2.0.0/requirements.txt0000664000175000017500000000122513621620626016310 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 netaddr>=0.7.18 # BSD oslo.concurrency>=3.20.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.privsep>=1.23.0 # Apache-2.0 oslo.versionedobjects>=1.28.0 # Apache-2.0 ovsdbapp>=0.12.1 # Apache-2.0 pyroute2>=0.5.2;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 debtcollector>=1.19.0 # Apache-2.0 os_vif-2.0.0/babel.cfg0000664000175000017500000000002113621620626014543 0ustar zuulzuul00000000000000[python: **.py] os_vif-2.0.0/releasenotes/0000775000175000017500000000000013621620704015512 5ustar zuulzuul00000000000000os_vif-2.0.0/releasenotes/notes/0000775000175000017500000000000013621620704016642 5ustar zuulzuul00000000000000os_vif-2.0.0/releasenotes/notes/add-no-op-plugin-763a6703e7328a24.yaml0000664000175000017500000000045413621620626024604 0ustar zuulzuul00000000000000--- features: - | A new VIF plugin, ``vif_plug_noop``, has been added which can be used with network backends that do not require any action to be performed when a network interface is plugged. This plugin allow for use of, for example, the generic vhost user VIF type without OVS. os_vif-2.0.0/releasenotes/notes/add-fast-path-vhostuser-support-fe87e558326909b6.yaml0000664000175000017500000000043013621620626030020 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/ensure-ovs-bridge-a0c1b51f469c92d0.yaml0000664000175000017500000000036013621620626025275 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/port-profile-info-linux-bridge-4800f5a0b7328615.yaml0000664000175000017500000000052213621620626027461 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml0000664000175000017500000000117613621620626026133 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/add-ovs-representor-portprofile-5f8290e5a40bf0a4.yaml0000664000175000017500000000044513621620626030211 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/always-plug-vifs-for-ovs-1d033fc49a9c6c4e.yaml0000664000175000017500000000403713621620626026635 0ustar zuulzuul00000000000000--- features: - | In this release the OVS plugin was extended to always plug VIFs even when libvirt could plug the vif. This will enable faster migration leveraging the multiple port bindings work completed in the Rocky release. security: - | In this release an edgecase where libvirt plugged the VIF instead of os-vif was addressed. Previously if ``ovs_hybrid_plug`` was set to ``False`` in the port binding details, os-vif would only ensure the ovs bridge existed and the plugging would be done by libvirt. As a result during live migration, there was a short interval where a guest could receive tagged broadcast, multicast, or flooded traffic to/from another tenant. This vulnerability is described in `bug 1734320`_. By ensuring that os-vif always creates the OVS port as part of vif plugging we enable neutron to isolate the port prior to nova resuming the VM on the destination node. Note that as Nova cannot rely on Neutron to send ``network-vif-plugged`` events on completion of wiring up an interface it cannot wait to receive a notification before proceeding with the migration. As a result this is a partial mitigation and additional changes will be required to fully address this bug. .. _bug 1734320: https://bugs.launchpad.net/neutron/+bug/1734320 - | A new config option was introduced for the OVS VIF plugin. The ``isolate_vif`` option was added as a partial mitigation of `bug 1734320`_. The ``isolate_vif`` option defaults to ``False`` for backwards compatibility with SDN controller based OpenStack deployments. For all deployments using the reference implementation of ML2/OVS with the neutron L2 agents, ``isolate_vif`` should be set to ``True``. This option instructs the OVS plugin to assign the VIF to the Neutron dead VLAN (4095) when attaching the interface to OVS. By setting the VIF's VLAN to this dead VLAN number, we eliminate the small attack vector that exists for other tenants to read packets during the VIF's bring up. os_vif-2.0.0/releasenotes/notes/fix-vif-openvswitch-fa0d19be9dd668e1.yaml0000664000175000017500000000031013621620626026026 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/vhost-user-mtu-support-cbc7d02a6665fab1.yaml0000664000175000017500000000055213621620626026533 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/port-profile-info-ovs-63b46a3eafc11de2.yaml0000664000175000017500000000057713621620626026264 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/contextlib-and-nested-with-statements-2747a9ebb9a5bfd7.yaml0000664000175000017500000000051113621620626031445 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/generic-datapath-offloads-41cabb6842b41533.yaml0000664000175000017500000000107613621620626026650 0ustar zuulzuul00000000000000--- features: - A new set of attributes to port profiles has been introduced, namely ``Datapath Offload Types``, with ``DatapathOffloadRepresentor`` allowing os-vif to pass the required metadata for representors conforming to the kernel switchdev representor model. deprecations: - The API for ``VIFPortProfileOVSRepresentor`` has been frozen pending deprecation of the class. Users should transition to setting the ``datapath_offload`` of ``VIFPortProfileOpenVSwitch`` to a ``DatapathOffloadRepresentor`` object to pass representor information. os_vif-2.0.0/releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml0000664000175000017500000000035213621620626027121 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/drop-python2-support-7a4bc7d31253c1e5.yaml0000664000175000017500000000011613621620626026013 0ustar zuulzuul00000000000000--- upgrade: - | Python 2 is no longer supported. Python 3 is required. os_vif-2.0.0/releasenotes/notes/fix-ovs-plugin-describe-049750609559f1ba.yaml0000664000175000017500000000034713621620626026204 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/brctl-removal-a5b0e69b865afa57.yaml0000664000175000017500000000102613621620626024603 0ustar zuulzuul00000000000000--- other: - | With this release, packagers of ``os-vif`` no longer need to create a dependency on ``brctl``. ``brctl`` is largely considered obsolete and has been replaced with iproute2 by default in many linux distributions. RHEL 8 will not ship ``brctl`` in its default repos. As part of a larger effort to remove usage of ``brctl`` from OpenStack ``os-vif`` has replaced its usage of ``brctl`` with ``pyroute2``. This does not introduce any new requirements as ``pyroute2`` is already a requirement. os_vif-2.0.0/releasenotes/notes/prevent-lb-reply-arp-6459133bfb056069.yaml0000664000175000017500000000053613621620626025524 0ustar zuulzuul00000000000000--- security: - | Prevent Linux Bridge from replying to ARP messages. It should reply only if the target IP address is a local address configured on the incoming interface and it should always use the best local address. See `The ARP flux problem `_ for more information. os_vif-2.0.0/releasenotes/notes/fix-stevedore-entrypoints-8002ec7a5166c977.yaml0000664000175000017500000000040213621620626026770 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/extend-vhostuser-object-fada14a1457d4e56.yaml0000664000175000017500000000054213621620626026615 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/notes/remove_iptools_implementation-2eb866573a680e61.yaml0000664000175000017500000000045213621620626027771 0ustar zuulzuul00000000000000--- upgrade: - | Removed IPTools implementation. IPTools driver was implemented to avoid a bug in pyroute2 library, currently solved. This implementation was marked as "deprecated" two releases ago. IP Linux commands now use `Pyroute2`_. .. _Pyroute2: https://docs.pyroute2.org/ ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000os_vif-2.0.0/releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.yamlos_vif-2.0.0/releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.y0000664000175000017500000000172513621620626032717 0ustar zuulzuul00000000000000--- security: - | In 1.13.0 it was reported that bug #1734320 was partially resolved by change Iaf15fa7a678ec2624f7c12f634269c465fbad930. It has since emerged that that change introduced another bug due to an interaction with libvirt. It was understood that libvirt would not recreate the ovs port if it was present on the ovs bridge when spawning a vm however on inspection of the libvirt code this is not the case. In this release we have reverted the change to os-vif and libvirt will be the only entity to create the ovs port when vif_type is set to ovs and hybrid_plug is set to false in the neutron port binding details. Bug #1734320 is not expected to be present if hybrid_plug=true or vif_type vhost-user is used on linux. On windows if hybrid_plug is false on bug #1734320 is also not expected to be present. A new mitigation to bug #1734320 will be developed for the remaining case of hybrid_plug=false on linux. os_vif-2.0.0/releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml0000664000175000017500000000057113621620626026257 0ustar zuulzuul00000000000000--- features: - | Added an abstract OVSDB API in ``vif_plug_ovs``. All calls to OVS database will de done using this unique API. Command line implementation using ``ovs-vsctl`` was refactored as a backend for this abstract API. A new configuration variable, ``ovsdb_interface``, is added to select the interface for interacting with the OVS database. os_vif-2.0.0/releasenotes/notes/add-ovsdb-native-322fffb49c91503d.yaml0000664000175000017500000000104613621620626025077 0ustar zuulzuul00000000000000--- features: - | Added native implementation of OVSDB API in ``vif_plug_ovs``. Both ``vsctl`` and ``native`` APIs could be selected by setting the configuration variable ``ovsdb_interface``. A new configuration variable, ``ovsdb_connection``, is added. This variable defines the connection string for the OVSDB backend. other: - | Changed default value of ``ovsdb_connection`` to "tcp:127.0.0.1:6640", to match the default value set in Neutron project. This connection string is needed by OVSDB native interface. os_vif-2.0.0/releasenotes/notes/do-not-force-mac-ageing-c6e8d750130c5740.yaml0000664000175000017500000000121013621620626026057 0ustar zuulzuul00000000000000--- fixes: - | As part of a `bug #1715317`_, MAC ageing was disabled for the intermediate bridge created as part of the hybrid plug mechanism. During the removal of ``brctl``, this behavior was inadvertently applied to all linux bridges created by os-vif including those used in the linuxbridge driver. As a result this can lead to packet flooding (see bug #1837252) when instances are migrated. This behavior has been reverted so that the default mac ageing is determined by the kernel and is not set when using the os-vif linux bridge plugin. .. _bug #1715317: https://bugs.launchpad.net/os-vif/+bug/1837252os_vif-2.0.0/releasenotes/notes/initial-release-2c71d6bbf55f763b.yaml0000664000175000017500000000076013621620626025103 0ustar zuulzuul00000000000000--- 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-2.0.0/releasenotes/source/0000775000175000017500000000000013621620704017012 5ustar zuulzuul00000000000000os_vif-2.0.0/releasenotes/source/pike.rst0000664000175000017500000000017213621620626020477 0ustar zuulzuul00000000000000========================= Pike Series Release Notes ========================= .. release-notes:: :branch: stable/pike os_vif-2.0.0/releasenotes/source/ocata.rst0000664000175000017500000000023013621620626020631 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata os_vif-2.0.0/releasenotes/source/stein.rst0000664000175000017500000000017613621620626020675 0ustar zuulzuul00000000000000========================== Stein Series Release Notes ========================== .. release-notes:: :branch: stable/stein os_vif-2.0.0/releasenotes/source/queens.rst0000664000175000017500000000020213621620626021041 0ustar zuulzuul00000000000000=========================== Queens Series Release Notes =========================== .. release-notes:: :branch: stable/queens os_vif-2.0.0/releasenotes/source/newton.rst0000664000175000017500000000023213621620626021056 0ustar zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton os_vif-2.0.0/releasenotes/source/rocky.rst0000664000175000017500000000022113621620626020671 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky os_vif-2.0.0/releasenotes/source/_templates/0000775000175000017500000000000013621620704021147 5ustar zuulzuul00000000000000os_vif-2.0.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013621620626023423 0ustar zuulzuul00000000000000os_vif-2.0.0/releasenotes/source/unreleased.rst0000664000175000017500000000015313621620626021675 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: os_vif-2.0.0/releasenotes/source/_static/0000775000175000017500000000000013621620704020440 5ustar zuulzuul00000000000000os_vif-2.0.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000013621620626022714 0ustar zuulzuul00000000000000os_vif-2.0.0/releasenotes/source/index.rst0000664000175000017500000000022713621620626020657 0ustar zuulzuul00000000000000============= Release Notes ============= .. toctree:: :maxdepth: 1 unreleased train stein rocky queens pike ocata newton os_vif-2.0.0/releasenotes/source/conf.py0000664000175000017500000000346413621620626020323 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # # os-vif Release Notes documentation build configuration file # -- General configuration ------------------------------------------------ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = u'2017, OpenStack Foundation' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The name of the Pygments (syntax highlighting) style to use. pygments_style = '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-2.0.0/releasenotes/source/train.rst0000664000175000017500000000017613621620626020670 0ustar zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train os_vif-2.0.0/doc/0000775000175000017500000000000013621620704013566 5ustar zuulzuul00000000000000os_vif-2.0.0/doc/requirements.txt0000664000175000017500000000030613621620626017054 0ustar zuulzuul00000000000000sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD openstackdocstheme>=1.20.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 os_vif-2.0.0/doc/source/0000775000175000017500000000000013621620704015066 5ustar zuulzuul00000000000000os_vif-2.0.0/doc/source/reference/0000775000175000017500000000000013621620704017024 5ustar zuulzuul00000000000000os_vif-2.0.0/doc/source/reference/glossary.rst0000664000175000017500000001564613621620626021440 0ustar zuulzuul00000000000000======== Glossary ======== .. glossary:: Calico A virtual networking solution that uses IP routing (layer 3) to provide connectivity in the form of a flat IP network instead of bridging and tunneling. Refer to the `Calico documentation`__ for more information. __ http://docs.projectcalico.org Fast Path When used, 6Wind's proprietary fast path technology behaves as a transparent acceleration layer for traditional switches (:term:`Open vSwitch`, :term:`Linux Bridge`) and for alternative networking mechanisms (:term:`Calico`, Midonet). Linux Bridge The native networking "backend" found in Linux. Refer to the `Linux Foundation wiki`__ for more information. __ https://wiki.linuxfoundation.org/networking/bridge Open vSwitch A software implementation of a :term:`virtual multilayer network switch ` Refer to the `OVS documentation`__ for more information. __ http://docs.openvswitch.org VEB Virtual Ethernet Bridge A virtual Ethernet switch that implmented in a virtualized server environment. It is anything that mimics a traditional external layer 2 (L2) switch or bridge for connecting VMs. Generally implemented as a :term:`vSwitch`, though hardware-based VEBs using SR-IOV are possible. Refer to this `Virtual networking technologies brief`__ for more information. __ http://cs.nyu.edu/courses/fall14/CSCI-GA.3033-010/Network/SDN.pdf vSwitch Virtual Switch A software-based virtual switch that connects virtual NICs to other virtual NICs and the broader physical network. Refer to this `presentation`__ for more information. __ http://cs.nyu.edu/courses/fall14/CSCI-GA.3033-010/Network/SDN.pdf VEPA Virtual Ethernet Port Aggregator An approach to virtual networking where VM traffic is handled on the physical network rather than by a virtual switch. Unlike :term:`VNTag`, frames are not tagged and the switch will use a single port to handle all :term:`VIFs ` for a host. The basis of the :term:`802.1Qbg` spec. Refer to this `presentation`__ for more information. __ http://www.ieee802.org/1/files/public/docs2009/new-hudson-vepa_summary-0509.pdf VN-Tag VNTag An approach to virtual networking where an interface virtualizer (IV) is used in place of a :term:`VEB` to connect multiple :term:`VIFs ` to a single, external, IV-capable hardware bridge. Each VIF is tagged with a unique ID (`vif_id`) which is used to route traffic through IVs, and VIFs are then treated like any other interface. The basis of the :term:`802.1Qbh` and :term:`802.1Qbr` specs. Refer to this `Cisco presentation`__ for more information. __ https://learningnetwork.cisco.com/docs/DOC-27114 vhost An alternative to :term:`virtio` that allows userspace guest processes to share *virtqueues* directly with the kernel (or, more specifically, a kernel module) preventing the QEMU process from becoming a bottleneck. vhost-user A variation of :term:`vhost` that operates entirely in userspace. This allows userspace guest processes to share *virtqueues* with other processes operating in userspace, such as virtual switches, avoiding the kernel entirely and maximize performance. When used, a guest exposes a UNIX socket for its control plane, allowing the external userspace service to provide the backend data plane via a mapped memory region. This process must implement the corresponding virtio vhost protocol, such as :term:`virtio-net` for networking, on this socket. Refer to the `QEMU documentation`__ for more information. __ https://github.com/qemu/qemu/blob/master/docs/specs/vhost-user.txt virtio A class of virtual device emulated by QEMU. Virtio devices have *virtqueues* which can be used to share data from host to guest. Refer to the `libvirt Wiki`__ for more information. __ https://wiki.libvirt.org/page/Virtio virtio-net A network driver implementation based on virtio. Guests share *virtqueues* with the QEMU process, which in turn receives this traffic and forwards it to the host. Refer to the `KVM documentation`__ for more information. __ http://www.linux-kvm.org/page/Virtio VIF A virtual network interface. IEEE 802.1Q 802.1Q A networking standard that supports virtual LANs (VLANs) on an Ethernet network. Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1Q.html IEEE 802.1Qbg 802.1Qbg An amendment to the :term:`802.1Q` spec known as "Edge Virtual Bridging", 802.1Qbg is an approach to networking where VM traffic is handled on the physical network rather than by a virtual switch. Originally based on :term:`VEPA`. Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1bg.html IEEE 802.1Qbh 802.1Qbh A withdrawn amendment to the :term:`802.1Q` spec known as "Bridge Port Extensions", replaced by :term:`802.1Qbr` spec. Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1bh.html IEEE 802.1Qbr 802.1Qbr An amendment to the :term:`802.1Q` spec known as "Bridge Port Extensions", Refer to the `IEEE spec`__ for more information. __ http://www.ieee802.org/1/pages/802.1br.html tc A framework for interacting with traffic control settings (QoS, essentially) in the Linux kernel. Refer to the `tc(8) man page`__ for more information. __ https://linux.die.net/man/8/tc SR-IOV Single Root I/O Virtualization An extension to the PCI Express (PCIe) specification that allows a device, typically a network adapter, to split access to its resources among various PCIe hardware functions, :term:`physical ` or :term:`virtual `. Refer to this `article by Scott Lowe`__ or the original `PCI-SIG spec`__ (paywall) for more information. __ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/ __ https://members.pcisig.com/wg/PCI-SIG/document/download/8272 PF Physical Function In SR-IOV, a PCIe function that has full configuration resources. An SR-IOV device can have *up to* 8 PFs, though this varies between devices. A PF would typically correspond to a single interface on a NIC. Refer to this `article by Scott Lowe`__ for more information. __ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/ VF Virtual Function In SR-IOV, a PCIe function that lacks configuration resources. An SR-IOV device can have *up to* 256 VFs, though this varies between devices. A VF must be of the same type as the parent device's :term:`PF`. Refer to this `article by Scott Lowe`__ for more information. __ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/ os_vif-2.0.0/doc/source/user/0000775000175000017500000000000013621620704016044 5ustar zuulzuul00000000000000os_vif-2.0.0/doc/source/user/usage.rst0000664000175000017500000000446013621620626017711 0ustar zuulzuul00000000000000===== Usage ===== The interface to the ``os_vif`` library is very simple. To begin using the library, first call the ``os_vif.initialize()`` function. This will load all installed plugins and register the object model: .. code-block:: python import os_vif os_vif.initialize() Once the ``os_vif`` library is initialized, there are only two other library functions: ``os_vif.plug()`` and ``os_vif.unplug()``. Both methods accept an argument of (a subclass of) type ``os_vif.objects.vif.VIFBase`` and an argument of type ``os_vif.objects.instance_info.InstanceInfo``: .. code-block:: python import uuid from nova import objects as nova_objects from os_vif import exception as vif_exc from os_vif.objects import fields from os_vif.objects import instance_info from os_vif.objects import network from os_vif.objects import subnet as os_subnet from os_vif.objects import vif as vif_obj instance_uuid = 'd7a730ca-3c28-49c3-8f26-4662b909fe8a' instance = nova_objects.Instance.get_by_uuid(instance_uuid) instance_info = instance_info.InstanceInfo( uuid=instance.uuid, name=instance.name, project_id=instance.project_id) subnet = os_subnet.Subnet(cidr='192.168.1.0/24') subnets = os_subnet.SubnetList([subnet]) network = network.Network(label='tenantnet', subnets=subnets, multi_host=False, should_provide_vlan=False, should_provide_bridge=False) vif_uuid = uuid.uuid4() vif = vif_obj.VIFVHostUser(id=vif_uuid, address=None, network=network, plugin='vhostuser', path='/path/to/socket', mode=fields.VIFVHostUserMode.SERVER) # Now do the actual plug operations to connect the VIF to # the backing network interface. try: os_vif.plug(vif, instance_info) except vif_exc.PlugException as err: # Handle the failure... # If you are removing a virtual machine and its interfaces, # you would use the unplug() operation: try: os_vif.unplug(vif, instance_info) except vif_exc.UnplugException as err: # Handle the failure... os_vif-2.0.0/doc/source/user/vif-types.rst0000664000175000017500000000421713621620626020533 0ustar zuulzuul00000000000000========= VIF Types ========= In *os-vif*, a VIF type refers to a particular approach for configuring the backend of a guest virtual network interface. There is a small, finite set of ways that a VIF backend can be configured for any given hypervisor and a limited amount of metadata is associated with each approach. .. py:module:: os_vif.objects.vif VIF objects =========== Each distinct type of VIF configuration is represented by a versioned object, subclassing :class:`VIFBase`. .. autoclass:: VIFBase .. autoclass:: VIFGeneric .. autoclass:: VIFBridge .. autoclass:: VIFOpenVSwitch .. autoclass:: VIFDirect .. autoclass:: VIFVHostUser .. autoclass:: VIFDirect .. autoclass:: VIFNestedDPDK VIF port profile objects ======================== Each VIF instance can optionally be associated with a port profile object. This provides a set of metadata attributes that serve to identify the guest virtual interface to the host. Different types of host connectivity will require different port profile object metadata. Each port profile type is associated with a versioned object, subclassing :class:`VIFPortProfileBase`. .. autoclass:: VIFPortProfileBase .. autoclass:: VIFPortProfileOpenVSwitch .. autoclass:: VIFPortProfileFPOpenVSwitch .. autoclass:: VIFPortProfileOVSRepresentor .. autoclass:: VIFPortProfileFPBridge .. autoclass:: VIFPortProfileFPTap .. autoclass:: VIFPortProfile8021Qbg .. autoclass:: VIFPortProfile8021Qbh .. autoclass:: VIFPortProfileK8sDPDK Datapath Offload type object ============================ Port profiles can be associated with a ``datapath_offload`` object. This provides a set of metadata attributes that serve to identify the datapath offload parameters of a VIF. Each different type of datapath offload is associated with a versioned object, subclassing :class:`DatapathOffloadBase`. .. autoclass:: DatapathOffloadBase .. autoclass:: DatapathOffloadRepresentor VIF network objects =================== Each VIF instance is associated with a set of objects which describe the logical network that the guest will be plugged into. This information is again represented by a set of versioned objects .. todo:: Populate this! os_vif-2.0.0/doc/source/user/host-info.rst0000664000175000017500000000536313621620626020516 0ustar zuulzuul00000000000000================ 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-2.0.0/doc/source/user/plugins/0000775000175000017500000000000013621620704017525 5ustar zuulzuul00000000000000os_vif-2.0.0/doc/source/user/plugins/ovs.rst0000664000175000017500000000356313621620626021100 0ustar zuulzuul00000000000000============ Open vSwitch ============ The Open vSwitch plugin, ``vif_plug_ovs``, is an *os-vif* VIF plugin for the Open vSwitch network backend. It is one of three plugins provided as part of *os-vif* itself, the others being :doc:`linux-bridge` and :doc:`noop`. Supported VIF Types ------------------- The Open vSwitch plugin provides support for the following VIF types: :mod:`~os_vif.objects.VIFOpenVSwitch` Configuration where a guest is directly connected an Open vSwitch bridge. :mod:`~os_vif.objects.VIFBridge` Configuration where a guest is connected to a Linux bridge via a TAP device, and that bridge is connected to the Open vSwitch bridge. This allows for the use of ``iptables`` rules for filtering traffic. :mod:`~os_vif.objects.VIFVHostUser` Configuration where a guest exposes a UNIX socket for its control plane. This configuration is used with the `DPDK datapath of Open vSwitch`__. __ http://docs.openvswitch.org/en/latest/howto/dpdk/ :mod:`~os_vif.objects.VIFHostDevice` Configuration where an :term:`SR-IOV` PCI device :term:`VF` is passed through to a guest. The ``hw-tc-offload`` feature should be enabled on the SR-IOV :term:`PF` using :command:`ethtool`: .. code-block:: shell ethtool -K hw-tc-offload This will create a *VF representor* per VF. The VF representor plays the same role as TAP devices in Para-Virtual (PV) setup. In this case the ``plug()`` method connects the VF representor to the OpenVSwitch bridge. .. important:: Support for this feature requires Linux Kernel >= 4.8 and Open vSwitch 2.8. These add support for :term:`tc`-based hardware offloads for SR-IOV VFs and offloading of OVS datapath rules using tc, respectively. .. versionadded:: 1.5.0 For information on the VIF type objects, refer to :doc:`/user/vif-types`. Note that only the above VIF types are supported by this plugin. os_vif-2.0.0/doc/source/user/plugins/linux-bridge.rst0000664000175000017500000000126213621620626022654 0ustar zuulzuul00000000000000============ Linux Bridge ============ The Linux Bridge plugin, ``vif_plug_linux_bridge``, is an *os-vif* VIF plugin for the Linux Bridge network backend. It is one of three plugins provided as part of *os-vif* itself, the others being :doc:`ovs` and :doc:`noop`. Supported VIF Types ------------------- The Linux Bridge plugin provides support for the following VIF types: :mod:`~os_vif.objects.VIFBridge` Configuration where a guest is connected to a Linux bridge via a TAP device. This is the only supported configuration for this plugin. For information on the VIF type objects, refer to :doc:`/user/vif-types`. Note that only the above VIF types are supported by this plugin. os_vif-2.0.0/doc/source/user/plugins/noop.rst0000664000175000017500000000132413621620626021235 0ustar zuulzuul00000000000000===== no-op ===== The no-op plugin, ``vif_plug_noop``, is an *os-vif* VIF plugin for use with network backends that do not require plugging of network interfaces. It is one of three plugins provided as part of *os-vif* itself, the others being :doc:`ovs` and :doc:`linux-bridge`. Supported VIF Types ------------------- The no-op plugin provides support for the following VIF types: :mod:`~os_vif.objects.VIFVHostUser` Configuration where a guest exposes a UNIX socket for its control plane. This configuration is used with a userspace dataplane such as VPP or Snabb switch. For information on the VIF type objects, refer to :doc:`/user/vif-types`. Note that only the above VIF types are supported by this plugin. os_vif-2.0.0/doc/source/index.rst0000664000175000017500000000151413621620626016733 0ustar zuulzuul00000000000000====== os-vif ====== `os-vif` is a library for plugging and unplugging virtual interfaces (VIFs) in OpenStack. It provides: - Versioned objects that represent various types of virtual interfaces and their components - Base VIF plugin class that supplies a ``plug()`` and ``unplug()`` interface - Plugins for two networking backends - Open vSwitch and Linux Bridge `os-vif` is intended to define a common model for representing VIF types in OpenStack. With the exception of the two included plugins, all plugins for other networking backends are maintained in separate code repositories. Usage Guide ----------- .. toctree:: :maxdepth: 2 user/usage user/vif-types user/host-info user/plugins/linux-bridge user/plugins/noop user/plugins/ovs Reference --------- .. toctree:: :maxdepth: 2 reference/glossary os_vif-2.0.0/doc/source/conf.py0000664000175000017500000000327513621620626016377 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.todo', 'sphinx.ext.autodoc', 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/os-vif' bug_project = 'os-vif' bug_tag = '' # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = u'2016, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = '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-2.0.0/.stestr.conf0000664000175000017500000000006213621620626015273 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-.} top_dir=./ os_vif-2.0.0/AUTHORS0000664000175000017500000000510613621620703014072 0ustar zuulzuul00000000000000Adrian Chiris Alin Balutoiu Andreas Jaeger Brian Haley Cao Xuan Hoang Carlos Goncalves ChangBo Guo(gcb) Charles Short Claudiu Belu Corey Bryant Daniel P. Berrange Davanum Srinivas Doug Hellmann Eric Fried Eric Fried Flavio Percoco Francesco Santoro Hamdy Khader Hangdong Zhang Ian Wienand Ihar Hrachyshka Jan Gutter Janonymous Jay Pipes Kevin Benton Lucian Petrut Maria Malyarova Matt Riedemann Michał Dulko Moshe Levi OpenStack Release Bot Przemyslaw Lal Rawlin Peters Rodolfo Alonso Hernandez Rodolfo Alonso Hernandez Rodolfo Alonso Hernandez <“ralonso@redhat.com”> Sahid Orentino Ferdjaoui Sean Dague Sean M. Collins Sean Mooney Sean Mooney Sergey Belous Spencer Yu Sriharsha Basavapatna Stephen Finucane Swapnil Kulkarni (coolsvap) Takashi NATSUME Thomas Bechtold Tony Breeds Tony Xu Vieri <15050873171@163.com> Vu Cong Tuan YAMAMOTO Takashi ZhijunWei blue55 caoyuan gecong1973 jacky06 kavithahr lingyongxu loooosy melanie witt melissaml pengyuesheng pranabjb qingszhao shaleijie sunjia vagrant os_vif-2.0.0/.mailmap0000664000175000017500000000013113621620626014440 0ustar zuulzuul00000000000000# Format is: # # os_vif-2.0.0/os_vif/0000775000175000017500000000000013621620704014306 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/exception.py0000664000175000017500000000641413621620626016666 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from os_vif.i18n import _ class ExceptionBase(Exception): """Base Exception To correctly use this class, inherit from it and define a 'msg_fmt' property. That msg_fmt will get printf'd with the keyword arguments provided to the constructor. """ msg_fmt = _("An unknown exception occurred.") def __init__(self, message=None, **kwargs): self.kwargs = kwargs if not message: try: message = self.msg_fmt % kwargs except Exception: # at least get the core message out if something happened message = self.msg_fmt self.message = message super(ExceptionBase, self).__init__(message) def format_message(self): # NOTE(mrodden): use the first argument to the python Exception object # which should be our full NovaException message, (see __init__) return self.args[0] class LibraryNotInitialized(ExceptionBase): msg_fmt = _("Before using the os_vif library, you need to call " "os_vif.initialize()") class NoMatchingPlugin(ExceptionBase): msg_fmt = _("No VIF plugin was found with the name %(plugin_name)s") class NoMatchingPortProfileClass(ExceptionBase): msg_fmt = _("No PortProfile class was found with the name %(name)s") class NoSupportedPortProfileVersion(ExceptionBase): msg_fmt = _("PortProfile class %(name)s " "versions %(got_versions)s do not satisfy " "min=%(min_version)s max=%(max_version)s") class NoMatchingVIFClass(ExceptionBase): msg_fmt = _("No VIF class was found with the name %(name)s") class NoSupportedVIFVersion(ExceptionBase): msg_fmt = _("VIF class %(name)s versions %(got_versions)s " "do not satisfy min=%(min_version)s max=%(max_version)s") class PlugException(ExceptionBase): msg_fmt = _("Failed to plug VIF %(vif)s. Got error: %(err)s") class UnplugException(ExceptionBase): msg_fmt = _("Failed to unplug VIF %(vif)s. Got error: %(err)s") class NetworkMissingPhysicalNetwork(ExceptionBase): msg_fmt = _("Physical network is missing for network %(network_uuid)s") class NetworkInterfaceNotFound(ExceptionBase): msg_fmt = _("Network interface %(interface)s not found") class NetworkInterfaceTypeNotDefined(ExceptionBase): msg_fmt = _("Network interface type %(type)s not defined") class ExternalImport(ExceptionBase): msg_fmt = _("Use of this module outside of os_vif is not allowed. It must " "not be imported in os-vif plugins that are out of tree as it " "is not a public interface of os-vif.") class NotImplementedForOS(ExceptionBase): msg_fmt = _("Function %(function)s for %(os)s operating system") os_vif-2.0.0/os_vif/__init__.py0000664000175000017500000001255413621620626016431 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_log import log as logging from stevedore import extension import os_vif.exception import os_vif.i18n import os_vif.objects _EXT_MANAGER = None LOG = logging.getLogger(__name__) def initialize(reset=False): """ Loads all os_vif plugins and initializes them with a dictionary of configuration options. These configuration options are passed as-is to the individual VIF plugins that are loaded via stevedore. :param reset: Recreate and load the VIF plugin extensions. """ global _EXT_MANAGER if _EXT_MANAGER is None: os_vif.objects.register_all() if reset or (_EXT_MANAGER is None): _EXT_MANAGER = extension.ExtensionManager(namespace='os_vif', invoke_on_load=False) loaded_plugins = [] for plugin_name in _EXT_MANAGER.names(): cls = _EXT_MANAGER[plugin_name].plugin obj = cls.load(plugin_name) LOG.debug(("Loaded VIF plugin class '%(cls)s' " "with name '%(plugin_name)s'"), {'cls': cls, 'plugin_name': plugin_name}) loaded_plugins.append(plugin_name) _EXT_MANAGER[plugin_name].obj = obj LOG.info("Loaded VIF plugins: %s", ", ".join(loaded_plugins)) def plug(vif, instance_info): """ Given a model of a VIF, perform operations to plug the VIF properly. :param vif: Instance of a subclass of ``os_vif.objects.vif.VIFBase``. :param instance_info: ``os_vif.objects.instance_info.InstanceInfo`` object. :raises ``exception.LibraryNotInitialized`` if the user of the library did not call ``os_vif.initialize(**config)`` before trying to plug a VIF. :raises ``exception.NoMatchingPlugin`` if there is no plugin for the type of VIF supplied. :raises ``exception.PlugException`` if anything fails during unplug operations. """ if _EXT_MANAGER is None: raise os_vif.exception.LibraryNotInitialized() plugin_name = vif.plugin try: plugin = _EXT_MANAGER[plugin_name].obj except KeyError: raise os_vif.exception.NoMatchingPlugin(plugin_name=plugin_name) try: LOG.debug("Plugging vif %s", vif) plugin.plug(vif, instance_info) LOG.info("Successfully plugged vif %s", vif) except Exception as err: LOG.error("Failed to plug vif %(vif)s", {"vif": vif}, exc_info=True) raise os_vif.exception.PlugException(vif=vif, err=err) def unplug(vif, instance_info): """ Given a model of a VIF, perform operations to unplug the VIF properly. :param vif: Instance of a subclass of `os_vif.objects.vif.VIFBase`. :param instance_info: `os_vif.objects.instance_info.InstanceInfo` object. :raises `exception.LibraryNotInitialized` if the user of the library did not call os_vif.initialize(**config) before trying to plug a VIF. :raises `exception.NoMatchingPlugin` if there is no plugin for the type of VIF supplied. :raises `exception.UnplugException` if anything fails during unplug operations. """ if _EXT_MANAGER is None: raise os_vif.exception.LibraryNotInitialized() plugin_name = vif.plugin try: plugin = _EXT_MANAGER[plugin_name].obj except KeyError: raise os_vif.exception.NoMatchingPlugin(plugin_name=plugin_name) try: LOG.debug("Unplugging vif %s", vif) plugin.unplug(vif, instance_info) LOG.info("Successfully unplugged vif %s", vif) except Exception as err: LOG.error("Failed to unplug vif %(vif)s", {"vif": vif}, exc_info=True) raise os_vif.exception.UnplugException(vif=vif, err=err) def host_info(permitted_vif_type_names=None): """ :param permitted_vif_type_names: list of VIF object names Get information about the host platform configuration to be provided to the network manager. This will include information about what plugins are installed in the host If permitted_vif_type_names is not None, the returned HostInfo will be filtered such that it only includes plugins which support one of the listed VIF types. This allows the caller to filter out impls which are not compatible with the current usage configuration. For example, to remove VIFVHostUser if the guest does not support shared memory. :returns: a os_vif.host_info.HostInfo class instance """ if _EXT_MANAGER is None: raise os_vif.exception.LibraryNotInitialized() plugins = [ _EXT_MANAGER[name].obj.describe() for name in sorted(_EXT_MANAGER.names()) ] info = os_vif.objects.host_info.HostInfo(plugin_info=plugins) if permitted_vif_type_names is not None: info.filter_vif_types(permitted_vif_type_names) return info os_vif-2.0.0/os_vif/objects/0000775000175000017500000000000013621620704015737 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/objects/vif.py0000664000175000017500000005224413621620626017107 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from debtcollector import removals from oslo_utils import versionutils from oslo_versionedobjects import base from oslo_versionedobjects import fields from os_vif.objects import base as osv_base from os_vif.objects import fields as osv_fields @base.VersionedObjectRegistry.register class VIFBase(osv_base.VersionedObject, base.ComparableVersionedObject): """Represents a virtual network interface. The base VIF defines fields that are common to all types of VIF and provides an association to the network the VIF is plugged into. It should not be instantiated itself - use a subclass instead. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Unique identifier of the VIF port. 'id': fields.UUIDField(), #: The guest MAC address. 'address': fields.MACAddressField(nullable=True), #: The network to which the VIF is connected. 'network': fields.ObjectField('Network', nullable=True), #: Name of the registered os_vif plugin. 'plugin': fields.StringField(), #: Whether the VIF is initially online. 'active': fields.BooleanField(default=True), #: Whether the host VIF should be preserved on unplug. 'preserve_on_delete': fields.BooleanField(default=False), #: Whether the network service has provided traffic filtering. 'has_traffic_filtering': fields.BooleanField(default=False), #: The virtual port profile metadata. 'port_profile': fields.ObjectField('VIFPortProfileBase', subclasses=True) } @base.VersionedObjectRegistry.register class VIFGeneric(VIFBase): """A generic-style VIF. Generic-style VIFs are unbound, floating TUN/TAP devices that should be setup by the plugin, not the hypervisor. The way the TAP device is connected to the host network stack is explicitly left undefined. For libvirt drivers, this maps to type="ethernet" which just implies a bare TAP device with all setup delegated to the plugin. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the device to create. 'vif_name': fields.StringField() } @base.VersionedObjectRegistry.register class VIFBridge(VIFBase): """A bridge-style VIF. Bridge-style VIFs are bound to a Linux host bridge by the hypervisor. This provides Ethernet layer bridging, typically to the LAN. Other devices may be bound to the same L2 virtual bridge. For libvirt drivers, this maps to type='bridge'. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the virtual device to create. 'vif_name': fields.StringField(), #: Name of the physical device to connect to (e.g. ``br0``). 'bridge_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFOpenVSwitch(VIFBase): """A bridge-style VIF specifically for use with OVS. Open vSwitch VIFs are bound directly (or indirectly) to an Open vSwitch bridge by the hypervisor. Other devices may be bound to the same virtual bridge. For libvirt drivers, this also maps to type='bridge'. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the virtual device to create. 'vif_name': fields.StringField(), #: Name of the physical device to connect to (e.g. ``br0``). 'bridge_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFDirect(VIFBase): """A direct-style VIF. Despite the confusing name, direct-style VIFs utilize macvtap which is a device driver that inserts a software layer between a guest and an SR-IOV Virtual Function (VF). Contrast this with :class:`~os_vif.objects.vif.VIFHostDevice`, which allows the guest to directly connect to the VF. The connection to the device may operate in one of a number of different modes, :term:`VEPA` (either :term:`802.1Qbg` or :term:`802.1Qbh`), passthrough (exclusive assignment of the host NIC) or bridge (ethernet layer bridging of traffic). The passthrough mode would be used when there is a network device which needs to have a MAC address or VLAN configuration. For passthrough of network devices without MAC/VLAN configuration, :class:`~os_vif.objects.vif.VIFHostDevice` should be used instead. For libvirt drivers, this maps to type='direct' """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name of the device to create. 'vif_name': fields.StringField(), #: The PCI address of the host device. 'dev_address': fields.PCIAddressField(), #: Port connection mode. 'mode': osv_fields.VIFDirectModeField(), #: The VLAN device name to use. 'vlan_name': fields.StringField(), } @base.VersionedObjectRegistry.register class VIFVHostUser(VIFBase): """A vhostuser-style VIF. vhostuser-style VIFs utilize a :term:`userspace vhost ` backend, which allows traffic to traverse between the guest and a host userspace application (commonly a virtual switch), bypassing the kernel network stack. Contrast this with :class:`~os_vif.objects.vif.VIFBridge`, where all packets must be handled by the hypervisor. For libvirt drivers, this maps to type='vhostuser' """ # Version 1.0: Initial release # Version 1.1: Added 'vif_name' VERSION = '1.1' fields = { #: Name of the vhostuser port to create. 'vif_name': fields.StringField(), #: UNIX socket path. 'path': fields.StringField(), #: UNIX socket access permissions. 'mode': osv_fields.VIFVHostUserModeField(), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'vif_name' in primitive: del primitive['vif_name'] super(VIFVHostUser, self).obj_make_compatible(primitive, '1.0') @base.VersionedObjectRegistry.register class VIFHostDevice(VIFBase): """A hostdev-style VIF. Hostdev-style VIFs provide a guest with direct access to an :term:`SR-IOV` :term:`Virtual Function` (VF) or an entire :term:`Physical Function` (PF). Contrast this with :class:`~ovs_vif.objects.vif.VIFDirect`, which includes a software layer between the interface and the guest. For libvirt drivers, this maps to type='hostdev' """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: The type of the host device. #: #: Valid values are ``ethernet`` and ``generic``. #: #: - ``ethernet`` is ```` #: - ``generic`` is ```` 'dev_type': osv_fields.VIFHostDeviceDevTypeField(), #: The PCI address of the host device. 'dev_address': fields.PCIAddressField(), } @base.VersionedObjectRegistry.register class VIFNestedDPDK(VIFBase): """A nested DPDK-style VIF. Nested DPDK-style VIFs are used by Kuryr-Kubernetes to provide accelerated DPDK datapath for nested Kubernetes pods running inside the VM. The port is first attached to the virtual machine, bound to the userspace driver (e.g. ``uio_pci_generic``, ``igb_uio`` or ``vfio-pci``) and then consumed by Kubernetes pod via the kuryr-kubernetes CNI plugin. This does not apply to libvirt drivers. """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: PCI address of the device. 'pci_address': fields.StringField(), #: Name of the driver the device was previously bound to; it makes #: the controller driver agnostic (virtio, SR-IOV, etc.). 'dev_driver': fields.StringField(), } @base.VersionedObjectRegistry.register class DatapathOffloadBase(osv_base.VersionedObject, base.ComparableVersionedObject): """Base class for all types of datapath offload.""" # Version 1.0: Initial release VERSION = '1.0' @base.VersionedObjectRegistry.register class DatapathOffloadRepresentor(DatapathOffloadBase): """Offload type for VF Representors conforming to the switchdev model. This datapath offloads provides the metadata required to associate a VIF with a :term:`VF` representor conforming to the `switchdev`_ kernel model. If ``representor_name`` is specified, it indicates a desire to rename the representor to the given name on plugging. .. _switchdev: https://netdevconf.org/1.2/session.html?or-gerlitz """ # Version 1.0: Initial release VERSION = '1.0' fields = { #: Name to set on the representor (if set). 'representor_name': fields.StringField(nullable=True), #: The PCI address of the Virtual Function. 'representor_address': fields.StringField(nullable=True), } @base.VersionedObjectRegistry.register class VIFPortProfileBase(osv_base.VersionedObject, base.ComparableVersionedObject): """Base class for all types of port profile. The base profile defines fields that are common to all types of profile. It should not be instantiated itself - use a subclass instead. """ # Version 1.0: Initial release # Version 1.1: Added 'datapath_offload' VERSION = '1.1' fields = { #: Datapath offload type of the port. 'datapath_offload': fields.ObjectField('DatapathOffloadBase', nullable=True, subclasses=True), } obj_relationships = { 'datapath_offload': (('1.1', '1.0'),), } @base.VersionedObjectRegistry.register class VIFPortProfileOpenVSwitch(VIFPortProfileBase): """Port profile info for Open vSwitch networks. This profile provides the metadata required to associate a VIF with an Open vSwitch interface. """ # Version 1.0: Initial release # Version 1.1: Added 'datapath_type' # Version 1.2: VIFPortProfileBase updated to 1.1 from 1.0 # Version 1.3: Added 'create_port' VERSION = '1.3' fields = { #: A UUID to uniquely identify the interface. If omitted one will be #: generated automatically. 'interface_id': fields.UUIDField(), #: The OpenVSwitch port profile for the interface. 'profile_id': fields.StringField(), #: Datapath type of the bridge. 'datapath_type': fields.StringField(nullable=True), #: Whether the os-vif plugin should add the port to the bridge. 'create_port': fields.BooleanField(default=False), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 3) and 'create_port' in primitive: del primitive['create_port'] if target_version < (1, 1) and 'datapath_type' in primitive: del primitive['datapath_type'] if target_version < (1, 2): super(VIFPortProfileOpenVSwitch, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileOpenVSwitch, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfileFPOpenVSwitch(VIFPortProfileOpenVSwitch): """Port profile info for Open vSwitch networks using fast path. This profile provides the metadata required to associate a :term:`fast path ` VIF with an :term:`Open vSwitch` port. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 from 1.0 # Version 1.2: VIFPortProfileOpenVSwitch updated to 1.2 from 1.1 # Version 1.3: VIFPortProfileOpenVSwitch updated to 1.3 from 1.2 VERSION = '1.3' fields = { #: Name of the bridge (managed by fast path) to connect to. 'bridge_name': fields.StringField(), #: Whether the OpenVSwitch network is using hybrid plug. 'hybrid_plug': fields.BooleanField(default=False), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.0') elif target_version < (1, 2): super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.1') elif target_version < (1, 3): super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.2') else: super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible( primitive, '1.3') @removals.removed_class("VIFPortProfileOVSRepresentor", category=PendingDeprecationWarning) @base.VersionedObjectRegistry.register class VIFPortProfileOVSRepresentor(VIFPortProfileOpenVSwitch): """Port profile info for OpenVSwitch networks using a representor. This profile provides the metadata required to associate a VIF with a :term:`VF` representor and :term:`Open vSwitch` port. If `representor_name` is specified, it indicates a desire to rename the representor to the given name on plugging. .. note:: This port profile is provided for backwards compatibility only. This interface has been superceded by the one provided by the :class:`DatapathOffloadRepresentor` class, which is now a field element of the :class:`VIFPortProfileBase` class. The ``datapath_offload`` field in port profiles should be used instead. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 from 1.0 # Version 1.2: VIFPortProfileOpenVSwitch updated to 1.2 from 1.1 # Version 1.3: VIFPortProfileOpenVSwitch updated to 1.3 from 1.2 VERSION = '1.3' fields = { #: Name to set on the representor (if set). 'representor_name': fields.StringField(nullable=True), #: The PCI address of the Virtual Function. 'representor_address': fields.PCIAddressField(nullable=True), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.0') elif target_version < (1, 2): super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.1') elif target_version < (1, 3): super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.2') else: super(VIFPortProfileOVSRepresentor, self).obj_make_compatible( primitive, '1.3') @base.VersionedObjectRegistry.register class VIFPortProfileFPBridge(VIFPortProfileBase): """Port profile info for Linux Bridge networks using fast path. This profile provides the metadata required to associate a :term:`fast path ` VIF with a :term:`Linux bridge` port. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: Name of the bridge (managed by fast path) to connect to. 'bridge_name': fields.StringField(), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileFPBridge, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileFPBridge, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfileFPTap(VIFPortProfileBase): """Port profile info for Calico networks using fast path. This profile provides the metadata required to associate a :term:`fast path ` VIF with a :term:`Calico` port. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: The MAC address of the host vhostuser port. 'mac_address': fields.MACAddressField(nullable=True), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileFPTap, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileFPTap, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfile8021Qbg(VIFPortProfileBase): """Port profile info for VEPA 802.1qbg networks. This profile provides the metadata required to associate a VIF with a VEPA host device supporting the :term:`802.1Qbg` spec. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { # TODO(stephenfin): Apparently the value 0 is reserved for manager_id, # so should we set 'minimum=1'? # https://libvirt.org/formatdomain.html#elementsNICS #: The VSI Manager ID identifies the database containing the VSI type #: and instance definitions. 'manager_id': fields.IntegerField(), #: The VSI Type ID identifies a VSI type characterizing the network #: access. VSI types are typically managed by network administrator. 'type_id': fields.IntegerField(), #: The VSI Type Version allows multiple versions of a VSI Type. 'type_id_version': fields.IntegerField(), #: The VSI Instance ID Identifier is generated when a VSI instance #: (i.e. a virtual interface of a virtual machine) is created. This is #: a globally unique identifier. 'instance_id': fields.UUIDField(), } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfile8021Qbg, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfile8021Qbg, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfile8021Qbh(VIFPortProfileBase): """Port profile info for VEPA 802.1qbh networks. This profile provides the metadata required to associate a VIF with a VEPA host device supporting the :term:`802.1Qbh` spec. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: The name of the port profile that is to be applied to this #: interface. This name is resolved by the port profile database into #: the network parameters from the port profile, and those network #: parameters will be applied to this interface. 'profile_id': fields.StringField() } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfile8021Qbh, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfile8021Qbh, self).obj_make_compatible( primitive, '1.1') @base.VersionedObjectRegistry.register class VIFPortProfileK8sDPDK(VIFPortProfileBase): """Port profile info for Kuryr-Kubernetes DPDK ports. This profile provides the metadata required to associate nested DPDK VIF with a Kubernetes pod. """ # Version 1.0: Initial release # Version 1.1: VIFPortProfileBase updated to 1.1 from 1.0 VERSION = '1.1' fields = { #: Specify whether this vif requires L3 setup. 'l3_setup': fields.BooleanField(), #: String containing URL representing object in Kubernetes v1 API. 'selflink': fields.StringField(), #: String used in Kubernetes v1 API to identify the server's internal #: version of this object. 'resourceversion': fields.StringField() } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1): super(VIFPortProfileK8sDPDK, self).obj_make_compatible( primitive, '1.0') else: super(VIFPortProfileK8sDPDK, self).obj_make_compatible( primitive, '1.1') os_vif-2.0.0/os_vif/objects/route.py0000664000175000017500000000251013621620626017450 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/objects/__init__.py0000664000175000017500000000156313621620626020060 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/objects/fields.py0000664000175000017500000000334113621620626017563 0ustar zuulzuul00000000000000# 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-2.0.0/os_vif/objects/network.py0000664000175000017500000000412613621620626020010 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_utils import versionutils from oslo_versionedobjects import base from oslo_versionedobjects import fields from os_vif import objects from os_vif.objects import base as osv_base @base.VersionedObjectRegistry.register class Network(osv_base.VersionedObject): """Represents a network.""" # Version 1.0: Initial version # Version 1.1: Added MTU field VERSION = '1.1' fields = { 'id': fields.UUIDField(), 'bridge': fields.StringField(), 'label': fields.StringField(), 'subnets': fields.ObjectField('SubnetList'), 'multi_host': fields.BooleanField(), 'should_provide_bridge': fields.BooleanField(), 'should_provide_vlan': fields.BooleanField(), 'bridge_interface': fields.StringField(nullable=True), 'vlan': fields.IntegerField(nullable=True), 'mtu': fields.IntegerField(nullable=True), } def __init__(self, **kwargs): kwargs.setdefault('subnets', objects.subnet.SubnetList(objects=[])) kwargs.setdefault('multi_host', False) kwargs.setdefault('should_provide_bridge', False) kwargs.setdefault('should_provide_vlan', False) kwargs.setdefault('mtu', None) super(Network, self).__init__(**kwargs) def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'mtu' in primitive: del primitive['mtu'] super(Network, self).obj_make_compatible(primitive, '1.0') os_vif-2.0.0/os_vif/objects/instance_info.py0000664000175000017500000000231213621620626021131 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/objects/host_info.py0000664000175000017500000001505213621620626020307 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_utils import versionutils from oslo_versionedobjects import base from oslo_versionedobjects import fields from os_vif import exception from os_vif.objects import base as osv_base def _get_common_version(object_name, max_version, min_version, exc_notmatch, exc_notsupported): """Returns the accepted version from the loaded OVO registry""" reg = base.VersionedObjectRegistry.obj_classes() if object_name not in reg: raise exc_notmatch(name=object_name) gotvers = [] for regobj in reg[object_name]: gotvers.append(regobj.VERSION) got = versionutils.convert_version_to_tuple(regobj.VERSION) minwant = versionutils.convert_version_to_tuple(min_version) maxwant = versionutils.convert_version_to_tuple(max_version) if minwant <= got <= maxwant: return regobj.VERSION raise exc_notsupported(name=object_name, got_versions=",".join(gotvers), min_version=min_version, max_version=max_version) @base.VersionedObjectRegistry.register class HostPortProfileInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a PortProfile class and its supported versions """ # Version 1.0: Initial version VERSION = "1.0" fields = { # object name of the subclass of os_vif.objects.vif.VIFPortProfileBase "profile_object_name": fields.StringField(), # String representing the earliest version of @name # that the plugin understands "min_version": fields.StringField(), # String representing the latest version of @name # that the plugin understands "max_version": fields.StringField(), } def get_common_version(self): return _get_common_version(self.profile_object_name, self.max_version, self.min_version, exception.NoMatchingPortProfileClass, exception.NoSupportedPortProfileVersion) @base.VersionedObjectRegistry.register class HostVIFInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a VIF class and its supported versions """ # Version 1.0: Initial version # Version 1.1: Adds 'supported_port_profiles' field VERSION = "1.1" fields = { # object name of the subclass of os_vif.objects.vif.VIFBase "vif_object_name": fields.StringField(), # String representing the earliest version of @name # that the plugin understands "min_version": fields.StringField(), # String representing the latest version of @name # that the plugin understands "max_version": fields.StringField(), # list of supported PortProfile objects and versions. "supported_port_profiles": fields.ListOfObjectsField( "HostPortProfileInfo") } def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'supported_port_profiles' in primitive: del primitive['supported_port_profiles'] super(HostVIFInfo, self).obj_make_compatible(primitive, '1.0') def get_common_version(self): return _get_common_version(self.vif_object_name, self.max_version, self.min_version, exception.NoMatchingVIFClass, exception.NoSupportedVIFVersion) @base.VersionedObjectRegistry.register class HostPluginInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a plugin and its supported VIF classes """ # Version 1.0: Initial version VERSION = "1.0" fields = { # name of the plugin "plugin_name": fields.StringField(), # list of HostVIFInfo instances supported by the plugin "vif_info": fields.ListOfObjectsField("HostVIFInfo"), } def has_vif(self, name): for vif in self.vif_info: if vif.vif_object_name == name: return True return False def get_vif(self, name): for vif in self.vif_info: if vif.vif_object_name == name: return vif raise exception.NoMatchingVIFClass(vif_name=name) def filter_vif_types(self, permitted_vif_type_names): new_vif_info = [] for vif in self.vif_info: if vif.vif_object_name in permitted_vif_type_names: new_vif_info.append(vif) self.vif_info = new_vif_info @base.VersionedObjectRegistry.register class HostInfo(osv_base.VersionedObject, base.ComparableVersionedObject, osv_base.VersionedObjectPrintableMixin): """ Class describing a host host and its supported plugin classes """ fields = { # list of HostPluginInfo instances supported by the host host "plugin_info": fields.ListOfObjectsField("HostPluginInfo"), } def has_plugin(self, name): for plugin in self.plugin_info: if name == plugin.plugin_name: return True return False def get_plugin(self, name): for plugin in self.plugin_info: if name == plugin.plugin_name: return plugin raise exception.NoMatchingPlugin(plugin_name=name) def filter_vif_types(self, permitted_vif_type_names): new_plugins = [] for plugin in self.plugin_info: plugin.filter_vif_types(permitted_vif_type_names) if len(plugin.vif_info) == 0: continue new_plugins.append(plugin) self.plugin_info = new_plugins os_vif-2.0.0/os_vif/objects/base.py0000664000175000017500000000216313621620626017230 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/objects/subnet.py0000664000175000017500000000266213621620626017622 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/objects/fixed_ip.py0000664000175000017500000000240213621620626020101 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/internal/0000775000175000017500000000000013621620704016122 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/internal/__init__.py0000664000175000017500000000170713621620626020243 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/internal/ip/0000775000175000017500000000000013621620704016532 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/internal/ip/windows/0000775000175000017500000000000013621620704020224 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/internal/ip/windows/impl_netifaces.py0000664000175000017500000000327013621620626023565 0ustar zuulzuul00000000000000# Derived from: neutron/agent/windows/ip_lib.py # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netifaces from oslo_log import log as logging from os_vif import exception from os_vif.internal.ip import ip_command LOG = logging.getLogger(__name__) class Netifaces(ip_command.IpCommand): def exists(self, device): """Return True if the device exists in the namespace.""" try: return bool(netifaces.ifaddresses(device)) except ValueError: LOG.warning("The device does not exist on the system: %s", device) except OSError: LOG.error("Failed to get interface addresses: %s", device) return False def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None, master=None): exception.NotImplementedForOS(function='ip.set', os='Windows') def add(self, device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None): exception.NotImplementedForOS(function='ip.add', os='Windows') def delete(self, device, check_exit_code=None): exception.NotImplementedForOS(function='ip.delete', os='Windows') os_vif-2.0.0/os_vif/internal/ip/windows/__init__.py0000664000175000017500000000000013621620626022326 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/internal/ip/__init__.py0000664000175000017500000000000013621620626020634 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/internal/ip/ip_command.py0000664000175000017500000000537713621620626021231 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 @six.add_metaclass(abc.ABCMeta) class IpCommand(object): TYPE_VETH = 'veth' TYPE_VLAN = 'vlan' TYPE_BRIDGE = 'bridge' @abc.abstractmethod def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None, master=None): """Method to set a parameter in an interface. :param device: A network device (string) :param check_exit_code: List of integers of allowed execution exit codes :param state: String network device state :param mtu: Integer MTU value :param address: String MAC address :param promisc: Boolean promiscuous mode :param master: String the master device that this device belongs to :return: status of the command execution """ @abc.abstractmethod def add(self, device, dev_type, check_exit_code=None, peer=None, link=None, vlan_id=None, ageing=None): """Method to add an interface. :param device: A network device (string) :param dev_type: String network device type (TYPE_VETH, TYPE_VLAN) :param check_exit_code: List of integers of allowed execution exit codes :param peer: String peer name, for veth interfaces :param link: String root network interface name, 'device' will be a VLAN tagged virtual interface :param vlan_id: Integer VLAN ID for VLAN devices :param ageing: integer value in seconds before learned mac addresses are forgotten. :return: status of the command execution """ @abc.abstractmethod def delete(self, device, check_exit_code=None): """Method to delete an interface. :param device: A network device (string) :param dev_type: String network device type (TYPE_VETH, TYPE_VLAN) :return: status of the command execution """ @abc.abstractmethod def exists(self, device): """Method to dectect if a device exists. :param device: A network device (string) :return: True if device exists else False """ os_vif-2.0.0/os_vif/internal/ip/api.py0000664000175000017500000000157013621620626017663 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from oslo_log import log as logging if os.name == 'nt': from os_vif.internal.ip.windows.impl_netifaces import \ Netifaces as ip_lib_class else: from os_vif.internal.ip.linux.impl_pyroute2 import \ PyRoute2 as ip_lib_class LOG = logging.getLogger(__name__) ip = ip_lib_class() os_vif-2.0.0/os_vif/internal/ip/linux/0000775000175000017500000000000013621620704017671 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/internal/ip/linux/__init__.py0000664000175000017500000000000013621620626021773 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/internal/ip/linux/impl_pyroute2.py0000664000175000017500000001242213621620626023061 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_log import log as logging from oslo_utils import excutils from pyroute2 import iproute from pyroute2.netlink import exceptions as ipexc from pyroute2.netlink.rtnl import ifinfmsg from os_vif import exception from os_vif.internal.ip import ip_command from os_vif import utils LOG = logging.getLogger(__name__) class PyRoute2(ip_command.IpCommand): def _ip_link(self, ip, command, check_exit_code, **kwargs): try: LOG.debug('pyroute2 command %(command)s, arguments %(args)s' % {'command': command, 'args': kwargs}) return ip.link(command, **kwargs) except ipexc.NetlinkError as e: with excutils.save_and_reraise_exception() as ctx: if e.code in check_exit_code: LOG.error('NetlinkError was raised, code %s, message: %s' % (e.code, str(e))) ctx.reraise = False def set(self, device, check_exit_code=None, state=None, mtu=None, address=None, promisc=None, master=None): check_exit_code = check_exit_code or [] with iproute.IPRoute() as ip: idx = 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 master: args['master'] = ip.link_lookup(ifname=master) 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, ageing=None): check_exit_code = check_exit_code or [] with iproute.IPRoute() as ip: args = {'ifname': device, 'kind': dev_type} if self.TYPE_VLAN == dev_type: args['vlan_id'] = vlan_id 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 elif self.TYPE_BRIDGE == dev_type: # NOTE(sean-k-mooney): the keys are defined in the pyroute2 # codebase but are not documented. see the nla_map field # in the bridge_data class located in the # pyroute2.netlink.rtnl.ifinfmsg module for mode details # https://github.com/svinota/pyroute2/blob/3ba9cdde34b2346ef8c2f8ba17cef5dbeb4c6d52/pyroute2/netlink/rtnl/ifinfmsg/__init__.py#L776-L820 args['IFLA_BR_FORWARD_DELAY'] = 0 # set no delay args['IFLA_BR_STP_STATE'] = 0 # disable spanning tree args['IFLA_BR_MCAST_SNOOPING'] = 0 # disable snooping # NOTE(sean-k-mooney): we conditionally enable mac ageing as # this code is shared between the ovs and linux bridge # plugins. For linux bridge we want to allow the default # ageing of 300 seconds, whereas for ovs with the ip-tables # firewall we want to disable ageing. None was chosen as # the default value of ageing to allow the caller to determine # what policy to use and keep this code generic. if ageing is not None: args['IFLA_BR_AGEING_TIME'] = ageing else: raise exception.NetworkInterfaceTypeNotDefined(type=dev_type) return self._ip_link(ip, 'add', check_exit_code, **args) def delete(self, device, check_exit_code=None): check_exit_code = check_exit_code or [] with iproute.IPRoute() as ip: idx = 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}) def exists(self, device): """Return True if the device exists.""" with iproute.IPRoute() as ip: idx = ip.link_lookup(ifname=device) return True if idx else False os_vif-2.0.0/os_vif/i18n.py0000664000175000017500000000202013621620626015434 0ustar zuulzuul00000000000000# 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-2.0.0/os_vif/tests/0000775000175000017500000000000013621620704015450 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/__init__.py0000664000175000017500000000000013621620626017552 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/0000775000175000017500000000000013621620704017612 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/__init__.py0000664000175000017500000000000013621620626021714 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/internal/0000775000175000017500000000000013621620704021426 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/internal/__init__.py0000664000175000017500000000000013621620626023530 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/internal/command/0000775000175000017500000000000013621620704023044 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/internal/command/__init__.py0000664000175000017500000000000013621620626025146 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/internal/command/ip/0000775000175000017500000000000013621620704023454 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py0000664000175000017500000002303613621620626027706 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import re from oslo_concurrency import processutils from oslo_utils import excutils from os_vif.internal.ip.api import ip as ip_lib from os_vif.tests.functional import base from os_vif.tests.functional import privsep @privsep.os_vif_pctxt.entrypoint def _execute_command(*args): return processutils.execute(*args) class ShellIpCommands(object): def add_device(self, device, dev_type, peer=None, link=None, vlan_id=None): if 'vlan' == dev_type: _execute_command('ip', 'link', 'add', 'link', link, 'name', device, 'type', dev_type, 'vlan', 'id', vlan_id) elif 'veth' == dev_type: _execute_command('ip', 'link', 'add', device, 'type', dev_type, 'peer', 'name', peer) elif 'dummy' == dev_type: _execute_command('ip', 'link', 'add', device, 'type', dev_type) def del_device(self, device): if self.exist_device(device): _execute_command('ip', 'link', 'del', device) def set_status_up(self, device): _execute_command('ip', 'link', 'set', device, 'up') def set_status_down(self, device): _execute_command('ip', 'link', 'set', device, 'down') def set_device_mtu(self, device, mtu): _execute_command('ip', 'link', 'set', device, 'mtu', mtu) def show_device(self, device): val, err = _execute_command('ip', 'link', 'show', device) return val.splitlines() def exist_device(self, device): try: _execute_command('ip', 'link', 'show', device) return True except processutils.ProcessExecutionError as e: with excutils.save_and_reraise_exception() as saved_exception: if e.exit_code == 1: saved_exception.reraise = False return False def show_state(self, device): regex = re.compile(r".*state (?P\w+)") match = regex.match(self.show_device(device)[0]) if match is None: return return match.group('state') def show_promisc(self, device): regex = re.compile(r".*(PROMISC)") match = regex.match(self.show_device(device)[0]) return True if match else False def show_mac(self, device): exp = r".*link/ether (?P([0-9A-Fa-f]{2}[:]){5}[0-9A-Fa-f]{2})" regex = re.compile(exp) match = regex.match(self.show_device(device)[1]) if match is None: return return match.group('mac') def show_mtu(self, device): regex = re.compile(r".*mtu (?P\d+)") match = regex.match(self.show_device(device)[0]) if match is None: return return int(match.group('mtu')) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_set(*args, **kwargs): ip_lib.set(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_add(*args, **kwargs): ip_lib.add(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_delete(*args, **kwargs): ip_lib.delete(*args, **kwargs) @privsep.os_vif_pctxt.entrypoint def _ip_cmd_exists(*args, **kwargs): return ip_lib.exists(*args, **kwargs) class TestIpCommand(ShellIpCommands, base.BaseFunctionalTestCase): def setUp(self): super(TestIpCommand, self).setUp() def test_set_state(self): device1 = "test_dev_1" device2 = "test_dev_2" self.addCleanup(self.del_device, device1) self.add_device(device1, 'veth', peer=device2) _ip_cmd_set(device1, state='up') _ip_cmd_set(device2, state='up') self.assertEqual('UP', self.show_state(device1)) self.assertEqual('UP', self.show_state(device2)) _ip_cmd_set(device1, state='down') _ip_cmd_set(device2, state='down') self.assertEqual('DOWN', self.show_state(device1)) self.assertEqual('DOWN', self.show_state(device2)) def test_set_mtu(self): device = "test_dev_3" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') _ip_cmd_set(device, mtu=1200) self.assertEqual(1200, self.show_mtu(device)) _ip_cmd_set(device, mtu=900) self.assertEqual(900, self.show_mtu(device)) def test_set_address(self): device = "test_dev_4" address1 = "36:a7:e4:f9:01:01" address2 = "36:a7:e4:f9:01:01" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') _ip_cmd_set(device, address=address1) self.assertEqual(address1, self.show_mac(device)) _ip_cmd_set(device, address=address2) self.assertEqual(address2, self.show_mac(device)) def test_set_promisc(self): device = "test_dev_5" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') _ip_cmd_set(device, promisc=True) self.assertTrue(self.show_promisc(device)) _ip_cmd_set(device, promisc=False) self.assertFalse(self.show_promisc(device)) def test_add_vlan(self): device = "test_dev_6" link = "test_devlink" self.addCleanup(self.del_device, device) self.addCleanup(self.del_device, link) self.add_device(link, 'dummy') _ip_cmd_add(device, 'vlan', link=link, vlan_id=100) self.assertTrue(self.exist_device(device)) def test_add_veth(self): device = "test_dev_7" peer = "test_devpeer" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'veth', peer=peer) self.assertTrue(self.exist_device(device)) self.assertTrue(self.exist_device(peer)) def test_delete(self): device = "test_dev_8" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') self.assertTrue(self.exist_device(device)) _ip_cmd_delete(device) self.assertFalse(self.exist_device(device)) def test_iproute_object_closes_correctly(self): # NOTE(ralonsoh): check https://bugs.launchpad.net/os-vif/+bug/1807949 device = "test_dev_9" link = "test_devlink_2" self.add_device(link, 'dummy') self.addCleanup(self.del_device, device) self.addCleanup(self.del_device, link) for _ in range(300): _ip_cmd_add(device, 'vlan', link=link, vlan_id=100) _ip_cmd_delete(device) def test_exists(self): device = "test_dev_10" self.addCleanup(self.del_device, device) self.add_device(device, 'dummy') self.assertTrue(_ip_cmd_exists(device)) self.del_device(device) self.assertFalse(_ip_cmd_exists(device)) def test_add_bridge(self): device = "test_dev_11" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'bridge') self.assertTrue(self.exist_device(device)) base_path = "/sys/class/net/test_dev_11/bridge/%s" with open(base_path % "forward_delay", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "stp_state", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "multicast_snooping", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "ageing_time", "r") as f: value = int(f.readline().rstrip('\n')) # NOTE(sean-k-mooney): IEEE 8021-Q recommends that the default # ageing should be 300 and the linux kernel defaults to 300 # via an unconditional define. As such we expect this to be # 300 however since services like network-manager could change # the default on bridge creation we check that if it is not 300 # then the value should not be 0. self.assertTrue(300 == value or value != 0) def test_add_bridge_with_mac_ageing_0(self): device = "test_dev_12" self.addCleanup(self.del_device, device) _ip_cmd_add(device, 'bridge', ageing=0) self.assertTrue(self.exist_device(device)) base_path = "/sys/class/net/test_dev_12/bridge/%s" with open(base_path % "forward_delay", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "stp_state", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "ageing_time", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) with open(base_path % "multicast_snooping", "r") as f: self.assertEqual("0", f.readline().rstrip('\n')) def test_add_port_to_bridge(self): device = "test_dev_13" bridge = "test_dev_14" self.addCleanup(self.del_device, device) self.addCleanup(self.del_device, bridge) self.add_device(device, 'dummy') _ip_cmd_add(bridge, 'bridge') self.assertTrue(self.exist_device(device)) self.assertTrue(self.exist_device(bridge)) _ip_cmd_set(device, master=bridge) path = "/sys/class/net/{}/brif/{}".format(bridge, device) self.assertTrue(os.path.exists(path)) os_vif-2.0.0/os_vif/tests/functional/internal/command/ip/__init__.py0000664000175000017500000000000013621620626025556 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/functional/base.py0000664000175000017500000001160413621620626021103 0ustar zuulzuul00000000000000# 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 sys import eventlet.timeout from os_vif import version as osvif_version from oslo_config import cfg from oslo_log import log as logging from oslo_utils import fileutils from oslotest import base CONF = cfg.CONF LOG = logging.getLogger(__name__) def _get_test_log_path(): return os.environ.get('OS_LOG_PATH', '/tmp') # This is the directory from which infra fetches log files for functional tests DEFAULT_LOG_DIR = os.path.join(_get_test_log_path(), 'osvif-functional-logs') def wait_until_true(predicate, timeout=15, sleep=1): """Wait until callable predicate is evaluated as True :param predicate: Callable deciding whether waiting should continue. Best practice is to instantiate predicate with ``functools.partial()``. :param timeout: Timeout in seconds how long should function wait. :param sleep: Polling interval for results in seconds. :return: True if the predicate is evaluated as True within the timeout, False in case of timeout evaluating the predicate. """ try: with eventlet.Timeout(timeout): while not predicate(): eventlet.sleep(sleep) except eventlet.Timeout: return False return True class _CatchTimeoutMetaclass(abc.ABCMeta): def __init__(cls, name, bases, dct): super(_CatchTimeoutMetaclass, cls).__init__(name, bases, dct) for name, method in inspect.getmembers( # NOTE(ihrachys): we should use isroutine because it will catch # both unbound methods (python2) and functions (python3) cls, predicate=inspect.isroutine): if name.startswith('test_'): setattr(cls, name, cls._catch_timeout(method)) @staticmethod def _catch_timeout(f): @functools.wraps(f) def func(self, *args, **kwargs): try: return f(self, *args, **kwargs) except eventlet.Timeout as e: self.fail('Execution of this test timed out: %s' % e) return func def setup_logging(component_name): """Sets up the logging options for a log with supplied name.""" logging.setup(cfg.CONF, component_name) LOG.info("Logging enabled!") LOG.info("%(prog)s version %(version)s", {'prog': sys.argv[0], 'version': osvif_version.__version__}) LOG.debug("command line: %s", " ".join(sys.argv)) def sanitize_log_path(path): """Sanitize the string so that its log path is shell friendly""" return path.replace(' ', '-').replace('(', '_').replace(')', '_') # Test worker cannot survive eventlet's Timeout exception, which effectively # kills the whole worker, with all test cases scheduled to it. This metaclass # makes all test cases convert Timeout exceptions into unittest friendly # failure mode (self.fail). @six.add_metaclass(_CatchTimeoutMetaclass) class BaseFunctionalTestCase(base.BaseTestCase): """Base class for functional tests.""" COMPONENT_NAME = 'os_vif' PRIVILEGED_GROUP = 'os_vif_privileged' def setUp(self): super(BaseFunctionalTestCase, self).setUp() logging.register_options(CONF) setup_logging(self.COMPONENT_NAME) fileutils.ensure_tree(DEFAULT_LOG_DIR, mode=0o755) log_file = sanitize_log_path( os.path.join(DEFAULT_LOG_DIR, "%s.txt" % self.id())) self.flags(log_file=log_file) privsep_helper = os.path.join( os.getenv('VIRTUAL_ENV', os.path.dirname(sys.executable)[:-4]), 'bin', 'privsep-helper') self.flags( helper_command=' '.join(['sudo', '-E', privsep_helper]), group=self.PRIVILEGED_GROUP) def flags(self, **kw): """Override some configuration values. The keyword arguments are the names of configuration options to override and their values. If a group argument is supplied, the overrides are applied to the specified configuration option group. All overrides are automatically cleared at the end of the current test by the fixtures cleanup process. """ group = kw.pop('group', None) for k, v in kw.items(): CONF.set_override(k, v, group) os_vif-2.0.0/os_vif/tests/functional/privsep.py0000664000175000017500000000147013621620626021661 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/tests/unit/0000775000175000017500000000000013621620704016427 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/test_objects.py0000664000175000017500000000717513621620626021506 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_versionedobjects import base as ovo_base from oslo_versionedobjects import fixture import os_vif from os_vif import objects from os_vif.tests.unit import base object_data = { 'HostInfo': '1.0-4dba5ce236ea2dc559de8764995dd247', 'HostPluginInfo': '1.0-5204e579864981c9891ecb5d1c9329f2', 'HostPortProfileInfo': '1.0-e0bc9228c1456b220830d67b05bc4bf2', 'HostVIFInfo': '1.1-00fdbeba3f9bb3bd2a723c17023ba182', 'FixedIP': '1.0-d1a0ec7e7b6ce021a784c54d44cce009', 'FixedIPList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'InstanceInfo': '1.0-84104d3435046b1a282ac8265ec2a976', 'Network': '1.1-27a8a3e236d1d239121668a590130154', 'Route': '1.0-5ca049cb82c4d4ec5edb1b839c1429c7', 'RouteList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'Subnet': '1.0-6a8c192ef7492120d1a5e0fd08e44272', 'SubnetList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'VIFBase': '1.0-4a5a8881dc999752cb050dd443458b6a', 'VIFBridge': '1.0-e78d355f3505361fafbf0797ffad484a', 'VIFDirect': '1.0-05c939280f4025fd1f7efb921a835c57', 'VIFGeneric': '1.0-c72e637ed620f0135ea50a9409a3f389', 'VIFHostDevice': '1.0-bb090f1869c3b4df36efda216ab97a61', 'VIFOpenVSwitch': '1.0-e78d355f3505361fafbf0797ffad484a', 'VIFPortProfile8021Qbg': '1.1-b3011621809dca9216b50579ce9d6b19', 'VIFPortProfile8021Qbh': '1.1-226b61b2e76ba452f7b31530cff80ac9', 'VIFPortProfileBase': '1.1-4982d1621df12ebd1f3b07948f3d0e5f', 'VIFPortProfileOpenVSwitch': '1.3-1ad9a350a9cae19c977d21fcce7c8c7f', 'VIFPortProfileFPOpenVSwitch': '1.3-06c425743430e7702ef112e09b987346', 'VIFPortProfileFPBridge': '1.1-49f1952bf50bab7a95112c908534751f', 'VIFPortProfileFPTap': '1.1-fd178229477604dfb65de5ce929488e5', 'VIFVHostUser': '1.1-1f95b43be1f884f090ca1f4d79adfd35', 'VIFPortProfileOVSRepresentor': '1.3-f625e17143473b93d6c7f97ded9f785a', 'VIFNestedDPDK': '1.0-fdbaf6b20afd116529929b21aa7158dc', 'VIFPortProfileK8sDPDK': '1.1-e2a2abd112b14e0239e76b99d9b252ae', 'DatapathOffloadBase': '1.0-77509ea1ea0dd750d5864b9bd87d3f9d', 'DatapathOffloadRepresentor': '1.0-802a5dff22f73046df3742c815c51421', } class TestObjectVersions(base.TestCase): def setUp(self): super(TestObjectVersions, self).setUp() os_vif.objects.register_all() def test_versions(self): checker = fixture.ObjectVersionChecker( ovo_base.VersionedObjectRegistry.obj_classes()) expected, actual = checker.test_hashes(object_data) self.assertEqual(expected, actual, 'Some objects have changed; please make sure the ' 'versions have been bumped, and then update their ' 'hashes here.') def test_vif_vhost_user_obj_make_compatible(self): vif = objects.vif.VIFVHostUser( path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="vhu123") primitive = vif.obj_to_primitive()['versioned_object.data'] self.assertIn('vif_name', primitive) vif.obj_make_compatible(primitive, '1.0') self.assertNotIn('vif_name', primitive) os_vif-2.0.0/os_vif/tests/unit/test_base.py0000664000175000017500000000611413621620626020757 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/tests/unit/__init__.py0000664000175000017500000000000013621620626020531 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/test_vif.py0000664000175000017500000004312013621620626020627 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import warnings import os_vif from os_vif import objects from os_vif.tests.unit import base class TestVIFS(base.TestCase): def setUp(self): super(TestVIFS, self).setUp() os_vif.objects.register_all() def _test_vif(self, cls, **kwargs): vif = cls(**kwargs) prim = vif.obj_to_primitive() self.assertEqual("os_vif", prim["versioned_object.namespace"]) vif2 = objects.vif.VIFBase.obj_from_primitive(prim) # The __eq__ function works by using obj_to_primitive() # and this includes a list of changed fields. Very # occassionally the ordering of the list of changes # varies, causing bogus equality failures. This is # arguably a bug in oslo.versionedobjects since the # set of changes fields should not affect equality # comparisons. Remove this hack once this is fixed: # # https://bugs.launchpad.net/oslo.versionedobjects/+bug/1563787 vif.obj_reset_changes(recursive=True) vif2.obj_reset_changes(recursive=True) self.assertEqual(vif, vif2) def test_vif_generic(self): self._test_vif(objects.vif.VIFGeneric, vif_name="vif123") def test_vif_bridge_plain(self): self._test_vif(objects.vif.VIFBridge, vif_name="vif123", bridge_name="br0") def test_port_profile_base_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileBase( datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertNotIn('datapath_type', data) def test_vif_bridge_ovs(self): prof = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev') self._test_vif(objects.vif.VIFOpenVSwitch, vif_name="vif123", bridge_name="br0", port_profile=prof) def test_port_profile_ovs_backport_1_0(self): obj = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev') primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertNotIn('datapath_type', data) def test_port_profile_ovs_backport_1_1(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.1') self.assertEqual('1.1', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('netdev', data['datapath_type']) self.assertNotIn('datapath_offload', data) def test_port_profile_ovs_backport_1_2(self): obj = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", create_port=True) primitive = obj.obj_to_primitive(target_version='1.2') self.assertEqual('1.2', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertNotIn('create_port', data) def test_vif_direct_plain(self): self._test_vif(objects.vif.VIFDirect, vif_name="vif123", dev_address="0002:24:12.3") def test_vif_direct_vepa_qbg(self): prof = objects.vif.VIFPortProfile8021Qbg( manager_id=8, type_id=23, type_id_version=523, instance_id="72a00fee-2fbb-43e6-a592-c858d056fcfc") self._test_vif(objects.vif.VIFDirect, vif_name="vif123", port_profile=prof, dev_address="0002:24:12.3") def test_vif_direct_vepa_qbh(self): prof = objects.vif.VIFPortProfile8021Qbh( profile_id="fishfood") self._test_vif(objects.vif.VIFDirect, vif_name="vif123", port_profile=prof, dev_address="0002:24:12.3") def test_vif_vhost_user(self): self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="vhu123") def test_vif_vhost_user_fp_ovs(self): prof = objects.vif.VIFPortProfileFPOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee8", profile_id="fishfood", datapath_type='netdev', bridge_name="br-int", hybrid_plug=False) self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_port_profile_fp_ovs_backport_1_0(self): obj = objects.vif.VIFPortProfileFPOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', bridge_name="br-int", hybrid_plug=False) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('br-int', data['bridge_name']) self.assertEqual(False, data['hybrid_plug']) self.assertNotIn('datapath_type', data) def test_port_profile_fp_ovs_backport_1_1(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileFPOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', bridge_name="br-int", hybrid_plug=False, datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.1') self.assertEqual('1.1', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('br-int', data['bridge_name']) self.assertEqual(False, data['hybrid_plug']) self.assertEqual('netdev', data['datapath_type']) self.assertNotIn('datapath_offload', data) def test_vif_vhost_user_ovs_representor(self): prof = objects.vif.VIFPortProfileOVSRepresentor( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee8", profile_id="fishfood", datapath_type='netdev', representor_name="tap123", representor_address="0002:24:12.3") self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_port_profile_ovs_representor_backport_1_0(self): obj = objects.vif.VIFPortProfileOVSRepresentor( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', representor_name="tap123", representor_address="0002:24:12.3") primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('tap123', data['representor_name']) self.assertEqual("0002:24:12.3", data['representor_address']) self.assertNotIn('datapath_type', data) def test_port_profile_ovs_representor_backport_1_1(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileOVSRepresentor( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood", datapath_type='netdev', representor_name="tap123", representor_address="0002:24:12.3", datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.1') self.assertEqual('1.1', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['interface_id']) self.assertEqual('fishfood', data['profile_id']) self.assertEqual('tap123', data['representor_name']) self.assertEqual("0002:24:12.3", data['representor_address']) self.assertEqual('netdev', data['datapath_type']) self.assertNotIn('datapath_offload', data) def test_vif_vhost_user_generic_representor(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") prof = objects.vif.VIFPortProfileBase( datapath_offload=datapath_offload, ) self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.SERVER, vif_name="felix", port_profile=prof) def test_vif_vhost_user_fp_lb(self): prof = objects.vif.VIFPortProfileFPBridge(bridge_name="brq456") self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_vif_vhost_user_fp_tap(self): prof = objects.vif.VIFPortProfileFPTap(mac_address="fa:16:3e:4c:2c:30") self._test_vif(objects.vif.VIFVHostUser, path="/some/socket.path", mode=objects.fields.VIFVHostUserMode.CLIENT, vif_name="tap123", port_profile=prof) def test_vif_host_dev_plain(self): self._test_vif( objects.vif.VIFHostDevice, dev_type=objects.fields.VIFHostDeviceDevType.ETHERNET, dev_address="0002:24:12.3") def test_vif_host_dev_vepa_qbh(self): prof = objects.vif.VIFPortProfile8021Qbh( profile_id="fishfood") self._test_vif(objects.vif.VIFHostDevice, dev_address="0002:24:12.3", port_profile=prof) def test_vif_nested_dpdk_k8s(self): prof = objects.vif.VIFPortProfileK8sDPDK( l3_setup=False, selflink="/some/url", resourceversion="1") self._test_vif( objects.vif.VIFNestedDPDK, pci_adress="0002:24:12.3", dev_driver="virtio_pci", port_profile=prof) def test_port_profile_fp_bridge_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileFPBridge( bridge_name='joe', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('joe', data['bridge_name']) self.assertNotIn('datapath_type', data) def test_port_profile_fp_tap_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileFPTap( mac_address='00:de:ad:be:ef:01', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('00:de:ad:be:ef:01', data['mac_address']) self.assertNotIn('datapath_type', data) def test_port_profile_8021qbg_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfile8021Qbg( manager_id=42, type_id=43, type_id_version=44, instance_id='07bd6cea-fb37-4594-b769-90fc51854ee9', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual(42, data['manager_id']) self.assertEqual(43, data['type_id']) self.assertEqual(44, data['type_id_version']) self.assertEqual('07bd6cea-fb37-4594-b769-90fc51854ee9', data['instance_id']) self.assertNotIn('datapath_type', data) def test_port_profile_8021qbh_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfile8021Qbh( profile_id='catfood', datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual('catfood', data['profile_id']) self.assertNotIn('datapath_type', data) def test_port_profile_dpdk_k8s_backport_1_0(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") obj = objects.vif.VIFPortProfileK8sDPDK( l3_setup=False, selflink="/some/url", resourceversion="1", datapath_offload=datapath_offload) primitive = obj.obj_to_primitive(target_version='1.0') self.assertEqual('1.0', primitive['versioned_object.version']) data = primitive['versioned_object.data'] self.assertEqual(False, data['l3_setup']) self.assertEqual("/some/url", data['selflink']) self.assertEqual("1", data['resourceversion']) self.assertNotIn('datapath_type', data) def test_vif_host_dev_ovs_offload(self): datapath_offload = objects.vif.DatapathOffloadRepresentor( representor_name="felix", representor_address="0002:24:12.3") prof = objects.vif.VIFPortProfileOpenVSwitch( interface_id="07bd6cea-fb37-4594-b769-90fc51854ee8", profile_id="fishfood", datapath_type='netdev', datapath_offload=datapath_offload) self._test_vif( objects.vif.VIFHostDevice, dev_type=objects.fields.VIFHostDeviceDevType.ETHERNET, dev_address="0002:24:12.3", port_profile=prof) def test_pending_warnings_emitted_class_direct(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") pp = objects.vif.VIFPortProfileOVSRepresentor() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) self.assertEqual(pp.VERSION, objects.vif.VIFPortProfileOVSRepresentor.VERSION) os_vif-2.0.0/os_vif/tests/unit/test_os_vif.py0000664000175000017500000001423113621620626021331 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 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() # NOTE(sean-k-mooney): as out of tree plugins could be # visable in path assert only at at least all the in # intree plugins are loaded instead of an exact match. self.assertTrue(len(info.plugin_info) >= 3) plugins = {p.plugin_name: p for p in info.plugin_info} in_tree_plugin_names = ("linux_bridge", "ovs", "noop") self.assertTrue(all(name in plugins for name in in_tree_plugin_names)) lb = plugins["linux_bridge"] self.assertTrue(any("VIFBridge" == vif.vif_object_name for vif in lb.vif_info)) ovs = plugins["ovs"] self.assertTrue(len(ovs.vif_info) >= 4) vif_names = (vif.vif_object_name for vif in ovs.vif_info) ovs_vifs = ("VIFBridge", "VIFOpenVSwitch", "VIFVHostUser", "VIFHostDevice") self.assertTrue(all(name in ovs_vifs for name in vif_names)) noop = plugins["noop"] self.assertTrue(any("VIFVHostUser" == vif.vif_object_name for vif in noop.vif_info)) def test_host_info_filtered(self): os_vif.initialize() info = os_vif.host_info(permitted_vif_type_names=["VIFOpenVSwitch"]) self.assertEqual(len(info.plugin_info), 1) self.assertEqual(info.plugin_info[0].plugin_name, "ovs") vif_info = info.plugin_info[0].vif_info self.assertEqual(len(vif_info), 1) self.assertEqual(vif_info[0].vif_object_name, "VIFOpenVSwitch") os_vif-2.0.0/os_vif/tests/unit/test_host_info.py0000664000175000017500000001663013621620626022041 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from vif_plug_linux_bridge import constants as lb_constants from vif_plug_ovs import constants as ovs_constants from os_vif import exception from os_vif import objects from os_vif.tests.unit import base class TestHostInfo(base.TestCase): def setUp(self): super(TestHostInfo, self).setUp() objects.register_all() self.host_info = objects.host_info.HostInfo( plugin_info=[ objects.host_info.HostPluginInfo( plugin_name=lb_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="1.0", max_version="3.0" ), ]), objects.host_info.HostPluginInfo( plugin_name=ovs_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="2.0", max_version="7.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1.0", max_version="2.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFVHostUser", min_version="1.0", max_version="2.0" ), ]) ]) # https://bugs.launchpad.net/oslo.versionedobjects/+bug/1563787 self.host_info.obj_reset_changes(recursive=True) def test_serialization(self): json = self.host_info.obj_to_primitive() self.assertEqual("os_vif", json["versioned_object.namespace"]) host_info = objects.host_info.HostInfo.obj_from_primitive(json) # Copied from test_vif.py: # # The __eq__ function works by using obj_to_primitive() # and this includes a list of changed fields. Very # occassionally the ordering of the list of changes # varies, causing bogus equality failures. This is # arguably a bug in oslo.versionedobjects since the # set of changes fields should not affect equality # comparisons. Remove this hack once this is fixed: # # https://bugs.launchpad.net/oslo.versionedobjects/+bug/1563787 host_info.obj_reset_changes(recursive=True) self.assertEqual(self.host_info, host_info) def test_plugin_existance(self): self.assertTrue(self.host_info.has_plugin(ovs_constants.PLUGIN_NAME)) self.assertFalse(self.host_info.has_plugin("fishfood")) def test_plugin_fetch(self): plugin = self.host_info.get_plugin(ovs_constants.PLUGIN_NAME) self.assertEqual(ovs_constants.PLUGIN_NAME, plugin.plugin_name) self.assertRaises(exception.NoMatchingPlugin, self.host_info.get_plugin, "fishfood") def test_vif_existance(self): plugin = self.host_info.get_plugin(ovs_constants.PLUGIN_NAME) self.assertTrue(plugin.has_vif("VIFOpenVSwitch")) self.assertFalse(plugin.has_vif("VIFFishFood")) def test_vif_fetch(self): plugin = self.host_info.get_plugin(ovs_constants.PLUGIN_NAME) vif = plugin.get_vif("VIFOpenVSwitch") self.assertEqual("VIFOpenVSwitch", vif.vif_object_name) self.assertRaises(exception.NoMatchingVIFClass, plugin.get_vif, "VIFFishFood") def test_common_version_no_obj(self): info = objects.host_info.HostVIFInfo( vif_object_name="VIFFishFood", min_version="1.0", max_version="1.8") self.assertRaises(exception.NoMatchingVIFClass, info.get_common_version) def test_common_version_no_version(self): info = objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1729.0", max_version="8753.0") self.assertRaises(exception.NoSupportedVIFVersion, info.get_common_version) def test_common_version_ok(self): info = objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1.0", max_version="10.0") ver = info.get_common_version() self.assertEqual(objects.vif.VIFOpenVSwitch.VERSION, ver) def test_filtering(self): host_info = objects.host_info.HostInfo( plugin_info=[ objects.host_info.HostPluginInfo( plugin_name=lb_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="1.0", max_version="3.0" ), ]), objects.host_info.HostPluginInfo( plugin_name=ovs_constants.PLUGIN_NAME, vif_info=[ objects.host_info.HostVIFInfo( vif_object_name="VIFBridge", min_version="2.0", max_version="7.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFOpenVSwitch", min_version="1.0", max_version="2.0" ), objects.host_info.HostVIFInfo( vif_object_name="VIFVHostUser", min_version="1.0", max_version="2.0" ), ]) ]) host_info.filter_vif_types(["VIFBridge", "VIFOpenVSwitch"]) self.assertEqual(len(host_info.plugin_info), 2) plugin = host_info.plugin_info[0] self.assertEqual(len(plugin.vif_info), 1) self.assertEqual(plugin.vif_info[0].vif_object_name, "VIFBridge") plugin = host_info.plugin_info[1] self.assertEqual(len(plugin.vif_info), 2) self.assertEqual(plugin.vif_info[0].vif_object_name, "VIFBridge") self.assertEqual(plugin.vif_info[1].vif_object_name, "VIFOpenVSwitch") host_info.filter_vif_types(["VIFOpenVSwitch"]) self.assertEqual(len(host_info.plugin_info), 1) plugin = host_info.plugin_info[0] self.assertEqual(len(plugin.vif_info), 1) self.assertEqual(plugin.vif_info[0].vif_object_name, "VIFOpenVSwitch") os_vif-2.0.0/os_vif/tests/unit/internal/0000775000175000017500000000000013621620704020243 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/internal/__init__.py0000664000175000017500000000000013621620626022345 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/internal/ip/0000775000175000017500000000000013621620704020653 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/internal/ip/test_api.py0000664000175000017500000000260613621620626023044 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 six import moves from os_vif.internal.ip import api from os_vif.tests.unit import base class TestIpApi(base.TestCase): @staticmethod def _reload_original_os_module(): moves.reload_module(api) def test_get_impl_windows(self): self.addCleanup(self._reload_original_os_module) with mock.patch('os.name', 'nt'): moves.reload_module(api) from os_vif.internal.ip.windows import impl_netifaces self.assertIsInstance(api.ip, impl_netifaces.Netifaces) def test_get_impl_linux(self): self.addCleanup(self._reload_original_os_module) with mock.patch('os.name', 'posix'): moves.reload_module(api) from os_vif.internal.ip.linux import impl_pyroute2 self.assertIsInstance(api.ip, impl_pyroute2.PyRoute2) os_vif-2.0.0/os_vif/tests/unit/internal/ip/windows/0000775000175000017500000000000013621620704022345 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/internal/ip/windows/__init__.py0000664000175000017500000000000013621620626024447 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/internal/ip/windows/test_impl_netifaces.py0000664000175000017500000000367113621620626026752 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 netifaces from os_vif.internal.ip.windows import impl_netifaces as ip_lib from os_vif.tests.unit import base class TestIPDevice(base.TestCase): def setUp(self): super(TestIPDevice, self).setUp() self.device_name = 'test_device' self.mock_log = mock.patch.object(ip_lib, "LOG").start() self.ip_lib = ip_lib.Netifaces() @mock.patch.object(netifaces, 'ifaddresses', return_value=True) def test_exists(self, mock_ifaddresses): self.assertTrue(self.ip_lib.exists(self.device_name)) mock_ifaddresses.assert_called_once_with(self.device_name) @mock.patch.object(netifaces, 'ifaddresses', side_effect=ValueError()) def test_exists_not_found(self, mock_ifaddresses): self.assertFalse(self.ip_lib.exists(self.device_name)) mock_ifaddresses.assert_called_once_with(self.device_name) self.mock_log.warning.assert_called_once_with( "The device does not exist on the system: %s", self.device_name) @mock.patch.object(netifaces, 'ifaddresses', side_effect=OSError()) def test_exists_os_error_exception(self, mock_ifaddresses): self.assertFalse(self.ip_lib.exists(self.device_name)) mock_ifaddresses.assert_called_once_with(self.device_name) self.mock_log.error.assert_called_once_with( "Failed to get interface addresses: %s", self.device_name) os_vif-2.0.0/os_vif/tests/unit/internal/ip/__init__.py0000664000175000017500000000000013621620626022755 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/internal/ip/linux/0000775000175000017500000000000013621620704022012 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/internal/ip/linux/test_impl_pyroute2.py0000664000175000017500000001625013621620626026244 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.ip.linux import impl_pyroute2 from os_vif.tests.unit import base class TestIpCommand(base.TestCase): ERROR_CODE = 40 OTHER_ERROR_CODE = 50 DEVICE = 'device' MTU = 1500 MAC = 'ca:fe:ca:fe:ca:fe' UP = 'up' TYPE_VETH = 'veth' TYPE_VLAN = 'vlan' TYPE_BRIDGE = 'bridge' LINK = 'device2' VLAN_ID = 14 def setUp(self): super(TestIpCommand, self).setUp() self.ip = impl_pyroute2.PyRoute2() self.ip_link_p = mock.patch.object(iproute.IPRoute, 'link') self.ip_link = self.ip_link_p.start() def test_set(self): with mock.patch.object(iproute.IPRoute, 'link_lookup', return_value=[1]) as mock_link_lookup: self.ip_link.return_value = [{'flags': 0x4000}] self.ip.set(self.DEVICE, state=self.UP, mtu=self.MTU, address=self.MAC, promisc=True) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) args = {'state': self.UP, 'mtu': self.MTU, 'address': self.MAC, 'flags': 0x4000 | ifinfmsg.IFF_PROMISC} calls = [mock.call('get', index=1), mock.call('set', index=1, **args)] self.ip_link.assert_has_calls(calls) def test_set_exit_code(self): with mock.patch.object(iproute.IPRoute, 'link_lookup', return_value=[1]) as mock_link_lookup: self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE, msg="Error message") self.ip.set(self.DEVICE, check_exit_code=[self.ERROR_CODE]) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) self.ip_link.assert_called_once_with('set', index=1) self.assertRaises(ipexc.NetlinkError, self.ip.set, self.DEVICE, check_exit_code=[self.OTHER_ERROR_CODE]) def test_set_no_interface_found(self): with mock.patch.object(iproute.IPRoute, 'link_lookup', return_value=[]) as mock_link_lookup: self.assertRaises(exception.NetworkInterfaceNotFound, self.ip.set, self.DEVICE) mock_link_lookup.assert_called_once_with(ifname=self.DEVICE) self.ip_link.assert_not_called() def test_add_veth(self): self.ip.add(self.DEVICE, self.TYPE_VETH, peer='peer') self.ip_link.assert_called_once_with( 'add', ifname=self.DEVICE, kind=self.TYPE_VETH, peer='peer') def test_add_vlan(self): with mock.patch.object(iproute.IPRoute, 'link_lookup', return_value=[1]) as mock_link_lookup: self.ip.add(self.DEVICE, self.TYPE_VLAN, link=self.LINK, vlan_id=self.VLAN_ID) mock_link_lookup.assert_called_once_with(ifname=self.LINK) args = {'ifname': self.DEVICE, 'kind': self.TYPE_VLAN, 'vlan_id': self.VLAN_ID, 'link': 1} self.ip_link.assert_called_once_with('add', **args) def test_add_bridge(self): self.ip.add(self.DEVICE, self.TYPE_BRIDGE) args = {'ifname': self.DEVICE, 'kind': self.TYPE_BRIDGE, 'IFLA_BR_FORWARD_DELAY': 0, 'IFLA_BR_STP_STATE': 0, 'IFLA_BR_MCAST_SNOOPING': 0} self.ip_link.assert_called_once_with('add', **args) def test_add_bridge_with_ageing(self): self.ip.add(self.DEVICE, self.TYPE_BRIDGE, ageing=0) args = {'ifname': self.DEVICE, 'kind': self.TYPE_BRIDGE, 'IFLA_BR_AGEING_TIME': 0, 'IFLA_BR_FORWARD_DELAY': 0, 'IFLA_BR_STP_STATE': 0, 'IFLA_BR_MCAST_SNOOPING': 0} self.ip_link.assert_called_once_with('add', **args) def test_add_vlan_no_interface_found(self): with mock.patch.object(iproute.IPRoute, 'link_lookup', return_value=[]) as mock_link_lookup: self.assertRaises(exception.NetworkInterfaceNotFound, self.ip.add, self.DEVICE, self.TYPE_VLAN, link=self.LINK) mock_link_lookup.assert_called_once_with(ifname=self.LINK) self.ip_link.assert_not_called() def test_add_other_type(self): self.assertRaises(exception.NetworkInterfaceTypeNotDefined, self.ip.add, self.DEVICE, 'type_not_defined') def test_add_exit_code(self): self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE, msg="Error message") self.ip.add(self.DEVICE, self.TYPE_VETH, peer='peer', check_exit_code=[self.ERROR_CODE]) self.ip_link.assert_called_once_with( 'add', ifname=self.DEVICE, kind=self.TYPE_VETH, peer='peer') self.assertRaises(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-2.0.0/os_vif/tests/unit/internal/ip/linux/__init__.py0000664000175000017500000000000013621620626024114 0ustar zuulzuul00000000000000os_vif-2.0.0/os_vif/tests/unit/test_exception.py0000664000175000017500000000254113621620626022043 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/tests/unit/base.py0000664000175000017500000000143313621620626017717 0ustar zuulzuul00000000000000# -*- 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-2.0.0/os_vif/version.py0000664000175000017500000000131113621620626016344 0ustar zuulzuul00000000000000# 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-2.0.0/os_vif/plugin.py0000664000175000017500000000576413621620626016175 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/os_vif/utils.py0000664000175000017500000000131113621620626016017 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/.coveragerc0000664000175000017500000000011513621620626015142 0ustar zuulzuul00000000000000[run] branch = True source = os_vif omit = os_vif/tests/*,os_vif/openstack/* os_vif-2.0.0/setup.cfg0000664000175000017500000000251213621620704014642 0ustar zuulzuul00000000000000[metadata] name = os_vif summary = A library for plugging and unplugging virtual interfaces in OpenStack. description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/os-vif/latest/ python-requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: Implementation :: CPython [files] packages = os_vif vif_plug_linux_bridge vif_plug_ovs vif_plug_noop [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [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 noop = vif_plug_noop.noop:NoOpPlugin os_vif-2.0.0/lower-constraints.txt0000664000175000017500000000311413621620626017261 0ustar zuulzuul00000000000000alabaster==0.7.10 amqp==2.2.2 appdirs==1.3.0 Babel==2.5.3 cachetools==2.0.1 cffi==1.11.5 contextlib2==0.5.5 coverage==4.0 debtcollector==1.19.0 docutils==0.11 dulwich==0.15.0 enum-compat==0.0.2 eventlet==0.20.0 extras==1.0.0 fasteners==0.14.1 fixtures==3.0.0 flake8==2.2.4 future==0.16.0 futurist==1.6.0 greenlet==0.4.13 hacking==0.10.2 imagesize==0.7.1 iso8601==0.1.12 Jinja2==2.10 keystoneauth1==3.4.0 kombu==4.1.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mock==2.0.0 monotonic==1.4 mox3==0.20.0 msgpack-python==0.4.0 msgpack==0.5.6 netaddr==0.7.18 netifaces==0.10.6 openstackdocstheme==1.20.0 os-client-config==1.28.0 oslo.concurrency==3.20.0 oslo.config==5.1.0 oslo.context==2.20.0 oslo.i18n==3.15.3 oslo.log==3.30.0 oslo.messaging==5.36.0 oslo.middleware==3.35.0 oslo.privsep==1.23.0 oslo.serialization==2.25.0 oslo.service==1.30.0 oslo.utils==3.36.0 oslo.versionedobjects==1.28.0 oslotest==1.10.0 ovs==2.9.2 ovsdbapp==0.12.1 Paste==2.0.3 PasteDeploy==1.5.2 pbr==2.0.0 pep8==1.5.7 pika-pool==0.1.3 pika==0.11.2 prettytable==0.7.2 pycparser==2.18 pyflakes==0.8.1 Pygments==2.2.0 pyinotify==0.9.6 pyparsing==2.2.0 pyroute2==0.5.2 python-dateutil==2.7.0 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2018.3 PyYAML==3.12 reno==2.5.0 repoze.lru==0.7 requests==2.14.2 requestsexceptions==1.2.0 rfc3986==1.1.0 Routes==2.4.1 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 statsd==3.2.2 stestr==1.0.0 stevedore==1.20.0 tenacity==4.9.0 testrepository==0.0.18 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 vine==1.1.4 WebOb==1.7.4 wrapt==1.10.11 os_vif-2.0.0/HACKING.rst0000664000175000017500000000020613621620626014620 0ustar zuulzuul00000000000000os_vif Style Commandments ========================= Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ os_vif-2.0.0/README.rst0000664000175000017500000000151313621620626014513 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/os-vif.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ====== os-vif ====== .. image:: https://img.shields.io/pypi/v/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Downloads A library for plugging and unplugging virtual interfaces in OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/os-vif/latest/ * Source: https://opendev.org/openstack/os-vif * Bugs: https://bugs.launchpad.net/os-vif * Release Notes: https://docs.openstack.org/releasenotes/os-vif os_vif-2.0.0/.testr.conf0000664000175000017500000000047713621620626015122 0ustar zuulzuul00000000000000[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-2.0.0/PKG-INFO0000664000175000017500000000367213621620704014126 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: os_vif Version: 2.0.0 Summary: A library for plugging and unplugging virtual interfaces in OpenStack. Home-page: https://docs.openstack.org/os-vif/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/os-vif.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ====== os-vif ====== .. image:: https://img.shields.io/pypi/v/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Downloads A library for plugging and unplugging virtual interfaces in OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/os-vif/latest/ * Source: https://opendev.org/openstack/os-vif * Bugs: https://bugs.launchpad.net/os-vif * Release Notes: https://docs.openstack.org/releasenotes/os-vif Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 os_vif-2.0.0/playbooks/0000775000175000017500000000000013621620704015024 5ustar zuulzuul00000000000000os_vif-2.0.0/playbooks/openstack-tox-functional-ovs-with-sudo/0000775000175000017500000000000013621620704024511 5ustar zuulzuul00000000000000os_vif-2.0.0/playbooks/openstack-tox-functional-ovs-with-sudo/RedHat.yaml0000664000175000017500000000007213621620626026546 0ustar zuulzuul00000000000000--- ovs_package: "openvswitch" ovs_service: "openvswitch" os_vif-2.0.0/playbooks/openstack-tox-functional-ovs-with-sudo/Gentoo.yaml0000664000175000017500000000010413621620626026626 0ustar zuulzuul00000000000000--- ovs_package: "net-misc/openvswitch" ovs_service: "ovs-vswitchd" os_vif-2.0.0/playbooks/openstack-tox-functional-ovs-with-sudo/Debian.yaml0000664000175000017500000000011013621620626026552 0ustar zuulzuul00000000000000--- ovs_package: "openvswitch-switch" ovs_service: "openvswitch-switch" os_vif-2.0.0/playbooks/openstack-tox-functional-ovs-with-sudo/Suse.yaml0000664000175000017500000000007213621620626026316 0ustar zuulzuul00000000000000--- ovs_package: "openvswitch" ovs_service: "openvswitch" os_vif-2.0.0/playbooks/openstack-tox-functional-ovs-with-sudo/pre.yaml0000664000175000017500000000106613621620626026171 0ustar zuulzuul00000000000000- hosts: all name: Functional tests pre-tasks tasks: - name: Include OS-specific variables include_vars: "{{ item }}" with_first_found: - "{{ ansible_distribution }}.yaml" - "{{ ansible_os_family }}.yaml" - name: Install Open vSwitch become: yes package: name: "{{ ovs_package }}" state: present register: ovs_installed - name: Start Open vSwitch become: yes service: name: "{{ ovs_service }}" state: started enabled: yes register: ovs_running os_vif-2.0.0/vif_plug_ovs/0000775000175000017500000000000013621620704015523 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/exception.py0000664000175000017500000000246013621620627020101 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/vif_plug_ovs/__init__.py0000664000175000017500000000000013621620627017626 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/constants.py0000664000175000017500000000162613621620627020122 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. PLUGIN_NAME = 'ovs' OVS_VHOSTUSER_INTERFACE_TYPE = 'dpdkvhostuser' OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE = 'dpdkvhostuserclient' OVS_VHOSTUSER_PREFIX = 'vhu' OVS_DATAPATH_SYSTEM = 'system' OVS_DATAPATH_NETDEV = 'netdev' PLATFORM_LINUX = 'linux2' PLATFORM_WIN32 = 'win32' OVS_DPDK_INTERFACE_TYPE = 'dpdk' # Neutron dead VLAN. DEAD_VLAN = 4095 os_vif-2.0.0/vif_plug_ovs/ovs.py0000664000175000017500000004023513621620627016714 0ustar zuulzuul00000000000000# 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.internal.ip.api import ip as ip_lib 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 from vif_plug_ovs.ovsdb import api as ovsdb_api from vif_plug_ovs.ovsdb import ovsdb_lib 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"), cfg.StrOpt('ovsdb_connection', default='tcp:127.0.0.1:6640', help='The connection string for the OVSDB backend. ' 'When executing commands using the native or vsctl ' 'ovsdb interface drivers this config option defines ' 'the ovsdb endpoint used.'), cfg.StrOpt('ovsdb_interface', choices=list(ovsdb_api.interface_map), default='vsctl', help='The interface for interacting with the OVSDB'), # NOTE(sean-k-mooney): This value is a bool for two reasons. # First I want to allow this config option to be reusable with # non ml2/ovs deployment in the future if required, as such I do not # want to encode how the isolation is done in the config option. # Second in the case of ml2/ovs the isolation is based on VLAN tags. # The 802.1Q IEEE spec that defines the VLAN format reserved two VLAN # id values, VLAN ID 0 means the packet is a member of no VLAN # and VLAN ID 4095 is reserved for implementation defined use. # Using VLAN ID 0 would not provide isolation and all other VLAN IDs # except VLAN ID 4095 are valid for the ml2/ovs agent to use for a # tenant network's local VLAN ID. As such only VLAN ID 4095 is valid # to use for vif isolation which is defined in Neutron as the # dead VLAN, a VLAN on which all traffic will be dropped. cfg.BoolOpt('isolate_vif', default=False, help='Controls if VIF should be isolated when plugged ' 'to the ovs bridge. This should only be set to True ' 'when using the neutron ovs ml2 agent.') ) def __init__(self, config): super(OvsPlugin, self).__init__(config) self.ovsdb = ovsdb_lib.BaseOVS(self.config) @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) # NOTE(sean-k-mooney): As part of a partial fix to bug #1734320 # we introduced the isolate_vif config option to enable isolation # of the vif prior to neutron wiring up the interface. To do # this we take advantage of the fact the ml2/ovs uses the # implementation defined VLAN 4095 as a dead VLAN to indicate # that all packets should be dropped. We only enable this # behaviour conditionally as it is not portable to SDN based # deployment such as ODL or OVN as such operator must opt-in # to this behaviour by setting the isolate_vif config option. # TODO(sean-k-mooney): Extend neutron to record what ml2 driver # bound the interface in the vif binding details so isolation # can be enabled automatically in the future. if self.config.isolate_vif: kwargs['tag'] = constants.DEAD_VLAN self.ovsdb.create_ovs_vif_port( vif.network.bridge, vif_name, vif.port_profile.interface_id, vif.address, instance_info.uuid, mtu, **kwargs) def _update_vif_port(self, vif, vif_name): mtu = self._get_mtu(vif) self.ovsdb.update_ovs_vif_port(vif_name, mtu) @staticmethod def _get_vif_datapath_type(vif, datapath=constants.OVS_DATAPATH_SYSTEM): profile = vif.port_profile if 'datapath_type' not in profile or not profile.datapath_type: return datapath return profile.datapath_type def _plug_vhostuser(self, vif, instance_info): self.ovsdb.ensure_ovs_bridge( vif.network.bridge, self._get_vif_datapath_type( vif, datapath=constants.OVS_DATAPATH_NETDEV)) vif_name = OvsPlugin.gen_port_name( constants.OVS_VHOSTUSER_PREFIX, vif.id) args = {} if vif.mode == "client": args['interface_type'] = \ constants.OVS_VHOSTUSER_INTERFACE_TYPE else: args['interface_type'] = \ constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE args['vhost_server_path'] = vif.path self._create_vif_port( vif, vif_name, instance_info, **args) def _plug_bridge(self, vif, instance_info): """Plug using hybrid strategy Create a per-VIF linux bridge, then link that bridge to the OVS integration bridge via a veth device, setting up the other end of the veth device just like a normal OVS port. Then boot the VIF on the linux bridge using standard libvirt mechanisms. """ v1_name, v2_name = self.get_veth_pair_names(vif) linux_net.ensure_bridge(vif.bridge_name) mtu = self._get_mtu(vif) if not ip_lib.exists(v2_name): linux_net.create_veth_pair(v1_name, v2_name, mtu) linux_net.add_bridge_port(vif.bridge_name, v1_name) self.ovsdb.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) self._create_vif_port(vif, v2_name, instance_info) else: linux_net.update_veth_pair(v1_name, v2_name, mtu) self._update_vif_port(vif, v2_name) def _plug_vif_windows(self, vif, instance_info): """Create a per-VIF OVS port.""" if not ip_lib.exists(vif.id): self.ovsdb.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) self._create_vif_port(vif, vif.id, instance_info) def _plug_vif_generic(self, vif, instance_info): """Create a per-VIF OVS port.""" self.ovsdb.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) # NOTE(sean-k-mooney): as part of a partial revert of # change Iaf15fa7a678ec2624f7c12f634269c465fbad930 # (always create ovs port during plug), we stopped calling # self._create_vif_port(vif, vif.vif_name, instance_info). # Calling _create_vif_port here was intended to ensure # that the vif was wired up by neutron before the vm was # spawned on boot or live migration to partially resolve # #1734320. When the "always create ovs port during plug" change # was written it was understood by me that libvirt would not # modify ovs if the port exists but in fact it deletes and # recreates the port. This both undoes the effort to resolve # bug #1734320 and intoduces other issues for neutron. # this comment will be removed when we actully fix #1734320 in # all cases. # NOTE(hamdyk): As a WA to the above note, one can use # VIFPortProfileOpenVSwitch.create_port flag to explicitly # plug the port to the switch. if ("create_port" in vif.port_profile and vif.port_profile.create_port): self._create_vif_port(vif, vif.vif_name, instance_info) def _plug_vf(self, vif, instance_info): datapath = self._get_vif_datapath_type(vif) self.ovsdb.ensure_ovs_bridge(vif.network.bridge, datapath) pci_slot = vif.dev_address vf_num = linux_net.get_vf_num_by_pci_address(pci_slot) args = [] kwargs = {} if datapath == constants.OVS_DATAPATH_SYSTEM: pf_ifname = linux_net.get_ifname_by_pci_address( pci_slot, pf_interface=True, switchdev=True) representor = linux_net.get_representor_port(pf_ifname, vf_num) linux_net.set_interface_state(representor, 'up') args = [vif, representor, instance_info] else: representor = linux_net.get_dpdk_representor_port_name( vif.id) pf_pci = linux_net.get_pf_pci_from_vf(pci_slot) args = [vif, representor, instance_info] kwargs = {'interface_type': constants.OVS_DPDK_INTERFACE_TYPE, 'pf_pci': pf_pci, 'vf_num': vf_num} self._create_vif_port(*args, **kwargs) def plug(self, vif, instance_info): if not hasattr(vif, "port_profile"): raise exception.MissingPortProfile() if not isinstance(vif.port_profile, objects.vif.VIFPortProfileOpenVSwitch): raise exception.WrongPortProfile( profile=vif.port_profile.__class__.__name__) if isinstance(vif, objects.vif.VIFOpenVSwitch): if sys.platform != constants.PLATFORM_WIN32: self._plug_vif_generic(vif, instance_info) 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(vif, instance_info) def _unplug_vhostuser(self, vif, instance_info): self.ovsdb.delete_ovs_vif_port(vif.network.bridge, OvsPlugin.gen_port_name( constants.OVS_VHOSTUSER_PREFIX, vif.id)) def _unplug_bridge(self, vif, instance_info): """UnPlug using hybrid strategy Unhook port from OVS, unhook port from bridge, delete bridge, and delete both veth devices. """ v1_name, v2_name = self.get_veth_pair_names(vif) linux_net.delete_bridge(vif.bridge_name, v1_name) self.ovsdb.delete_ovs_vif_port(vif.network.bridge, v2_name) def _unplug_vif_windows(self, vif, instance_info): """Remove port from OVS.""" self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.id, delete_netdev=False) def _unplug_vif_generic(self, vif, instance_info): """Remove port from OVS.""" # NOTE(sean-k-mooney): even with the partial revert of change # Iaf15fa7a678ec2624f7c12f634269c465fbad930 this should be correct # so this is not removed. self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.vif_name) def _unplug_vf(self, vif): """Remove port from OVS.""" datapath = self._get_vif_datapath_type(vif) if datapath == constants.OVS_DATAPATH_SYSTEM: pci_slot = vif.dev_address pf_ifname = linux_net.get_ifname_by_pci_address( pci_slot, pf_interface=True, switchdev=True) vf_num = linux_net.get_vf_num_by_pci_address(pci_slot) representor = linux_net.get_representor_port(pf_ifname, vf_num) else: representor = linux_net.get_dpdk_representor_port_name( vif.id) # The representor interface can't be deleted because it bind the # SR-IOV VF, therefore we just need to remove it from the ovs bridge # and set the status to down self.ovsdb.delete_ovs_vif_port( vif.network.bridge, representor, delete_netdev=False) if datapath == constants.OVS_DATAPATH_SYSTEM: linux_net.set_interface_state(representor, 'down') def unplug(self, vif, instance_info): if not hasattr(vif, "port_profile"): raise exception.MissingPortProfile() if not isinstance(vif.port_profile, objects.vif.VIFPortProfileOpenVSwitch): raise exception.WrongPortProfile( profile=vif.port_profile.__class__.__name__) if isinstance(vif, objects.vif.VIFOpenVSwitch): if sys.platform != constants.PLATFORM_WIN32: self._unplug_vif_generic(vif, instance_info) else: 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(vif) os_vif-2.0.0/vif_plug_ovs/privsep.py0000664000175000017500000000154213621620627017573 0ustar zuulzuul00000000000000# # 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-2.0.0/vif_plug_ovs/tests/0000775000175000017500000000000013621620704016665 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/__init__.py0000664000175000017500000000000013621620627020770 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/functional/0000775000175000017500000000000013621620704021027 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/functional/__init__.py0000664000175000017500000000000013621620627023132 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/functional/base.py0000664000175000017500000000160113621620627022315 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from os_vif.tests.functional import base as os_vif_base wait_until_true = os_vif_base.wait_until_true class VifPlugOvsBaseFunctionalTestCase(os_vif_base.BaseFunctionalTestCase): """Base class for vif_plug_ovs functional tests.""" COMPONENT_NAME = 'vif_plug_ovs' PRIVILEGED_GROUP = 'vif_plug_ovs_privileged' os_vif-2.0.0/vif_plug_ovs/tests/functional/ovsdb/0000775000175000017500000000000013621620704022144 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/functional/ovsdb/__init__.py0000664000175000017500000000000013621620627024247 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py0000664000175000017500000001411213621620627025523 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 random from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import uuidutils from ovsdbapp.schema.open_vswitch import impl_idl import testscenarios from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs import ovs from vif_plug_ovs.ovsdb import ovsdb_lib from vif_plug_ovs import privsep from vif_plug_ovs.tests.functional import base CONF = cfg.CONF @privsep.vif_plug.entrypoint def run_privileged(*full_args): return processutils.execute(*full_args)[0].rstrip() class TestOVSDBLib(testscenarios.WithScenarios, base.VifPlugOvsBaseFunctionalTestCase): scenarios = [ ('native', {'interface': 'native'}), ('vsctl', {'interface': 'vsctl'}) ] def setUp(self): super(TestOVSDBLib, self).setUp() run_privileged('ovs-vsctl', 'set-manager', 'ptcp:6640') # NOTE: (ralonsoh) load default configuration variables "CONFIG_OPTS" ovs.OvsPlugin.load('ovs') self.flags(ovsdb_interface=self.interface, group='os_vif_ovs') self.ovs = ovsdb_lib.BaseOVS(CONF.os_vif_ovs) self._ovsdb = self.ovs.ovsdb self.brname = ('br' + str(random.randint(1000, 9999)) + '-' + self.interface) # Make sure exceptions pass through by calling do_post_commit directly mock.patch.object( impl_idl.OvsVsctlTransaction, 'post_commit', side_effect=impl_idl.OvsVsctlTransaction.do_post_commit).start() def _check_parameter(self, table, port, parameter, expected_value): def check_value(): return (self._ovsdb.db_get( table, port, parameter).execute() == expected_value) self.assertTrue(base.wait_until_true(check_value, timeout=2, sleep=0.5)) def _add_port(self, bridge, port, may_exist=True): with self._ovsdb.transaction() as txn: txn.add(self._ovsdb.add_port(bridge, port, may_exist=may_exist)) txn.add(self._ovsdb.db_set('Interface', port, ('type', 'internal'))) self.assertIn(port, self._list_ports_in_bridge(bridge)) def _list_ports_in_bridge(self, bridge): return self._ovsdb.list_ports(bridge).execute() def _check_bridge(self, name): return self._ovsdb.br_exists(name).execute() def _add_bridge(self, name, may_exist=True, datapath_type=None): self._ovsdb.add_br(name, may_exist=may_exist, datapath_type=datapath_type).execute() self.assertTrue(self._check_bridge(name)) def _del_bridge(self, name): self._ovsdb.del_br(name).execute() def test__set_mtu_request(self): port_name = 'port1-' + self.interface self._add_bridge(self.brname) self.addCleanup(self._del_bridge, self.brname) self._add_port(self.brname, port_name) if self.ovs._ovs_supports_mtu_requests(): self.ovs._set_mtu_request(port_name, 1000) self._check_parameter('Interface', port_name, 'mtu', 1000) self.ovs._set_mtu_request(port_name, 1500) self._check_parameter('Interface', port_name, 'mtu', 1500) else: self.skipTest('Current version of Open vSwitch does not support ' '"mtu_request" parameter') def test_create_ovs_vif_port(self): port_name = 'port2-' + self.interface iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = constants.OVS_VHOSTUSER_INTERFACE_TYPE vhost_server_path = '/fake/path' mtu = 1500 self._add_bridge(self.brname) self.addCleanup(self._del_bridge, self.brname) self.ovs.create_ovs_vif_port(self.brname, port_name, iface_id, mac, instance_id, mtu=mtu, interface_type=interface_type, vhost_server_path=vhost_server_path, tag=2000) expected_external_ids = {'iface-status': 'active', 'iface-id': iface_id, 'attached-mac': mac, 'vm-uuid': instance_id} self._check_parameter('Interface', port_name, 'external_ids', expected_external_ids) self._check_parameter('Interface', port_name, 'type', interface_type) expected_vhost_server_path = {'vhost-server-path': vhost_server_path} self._check_parameter('Interface', port_name, 'options', expected_vhost_server_path) self._check_parameter('Interface', port_name, 'options', expected_vhost_server_path) self._check_parameter('Port', port_name, 'tag', 2000) @mock.patch.object(linux_net, 'delete_net_dev') def test_delete_ovs_vif_port(self, *mock): port_name = 'port3-' + self.interface self._add_bridge(self.brname) self.addCleanup(self._del_bridge, self.brname) self._add_port(self.brname, port_name) self.ovs.delete_ovs_vif_port(self.brname, port_name) self.assertNotIn(port_name, self._list_ports_in_bridge(self.brname)) def test_ensure_ovs_bridge(self): bridge_name = 'bridge2-' + self.interface self.ovs.ensure_ovs_bridge(bridge_name, constants.OVS_DATAPATH_SYSTEM) self.assertTrue(self._check_bridge(bridge_name)) self.addCleanup(self._del_bridge, bridge_name) os_vif-2.0.0/vif_plug_ovs/tests/unit/0000775000175000017500000000000013621620704017644 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/unit/test_plugin.py0000664000175000017500000006146013621620627022566 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.internal.ip.api import ip as ip_lib from os_vif import objects from os_vif.objects import fields from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs import ovs from vif_plug_ovs.ovsdb import ovsdb_lib class PluginTest(testtools.TestCase): def __init__(self, *args, **kwargs): super(PluginTest, self).__init__(*args, **kwargs) objects.register_all() self.subnet_bridge_4 = objects.subnet.Subnet( cidr='101.168.1.0/24', dns=['8.8.8.8'], gateway='101.168.1.1', dhcp_server='191.168.1.1') self.subnet_bridge_6 = objects.subnet.Subnet( cidr='101:1db9::/64', gateway='101:1db9::1') self.subnets = objects.subnet.SubnetList( objects=[self.subnet_bridge_4, self.subnet_bridge_6]) self.network_ovs = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br0', subnets=self.subnets, vlan=99) self.network_ovs_mtu = objects.network.Network( id='437c6db5-4e6f-4b43-b64b-ed6a11ee5ba7', bridge='br0', subnets=self.subnets, vlan=99, mtu=1234) self.profile_ovs = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='netdev') self.profile_ovs_system = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='system') self.profile_ovs_smart_nic = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', create_port=True) self.profile_ovs_no_datatype = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', datapath_type='') self.vif_ovs_hybrid = objects.vif.VIFBridge( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', bridge_name="qbrvif-xxx-yyy", port_profile=self.profile_ovs_no_datatype) self.vif_ovs = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='tap-xxx-yyy-zzz', port_profile=self.profile_ovs) self.vif_ovs_smart_nic = objects.vif.VIFOpenVSwitch( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, vif_name='rep0-0', port_profile=self.profile_ovs_smart_nic) self.vif_vhostuser = objects.vif.VIFVHostUser( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, path='/var/run/openvswitch/vhub679325f-ca', mode='client', port_profile=self.profile_ovs) self.vif_vhostuser_client = objects.vif.VIFVHostUser( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, path='/var/run/openvswitch/vhub679325f-ca', mode='server', # qemu server mode <=> ovs client mode port_profile=self.profile_ovs) self.vif_ovs_vf_passthrough = objects.vif.VIFHostDevice( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, dev_type=fields.VIFHostDeviceDevType.ETHERNET, dev_address='0002:24:12.3', bridge_name='br-int', port_profile=self.profile_ovs_system) self.vif_ovs_vf_dpdk = objects.vif.VIFHostDevice( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', network=self.network_ovs, dev_type=fields.VIFHostDeviceDevType.ETHERNET, dev_address='0002:24:12.3', port_profile=self.profile_ovs) self.instance = objects.instance_info.InstanceInfo( name='demo', uuid='f0000000-0000-0000-0000-000000000001') def test__get_vif_datapath_type(self): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) dp_type = plugin._get_vif_datapath_type( self.vif_ovs, datapath=constants.OVS_DATAPATH_SYSTEM) self.assertEqual(self.profile_ovs.datapath_type, dp_type) dp_type = plugin._get_vif_datapath_type( self.vif_ovs_hybrid, datapath=constants.OVS_DATAPATH_SYSTEM) self.assertEqual(constants.OVS_DATAPATH_SYSTEM, dp_type) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_create_vif_port(self, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, plugin.config.network_device_mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_create_vif_port_mtu_in_model(self, mock_create_ovs_vif_port): self.vif_ovs.network = self.network_ovs_mtu plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, self.network_ovs_mtu.mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_create_vif_port_isolate(self, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) with mock.patch.object(plugin.config, 'isolate_vif', True): plugin._create_vif_port( self.vif_ovs, mock.sentinel.vif_name, self.instance, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_create_ovs_vif_port.assert_called_once_with( self.vif_ovs.network.bridge, mock.sentinel.vif_name, self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, plugin.config.network_device_mtu, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE, tag=constants.DEAD_VLAN) @mock.patch.object(ovs, 'sys') @mock.patch.object(ovs.OvsPlugin, '_plug_vif_generic') def test_plug_ovs(self, plug_vif_generic, mock_sys): mock_sys.platform = 'linux' plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs, self.instance) plug_vif_generic.assert_called_once_with(self.vif_ovs, self.instance) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, "_create_vif_port") def test_plug_vif_generic(self, create_port, ensure_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._plug_vif_generic(self.vif_ovs, self.instance) ensure_bridge.assert_called_once() # NOTE(sean-k-mooney): the interface will be plugged # by libvirt so we assert _create_vif_port is not called. create_port.assert_not_called() @mock.patch.object(linux_net, 'set_interface_state') @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_update_vif_port') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') @mock.patch.object(linux_net, 'add_bridge_port') @mock.patch.object(linux_net, 'update_veth_pair') @mock.patch.object(linux_net, 'create_veth_pair') @mock.patch.object(ip_lib, 'exists') @mock.patch.object(linux_net, 'ensure_bridge') @mock.patch.object(ovs, 'sys') def test_plug_ovs_bridge(self, mock_sys, ensure_bridge, device_exists, create_veth_pair, update_veth_pair, add_bridge_port, _create_vif_port, _update_vif_port, ensure_ovs_bridge, set_interface_state): dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_ovs_hybrid) calls = { 'device_exists': [mock.call('qvob679325f-ca')], 'create_veth_pair': [mock.call('qvbb679325f-ca', 'qvob679325f-ca', 1500)], 'update_veth_pair': [mock.call('qvbb679325f-ca', 'qvob679325f-ca', 1500)], 'ensure_bridge': [mock.call('qbrvif-xxx-yyy')], 'set_interface_state': [mock.call('qbrvif-xxx-yyy', 'up')], 'add_bridge_port': [mock.call('qbrvif-xxx-yyy', 'qvbb679325f-ca')], '_update_vif_port': [mock.call(self.vif_ovs_hybrid, 'qvob679325f-ca')], '_create_vif_port': [mock.call(self.vif_ovs_hybrid, 'qvob679325f-ca', self.instance)], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } # plugging new devices should result in devices being created device_exists.return_value = False mock_sys.platform = 'linux' plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs_hybrid, self.instance) ensure_bridge.assert_has_calls(calls['ensure_bridge']) device_exists.assert_has_calls(calls['device_exists']) create_veth_pair.assert_has_calls(calls['create_veth_pair']) update_veth_pair.assert_not_called() _update_vif_port.assert_not_called() add_bridge_port.assert_has_calls(calls['add_bridge_port']) _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) # reset call stacks create_veth_pair.reset_mock() _create_vif_port.reset_mock() # plugging existing devices should result in devices being updated device_exists.return_value = True plugin.plug(self.vif_ovs_hybrid, self.instance) create_veth_pair.assert_not_called() _create_vif_port.assert_not_called() update_veth_pair.assert_has_calls(calls['update_veth_pair']) _update_vif_port.assert_has_calls(calls['_update_vif_port']) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') @mock.patch.object(ip_lib, 'exists', return_value=False) @mock.patch.object(ovs, 'sys') def _check_plug_ovs_windows(self, vif, mock_sys, mock_exists, _create_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type(vif) calls = { 'exists': [mock.call(vif.id)], '_create_vif_port': [mock.call(vif, vif.id, self.instance)], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } mock_sys.platform = constants.PLATFORM_WIN32 plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(vif, self.instance) mock_exists.assert_has_calls(calls['exists']) _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) def test_plug_ovs_windows(self): self._check_plug_ovs_windows(self.vif_ovs) def test_plug_ovs_bridge_windows(self): self._check_plug_ovs_windows(self.vif_ovs_hybrid) @mock.patch.object(ovs, 'sys') @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_ovs(self, unplug, mock_sys): mock_sys.platform = 'linux' plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs, self.instance) unplug.assert_called_once_with(self.vif_ovs, self.instance) @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_vif_generic(self, delete_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._unplug_vif_generic(self.vif_ovs, self.instance) delete_port.assert_called_once() @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(linux_net, 'delete_bridge') @mock.patch.object(ovs, 'sys') def test_unplug_ovs_bridge(self, mock_sys, delete_bridge, delete_ovs_vif_port): calls = { 'delete_bridge': [mock.call('qbrvif-xxx-yyy', 'qvbb679325f-ca')], 'delete_ovs_vif_port': [mock.call('br0', 'qvob679325f-ca')] } mock_sys.platform = 'linux' plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs_hybrid, self.instance) delete_bridge.assert_has_calls(calls['delete_bridge']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(ovs, 'sys') def _check_unplug_ovs_windows(self, vif, mock_sys, delete_ovs_vif_port): mock_sys.platform = constants.PLATFORM_WIN32 plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(vif, self.instance) delete_ovs_vif_port.assert_called_once_with('br0', vif.id, delete_netdev=False) def test_unplug_ovs_windows(self): self._check_unplug_ovs_windows(self.vif_ovs) def test_unplug_ovs_bridge_windows(self): self._check_unplug_ovs_windows(self.vif_ovs_hybrid) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vhostuser(self, _create_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_vhostuser) calls = { '_create_vif_port': [mock.call( self.vif_vhostuser, 'vhub679325f-ca', self.instance, interface_type='dpdkvhostuser')], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_vhostuser, self.instance) _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_plug_ovs_vhostuser_client(self, create_ovs_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type( self.vif_vhostuser_client) calls = { 'create_ovs_vif_port': [ mock.call( 'br0', 'vhub679325f-ca', 'e65867e0-9340-4a7f-a256-09af6eb7a3aa', 'ca:fe:de:ad:be:ef', 'f0000000-0000-0000-0000-000000000001', 1500, interface_type='dpdkvhostuserclient', vhost_server_path='/var/run/openvswitch/vhub679325f-ca' )], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_vhostuser_client, self.instance) create_ovs_vif_port.assert_has_calls(calls['create_ovs_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vhostuser(self, delete_ovs_vif_port): calls = { 'delete_ovs_vif_port': [mock.call('br0', 'vhub679325f-ca')] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_vhostuser, self.instance) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(linux_net, 'get_ifname_by_pci_address') @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_representor_port') @mock.patch.object(linux_net, 'set_interface_state') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vf_passthrough(self, _create_vif_port, set_interface_state, get_representor_port, get_vf_num_by_pci_address, get_ifname_by_pci_address, ensure_ovs_bridge): get_ifname_by_pci_address.return_value = 'eth0' get_vf_num_by_pci_address.return_value = '2' get_representor_port.return_value = 'eth0_2' calls = { 'ensure_ovs_bridge': [mock.call('br0', constants.OVS_DATAPATH_SYSTEM)], 'get_ifname_by_pci_address': [mock.call('0002:24:12.3', pf_interface=True, switchdev=True)], 'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')], 'get_representor_port': [mock.call('eth0', '2')], 'set_interface_state': [mock.call('eth0_2', 'up')], '_create_vif_port': [mock.call( self.vif_ovs_vf_passthrough, 'eth0_2', self.instance)] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs_vf_passthrough, self.instance) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) get_ifname_by_pci_address.assert_has_calls( calls['get_ifname_by_pci_address']) get_vf_num_by_pci_address.assert_has_calls( calls['get_vf_num_by_pci_address']) get_representor_port.assert_has_calls( calls['get_representor_port']) set_interface_state.assert_has_calls(calls['set_interface_state']) _create_vif_port.assert_has_calls(calls['_create_vif_port']) @mock.patch.object(linux_net, 'get_ifname_by_pci_address') @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_representor_port') @mock.patch.object(linux_net, 'set_interface_state') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vf_passthrough(self, delete_ovs_vif_port, set_interface_state, get_representor_port, get_vf_num_by_pci_address, get_ifname_by_pci_address): calls = { 'get_ifname_by_pci_address': [mock.call('0002:24:12.3', pf_interface=True, switchdev=True)], 'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')], 'get_representor_port': [mock.call('eth0', '2')], 'set_interface_state': [mock.call('eth0_2', 'down')], 'delete_ovs_vif_port': [mock.call('br0', 'eth0_2', delete_netdev=False)] } get_ifname_by_pci_address.return_value = 'eth0' get_vf_num_by_pci_address.return_value = '2' get_representor_port.return_value = 'eth0_2' plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs_vf_passthrough, self.instance) get_ifname_by_pci_address.assert_has_calls( calls['get_ifname_by_pci_address']) get_vf_num_by_pci_address.assert_has_calls( calls['get_vf_num_by_pci_address']) get_representor_port.assert_has_calls( calls['get_representor_port']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) set_interface_state.assert_has_calls(calls['set_interface_state']) @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, "_create_vif_port") def test_plug_vif_ovs_smart_nic(self, create_port, ensure_bridge): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs_smart_nic, self.instance) ensure_bridge.assert_called_once() create_port.assert_called_once() @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic') def test_unplug_vif_ovs_smart_nic(self, delete_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs_smart_nic, self.instance) delete_port.assert_called_once() @mock.patch.object(linux_net, 'get_dpdk_representor_port_name') @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_pf_pci_from_vf') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vf_dpdk(self, _create_vif_port, get_pf_pci_from_vf, get_vf_num_by_pci_address, ensure_ovs_bridge, get_dpdk_representor_port_name): pf_pci = self.vif_ovs_vf_dpdk.dev_address devname = 'vfrb679325f-ca' get_vf_num_by_pci_address.return_value = '2' get_pf_pci_from_vf.return_value = pf_pci get_dpdk_representor_port_name.return_value = devname calls = { 'ensure_ovs_bridge': [mock.call('br0', constants.OVS_DATAPATH_NETDEV)], 'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')], 'get_pf_pci_from_vf': [mock.call(pf_pci)], 'get_dpdk_representor_port_name': [mock.call( self.vif_ovs_vf_dpdk.id)], '_create_vif_port': [mock.call( self.vif_ovs_vf_dpdk, devname, self.instance, interface_type='dpdk', pf_pci=pf_pci, vf_num='2')]} plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(self.vif_ovs_vf_dpdk, self.instance) ensure_ovs_bridge.assert_has_calls( calls['ensure_ovs_bridge']) get_vf_num_by_pci_address.assert_has_calls( calls['get_vf_num_by_pci_address']) get_pf_pci_from_vf.assert_has_calls( calls['get_pf_pci_from_vf']) get_dpdk_representor_port_name.assert_has_calls( calls['get_dpdk_representor_port_name']) _create_vif_port.assert_has_calls( calls['_create_vif_port']) @mock.patch.object(linux_net, 'get_dpdk_representor_port_name') @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vf_dpdk(self, delete_ovs_vif_port, get_dpdk_representor_port_name): devname = 'vfrb679325f-ca' get_dpdk_representor_port_name.return_value = devname calls = { 'get_dpdk_representor_port_name': [mock.call( self.vif_ovs_vf_dpdk.id)], 'delete_ovs_vif_port': [mock.call('br0', devname, delete_netdev=False)]} plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_ovs_vf_dpdk, self.instance) get_dpdk_representor_port_name.assert_has_calls( calls['get_dpdk_representor_port_name']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) os_vif-2.0.0/vif_plug_ovs/tests/unit/__init__.py0000664000175000017500000000000013621620627021747 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/unit/test_linux_net.py0000664000175000017500000004327213621620627023276 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.ip.api import ip as ip_lib from six.moves import builtins from vif_plug_ovs import exception from vif_plug_ovs import linux_net from vif_plug_ovs import privsep class LinuxNetTest(testtools.TestCase): def setUp(self): super(LinuxNetTest, self).setUp() privsep.vif_plug.set_client_mode(False) @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "exists", return_value=False) def test_ensure_bridge(self, mock_dev_exists, mock_add, mock_disable_ipv6, mock_set_state, mock_arp_filtering): linux_net.ensure_bridge("br0") mock_dev_exists.assert_called_once_with("br0") mock_add.assert_called_once_with("br0", "bridge", ageing=0) mock_disable_ipv6.assert_called_once_with("br0") mock_set_state.assert_called_once_with("br0", "up") mock_arp_filtering.assert_called_once_with("br0") @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "exists", return_value=True) def test_ensure_bridge_exists(self, mock_dev_exists, mock_add, mock_disable_ipv6, mock_set_state, mock_arp_filtering): linux_net.ensure_bridge("br0") mock_dev_exists.assert_called_once_with("br0") mock_add.assert_not_called() mock_disable_ipv6.assert_called_once_with("br0") mock_set_state.assert_called_once_with("br0", "up") mock_arp_filtering.assert_called_once_with("br0") @mock.patch('six.moves.builtins.open') @mock.patch("os.path.exists") def test__disable_ipv6(self, mock_exists, mock_open): exists_path = "/proc/sys/net/ipv6/conf/br0/disable_ipv6" mock_exists.return_value = False linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_not_called() mock_exists.reset_mock() mock_exists.return_value = True linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_called_once_with(exists_path, 'w') @mock.patch.object(os.path, 'exists', return_value=True) @mock.patch.object(builtins, 'open') def test__arp_filtering(self, mock_open, *args): mock_open.side_effect = mock.mock_open() linux_net._arp_filtering("br0") mock_open.assert_has_calls([ mock.call('/proc/sys/net/ipv4/conf/br0/arp_ignore', 'w'), mock.call('/proc/sys/net/ipv4/conf/br0/arp_announce', 'w')]) mock_open.side_effect.return_value.write.assert_has_calls([ mock.call('1'), mock.call('2')]) @mock.patch.object(ip_lib, "delete") @mock.patch.object(ip_lib, "exists", return_value=False) def test_delete_bridge_none(self, mock_dev_exists, mock_delete): linux_net.delete_bridge("br0", "vnet1") mock_delete.assert_not_called() mock_dev_exists.assert_called_once_with("br0") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(ip_lib, "delete") @mock.patch.object(ip_lib, "exists", return_value=True) def test_delete_bridge_exists(self, mock_dev_exists, mock_delete, mock_set_state): linux_net.delete_bridge("br0", "vnet1") mock_dev_exists.assert_has_calls([mock.call("br0"), mock.call("vnet1")]) mock_delete.assert_called_once_with("br0", check_exit_code=[0, 2, 254]) mock_set_state.assert_called_once_with("vnet1", "down") @mock.patch.object(linux_net, "set_interface_state") @mock.patch.object(ip_lib, "delete") @mock.patch.object(ip_lib, "exists") def test_delete_interface_not_present(self, mock_dev_exists, mock_delete, mock_set_state): mock_dev_exists.return_value = next(lambda: (yield True), (yield False)) linux_net.delete_bridge("br0", "vnet1") mock_dev_exists.assert_has_calls([mock.call("br0"), mock.call("vnet1")]) mock_delete.assert_called_once_with("br0", check_exit_code=[0, 2, 254]) mock_set_state.assert_not_called() @mock.patch.object(ip_lib, "set") def test_add_bridge_port(self, mock_set): linux_net.add_bridge_port("br0", "vnet1") mock_set.assert_called_once_with("vnet1", master="br0") @mock.patch('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') @mock.patch.object(linux_net, "get_function_by_ifname") def test_get_representor_port(self, mock_get_function_by_ifname, 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']) # PCI IDs mocked: # PF0: 0000:0a:00.0 # PF0VF1: 0000:0a:02.1 PF0VF2: 0000:0a:02.2 mock_get_function_by_ifname.side_effect = ( [("0000:0a:00.0", True), ("0000:0a:02.1", False), ("0000:0a:02.2", False), ("0000:0a:00.0", True)]) 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') @mock.patch.object(linux_net, "get_function_by_ifname") def test_get_representor_port_2_pfs( self, mock_get_function_by_ifname, 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 = ( ['pf_sw_id', 'pf_sw_id', 'VF1@PF1', 'pf_sw_id', 'vf2@pf1', 'pf_sw_id', 'pf2vf1', 'pf_sw_id', 'pf2vf2']) # PCI IDs mocked: # PF1: 0000:0a:00.1 PF2: 0000:0a:00.2 # PF1VF1: 0000:0a:02.1 PF1VF2: 0000:0a:02.2 # PF2VF1: 0000:0a:04.1 PF2VF2: 0000:0a:04.2 mock_get_function_by_ifname.side_effect = ( [("0000:0a:00.1", True), ("0000:0a:00.2", True), ("0000:0a:02.1", False), ("0000:0a:00.2", True), ("0000:0a:02.2", False), ("0000:0a:00.2", True), ("0000:0a:04.1", False), ("0000:0a:00.2", True), ("0000:0a:04.2", False), ("0000:0a:00.2", True)]) ifname = linux_net.get_representor_port('pf_ifname2', '2') self.assertEqual('rep_pf2_vf_2', ifname) @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') @mock.patch.object(os, 'listdir') @mock.patch.object(linux_net, "get_function_by_ifname") def test_get_representor_port_not_found( self, mock_get_function_by_ifname, 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']) # PCI IDs mocked: # PF0: 0000:0a:00.0 # PF0VF1: 0000:0a:02.1 PF0VF2: 0000:0a:02.2 mock_get_function_by_ifname.side_effect = ( [("0000:0a:00.0", True), ("0000:0a:02.1", False), ("0000:0a:02.2", False)]) 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') @mock.patch.object(linux_net, "get_function_by_ifname") def test_get_representor_port_exception_io_error( self, mock_get_function_by_ifname, 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']) # PCI IDs mocked: # PF0: 0000:0a:00.0 # PF0VF1: 0000:0a:02.1 PF0VF2: 0000:0a:02.2 mock_get_function_by_ifname.side_effect = ( [("0000:0a:00.0", True), ("0000:0a:02.1", False), ("0000:0a:02.2", False), ("0000:0a:00.0", True)]) 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') @mock.patch.object(linux_net, "get_function_by_ifname") def test_get_representor_port_exception_value_error( self, mock_get_function_by_ifname, 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']) # PCI IDs mocked: # PF0: 0000:0a:00.0 # PF0VF1: 0000:0a:02.1 PF0VF2: 0000:0a:02.2 mock_get_function_by_ifname.side_effect = ( [("0000:0a:00.0", True), ("0000:0a:02.1", False), ("0000:0a:02.2", False)]) 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-2.0.0/vif_plug_ovs/tests/unit/ovsdb/0000775000175000017500000000000013621620704020761 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/unit/ovsdb/__init__.py0000664000175000017500000000000013621620627023064 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py0000664000175000017500000002335613621620627024352 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import uuidutils from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs.ovsdb import ovsdb_lib CONF = cfg.CONF class BaseOVSTest(testtools.TestCase): def setUp(self): super(BaseOVSTest, self).setUp() test_vif_plug_ovs_group = cfg.OptGroup('test_vif_plug_ovs') CONF.register_group(test_vif_plug_ovs_group) CONF.register_opt(cfg.IntOpt('ovs_vsctl_timeout', default=1500), test_vif_plug_ovs_group) CONF.register_opt(cfg.StrOpt('ovsdb_interface', default='vsctl'), test_vif_plug_ovs_group) CONF.register_opt(cfg.StrOpt('ovsdb_connection', default=None), test_vif_plug_ovs_group) self.br = ovsdb_lib.BaseOVS(cfg.CONF.test_vif_plug_ovs) self.mock_db_set = mock.patch.object(self.br.ovsdb, 'db_set').start() self.mock_del_port = mock.patch.object(self.br.ovsdb, 'del_port').start() self.mock_add_port = mock.patch.object(self.br.ovsdb, 'add_port').start() self.mock_add_br = mock.patch.object(self.br.ovsdb, 'add_br').start() self.mock_transaction = mock.patch.object(self.br.ovsdb, 'transaction').start() def test__set_mtu_request(self): self.br._set_mtu_request('device', 1500) calls = [mock.call('Interface', 'device', ('mtu_request', 1500))] self.mock_db_set.assert_has_calls(calls) @mock.patch('sys.platform', constants.PLATFORM_LINUX) @mock.patch.object(linux_net, 'set_device_mtu') def test__update_device_mtu_interface_not_vhostuser_linux(self, mock_set_device_mtu): self.br.update_device_mtu('device', 1500, 'not_vhost') mock_set_device_mtu.assert_has_calls([mock.call('device', 1500)]) @mock.patch('sys.platform', constants.PLATFORM_WIN32) @mock.patch.object(linux_net, 'set_device_mtu') def test__update_device_mtu_interface_not_vhostuser_windows(self, mock_set_device_mtu): self.br.update_device_mtu('device', 1500, 'not_vhost') mock_set_device_mtu.assert_not_called() def test__update_device_mtu_interface_vhostuser_supports_mtu_req(self): with mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=True), \ mock.patch.object(self.br, '_set_mtu_request') as \ mock_set_mtu_request: self.br.update_device_mtu('device', 1500, constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_set_mtu_request.assert_has_calls([mock.call('device', 1500)]) def test__update_device_mtu_interface_vhostuser_not_supports_mtu_req(self): with mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=False), \ mock.patch.object(self.br, '_set_mtu_request') as \ mock_set_mtu_request: self.br.update_device_mtu('device', 1500, constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_set_mtu_request.assert_not_called() def test_create_ovs_vif_port(self): iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = constants.OVS_VHOSTUSER_INTERFACE_TYPE vhost_server_path = '/fake/path' device = 'device' bridge = 'bridge' mtu = 1500 external_ids = {'iface-id': iface_id, 'iface-status': 'active', 'attached-mac': mac, 'vm-uuid': instance_id} values = [('external_ids', external_ids), ('type', interface_type), ('options', {'vhost-server-path': vhost_server_path}) ] with mock.patch.object(self.br, 'update_device_mtu', return_value=True) as mock_update_device_mtu, \ mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=True): self.br.create_ovs_vif_port(bridge, device, iface_id, mac, instance_id, mtu=mtu, interface_type=interface_type, vhost_server_path=vhost_server_path, tag=4000) self.mock_add_port.assert_has_calls([mock.call(bridge, device)]) self.mock_db_set.assert_has_calls( [mock.call('Port', device, ('tag', 4000)), mock.call('Interface', device, *values)]) mock_update_device_mtu.assert_has_calls( [mock.call(device, mtu, interface_type=interface_type)]) def test_create_ovs_vif_port_type_dpdk(self): iface_id = 'iface_id' mac = 'ca:fe:ca:fe:ca:fe' instance_id = uuidutils.generate_uuid() interface_type = constants.OVS_DPDK_INTERFACE_TYPE device = 'device' bridge = 'bridge' mtu = 1500 pf_pci = '0000:02:00.1' vf_num = '0' external_ids = {'iface-id': iface_id, 'iface-status': 'active', 'attached-mac': mac, 'vm-uuid': instance_id} values = [('external_ids', external_ids), ('type', interface_type), ('options', {'dpdk-devargs': '0000:02:00.1,representor=[0]'})] with mock.patch.object(self.br, 'update_device_mtu', return_value=True) as mock_update_device_mtu, \ mock.patch.object(self.br, '_ovs_supports_mtu_requests', return_value=True): self.br.create_ovs_vif_port(bridge, device, iface_id, mac, instance_id, mtu=mtu, interface_type=interface_type, pf_pci=pf_pci, vf_num=vf_num) self.mock_add_port.assert_has_calls([mock.call(bridge, device)]) self.mock_db_set.assert_has_calls( [mock.call('Interface', device, *values)]) mock_update_device_mtu.assert_has_calls( [mock.call(device, mtu, interface_type=interface_type)]) def test_update_ovs_vif_port(self): with mock.patch.object(self.br, 'update_device_mtu') as \ mock_update_device_mtu: self.br.update_ovs_vif_port('device', mtu=1500, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) mock_update_device_mtu.assert_has_calls( [mock.call( 'device', 1500, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE)]) @mock.patch.object(linux_net, 'delete_net_dev') def test_delete_ovs_vif_port(self, mock_delete_net_dev): self.br.delete_ovs_vif_port('bridge', 'device') self.mock_del_port.assert_has_calls( [mock.call('device', bridge='bridge', if_exists=True)]) mock_delete_net_dev.assert_has_calls([mock.call('device')]) @mock.patch.object(linux_net, 'delete_net_dev') def test_delete_ovs_vif_port_no_delete_netdev(self, mock_delete_net_dev): self.br.delete_ovs_vif_port('bridge', 'device', delete_netdev=False) self.mock_del_port.assert_has_calls( [mock.call('device', bridge='bridge', if_exists=True)]) mock_delete_net_dev.assert_not_called() def test_ensure_ovs_bridge(self): self.br.ensure_ovs_bridge('bridge', constants.OVS_DATAPATH_SYSTEM) self.mock_add_br('bridge', may_exist=True, datapath_type=constants.OVS_DATAPATH_SYSTEM) def test__ovs_supports_mtu_requests(self): with mock.patch.object(self.br.ovsdb, 'db_list') as mock_db_list: self.assertTrue(self.br._ovs_supports_mtu_requests()) mock_db_list.assert_called_once_with('Interface', columns=['mtu_request']) def test__ovs_supports_mtu_requests_not_supported(self): with mock.patch.object(self.br.ovsdb, 'db_list') as mock_db_list: mock_db_list.side_effect = processutils.ProcessExecutionError( stderr='ovs-vsctl: Interface does not contain a column whose ' 'name matches "mtu_request"') self.assertFalse(self.br._ovs_supports_mtu_requests()) mock_db_list.assert_called_once_with('Interface', columns=['mtu_request']) def test__ovs_supports_mtu_requests_other_error(self): with mock.patch.object(self.br.ovsdb, 'db_list') as mock_db_list: mock_db_list.side_effect = processutils.ProcessExecutionError( stderr='other error') self.assertRaises(processutils.ProcessExecutionError, self.br._ovs_supports_mtu_requests) mock_db_list.assert_called_once_with('Interface', columns=['mtu_request']) os_vif-2.0.0/vif_plug_ovs/ovsdb/0000775000175000017500000000000013621620704016640 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/ovsdb/impl_idl.py0000664000175000017500000000330413621620627021007 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 ovs.db import idl from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import vlog from ovsdbapp.schema.open_vswitch import impl_idl from vif_plug_ovs.ovsdb import api def idl_factory(config): conn = config.connection schema_name = 'Open_vSwitch' helper = idlutils.get_schema_helper(conn, schema_name) helper.register_all() return idl.Idl(conn, helper) def api_factory(config): conn = connection.Connection( idl=idl_factory(config), timeout=config.timeout) return NeutronOvsdbIdl(conn) class NeutronOvsdbIdl(impl_idl.OvsdbIdl, api.ImplAPI): """IDL interface for OVS database back-end This class provides an OVSDB IDL (Open vSwitch Database Interface Definition Language) interface to the OVS back-end. """ def __init__(self, conn): vlog.use_python_logger() super(NeutronOvsdbIdl, self).__init__(conn) def _get_table_columns(self, table): return list(self.tables[table].columns) def has_table_column(self, table, column): return column in self._get_table_columns(table) os_vif-2.0.0/vif_plug_ovs/ovsdb/ovsdb_lib.py0000664000175000017500000001163113621620627021163 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys from oslo_log import log as logging from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs.ovsdb import api as ovsdb_api LOG = logging.getLogger(__name__) class BaseOVS(object): def __init__(self, config): self.timeout = config.ovs_vsctl_timeout self.connection = config.ovsdb_connection self.interface = config.ovsdb_interface self.ovsdb = ovsdb_api.get_instance(self) def _ovs_supports_mtu_requests(self): return self.ovsdb.has_table_column('Interface', 'mtu_request') def _set_mtu_request(self, dev, mtu): self.ovsdb.db_set('Interface', dev, ('mtu_request', mtu)).execute() def update_device_mtu(self, dev, mtu, interface_type=None): if not mtu: return if interface_type not in [ constants.OVS_VHOSTUSER_INTERFACE_TYPE, constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE]: if sys.platform != constants.PLATFORM_WIN32: # Hyper-V with OVS does not support external programming of # virtual interface MTUs via netsh or other Windows tools. # When plugging an interface on Windows, we therefore skip # programming the MTU and fallback to DHCP advertisement. linux_net.set_device_mtu(dev, mtu) elif self._ovs_supports_mtu_requests(): self._set_mtu_request(dev, mtu) else: LOG.debug("MTU not set on %(interface_name)s interface " "of type %(interface_type)s.", {'interface_name': dev, 'interface_type': interface_type}) def ensure_ovs_bridge(self, bridge, datapath_type): return self.ovsdb.add_br(bridge, may_exist=True, datapath_type=datapath_type).execute() def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id, mtu=None, interface_type=None, vhost_server_path=None, tag=None, pf_pci=None, vf_num=None): """Create OVS port :param bridge: bridge name to create the port on. :param dev: port name. :param iface_id: port ID. :param mac: port MAC. :param instance_id: VM ID on which the port is attached to. :param mtu: port MTU. :param interface_type: OVS interface type. :param vhost_server_path: path to socket file of vhost server. :param tag: OVS interface tag. :param pf_pci: PCI address of PF for dpdk representor port. :param vf_num: VF number of PF for dpdk representor port. .. note:: create DPDK representor port by setting all three values: `interface_type`, `pf_pci` and `vf_num`. if interface type is not `OVS_DPDK_INTERFACE_TYPE` then `pf_pci` and `vf_num` values are ignored. """ external_ids = {'iface-id': iface_id, 'iface-status': 'active', 'attached-mac': mac, 'vm-uuid': instance_id} col_values = [('external_ids', external_ids)] if interface_type: col_values.append(('type', interface_type)) if vhost_server_path: col_values.append(('options', {'vhost-server-path': vhost_server_path})) if (interface_type == constants.OVS_DPDK_INTERFACE_TYPE and pf_pci and vf_num): devargs_string = "{PF_PCI},representor=[{VF_NUM}]".format( PF_PCI=pf_pci, VF_NUM=vf_num) col_values.append(('options', {'dpdk-devargs': devargs_string})) with self.ovsdb.transaction() as txn: txn.add(self.ovsdb.add_port(bridge, dev)) if tag: txn.add(self.ovsdb.db_set('Port', dev, ('tag', tag))) txn.add(self.ovsdb.db_set('Interface', dev, *col_values)) self.update_device_mtu(dev, mtu, interface_type=interface_type) def update_ovs_vif_port(self, dev, mtu=None, interface_type=None): self.update_device_mtu(dev, mtu, interface_type=interface_type) def delete_ovs_vif_port(self, bridge, dev, delete_netdev=True): self.ovsdb.del_port(dev, bridge=bridge, if_exists=True).execute() if delete_netdev: linux_net.delete_net_dev(dev) os_vif-2.0.0/vif_plug_ovs/ovsdb/__init__.py0000664000175000017500000000000013621620627020743 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_ovs/ovsdb/api.py0000664000175000017500000000245113621620627017771 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc from oslo_utils import importutils import six interface_map = { 'vsctl': 'vif_plug_ovs.ovsdb.impl_vsctl', 'native': 'vif_plug_ovs.ovsdb.impl_idl', } def get_instance(context, iface_name=None): """Return the configured OVSDB API implementation""" iface = importutils.import_module( interface_map[iface_name or context.interface]) return iface.api_factory(context) @six.add_metaclass(abc.ABCMeta) class ImplAPI(object): @abc.abstractmethod def has_table_column(self, table, column): """Check if a column exists in a database table :param table: (string) table name :param column: (string) column name :return: True if the column exists, False if not. """ os_vif-2.0.0/vif_plug_ovs/ovsdb/impl_vsctl.py0000664000175000017500000003367613621620627021411 0ustar zuulzuul00000000000000# Derived from neutron/agent/ovsdb/impl_vsctl.py # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import itertools import uuid from oslo_concurrency import processutils from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils from oslo_utils import uuidutils from ovsdbapp import api as ovsdb_api import six from vif_plug_ovs.ovsdb import api from vif_plug_ovs import privsep LOG = logging.getLogger(__name__) def _val_to_py(val): """Convert a json ovsdb return value to native python object""" if isinstance(val, collections.Sequence) and len(val) == 2: if val[0] == "uuid": return uuid.UUID(val[1]) elif val[0] == "set": return [_val_to_py(x) for x in val[1]] elif val[0] == "map": return {_val_to_py(x): _val_to_py(y) for x, y in val[1]} return val def _py_to_val(pyval): """Convert python value to ovs-vsctl value argument""" if isinstance(pyval, bool): return 'true' if pyval is True else 'false' elif pyval == '': return '""' else: # NOTE(twilson) If a Command object, return its record_id as a value return getattr(pyval, "record_id", pyval) def api_factory(context): return OvsdbVsctl(context) @privsep.vif_plug.entrypoint def _run_vsctl(full_args): # NOTE(ralonsoh): this function is defined outside the class Transaction # to allow oslo_privsep.PrivContext.entrypoint to wrap # the function correctly. return processutils.execute(*full_args)[0].rstrip() class Transaction(ovsdb_api.Transaction): def __init__(self, context, check_error=False, log_errors=True, opts=None): self.context = context self.check_error = check_error self.log_errors = log_errors self.opts = ['--timeout=%d' % self.context.timeout, '--oneline', '--format=json'] if self.context.connection: self.opts += ['--db=%s' % self.context.connection] if opts: self.opts += opts self.commands = [] def add(self, command): self.commands.append(command) return command def commit(self): args = [] for cmd in self.commands: cmd.result = None args += cmd.vsctl_args() res = self.run_vsctl(args) if res is None: return res = res.replace(r'\\', '\\').splitlines() for i, record in enumerate(res): self.commands[i].result = record return [cmd.result for cmd in self.commands] def run_vsctl(self, args): full_args = ["ovs-vsctl"] + self.opts + args try: # We log our own errors, so never have utils.execute do it return _run_vsctl(full_args) except Exception as e: with excutils.save_and_reraise_exception() as ctxt: if self.log_errors: LOG.error("Unable to execute %(cmd)s. Exception: " "%(exception)s", {'cmd': full_args, 'exception': e}) if not self.check_error: ctxt.reraise = False class BaseCommand(ovsdb_api.Command): def __init__(self, context, cmd, opts=None, args=None): self.context = context self.cmd = cmd self.opts = [] if opts is None else opts self.args = [] if args is None else args def execute(self, check_error=False, log_errors=True): with Transaction(self.context, check_error=check_error, log_errors=log_errors) as txn: txn.add(self) return self.result def vsctl_args(self): return itertools.chain(('--',), self.opts, (self.cmd,), self.args) class MultiLineCommand(BaseCommand): """Command for ovs-vsctl commands that return multiple lines""" @property def result(self): return self._result @result.setter def result(self, raw_result): self._result = raw_result.split(r'\n') if raw_result else [] class DbCommand(BaseCommand): def __init__(self, context, cmd, opts=None, args=None, columns=None): if opts is None: opts = [] if columns: opts += ['--columns=%s' % ",".join(columns)] super(DbCommand, self).__init__(context, cmd, opts, args) @property def result(self): return self._result @result.setter def result(self, raw_result): # If check_error=False, run_vsctl can return None if not raw_result: self._result = None return try: json = jsonutils.loads(raw_result) except (ValueError, TypeError) as e: # This shouldn't happen, but if it does and we check_errors # log and raise. with excutils.save_and_reraise_exception(): LOG.error("Could not parse: %(raw_result)s. Exception: " "%(exception)s", {'raw_result': raw_result, 'exception': e}) headings = json['headings'] data = json['data'] results = [] for record in data: obj = {} for pos, heading in enumerate(headings): obj[heading] = _val_to_py(record[pos]) results.append(obj) self._result = results class DbGetCommand(DbCommand): @DbCommand.result.setter def result(self, val): # super()'s never worked for setters http://bugs.python.org/issue14965 DbCommand.result.fset(self, val) # DbCommand will return [{'column': value}] and we just want value. if self._result: self._result = list(self._result[0].values())[0] class DbCreateCommand(BaseCommand): def __init__(self, context, opts=None, args=None): super(DbCreateCommand, self).__init__(context, "create", opts, args) # NOTE(twilson) pre-commit result used for intra-transaction reference self.record_id = "@%s" % uuidutils.generate_uuid() self.opts.append("--id=%s" % self.record_id) @property def result(self): return self._result @result.setter def result(self, val): self._result = uuid.UUID(val) if val else val class BrExistsCommand(DbCommand): @DbCommand.result.setter def result(self, val): self._result = val is not None def execute(self): return super(BrExistsCommand, self).execute(check_error=False, log_errors=False) class OvsdbVsctl(ovsdb_api.API, api.ImplAPI): def __init__(self, context): super(OvsdbVsctl, self).__init__() self.context = context def create_transaction(self, check_error=False, log_errors=True, **kwargs): return Transaction(self.context, check_error, log_errors, **kwargs) def add_manager(self, connection_uri): # This will add a new manager without overriding existing ones. conn_uri = 'target="%s"' % connection_uri args = ['create', 'Manager', conn_uri, '--', 'add', 'Open_vSwitch', '.', 'manager_options', '@manager'] return BaseCommand(self.context, '--id=@manager', args=args) def get_manager(self): return MultiLineCommand(self.context, 'get-manager') def remove_manager(self, connection_uri): args = ['get', 'Manager', connection_uri, '--', 'remove', 'Open_vSwitch', '.', 'manager_options', '@manager'] return BaseCommand(self.context, '--id=@manager', args=args) def add_br(self, name, may_exist=True, datapath_type=None): opts = ['--may-exist'] if may_exist else None params = [name] if datapath_type: params += ['--', 'set', 'Bridge', name, 'datapath_type=%s' % datapath_type] return BaseCommand(self.context, 'add-br', opts, params) def del_br(self, name, if_exists=True): opts = ['--if-exists'] if if_exists else None return BaseCommand(self.context, 'del-br', opts, [name]) def br_exists(self, name): return BrExistsCommand(self.context, 'list', args=['Bridge', name]) def port_to_br(self, name): return BaseCommand(self.context, 'port-to-br', args=[name]) def iface_to_br(self, name): return BaseCommand(self.context, 'iface-to-br', args=[name]) def list_br(self): return MultiLineCommand(self.context, 'list-br') def br_get_external_id(self, name, field): return BaseCommand(self.context, 'br-get-external-id', args=[name, field]) def db_create(self, table, **col_values): args = [table] args += _set_colval_args(*col_values.items()) return DbCreateCommand(self.context, args=args) def db_destroy(self, table, record): args = [table, record] return BaseCommand(self.context, 'destroy', args=args) def db_set(self, table, record, *col_values): args = [table, record] args += _set_colval_args(*col_values) return BaseCommand(self.context, 'set', args=args) def db_add(self, table, record, column, *values): args = [table, record, column] for value in values: if isinstance(value, collections.Mapping): args += ["{}={}".format(_py_to_val(k), _py_to_val(v)) for k, v in value.items()] else: args.append(_py_to_val(value)) return BaseCommand(self.context, 'add', args=args) def db_clear(self, table, record, column): return BaseCommand(self.context, 'clear', args=[table, record, column]) def db_get(self, table, record, column): # Use the 'list' command as it can return json and 'get' cannot so that # we can get real return types instead of treating everything as string # NOTE: openvswitch can return a single atomic value for fields that # are sets, but only have one value. This makes directly iterating over # the result of a db_get() call unsafe. return DbGetCommand(self.context, 'list', args=[table, record], columns=[column]) def db_list(self, table, records=None, columns=None, if_exists=False): opts = ['--if-exists'] if if_exists else None args = [table] if records: args += records return DbCommand(self.context, 'list', opts=opts, args=args, columns=columns) def db_find(self, table, *conditions, **kwargs): columns = kwargs.pop('columns', None) args = itertools.chain([table], *[_set_colval_args(c) for c in conditions]) return DbCommand(self.context, 'find', args=args, columns=columns) def set_controller(self, bridge, controllers): return BaseCommand(self.context, 'set-controller', args=[bridge] + list(controllers)) def del_controller(self, bridge): return BaseCommand(self.context, 'del-controller', args=[bridge]) def get_controller(self, bridge): return MultiLineCommand(self.context, 'get-controller', args=[bridge]) def set_fail_mode(self, bridge, mode): return BaseCommand(self.context, 'set-fail-mode', args=[bridge, mode]) def add_port(self, bridge, port, may_exist=True): opts = ['--may-exist'] if may_exist else None return BaseCommand(self.context, 'add-port', opts, [bridge, port]) def del_port(self, port, bridge=None, if_exists=True): opts = ['--if-exists'] if if_exists else None args = filter(None, [bridge, port]) return BaseCommand(self.context, 'del-port', opts, args) def list_ports(self, bridge): return MultiLineCommand(self.context, 'list-ports', args=[bridge]) def list_ifaces(self, bridge): return MultiLineCommand(self.context, 'list-ifaces', args=[bridge]) def db_list_rows(self, table, record=None, if_exists=False): raise NotImplementedError() def db_find_rows(self, table, *conditions, **kwargs): raise NotImplementedError() def db_remove(self, table, record, column, *values, **keyvalues): raise NotImplementedError() def has_table_column(self, table, column): try: self.db_list(table, columns=[column]).execute(check_error=True) return True except processutils.ProcessExecutionError as e: msg = ('ovs-vsctl: %s does not contain a column whose name ' 'matches "%s"' % (table, column)) if msg in e.stderr: return False raise e def _set_colval_args(*col_values): args = [] # TODO(twilson) This is ugly, but set/find args are very similar except for # op. Will try to find a better way to default this op to '=' for entry in col_values: if len(entry) == 2: col, op, val = entry[0], '=', entry[1] else: col, op, val = entry if isinstance(val, collections.Mapping): args += ["%s:%s%s%s" % ( col, k, op, _py_to_val(v)) for k, v in val.items()] elif (isinstance(val, collections.Sequence) and not isinstance(val, six.string_types)): if len(val) == 0: args.append("%s%s%s" % (col, op, "[]")) else: args.append( "%s%s%s" % (col, op, ",".join(map(_py_to_val, val)))) else: args.append("%s%s%s" % (col, op, _py_to_val(val))) return args os_vif-2.0.0/vif_plug_ovs/linux_net.py0000664000175000017500000003203213621620627020106 0ustar zuulzuul00000000000000# Derived from nova/network/linux_net.py # # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Implements vlans, bridges using linux utilities.""" import glob import os import re import sys from os_vif.internal.ip.api import ip as ip_lib from oslo_concurrency import processutils from oslo_log import log as logging from oslo_utils import excutils from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs import privsep LOG = logging.getLogger(__name__) VIRTFN_RE = re.compile(r"virtfn(\d+)") # phys_port_name only contains the VF number INT_RE = re.compile(r"^(\d+)$") # phys_port_name contains VF## or vf## VF_RE = re.compile(r"vf(\d+)", re.IGNORECASE) # phys_port_name contains PF## or pf## PF_RE = re.compile(r"pf(\d+)", re.IGNORECASE) # bus_info (bdf) contains :. PF_FUNC_RE = re.compile(r"\.(\d+)", 0) _SRIOV_TOTALVFS = "sriov_totalvfs" NIC_NAME_LEN = 14 def _update_device_mtu(dev, mtu): if not mtu: return if sys.platform != constants.PLATFORM_WIN32: # Hyper-V with OVS does not support external programming of # virtual interface MTUs via netsh or other Windows tools. # When plugging an interface on Windows, we therefore skip # programming the MTU and fallback to DHCP advertisement. set_device_mtu(dev, mtu) @privsep.vif_plug.entrypoint def delete_net_dev(dev): """Delete a network device only if it exists.""" if ip_lib.exists(dev): try: ip_lib.delete(dev, check_exit_code=[0, 2, 254]) LOG.debug("Net device removed: '%s'", dev) except processutils.ProcessExecutionError: with excutils.save_and_reraise_exception(): LOG.error("Failed removing net device: '%s'", dev) @privsep.vif_plug.entrypoint def create_veth_pair(dev1_name, dev2_name, mtu): """Create a pair of veth devices with the specified names, deleting any previous devices with those names. """ for dev in [dev1_name, dev2_name]: delete_net_dev(dev) ip_lib.add(dev1_name, 'veth', peer=dev2_name) for dev in [dev1_name, dev2_name]: ip_lib.set(dev, state='up') ip_lib.set(dev, promisc='on') _update_device_mtu(dev, mtu) @privsep.vif_plug.entrypoint def update_veth_pair(dev1_name, dev2_name, mtu): """Update a pair of veth devices with new configuration.""" for dev in [dev1_name, dev2_name]: _update_device_mtu(dev, mtu) def _disable_ipv6(bridge): """Disable ipv6 if available for bridge. Must be called from privsep context. """ # NOTE(sean-k-mooney): os-vif disables ipv6 to ensure the Bridge # does not aquire an ipv6 auto config or link local adress. # This is required to prevent bug 1302080. # https://bugs.launchpad.net/neutron/+bug/1302080 disv6 = ('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % bridge) if os.path.exists(disv6): with open(disv6, 'w') as f: f.write('1') # TODO(ralonsoh): extract into common module def _arp_filtering(bridge): """Prevent the bridge from replying to ARP messages with machine local IPs 1. Reply only if the target IP address is local address configured on the incoming interface. 2. Always use the best local address. """ arp_params = [('/proc/sys/net/ipv4/conf/%s/arp_ignore' % bridge, '1'), ('/proc/sys/net/ipv4/conf/%s/arp_announce' % bridge, '2')] for parameter, value in arp_params: if os.path.exists(parameter): with open(parameter, 'w') as f: f.write(value) @privsep.vif_plug.entrypoint def ensure_bridge(bridge): if not ip_lib.exists(bridge): # NOTE(sean-k-mooney): we set mac ageing to 0 to disable mac ageing # on the hybrid plug bridge to avoid packet loss during live # migration. This avoids bug #1715317 and related bug #1414559 ip_lib.add(bridge, 'bridge', ageing=0) _disable_ipv6(bridge) _arp_filtering(bridge) # we bring up the bridge to allow it to switch packets set_interface_state(bridge, 'up') @privsep.vif_plug.entrypoint def delete_bridge(bridge, dev): if ip_lib.exists(bridge): # Note(sean-k-mooney): this will detach all ports on # the bridge before deleting the bridge. ip_lib.delete(bridge, check_exit_code=[0, 2, 254]) # howver it will not set the detached interface down # so we set the dev down if dev is not None and exists. if dev and ip_lib.exists(dev): set_interface_state(dev, "down") @privsep.vif_plug.entrypoint def add_bridge_port(bridge, dev): ip_lib.set(dev, master=bridge) @privsep.vif_plug.entrypoint def set_device_mtu(dev, mtu): """Set the device MTU.""" if ip_lib.exists(dev): ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254]) @privsep.vif_plug.entrypoint def set_interface_state(interface_name, port_state): ip_lib.set(interface_name, state=port_state, check_exit_code=[0, 2, 254]) def _parse_vf_number(phys_port_name): """Parses phys_port_name and returns VF number or None. To determine the VF number of a representor, parse phys_port_name in the following sequence and return the first valid match. If none match, then the representor is not for a VF. """ match = INT_RE.search(phys_port_name) if match: return match.group(1) match = VF_RE.search(phys_port_name) if match: return match.group(1) return None def _parse_pf_number(phys_port_name): """Parses phys_port_name and returns PF number or None. To determine the PF number of a representor, parse phys_port_name in the following sequence and return the first valid match. If none match, then the representor is not for a PF. """ match = PF_RE.search(phys_port_name) if match: return match.group(1) return None # This function is taken from nova/pci/utils.py def get_function_by_ifname(ifname): """Given the device name, returns the PCI address of a device and returns True if the address is in a physical function. """ dev_path = "/sys/class/net/%s/device" % ifname sriov_totalvfs = 0 if os.path.isdir(dev_path): try: # sriov_totalvfs contains the maximum possible VFs for this PF dev_path_file = os.path.join(dev_path, _SRIOV_TOTALVFS) with open(dev_path_file, 'r') as fd: sriov_totalvfs = int(fd.readline().rstrip()) return (os.readlink(dev_path).strip("./"), sriov_totalvfs > 0) except (IOError, ValueError): return os.readlink(dev_path).strip("./"), False return None, False def _get_pf_func(pf_ifname): """Gets PF function number using pf_ifname and returns function number or None. """ address_str, pf = get_function_by_ifname(pf_ifname) if not address_str: return None match = PF_FUNC_RE.search(address_str) if match: return match.group(1) return None def get_representor_port(pf_ifname, vf_num): """Get the representor netdevice which is corresponding to the VF. This method gets PF interface name and number of VF. It iterates over all the interfaces under the PF location and looks for interface that has the VF number in the phys_port_name. That interface is the representor for the requested VF. """ pf_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: address_str, pf = get_function_by_ifname(device) if pf: 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 # If the phys_port_name of the VF-rep is of the format pfXvfY # (or vfY@pfX), then match "X" (parent PF's func number) with # the PCI func number of pf_ifname. rep_parent_pf_func = _parse_pf_number(phys_port_name) if rep_parent_pf_func is not None: ifname_pf_func = _get_pf_func(pf_ifname) if ifname_pf_func is None: continue if int(rep_parent_pf_func) != int(ifname_pf_func): continue representor_num = _parse_vf_number(phys_port_name) # Note: representor_num can be 0, referring to VF0 if representor_num is None: continue # At this point we're confident we have a representor. try: if int(representor_num) == int(vf_num): return device except (ValueError): continue raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num) def _get_sysfs_netdev_path(pci_addr, pf_interface): """Get the sysfs path based on the PCI address of the device. Assumes a networking device - will not check for the existence of the path. """ if pf_interface: return "/sys/bus/pci/devices/%s/physfn/net" % (pci_addr) return "/sys/bus/pci/devices/%s/net" % (pci_addr) def _is_switchdev(netdev): """Returns True if a netdev has a readable phys_switch_id""" try: 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 def get_dpdk_representor_port_name(port_id): devname = "vfr" + port_id return devname[:NIC_NAME_LEN] def get_pf_pci_from_vf(vf_pci): """Get physical function PCI address of a VF :param vf_pci: the PCI address of the VF :return: the PCI address of the PF """ physfn_path = os.readlink("/sys/bus/pci/devices/%s/physfn" % vf_pci) return os.path.basename(physfn_path) os_vif-2.0.0/tox.ini0000664000175000017500000000461513621620626014345 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py37,pep8 skipsdist = True ignore_basepython_conflict = True [testenv] basepython = python3 usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} CONSTRAINTS_OPT=-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} deps = {env:CONSTRAINTS_OPT} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt whitelist_externals = bash commands = stestr run --black-regex ".tests.functional" {posargs} [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} deps = {env:CONSTRAINTS_OPT} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/doc/requirements.txt [testenv:functional] setenv = {[testenv]setenv} commands = stestr run --black-regex ".tests.unit" '{posargs}' [testenv:docs] deps = {env:CONSTRAINTS_OPT} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:releasenotes] deps = {env:CONSTRAINTS_OPT} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/doc/requirements.txt commands = stestr run --black-regex ".tests.functional" {posargs} sphinx-build -W doc/source doc/build [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.opendev.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-2.0.0/setup.py0000664000175000017500000000200613621620626014534 0ustar zuulzuul00000000000000# 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-2.0.0/.zuul.yaml0000664000175000017500000001065213621620626014771 0ustar zuulzuul00000000000000- job: name: openstack-tox-functional-ovs-with-sudo parent: openstack-tox-functional-with-sudo required-projects: - opendev.org/openstack/devstack pre-run: playbooks/openstack-tox-functional-ovs-with-sudo/pre.yaml timeout: 600 - job: name: os-vif-tempest-base parent: devstack-tempest description: | Base integration test with Neutron networking and py3. This is derived from tempest-full-py3 and adapted for use in os-vif required-projects: - openstack/devstack-gate - openstack/nova - openstack/os-vif - openstack/neutron - openstack/tempest vars: tempest_concurrency: 4 tox_envlist: full devstack_plugins: neutron: https://opendev.org/openstack/neutron.git devstack_localrc: USE_PYTHON3: true FORCE_CONFIG_DRIVE: true ENABLE_VOLUME_MULTIATTACH: true # NOTE(sean-k-mooney) we do not have to set # DEVSTACK_PROJECT_FROM_GIT: "os-vif" # in the local.conf because os-vif is listed as a required # project and will be added to the LIB_FROM_GIT automatically. devstack_services: s-account: false s-container: false s-object: false s-proxy: false # without Swift, c-bak cannot run (in the Gate at least) c-bak: false - job: name: os-vif-ovs-base parent: os-vif-tempest-base timeout: 7800 description: | os-vif ovs base job, this should not be used directly. vars: devstack_local_conf: post-config: $NEUTRON_L3_CONF: agent: availability_zone: nova $NEUTRON_DHCP_CONF: agent: availability_zone: nova "/$NEUTRON_CORE_PLUGIN_CONF": ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: enable_distributed_routing: true l2_population: true tunnel_types: vxlan,gre test-config: $TEMPEST_CONFIG: neutron_plugin_options: provider_vlans: foo, agent_availability_zone: nova image_is_advanced: true available_type_drivers: flat,vlan,gre,local,vxlan - job: name: os-vif-ovs parent: os-vif-ovs-base description: | os-vif ovs job with ovs firewall driver vars: devstack_local_conf: post-config: # NOTE(sean-k-mooney): i do not believe that the devstack role # will merge the base /$NEUTRON_CORE_PLUGIN_CONF with the parent # job so we redefine the entire section "/$NEUTRON_CORE_PLUGIN_CONF": ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: enable_distributed_routing: true l2_population: true tunnel_types: vxlan,gre securitygroup: firewall_driver: openvswitch - job: name: os-vif-ovs-iptables parent: os-vif-ovs-base description: | os-vif iptables job derived from neutron-tempest-iptables_hybrid vars: tox_envlist: full devstack_local_conf: post-config: # NOTE(sean-k-mooney): i do not believe that the devstack role # will merge the base /$NEUTRON_CORE_PLUGIN_CONF with the parent # job so we redefine the entire section "/$NEUTRON_CORE_PLUGIN_CONF": ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: enable_distributed_routing: true l2_population: true tunnel_types: vxlan,gre securitygroup: firewall_driver: iptables_hybrid - job: name: os-vif-linuxbridge parent: os-vif-tempest-base timeout: 10800 description: | os-vif linux bridge job derived from neutron-tempest-linuxbridge vars: devstack_localrc: Q_AGENT: linuxbridge - project: templates: - check-requirements - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - kuryr-kubernetes-tempest: voting: false - openstack-tox-functional-ovs-with-sudo - os-vif-ovs - os-vif-ovs-iptables - os-vif-linuxbridge gate: jobs: - openstack-tox-functional-ovs-with-sudo - os-vif-ovs - os-vif-ovs-iptables - os-vif-linuxbridge os_vif-2.0.0/vif_plug_linux_bridge/0000775000175000017500000000000013621620704017367 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_linux_bridge/__init__.py0000664000175000017500000000000013621620626021471 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_linux_bridge/linux_bridge.py0000664000175000017500000001152513621620626022423 0ustar zuulzuul00000000000000# 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-2.0.0/vif_plug_linux_bridge/constants.py0000664000175000017500000000113213621620626021755 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/vif_plug_linux_bridge/iptables.py0000664000175000017500000005032513621620626021554 0ustar zuulzuul00000000000000# 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-2.0.0/vif_plug_linux_bridge/privsep.py0000664000175000017500000000156413621620626021442 0ustar zuulzuul00000000000000# # 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-2.0.0/vif_plug_linux_bridge/tests/0000775000175000017500000000000013621620704020531 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_linux_bridge/tests/__init__.py0000664000175000017500000000000013621620626022633 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_linux_bridge/tests/unit/0000775000175000017500000000000013621620704021510 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_linux_bridge/tests/unit/test_plugin.py0000664000175000017500000001126413621620627024427 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-2.0.0/vif_plug_linux_bridge/tests/unit/__init__.py0000664000175000017500000000000013621620626023612 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_linux_bridge/tests/unit/test_linux_net.py0000664000175000017500000001512013621620627025131 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import fixtures from os_vif.internal.ip.api 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(ip_lib, "exists", return_value=False) @mock.patch.object(linux_net, "_set_device_mtu") def test_ensure_vlan(self, mock_set_mtu, mock_dev_exists, mock_ip_set, mock_ip_add): linux_net._ensure_vlan_privileged(123, 'fake-bridge', mac_address='fake-mac', mtu=1500) self.assertTrue(mock_dev_exists.called) set_calls = [mock.call('vlan123', address='fake-mac', check_exit_code=[0, 2, 254]), mock.call('vlan123', state='up', check_exit_code=[0, 2, 254])] mock_ip_add.assert_called_once_with( 'vlan123', 'vlan', link='fake-bridge', vlan_id=123, check_exit_code=[0, 2, 254]) mock_ip_set.assert_has_calls(set_calls) mock_set_mtu.assert_called_once_with('vlan123', 1500) @mock.patch.object(linux_net, "_ensure_bridge_privileged") @mock.patch.object(linux_net, "_ensure_bridge_filtering") def test_ensure_bridge(self, mock_filtering, mock_priv): linux_net.ensure_bridge("br0", None, filtering=False) mock_priv.assert_called_once_with("br0", None, None, True, filtering=False, mtu=None) mock_filtering.assert_not_called() linux_net.ensure_bridge("br0", None, filtering=True) mock_filtering.assert_called_once_with("br0", True) @mock.patch.object(ip_lib, "exists", return_value=False) @mock.patch.object(ip_lib, "add") def test_ensure_bridge_addbr_exception(self, mock_add, mock_dev_exists): mock_add.side_effect = ValueError() with testtools.ExpectedException(ValueError): linux_net.ensure_bridge("br0", None, filtering=False) @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "set") @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(linux_net, "_update_bridge_routes") @mock.patch.object(ip_lib, "exists") def test_ensure_bridge_priv_mtu_not_called(self, mock_dev_exists, mock_routes, mock_disable_ipv6, mock_set_mtu, mock_arp_filtering, mock_ip_set, mock_add): """This test validates that mtus are updated only if an interface is added to the bridge """ mock_dev_exists.return_value = False linux_net._ensure_bridge_privileged("fake-bridge", None, None, False, mtu=1500) mock_set_mtu.assert_not_called() mock_ip_set.assert_called_once_with('fake-bridge', state='up') @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "set") @mock.patch.object(linux_net, "_arp_filtering") @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(linux_net, "_disable_ipv6") @mock.patch.object(linux_net, "_update_bridge_routes") @mock.patch.object(ip_lib, "exists") def test_ensure_bridge_priv_mtu_order(self, mock_dev_exists, mock_routes, mock_disable_ipv6, mock_set_mtu, mock_arp_filtering, mock_ip_set, mock_add): """This test validates that when adding an interface to a bridge, the interface mtu is updated first followed by the bridge. This is required to work around https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1399064 """ mock_dev_exists.side_effect = [False, True] linux_net._ensure_bridge_privileged("fake-bridge", "fake-interface", None, False, mtu=1500) calls = [mock.call('fake-interface', 1500), mock.call('fake-bridge', 1500)] mock_set_mtu.assert_has_calls(calls) calls = [mock.call('fake-bridge', state = 'up'), mock.call('fake-interface', master='fake-bridge', state='up', check_exit_code=[0, 2, 254])] mock_ip_set.assert_has_calls(calls) @mock.patch('six.moves.builtins.open') @mock.patch("os.path.exists") def test__disable_ipv6(self, mock_exists, mock_open): exists_path = "/proc/sys/net/ipv6/conf/br0/disable_ipv6" mock_exists.return_value = False linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_not_called() mock_exists.reset_mock() mock_exists.return_value = True linux_net._disable_ipv6("br0") mock_exists.assert_called_once_with(exists_path) mock_open.assert_called_once_with(exists_path, 'w') os_vif-2.0.0/vif_plug_linux_bridge/linux_net.py0000664000175000017500000002241213621620626021752 0ustar zuulzuul00000000000000# Derived from nova/network/linux_net.py # # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Implements vlans, bridges, and iptables rules using linux utilities.""" import os from os_vif.internal.ip.api import ip as ip_lib from oslo_concurrency import lockutils from oslo_concurrency import processutils from oslo_log import log as logging from vif_plug_linux_bridge import privsep LOG = logging.getLogger(__name__) _IPTABLES_MANAGER = None def _set_device_mtu(dev, mtu): """Set the device MTU.""" if mtu: ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254]) else: LOG.debug("MTU not set on %(interface_name)s interface", {'interface_name': dev}) def _ip_bridge_cmd(action, params, device): """Build commands to add/del ips to bridges/devices.""" cmd = ['ip', 'addr', action] cmd.extend(params) cmd.extend(['dev', device]) return cmd @privsep.vif_plug.entrypoint def ensure_vlan_bridge(vlan_num, bridge, bridge_interface, net_attrs=None, mac_address=None, mtu=None): """Create a vlan and bridge unless they already exist.""" interface = _ensure_vlan_privileged(vlan_num, bridge_interface, mac_address, mtu=mtu) _ensure_bridge_privileged(bridge, interface, net_attrs) _ensure_bridge_filtering(bridge, None) return interface @lockutils.synchronized('nova-lock_vlan', external=True) def _ensure_vlan_privileged(vlan_num, bridge_interface, mac_address, mtu): """Create a vlan unless it already exists. This assumes the caller is already annotated to run with elevated privileges. """ interface = 'vlan%s' % vlan_num if not ip_lib.exists(interface): LOG.debug('Starting VLAN interface %s', interface) ip_lib.add(interface, 'vlan', link=bridge_interface, vlan_id=vlan_num, check_exit_code=[0, 2, 254]) # (danwent) the bridge will inherit this address, so we want to # make sure it is the value set from the NetworkManager if mac_address: ip_lib.set(interface, address=mac_address, check_exit_code=[0, 2, 254]) ip_lib.set(interface, state='up', check_exit_code=[0, 2, 254]) # NOTE(vish): set mtu every time to ensure that changes to mtu get # 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) # TODO(sean-k-mooney): extract into common module def _disable_ipv6(bridge): """disable ipv6 for bridge if available, must be called from privsep context. :param bridge: string bridge name """ # NOTE(sean-k-mooney): os-vif disables ipv6 to ensure the Bridge # does not aquire an ipv6 auto config or link local adress. # This is required to prevent bug 1302080. # https://bugs.launchpad.net/neutron/+bug/1302080 disv6 = ('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % bridge) if os.path.exists(disv6): with open(disv6, 'w') as f: f.write('1') # TODO(ralonsoh): extract into common module def _arp_filtering(bridge): """Prevent the bridge from replying to ARP messages with machine local IPs 1. Reply only if the target IP address is local address configured on the incoming interface. 2. Always use the best local address. """ arp_params = [('/proc/sys/net/ipv4/conf/%s/arp_ignore' % bridge, '1'), ('/proc/sys/net/ipv4/conf/%s/arp_announce' % bridge, '2')] for parameter, value in arp_params: if os.path.exists(parameter): with open(parameter, 'w') as f: f.write(value) def _update_bridge_routes(interface, bridge): """Updates routing table for a given bridge and interface. :param interface: string interface name :param bridge: string bridge name """ # TODO(sean-k-mooney): investigate deleting all this route # handling code. The vm tap devices should never have an ip, # this is old nova networks code and i dont think it will ever # be needed in os-vif. # NOTE(vish): This will break if there is already an ip on the # interface, so we move any ips to the bridge # NOTE(danms): We also need to copy routes to the bridge so as # not to break existing connectivity on the interface old_routes = [] out, _ = processutils.execute('ip', 'route', 'show', 'dev', interface) for line in out.split('\n'): fields = line.split() if fields and 'via' in fields: old_routes.append(fields) processutils.execute('ip', 'route', 'del', *fields) out, _ = processutils.execute('ip', 'addr', 'show', 'dev', interface, 'scope', 'global') for line in out.split('\n'): fields = line.split() if fields and fields[0] == 'inet': if fields[-2] in ('secondary', 'dynamic', ): params = fields[1:-2] else: params = fields[1:-1] processutils.execute(*_ip_bridge_cmd('del', params, fields[-1]), check_exit_code=[0, 2, 254]) processutils.execute(*_ip_bridge_cmd('add', params, bridge), check_exit_code=[0, 2, 254]) for fields in old_routes: processutils.execute('ip', 'route', 'add', *fields) @privsep.vif_plug.entrypoint def _ensure_bridge_privileged(bridge, interface, net_attrs, gateway, filtering=True, mtu=None): """Create a bridge unless it already exists. :param interface: the interface to create the bridge on. :param net_attrs: dictionary with attributes used to create bridge. :param gateway: whether or not the bridge is a gateway. :param filtering: whether or not to create filters on the bridge. :param mtu: MTU of bridge. If net_attrs is set, it will add the net_attrs['gateway'] to the bridge using net_attrs['broadcast'] and net_attrs['cidr']. It will also add the ip_v6 address specified in net_attrs['cidr_v6'] if use_ipv6 is set. The code will attempt to move any ips that already exist on the interface onto the bridge and reset the default gateway if necessary. """ if not ip_lib.exists(bridge): LOG.debug('Starting Bridge %s', bridge) ip_lib.add(bridge, 'bridge') _disable_ipv6(bridge) _arp_filtering(bridge) ip_lib.set(bridge, state='up') if interface and ip_lib.exists(interface): LOG.debug('Adding interface %(interface)s to bridge %(bridge)s', {'interface': interface, 'bridge': bridge}) ip_lib.set(interface, master=bridge, state='up', check_exit_code=[0, 2, 254]) _set_device_mtu(interface, mtu) _update_bridge_routes(interface, bridge) # NOTE(sean-k-mooney): # The bridge mtu 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-2.0.0/vif_plug_noop/0000775000175000017500000000000013621620704015667 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_noop/noop.py0000664000175000017500000000313513621620627017222 0ustar zuulzuul00000000000000# Copyright (C) 2011 Midokura KK # Copyright (C) 2011 Nicira, Inc # Copyright 2011 OpenStack Foundation # Copyright 2018 Intel Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from os_vif import objects from os_vif import plugin class NoOpPlugin(plugin.PluginBase): """A no op plugin The no op plugin can be used for any vif type that requires no action to be performed on the backend network when a vif is plugged. Currently only the VIFVHostUser VIF type is supported. This pluggin allows for the use of generic vhost user without ovs. """ def describe(self): return objects.host_info.HostPluginInfo( plugin_name="noop", vif_info=[ objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFVHostUser.__name__, min_version="1.0", max_version="1.0", supported_port_profiles=[]) ]) def plug(self, vif, instance_info): pass def unplug(self, vif, instance_info): pass os_vif-2.0.0/vif_plug_noop/__init__.py0000664000175000017500000000000013621620627017772 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_noop/tests/0000775000175000017500000000000013621620704017031 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_noop/tests/__init__.py0000664000175000017500000000000013621620627021134 0ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_noop/tests/unit/0000775000175000017500000000000013621620704020010 5ustar zuulzuul00000000000000os_vif-2.0.0/vif_plug_noop/tests/unit/test_plugin.py0000664000175000017500000000232413621620627022724 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from os_vif import objects from vif_plug_noop import noop class PluginTest(testtools.TestCase): def __init__(self, *args, **kwargs): super(PluginTest, self).__init__(*args, **kwargs) objects.register_all() self.plugin = noop.NoOpPlugin.load("noop") def test_plug_noop(self): self.assertIn("plug", dir(self.plugin)) self.plugin.plug(None, None) def test_unplug_noop(self): self.assertIn("unplug", dir(self.plugin)) self.plugin.unplug(None, None) def test_describe_noop(self): self.assertIn("describe", dir(self.plugin)) self.assertTrue(len(self.plugin.describe().vif_info) > 0) os_vif-2.0.0/vif_plug_noop/tests/unit/__init__.py0000664000175000017500000000000013621620627022113 0ustar zuulzuul00000000000000os_vif-2.0.0/ChangeLog0000664000175000017500000002513213621620703014575 0ustar zuulzuul00000000000000CHANGES ======= 2.0.0 ----- * [OVS] VLAN tag should be set in the Port register * Revert "[Follow Up] OVS DPDK port representors support" * move os-vif-ovs to be a non legacy job * [Follow Up] OVS DPDK port representors support * Drop python2 support and testing * Switch to Ussuri jobs * Update the constraints url * Update master for stable/train 1.17.0 ------ * Fix code bug in document * only disable mac ageing for ovs hybrid plug * Bump the openstackdocstheme extension to 1.20 * Blacklist sphinx 2.1.0 (autodoc bug) * Sync Sphinx requirement * Add Python 3 Train unit tests * set ignore\_basepython\_conflict = True in tox.ini * OVS DPDK port representors support * Fix mock of built in "open" function in unit tests 1.16.0 ------ * Remove unused vif\_plug\_ovs.i18n module * Fix Kuryr-Kubernetes job name * Replace git.openstack.org URLs with opendev.org URLs * Prevent "qbr" Linux Bridge from replying to ARP messages * Remove IP proxy methods * OpenDev Migration Patch * Refactor functional base test classes * Drop testtools from test-requirements.txt * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 1.15.1 ------ * add additional check and gate jobs for os-vif * Add "master" parameter to ip.set() API function 1.15.0 ------ * Add native implementation OVSDB API * docs: Use sphinx.ext.autodoc for profile, datapath offload types * docs: Use sphinx.ext.autodoc for VIF types * make functional tests run on python 3 * Fix nits in brctl removal (vif\_plug\_linux\_bridge) * docs: Add API docs for profile, datapath offload types * docs: Add API docs for VIF types * remove use of brctl from vif\_plug\_linux\_bridge * remove brctl from vif\_plug\_ovs * Add function "has\_table\_columns" to OVSDB implementation API * Clean up versioned object backlevelling code * Change python3.5 job to python3.7 job on Stein+ * Add create\_port field in VIFPortProfileOpenVSwitch profile * Convert hardcoded regexes to raw strings for py36 1.14.0 ------ * make kuryr-kubernetes-tempest-daemon-octavia non voting * Add test to check os\_vif.internal.command.ip.exists * Import IP implementation modules outside privsep context * Cleanup device at the end of 'test\_iproute\_object\_closes\_correctly' test * do not always plug ovs ports * Apply workaround to host\_info serialization test * Extend port profiles with datapath offload type * Update hacking version 1.13.1 ------ * Create iproute.IPRoute() inside a context 1.13.0 ------ * add isolate\_vif config option * Change openstack-dev to openstack-discuss * always create ovs port during plug * Update min tox version to 2.0 * Do not import pyroute2 on Windows 1.12.0 ------ * Do not call linux\_net.delete\_net\_dev on Windows * Fix random test\_unplug\_ovs failures * Reflow docs to 79 columns * clean up ip\_command interface * Remove IPTools deprecated implementation * Add abstract OVSDB API * Add support for Windows network commands * add nested DPDK VIF classes for kuryr-kubernetes * Fix upper-constraints link in tox file * Cleanup zuul config file * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Support for OVS DB TCP socket communication * Update reno for stable/rocky * Add vif\_plug\_noop to setup.cfg packages 1.11.0 ------ * add upper\_constraints support * convert os-vif docs to follow PTI * doc: Fix arg specs and object types in docs * Remove [tox:jenkins] section from tox.ini * Remove unnecessary pyNN testenv sections * move legacy-tempest-dsvm-nova-os-vif to repo * doc: Fix formatting issues * add noop plugin * Add release note link in README * Fix docstrings to work with Sphinx 1.7.4 * fix tox python3 overrides * fix tox py27 job * Trivial: Update pypi url to new url * Add lower-constraints job 1.10.0 ------ * Add kuryr-kubernetes Tempest job * Update links in README * ovs: do not delete port if already exists * zuul: Enable functional tests in gate * Update reno for stable/queens * Configure privsep binary * Fix VF-rep lookup routine to use parent PF number 1.9.0 ----- * adds iptools driver for ip commands * Revert "Move 'ips' field from Subnet object to VIF object" * Git ignore .stestr 1.8.0 ----- * Migrate from 'ip' commands to 'pyroute2' * Check if interface belongs to a Linux Bridge before removing * Updated from global requirements * Remove setting of version/release from releasenotes * Updated from global requirements * Move 'ips' field from Subnet object to VIF object * Add VersionedObjectPrintable mixin * Add Port Profile info to VIF objects Linux Bridge plugin * Updated from global requirements * ovs-hybrid: should permanently keep MAC entries * Add Port Profile info to VIF objects OVS plugin * Rehome OVO unit tests to tests.unit.test\_object.py * Add \`\`HostPortProfileInfo\`\` class * Add plugin names as constants * Updated from global requirements * Using assertIsNone() instead of assertEqual(None) * Read datapath\_type from VIF object * Update reno for stable/pike * Update the documentation link for doc migration * doc: Remove cruft from releasenotes conf.py 1.7.0 ----- * Improve OVS Representor VF Lookup * Improve OVS Representor Lookup * Add support for VIFPortProfileOVSRepresentor * unplug\_vf\_passthrough: don't try to delete representor netdev * Enable some off-by-default checks * set mtu on all code paths * fix read the representor phys\_port\_name * doc: Switch from oslosphinx to openstackdocstheme * doc: Create directory structure for docs migration * Use \`\`assert\_has\_calls\`\` to check function calls * Updated from global requirements * Rehome unit tests to \`\`tests\unit\`\` folder 1.6.0 ----- * hardware offload support for openvswitch * Use versionedobjects PCIAddress field * Fix typo VIFVIFHostDeviceDevType to VIFHostDeviceDevType 1.5.0 ----- * Updated from global requirements * Standardize README * Revert "hardware offload support for openvswitch" * hardware offload support for openvswitch * Fix typos in vif\_types.rst * Add documentation for Linux Bridge plugin * Add documentation for OVS plugin * docs: Stop building anything but html output * doc: Add glossary * doc: Rewrap 'vif\_types' document * Argument should have 2 params * Explain why we bring up the lb in hybird mode * Remove log translations * Use Sphinx 1.5 warning-is-error * Updated from global requirements * Updated from global requirements * vif\_plug\_ovs: Skip setting MTU on Windows when plugging devices * The Python 3.5 is added * Don't install iptables rules if neutron is filtering * Correct object path in comments * Delete H803 from ignore list * Update reno for stable/ocata * Removing Deprecated hacking Check * Fix broken Link * [py35] Switch filter to list comprehensions * Remove support for py33 1.4.0 ----- * introduces MTU support for vhost-user * vif\_plug\_ovs: Always set MTU when plugging devices * os-vif: add new port profiles to enable fast path vhostuser * add support for vhost-user reconnect * os-vif: add vif\_name to VIFVHostUser class * Changed the home-page link * Drop MANIFEST.in - it's not needed by pbr * Show team and repo badges on README * remove use of contextlib and with nested * host\_info: add ability to filter list of supported vifs * host\_info: fix get\_common\_version method on HostVIFInfo * host\_info: fix has\_vif/get\_vif methods on HostPluginInfo * Updated from global requirements * host\_info: fix has\_plugin/get\_plugin methods on HostInfo 1.3.0 ----- * Enable release notes translation * os-vif: add initial documentation about object model * Add oslo.concurrency to requirements * Make plugin loading more consistent with logging guidelines * Updated from global requirements * vif: stop VIFOpenVSwitch inheriting VIFBridge * Updated from global requirements * Add MTU to Network model and use it in plugging * Update reno for stable/newton * Adds Windows support for OvsPlugin * Check for concurrent bridge creation in bridge add 1.2.0 ----- * Add a reminder to remove Route.interface field * Updated from global requirements * Disable IPv6 on bridge devices in linux bridge code * Trivial: clean up oslo-incubator related stuff * Fix logging calls * Remove discover from test-requirements 1.1.0 ----- * Simplified if statement * Updated from global requirements * revert removal of create\_ovs\_vif\_port timeout * Ensure the OVS bridge exists when plugging * Don't create extraneous linux bridge/veth pair for VIFOpenVSwitch * Updated from global requirements * mtu: don't attempt to set link mtu if it's invalid * ovs: Avoids setting MTU if MTU is None or 0 * os\_vif: fix logging of exceptions during plug/unplug * vif\_plug\_ovs: clarify that the plugin was not in fact renamed * os\_vif: add logging for each plugin that is loaded * os\_vif: register objects before loading plugins * Add support for vhost-user * This change renames the ovs plugin * Updated from global requirements * remove unused entrypoints 1.0.0 ----- * Start using reno for release notes * vif\_plug\_ovs: merge both plugins into one * ovs: convert over to use privsep module * ovs: move code from plugin into linux\_net helper * linux\_bridge: convert over to use privsep module * test: use real UUID in all UUID fields * test: add workaround for non-deterministic ovo object comparison * os-vif: introduce a ComputeInfo object to represent compute info * linux\_bridge: actually apply the iptables rules * Fix calls to create\_ovs\_vif\_port * Remove vlan from hostdev and direct vif * Change network vlan to integer * VIFDirect: replace dev\_name with dev\_address * Use names() method of ExtensionManager insted of keys() * Remove obsolete obj\_relationships attribute * os-vif: add test for versioned object fingerprints * os\_vif: ensure objects are in an 'os\_vif' namespace * vif\_plug\_ovs: Disable IPv6 on bridge devices * import openvswitch plugin implementation * import linux bridge plugin implementation * Provide plugins an oslo\_config group for their setup * Adding dev\_type field to VIFHostDevice * Fix PciAddress regex * Update the test\_os\_vif.test\_initialize documentation * tox: ignore E126, E127, E128 indentation checks * Fix logic getting access to stevedore loaded plugin instance * plugin: fix typo in method annotation * Pass InstanceInfo to the plug/unplug methods * Fix definition of subnet object to not be untyped strings * Add formal classes for each of the types of VIF backend config * don't catch ProcessExecutionError exception as special case * remove dependancy on nova object model * actually register the various objects we define * remove obsolete requirements * Remove raise NotImplementedError from abstractmethods * remove python 2.6 trove classifier * reorder tox envlist to run python 3.4 before 2.7 * Import of code from https://github.com/jaypipes/os\_vif * Added .gitreview os_vif-2.0.0/test-requirements.txt0000664000175000017500000000071613621620626017271 0ustar zuulzuul00000000000000# 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>=1.1.0,<1.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 ovs>=2.9.2 stestr>=1.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD os_vif-2.0.0/LICENSE0000664000175000017500000002363713621620626014044 0ustar zuulzuul00000000000000 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-2.0.0/os_vif.egg-info/0000775000175000017500000000000013621620704016000 5ustar zuulzuul00000000000000os_vif-2.0.0/os_vif.egg-info/requires.txt0000664000175000017500000000042613621620703020401 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 netaddr>=0.7.18 oslo.concurrency>=3.20.0 oslo.config>=5.1.0 oslo.log>=3.30.0 oslo.i18n>=3.15.3 oslo.privsep>=1.23.0 oslo.versionedobjects>=1.28.0 ovsdbapp>=0.12.1 six>=1.10.0 stevedore>=1.20.0 debtcollector>=1.19.0 [:(sys_platform!='win32')] pyroute2>=0.5.2 os_vif-2.0.0/os_vif.egg-info/top_level.txt0000664000175000017500000000007013621620703020526 0ustar zuulzuul00000000000000os_vif vif_plug_linux_bridge vif_plug_noop vif_plug_ovs os_vif-2.0.0/os_vif.egg-info/pbr.json0000664000175000017500000000005613621620703017456 0ustar zuulzuul00000000000000{"git_version": "d5b61d1", "is_release": true}os_vif-2.0.0/os_vif.egg-info/SOURCES.txt0000664000175000017500000001346113621620704017671 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .testr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/reference/glossary.rst doc/source/user/host-info.rst doc/source/user/usage.rst doc/source/user/vif-types.rst doc/source/user/plugins/linux-bridge.rst doc/source/user/plugins/noop.rst doc/source/user/plugins/ovs.rst os_vif/__init__.py os_vif/exception.py os_vif/i18n.py os_vif/plugin.py os_vif/utils.py os_vif/version.py os_vif.egg-info/PKG-INFO os_vif.egg-info/SOURCES.txt os_vif.egg-info/dependency_links.txt os_vif.egg-info/entry_points.txt os_vif.egg-info/not-zip-safe os_vif.egg-info/pbr.json os_vif.egg-info/requires.txt os_vif.egg-info/top_level.txt os_vif/internal/__init__.py os_vif/internal/ip/__init__.py os_vif/internal/ip/api.py os_vif/internal/ip/ip_command.py os_vif/internal/ip/linux/__init__.py os_vif/internal/ip/linux/impl_pyroute2.py os_vif/internal/ip/windows/__init__.py os_vif/internal/ip/windows/impl_netifaces.py os_vif/objects/__init__.py os_vif/objects/base.py os_vif/objects/fields.py os_vif/objects/fixed_ip.py os_vif/objects/host_info.py os_vif/objects/instance_info.py os_vif/objects/network.py os_vif/objects/route.py os_vif/objects/subnet.py os_vif/objects/vif.py os_vif/tests/__init__.py os_vif/tests/functional/__init__.py os_vif/tests/functional/base.py os_vif/tests/functional/privsep.py os_vif/tests/functional/internal/__init__.py os_vif/tests/functional/internal/command/__init__.py os_vif/tests/functional/internal/command/ip/__init__.py os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py os_vif/tests/unit/__init__.py os_vif/tests/unit/base.py os_vif/tests/unit/test_base.py os_vif/tests/unit/test_exception.py os_vif/tests/unit/test_host_info.py os_vif/tests/unit/test_objects.py os_vif/tests/unit/test_os_vif.py os_vif/tests/unit/test_vif.py os_vif/tests/unit/internal/__init__.py os_vif/tests/unit/internal/ip/__init__.py os_vif/tests/unit/internal/ip/test_api.py os_vif/tests/unit/internal/ip/linux/__init__.py os_vif/tests/unit/internal/ip/linux/test_impl_pyroute2.py os_vif/tests/unit/internal/ip/windows/__init__.py os_vif/tests/unit/internal/ip/windows/test_impl_netifaces.py playbooks/openstack-tox-functional-ovs-with-sudo/Debian.yaml playbooks/openstack-tox-functional-ovs-with-sudo/Gentoo.yaml playbooks/openstack-tox-functional-ovs-with-sudo/RedHat.yaml playbooks/openstack-tox-functional-ovs-with-sudo/Suse.yaml playbooks/openstack-tox-functional-ovs-with-sudo/pre.yaml releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml releasenotes/notes/add-fast-path-vhostuser-support-fe87e558326909b6.yaml releasenotes/notes/add-no-op-plugin-763a6703e7328a24.yaml releasenotes/notes/add-ovs-representor-portprofile-5f8290e5a40bf0a4.yaml releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml releasenotes/notes/add-ovsdb-native-322fffb49c91503d.yaml releasenotes/notes/always-plug-vifs-for-ovs-1d033fc49a9c6c4e.yaml releasenotes/notes/brctl-removal-a5b0e69b865afa57.yaml releasenotes/notes/contextlib-and-nested-with-statements-2747a9ebb9a5bfd7.yaml releasenotes/notes/do-not-force-mac-ageing-c6e8d750130c5740.yaml releasenotes/notes/drop-python2-support-7a4bc7d31253c1e5.yaml releasenotes/notes/ensure-ovs-bridge-a0c1b51f469c92d0.yaml releasenotes/notes/extend-vhostuser-object-fada14a1457d4e56.yaml releasenotes/notes/fix-ovs-plugin-describe-049750609559f1ba.yaml releasenotes/notes/fix-stevedore-entrypoints-8002ec7a5166c977.yaml releasenotes/notes/fix-vif-openvswitch-fa0d19be9dd668e1.yaml releasenotes/notes/generic-datapath-offloads-41cabb6842b41533.yaml releasenotes/notes/initial-release-2c71d6bbf55f763b.yaml releasenotes/notes/port-profile-info-linux-bridge-4800f5a0b7328615.yaml releasenotes/notes/port-profile-info-ovs-63b46a3eafc11de2.yaml releasenotes/notes/prevent-lb-reply-arp-6459133bfb056069.yaml releasenotes/notes/remove_iptools_implementation-2eb866573a680e61.yaml releasenotes/notes/revert-always-plug-port-for-ovs-hybrid-plug-false-dc015985cbc5443b.yaml releasenotes/notes/vhost-user-mtu-support-cbc7d02a6665fab1.yaml releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder vif_plug_linux_bridge/__init__.py vif_plug_linux_bridge/constants.py vif_plug_linux_bridge/iptables.py vif_plug_linux_bridge/linux_bridge.py vif_plug_linux_bridge/linux_net.py vif_plug_linux_bridge/privsep.py vif_plug_linux_bridge/tests/__init__.py vif_plug_linux_bridge/tests/unit/__init__.py vif_plug_linux_bridge/tests/unit/test_linux_net.py vif_plug_linux_bridge/tests/unit/test_plugin.py vif_plug_noop/__init__.py vif_plug_noop/noop.py vif_plug_noop/tests/__init__.py vif_plug_noop/tests/unit/__init__.py vif_plug_noop/tests/unit/test_plugin.py vif_plug_ovs/__init__.py vif_plug_ovs/constants.py vif_plug_ovs/exception.py vif_plug_ovs/linux_net.py vif_plug_ovs/ovs.py vif_plug_ovs/privsep.py vif_plug_ovs/ovsdb/__init__.py vif_plug_ovs/ovsdb/api.py vif_plug_ovs/ovsdb/impl_idl.py vif_plug_ovs/ovsdb/impl_vsctl.py vif_plug_ovs/ovsdb/ovsdb_lib.py vif_plug_ovs/tests/__init__.py vif_plug_ovs/tests/functional/__init__.py vif_plug_ovs/tests/functional/base.py vif_plug_ovs/tests/functional/ovsdb/__init__.py vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py vif_plug_ovs/tests/unit/__init__.py vif_plug_ovs/tests/unit/test_linux_net.py vif_plug_ovs/tests/unit/test_plugin.py vif_plug_ovs/tests/unit/ovsdb/__init__.py vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.pyos_vif-2.0.0/os_vif.egg-info/not-zip-safe0000664000175000017500000000000113621620703020225 0ustar zuulzuul00000000000000 os_vif-2.0.0/os_vif.egg-info/dependency_links.txt0000664000175000017500000000000113621620703022045 0ustar zuulzuul00000000000000 os_vif-2.0.0/os_vif.egg-info/PKG-INFO0000664000175000017500000000367213621620703017104 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: os-vif Version: 2.0.0 Summary: A library for plugging and unplugging virtual interfaces in OpenStack. Home-page: https://docs.openstack.org/os-vif/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/os-vif.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ====== os-vif ====== .. image:: https://img.shields.io/pypi/v/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/os-vif.svg :target: https://pypi.org/project/os-vif/ :alt: Downloads A library for plugging and unplugging virtual interfaces in OpenStack. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/os-vif/latest/ * Source: https://opendev.org/openstack/os-vif * Bugs: https://bugs.launchpad.net/os-vif * Release Notes: https://docs.openstack.org/releasenotes/os-vif Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 os_vif-2.0.0/os_vif.egg-info/entry_points.txt0000664000175000017500000000022413621620703021273 0ustar zuulzuul00000000000000[os_vif] linux_bridge = vif_plug_linux_bridge.linux_bridge:LinuxBridgePlugin noop = vif_plug_noop.noop:NoOpPlugin ovs = vif_plug_ovs.ovs:OvsPlugin