diff --git a/README.rst b/README.rst index e2e6f41f..6f8ee04c 100644 --- a/README.rst +++ b/README.rst @@ -55,20 +55,12 @@ argument of type `os_vif.objects.VIF`:: should_provide_bridge=False) vif_uuid = uuid.uuid4() - details = { - 'ovs_hybrid_plug': True, - 'vhostuser_socket': '/path/to/socket', - } - vif = vif_objects.VIF(id=vif_uuid, - address=None, - network=network, - plugin='vhostuser', - details=details, - profile=None, - vnic_type=vnic_types.NORMAL, - active=True, - preserve_on_delete=False, - instance_info=instance_info) + vif = vif_objects.VIFVHostUser(id=vif_uuid, + address=None, + network=network, + plugin='vhostuser', + path='/path/to/socket', + mode=vif_objects.fields.VIFVHostUserMode.SERVER) # Now do the actual plug operations to connect the VIF to # the backing network interface. diff --git a/os_vif/objects/fields.py b/os_vif/objects/fields.py new file mode 100644 index 00000000..ed048342 --- /dev/null +++ b/os_vif/objects/fields.py @@ -0,0 +1,64 @@ +# 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 + +from os_vif.i18n import _LE + +import re + + +class PCIAddress(fields.FieldType): + @staticmethod + def coerce(obj, attr, value): + m = "[0-9a-f]{1,4}:[0-9a-f]{1,2}:[0-9a-f]{1,2}.[0-9a-f]" + newvalue = value.lower() + if not re.match(m, newvalue): + raise ValueError(_LE("Malformed PCI address %s"), value) + return newvalue + + +class PCIAddressField(fields.AutoTypedField): + AUTO_TYPE = PCIAddress() + + +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() diff --git a/os_vif/objects/vif.py b/os_vif/objects/vif.py index 10bb0180..ed8ab27e 100644 --- a/os_vif/objects/vif.py +++ b/os_vif/objects/vif.py @@ -13,136 +13,174 @@ from oslo_versionedobjects import base from oslo_versionedobjects import fields -from os_vif import vnic_types - -# Constants for dictionary keys in the 'vif_details' field in the VIF -# class -VIF_DETAILS_OVS_HYBRID_PLUG = 'ovs_hybrid_plug' -VIF_DETAILS_PHYSICAL_NETWORK = 'physical_network' - -# The following two constants define the SR-IOV related fields in the -# 'vif_details'. 'profileid' should be used for VIF_TYPE_802_QBH, -# 'vlan' for VIF_TYPE_HW_VEB -VIF_DETAILS_PROFILEID = 'profileid' -VIF_DETAILS_VLAN = 'vlan' - -# Constants for vhost-user related fields in 'vif_details'. -# vhost-user socket path -VIF_DETAILS_VHOSTUSER_SOCKET = 'vhostuser_socket' -# Specifies whether vhost-user socket should be plugged -# into ovs bridge. Valid values are True and False -VIF_DETAILS_VHOSTUSER_OVS_PLUG = 'vhostuser_ovs_plug' -# Constants for vhost-user related fields in 'vif_details'. -# Sets mode on vhost-user socket, valid values are 'client' -# and 'server' -VIF_DETAILS_VHOSTUSER_MODE = 'vhostuser_mode' - -# Constant for max length of network interface names -# eg 'bridge' in the Network class or 'devname' in -# the VIF class -_NIC_NAME_LEN = 14 +from os_vif.objects import fields as osv_fields @base.VersionedObjectRegistry.register -class VIF(base.VersionedObject): +class VIFBase(base.VersionedObject, base.ComparableVersionedObject): """Represents a virtual network interface.""" # Version 1.0: Initial version VERSION = '1.0' fields = { + # Unique identifier of the VIF port 'id': fields.UUIDField(), + + # Metadata about the instance using the VIF 'instance_info': fields.ObjectField('InstanceInfo'), - 'ovs_interfaceid': fields.StringField(), - # MAC address - 'address': fields.StringField(nullable=True), + + # The guest MAC address + 'address': fields.MACAddressField(nullable=True), + + # The network to which the VIF is connected 'network': fields.ObjectField('Network', nullable=True), - # The name or alias of the plugin that should handle the VIF + + # Name of the registered os_vif plugin 'plugin': fields.StringField(), - 'details': fields.DictOfStringsField(nullable=True), - 'profile': fields.DictOfStringsField(nullable=True), - 'devname': fields.StringField(nullable=True), - 'vnic_type': fields.StringField(), - 'active': fields.BooleanField(), - 'preserve_on_delete': fields.BooleanField(), + + # 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) } - def __init__(self, id=None, address=None, network=None, plugin=None, - details=None, devname=None, ovs_interfaceid=None, - qbh_params=None, qbg_params=None, active=False, - vnic_type=vnic_types.NORMAL, profile=None, - preserve_on_delete=False, instance_info=None): - details = details or {} - ovs_id = ovs_interfaceid or id - if not devname: - devname = ("nic" + id)[:_NIC_NAME_LEN] - super(VIF, self).__init__(id=id, address=address, network=network, - plugin=plugin, details=details, - devname=devname, - ovs_interfaceid=ovs_id, - qbg_params=qbg_params, qbh_params=qbh_params, - active=active, vnic_type=vnic_type, - profile=profile, - preserve_on_delete=preserve_on_delete, - instance_info=instance_info, - ) - def devname_with_prefix(self, prefix): - """Returns the device name for the VIF, with the a replaced prefix.""" - return prefix + self.devname[3:] +@base.VersionedObjectRegistry.register +class VIFGeneric(VIFBase): + # For libvirt drivers, this maps to type="ethernet" which + # just implies a bare TAP device, all setup delegated to + # the plugin - # TODO(jaypipes): It's silly that there is a br_name and a (different) - # bridge_name attribute, but this comes from the original libvirt/vif.py. - # Clean this up and use better names for the attributes. - @property - def bridge_name(self): - return self.network.bridge + VERSION = '1.0' - @property - def br_name(self): - return ("qbr" + self.id)[:_NIC_NAME_LEN] + fields = { + # Name of the device to create + 'vif_name': fields.StringField() + } - @property - def veth_pair_names(self): - return (("qvb%s" % self.id)[:_NIC_NAME_LEN], - ("qvo%s" % self.id)[:_NIC_NAME_LEN]) - @property - def ovs_hybrid_plug(self): - return self.details.get(VIF_DETAILS_OVS_HYBRID_PLUG, False) +@base.VersionedObjectRegistry.register +class VIFBridge(VIFBase): + # For libvirt drivers, this maps to type='bridge' - @property - def physical_network(self): - phy_network = self.network['meta'].get('physical_network') - if not phy_network: - phy_network = self.details.get(VIF_DETAILS_PHYSICAL_NETWORK) - return phy_network + VERSION = '1.0' - @property - def profileid(self): - return self.details.get(VIF_DETAILS_PROFILEID) + fields = { + # Name of the virtual device to create + 'vif_name': fields.StringField(), - @property - def vlan(self): - return self.details.get(VIF_DETAILS_VLAN) + # Name of the physical device to connect to (eg br0) + 'bridge_name': fields.StringField(), + } - @property - def vhostuser_mode(self): - return self.details.get(VIF_DETAILS_VHOSTUSER_MODE) - @property - def vhostuser_socket(self): - return self.details.get(VIF_DETAILS_VHOSTUSER_SOCKET) +@base.VersionedObjectRegistry.register +class VIFOpenVSwitch(VIFBridge): + # For libvirt drivers, this also maps to type='bridge' - @property - def vhostuser_ovs_plug(self): - return self.details.get(VIF_DETAILS_VHOSTUSER_OVS_PLUG) + VERSION = '1.0' - @property - def fixed_ips(self): - return [fixed_ip for subnet in self.network['subnets'] - for fixed_ip in subnet['ips']] - @property - def floating_ips(self): - return [floating_ip for fixed_ip in self.fixed_ips - for floating_ip in fixed_ip['floating_ips']] +@base.VersionedObjectRegistry.register +class VIFDirect(VIFBase): + # For libvirt drivers, this maps to type='direct' + + VERSION = '1.0' + + fields = { + # Name of the device to create + 'vif_name': fields.StringField(), + + # Name of the physical device to connect to (eg eth0) + 'dev_name': fields.StringField(), + + # Port connection mode + 'mode': osv_fields.VIFDirectModeField(), + + # The VLAN number to use + 'vlan': fields.IntegerField(), + + # The VLAN device name to use + 'vlan_name': fields.StringField(), + } + + +@base.VersionedObjectRegistry.register +class VIFVHostUser(VIFBase): + # For libvirt drivers, this maps to type='vhostuser' + + VERSION = '1.0' + + fields = { + # UNIX socket path + 'path': fields.StringField(), + + # UNIX socket access permissions + 'mode': osv_fields.VIFVHostUserModeField(), + } + + +@base.VersionedObjectRegistry.register +class VIFHostDevice(VIFBase): + # For libvirt drivers, this maps to type='hostdev' + + VERSION = '1.0' + + fields = { + # The PCI address of the host device + 'dev_address': osv_fields.PCIAddressField(), + + # The VLAN number to use + 'vlan': fields.IntegerField(), + } + + +@base.VersionedObjectRegistry.register +class VIFPortProfileBase(base.VersionedObject, base.ComparableVersionedObject): + # Base class for all types of port profile + VERSION = '1.0' + + +@base.VersionedObjectRegistry.register +class VIFPortProfileOpenVSwitch(VIFPortProfileBase): + # Port profile info for OpenVSwitch networks + + VERSION = '1.0' + + fields = { + 'interface_id': fields.UUIDField(), + 'profile_id': fields.StringField(), + } + + +@base.VersionedObjectRegistry.register +class VIFPortProfile8021Qbg(VIFPortProfileBase): + # Port profile info for VEPA 802.1qbg networks + + VERSION = '1.0' + + fields = { + 'manager_id': fields.IntegerField(), + 'type_id': fields.IntegerField(), + 'type_id_version': fields.IntegerField(), + 'instance_id': fields.UUIDField(), + } + + +@base.VersionedObjectRegistry.register +class VIFPortProfile8021Qbh(VIFPortProfileBase): + # Port profile info for VEPA 802.1qbh networks + + VERSION = '1.0' + + fields = { + 'profile_id': fields.StringField() + } diff --git a/os_vif/plugin.py b/os_vif/plugin.py index fe9ea48f..75e1e7e6 100644 --- a/os_vif/plugin.py +++ b/os_vif/plugin.py @@ -15,28 +15,39 @@ import abc import six +class PluginVIFInfo(object): + """ + Class describing the plugin and the versions of VIF object it understands. + """ + + def __init__(self, vif_class, min_version, max_version): + """ + Constructs the PluginInfo object. + + :param vif_class: subclass of os_vif.objects.vif.VIF that is supported + :param min_version: String representing the earliest version of + @vif_class that the plugin understands. + :param_max_version: String representing the latest version of + @vif_class that the plugin understands. + """ + self.vif_class = vif_class + self.min_version = min_version + self.max_version = max_version + + class PluginInfo(object): """ Class describing the plugin and the versions of VIF object it understands. """ - def __init__(self, vif_types, vif_object_min_version, - vif_object_max_version): + def __init__(self, vif_info): """ Constructs the PluginInfo object. - :param vif_types: set of strings identifying the VIF types that are - implemented by the plugin. - :param vif_object_min_version: String representing the earliest version - of the `os_vif.objects.VIF` object that the plugin - understands. - :param vif_object_max_version: String representing the latest version - of the `os_vif.objects.VIF` object that the plugin - understands. + :param vif_info: list of PluginVIFInfo instances supported by the + plugin """ - self.vif_types = vif_types - self.vif_object_min_version = vif_object_min_version - self.vif_object_max_version = vif_object_max_version + self.vif_info = vif_info @six.add_metaclass(abc.ABCMeta) diff --git a/os_vif/tests/test_os_vif.py b/os_vif/tests/test_os_vif.py index f7a02d26..09e08a81 100644 --- a/os_vif/tests/test_os_vif.py +++ b/os_vif/tests/test_os_vif.py @@ -50,9 +50,9 @@ class TestOSVIF(base.TestCase): os_vif.initialize() instance = mock.MagicMock() info = objects.instance_info.InstanceInfo() - vif = objects.vif.VIF(id='uniq', - plugin='foobar', - instance_info=info) + vif = objects.vif.VIFBridge(id='uniq', + plugin='foobar', + instance_info=info) os_vif.plug(vif, instance) plugin.plug.assert_called_once_with(vif, instance) @@ -62,8 +62,8 @@ class TestOSVIF(base.TestCase): return_value={'foobar': plugin}): os_vif.initialize() info = objects.instance_info.InstanceInfo() - vif = objects.vif.VIF(id='uniq', - plugin='foobar', - instance_info=info) + vif = objects.vif.VIFBridge(id='uniq', + plugin='foobar', + instance_info=info) os_vif.unplug(vif) plugin.unplug.assert_called_once_with(vif) diff --git a/os_vif/tests/test_vif.py b/os_vif/tests/test_vif.py new file mode 100644 index 00000000..de951d8c --- /dev/null +++ b/os_vif/tests/test_vif.py @@ -0,0 +1,91 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os_vif +from os_vif import objects +from os_vif.tests 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() + vif2 = objects.vif.VIFBase.obj_from_primitive(prim) + + self.assertEqual(vif, vif2) + + def test_vif_generic(self): + self._test_vif(objects.vif.VIFGeneric, + vif_name="vif123") + + def test_vif_bridge_plain(self): + self._test_vif(objects.vif.VIFBridge, + vif_name="vif123", + bridge_name="br0") + + def test_vif_bridge_ovs(self): + prof = objects.vif.VIFPortProfileOpenVSwitch( + interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", + profile_id="fishfood") + self._test_vif(objects.vif.VIFOpenVSwitch, + vif_name="vif123", + bridge_name="br0", + port_profile=prof) + + def test_vif_direct_plain(self): + self._test_vif(objects.vif.VIFDirect, + vif_name="vif123", + dev_name="eth0") + + 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", + dev_name="eth0", + port_profile=prof) + + def test_vif_direct_vepa_qbh(self): + prof = objects.vif.VIFPortProfile8021Qbh( + profile_id="fishfood") + self._test_vif(objects.vif.VIFDirect, + vif_name="vif123", + dev_name="eth0", + port_profile=prof) + + def test_vif_vhost_user(self): + self._test_vif(objects.vif.VIFVHostUser, + path="/some/socket.path", + mode=objects.fields.VIFVHostUserMode.CLIENT) + + def test_vif_host_dev_plain(self): + self._test_vif(objects.vif.VIFHostDevice, + dev_address="02:24:22.3", + vlan=8) + + def test_vif_host_dev_vepa_qbh(self): + prof = objects.vif.VIFPortProfile8021Qbh( + profile_id="fishfood") + self._test_vif(objects.vif.VIFHostDevice, + dev_address="02:24:22.3", + vlan=8, + port_profile=prof) diff --git a/os_vif/vnic_types.py b/os_vif/vnic_types.py deleted file mode 100644 index bd91b4ff..00000000 --- a/os_vif/vnic_types.py +++ /dev/null @@ -1,17 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# Define supported virtual NIC types. DIRECT and MACVTAP -# are used for SR-IOV ports -NORMAL = 'normal' -DIRECT = 'direct' -MACVTAP = 'macvtap'