From 11af752a5af8a58a4f8fd5af196d1fce7729463d Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Tue, 10 Nov 2015 13:46:50 +0000 Subject: [PATCH] Add formal classes for each of the types of VIF backend config The current VIF object model is just a direct representation of the ill-defined nova.network.model.VIF class. Many of the attributes are only relevant for certain VIF types. Other attributes are just indirectly representing different plugin schemes (eg OVS hybrid vs direct should be done as two plugins, not a boolean on the VIF object). Some of the attributes are generic metadata related to the network port that can be associated with multiple VIF types regardless of how the port is connected to the guest. This refactors the VIF class so that there is a base class defining the common data, and then subclasses providing the VIF type specific data. There are initially 5 core VIF backend class defined, which are sufficient to cover all the current usage in the libvirt driver and some usage in other drivers. It is expected that a couple more VIF types may be added for vmware/hyper, when those drivers are later converted. The generic network port profile data is represented by the new VIFPortProfileBase class and its subclasses. The various property/methods which were defined are also removed as most of this is logic that belongs in the corresponding vif plugin implementation, not on the core data model. Change-Id: Id286f85cd5fe7ca80f02d95f6380979a0d920ef6 --- README.rst | 20 +-- os_vif/objects/fields.py | 64 +++++++++ os_vif/objects/vif.py | 250 +++++++++++++++++++++--------------- os_vif/plugin.py | 37 ++++-- os_vif/tests/test_os_vif.py | 12 +- os_vif/tests/test_vif.py | 91 +++++++++++++ os_vif/vnic_types.py | 17 --- 7 files changed, 335 insertions(+), 156 deletions(-) create mode 100644 os_vif/objects/fields.py create mode 100644 os_vif/tests/test_vif.py delete mode 100644 os_vif/vnic_types.py 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'