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
This commit is contained in:
Daniel P. Berrange 2015-11-10 13:46:50 +00:00
parent 330eaaa97c
commit 11af752a5a
7 changed files with 335 additions and 156 deletions

View File

@ -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.

64
os_vif/objects/fields.py Normal file
View File

@ -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()

View File

@ -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()
}

View File

@ -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)

View File

@ -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)

91
os_vif/tests/test_vif.py Normal file
View File

@ -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)

View File

@ -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'