From a8a0d89fdd7d8b85d64368747618bdf38199b9c3 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Tue, 19 Jan 2016 14:17:35 +0000 Subject: [PATCH] network: introduce helper APIs for dealing with os-vif objects This introduces a os_vif_utils module to nova/network which contains logic for converting between the existing Nova python objects in the nova.network.model namespace, and the os-vif objects under os_vif.objects The conversion is currently only wired up for the Linux Bridge and OVS VIF types, which are directly supported by os-vif. Conversion of other types will follow as & when os-vif plugins are created for them. This object model conversion is only expected to exist for a short-term. The intention is that Neutron will later be converted so that it sends across a serialized os_vif.objects.vif.VIFBase object, instead of the port binding dict. When this happens, Nova will be able to directly deserialize the os-vif objects, skipping all use of the nova.network.model objects. As part of using os-vif, each registered plugin gets the ability to read config parameters from nova.conf in a private section named 'vif_plug_'. For example the OpenVSwitch plugin will be in 'vif_plug_ovs'. Plugins which make use of oslo.privsep for running some privileged code will have a further private section in nova.conf named 'vif_plug__privileged' from which the privsep library will pull its configuration. It is unlikely that operators will need to set any params in this section, since the defaults should work optimally for almost all scenarios. Blueprint: os-vif-library Change-Id: Id78858bc55ce4f3f0727cad644ef9c8f9f718556 --- nova/network/os_vif_util.py | 375 ++++++++++++ nova/tests/unit/network/test_os_vif_util.py | 550 ++++++++++++++++++ .../objects/test_notification.py | 2 +- nova/tests/unit/objects/test_objects.py | 23 +- requirements.txt | 1 + 5 files changed, 949 insertions(+), 2 deletions(-) create mode 100644 nova/network/os_vif_util.py create mode 100644 nova/tests/unit/network/test_os_vif_util.py diff --git a/nova/network/os_vif_util.py b/nova/network/os_vif_util.py new file mode 100644 index 000000000000..3ca431aef4f1 --- /dev/null +++ b/nova/network/os_vif_util.py @@ -0,0 +1,375 @@ +# 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. + +''' +This module contains code for converting from the original +nova.network.model data structure, to the new os-vif based +versioned object model os_vif.objects.* +''' + +import sys + +import os_vif +from os_vif import objects +from oslo_config import cfg +from oslo_log import log as logging + +from nova import exception +from nova.network import model + + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + +# Ensure os-vif objects are registered and plugins loaded +os_vif.initialize() + + +def _get_vif_name(vif): + """Get a VIF device name + + :param vif: the nova.nework.model.VIF instance + + Get a string suitable for use as a host OS network + device name + + :returns: a device name + """ + + if vif.get('devname', None) is not None: + return vif['devname'] + return ('nic' + vif['id'])[:model.NIC_NAME_LEN] + + +def _get_hybrid_bridge_name(vif): + """Get a bridge device name + + :param vif: the nova.nework.model.VIF instance + + Get a string suitable for use as a host OS bridge + device name + + :returns: a bridge name + """ + + return ('qbr' + vif['id'])[:model.NIC_NAME_LEN] + + +def _is_firewall_required(vif): + """Check if local firewall is required + + :param vif: the nova.nework.model.VIF instance + + :returns: True if local firewall is required + """ + + if vif.is_neutron_filtering_enabled(): + return False + if CONF.firewall_driver != "nova.virt.firewall.NoopFirewallDriver": + return True + return False + + +def nova_to_osvif_instance(instance): + """Convert a Nova instance object to an os-vif instance object + + :param vif: a nova.objects.Instance instance + + :returns: a os_vif.objects.instance_info.InstanceInfo + """ + + info = objects.instance_info.InstanceInfo( + uuid=instance.uuid, + name=instance.name) + + if (instance.obj_attr_is_set("project_id") and + instance.project_id is not None): + info.project_id = instance.project_id + + return info + + +def _nova_to_osvif_ip(ip): + """Convert Nova IP object into os_vif object + + :param route: nova.network.model.IP instance + + :returns: os_vif.objects.fixed_ip.FixedIP instance + """ + floating_ips = [fip['address'] for fip in ip.get('floating_ips', [])] + return objects.fixed_ip.FixedIP( + address=ip['address'], + floating_ips=floating_ips) + + +def _nova_to_osvif_ips(ips): + """Convert Nova IP list into os_vif object + + :param routes: list of nova.network.model.IP instances + + :returns: os_vif.objects.fixed_ip.FixedIPList instance + """ + + return objects.fixed_ip.FixedIPList( + objects=[_nova_to_osvif_ip(ip) for ip in ips]) + + +def _nova_to_osvif_route(route): + """Convert Nova route object into os_vif object + + :param route: nova.network.model.Route instance + + :returns: os_vif.objects.route.Route instance + """ + + obj = objects.route.Route( + cidr=route['cidr'], + interface=route['interface']) + + if (route['gateway'] is not None and + route['gateway']['address'] is not None): + obj.gateway = route['gateway']['address'] + + return obj + + +def _nova_to_osvif_routes(routes): + """Convert Nova route list into os_vif object + + :param routes: list of nova.network.model.Route instances + + :returns: os_vif.objects.route.RouteList instance + """ + + return objects.route.RouteList( + objects=[_nova_to_osvif_route(route) for route in routes]) + + +def _nova_to_osvif_subnet(subnet): + """Convert Nova subnet object into os_vif object + + :param subnet: nova.network.model.Subnet instance + + :returns: os_vif.objects.subnet.Subnet instance + """ + + dnsaddrs = [ip['address'] for ip in subnet['dns']] + + obj = objects.subnet.Subnet( + dns=dnsaddrs, + ips=_nova_to_osvif_ips(subnet['ips']), + routes=_nova_to_osvif_routes(subnet['routes'])) + if subnet['cidr'] is not None: + obj.cidr = subnet['cidr'] + if (subnet['gateway'] is not None and + subnet['gateway']['address'] is not None): + obj.gateway = subnet['gateway']['address'] + return obj + + +def _nova_to_osvif_subnets(subnets): + """Convert Nova subnet list into os_vif object + + :param subnets: list of nova.network.model.Subnet instances + + :returns: os_vif.objects.subnet.SubnetList instance + """ + + return objects.subnet.SubnetList( + objects=[_nova_to_osvif_subnet(subnet) for subnet in subnets]) + + +def _nova_to_osvif_network(network): + """Convert Nova network object into os_vif object + + :param network: nova.network.model.Network instance + + :returns: os_vif.objects.network.Network instance + """ + + netobj = objects.network.Network( + id=network['id'], + subnets=_nova_to_osvif_subnets(network['subnets'])) + + if network["bridge"] is not None: + netobj.bridge = network['bridge'] + if network['label'] is not None: + netobj.label = network['label'] + + if network.get_meta("multi_host") is not None: + netobj.multi_host = network.get_meta("multi_host") + if network.get_meta("should_provide_bridge") is not None: + netobj.should_provide_bridge = \ + network.get_meta("should_provide_bridge") + if network.get_meta("bridge_interface") is not None: + netobj.bridge_interface = network.get_meta("bridge_interface") + if network.get_meta("should_provide_vlan") is not None: + netobj.should_provide_vlan = network.get_meta("should_provide_vlan") + if network.get_meta("vlan") is not None: + netobj.vlan = network.get_meta("vlan") + + return netobj + + +def _get_vif_instance(vif, cls, **kwargs): + """Instantiate an os-vif VIF instance + + :param vif: the nova.nework.model.VIF instance + :param cls: class for a os_vif.objects.vif.VIFBase subclass + + :returns: a os_vif.objects.vif.VIFBase instance + """ + + return cls( + id=vif['id'], + address=vif['address'], + network=_nova_to_osvif_network(vif['network']), + has_traffic_filtering=vif.is_neutron_filtering_enabled(), + preserve_on_delete=vif['preserve_on_delete'], + active=vif['active'], + **kwargs) + + +# VIF_TYPE_BRIDGE = 'bridge' +def _nova_to_osvif_vif_bridge(vif): + obj = _get_vif_instance( + vif, + objects.vif.VIFBridge, + plugin="linux_bridge", + vif_name=_get_vif_name(vif)) + if vif["network"]["bridge"] is not None: + obj.bridge_name = vif["network"]["bridge"] + return obj + + +# VIF_TYPE_OVS = 'ovs' +def _nova_to_osvif_vif_ovs(vif): + profile = objects.vif.VIFPortProfileOpenVSwitch( + interface_id=vif.get('ovs_interfaceid') or vif['id']) + if _is_firewall_required(vif) or vif.is_hybrid_plug_enabled(): + obj = _get_vif_instance( + vif, + objects.vif.VIFBridge, + port_profile=profile, + plugin="ovs", + vif_name=_get_vif_name(vif), + bridge_name=_get_hybrid_bridge_name(vif)) + else: + obj = _get_vif_instance( + vif, + objects.vif.VIFOpenVSwitch, + port_profile=profile, + plugin="ovs", + vif_name=_get_vif_name(vif)) + if vif["network"]["bridge"] is not None: + obj.bridge_name = vif["network"]["bridge"] + return obj + + +# VIF_TYPE_IVS = 'ivs' +def _nova_to_osvif_vif_ivs(vif): + raise NotImplementedError() + + +# VIF_TYPE_DVS = 'dvs' +def _nova_to_osvif_vif_dvs(vif): + raise NotImplementedError() + + +# VIF_TYPE_IOVISOR = 'iovisor' +def _nova_to_osvif_vif_iovisor(vif): + raise NotImplementedError() + + +# VIF_TYPE_802_QBG = '802.1qbg' +def _nova_to_osvif_vif_802_1qbg(vif): + raise NotImplementedError() + + +# VIF_TYPE_802_QBH = '802.1qbh' +def _nova_to_osvif_vif_802_1qbh(vif): + raise NotImplementedError() + + +# VIF_TYPE_HW_VEB = 'hw_veb' +def _nova_to_osvif_vif_hw_veb(vif): + raise NotImplementedError() + + +# VIF_TYPE_IB_HOSTDEV = 'ib_hostdev' +def _nova_to_osvif_vif_ib_hostdev(vif): + raise NotImplementedError() + + +# VIF_TYPE_MIDONET = 'midonet' +def _nova_to_osvif_vif_midonet(vif): + raise NotImplementedError() + + +# VIF_TYPE_VHOSTUSER = 'vhostuser' +def _nova_to_osvif_vif_vhostuser(vif): + raise NotImplementedError() + + +# VIF_TYPE_VROUTER = 'vrouter' +def _nova_to_osvif_vif_vrouter(vif): + raise NotImplementedError() + + +# VIF_TYPE_TAP = 'tap' +def _nova_to_osvif_vif_tap(vif): + raise NotImplementedError() + + +# VIF_TYPE_MACVTAP = 'macvtap' +def _nova_to_osvif_vif_macvtap(vif): + raise NotImplementedError() + + +# VIF_TYPE_HOSTDEV = 'hostdev_physical' +def _nova_to_osvif_vif_hostdev_physical(vif): + raise NotImplementedError() + + +def nova_to_osvif_vif(vif): + """Convert a Nova VIF model to an os-vif object + + :param vif: a nova.network.model.VIF instance + + Attempt to convert a nova VIF instance into an os-vif + VIF object, pointing to a suitable plugin. This will + return None if there is no os-vif plugin available yet. + + :returns: a os_vif.objects.vif.VIFBase subclass, or None + if not supported with os-vif yet + """ + + LOG.debug("Converting VIF %s", vif) + + funcname = "_nova_to_osvif_vif_" + vif['type'].replace(".", "_") + func = getattr(sys.modules[__name__], funcname, None) + + if not func: + raise exception.NovaException( + "Unsupported VIF type %(type)s convert '%(func)s'" % + {'type': vif['type'], 'func': funcname}) + + try: + vifobj = func(vif) + LOG.debug("Converted object %s", vifobj) + return vifobj + except NotImplementedError: + LOG.debug("No conversion for VIF type %s yet", + vif['type']) + return None diff --git a/nova/tests/unit/network/test_os_vif_util.py b/nova/tests/unit/network/test_os_vif_util.py new file mode 100644 index 000000000000..944f87ff27b7 --- /dev/null +++ b/nova/tests/unit/network/test_os_vif_util.py @@ -0,0 +1,550 @@ +# 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 os_vif import objects as osv_objects + +from nova import exception +from nova.network import model +from nova.network import os_vif_util +from nova import objects +from nova import test + + +class OSVIFUtilTestCase(test.NoDBTestCase): + + def setUp(self): + super(OSVIFUtilTestCase, self).setUp() + + osv_objects.register_all() + + # Remove when all os-vif objects include the + # ComparableVersionedObject mix-in + def assertObjEqual(self, expect, actual): + actual.obj_reset_changes(recursive=True) + expect.obj_reset_changes(recursive=True) + self.assertEqual(expect.obj_to_primitive(), + actual.obj_to_primitive()) + + def _test_is_firewall_required(self, port_filter, driver, expect): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_BRIDGE, + address="22:52:25:62:e2:aa", + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + details={ + model.VIF_DETAILS_PORT_FILTER: port_filter, + } + ) + self.flags(firewall_driver=driver) + + self.assertEqual(expect, os_vif_util._is_firewall_required(vif)) + + def test_is_firewall_required_via_vif(self): + self._test_is_firewall_required( + True, "nova.virt.libvirt.firewall.IptablesFirewallDriver", False) + + def test_is_firewall_required_via_driver(self): + self._test_is_firewall_required( + False, "nova.virt.libvirt.firewall.IptablesFirewallDriver", True) + + def test_is_firewall_required_not(self): + self._test_is_firewall_required( + False, "nova.virt.firewall.NoopFirewallDriver", False) + + def test_nova_to_osvif_instance(self): + inst = objects.Instance( + id="1242", + uuid="d5b1090c-9e00-4fa4-9504-4b1494857970", + project_id="2f37d7f6-e51a-4a1f-8b6e-b0917ffc8390") + + info = os_vif_util.nova_to_osvif_instance(inst) + + expect = osv_objects.instance_info.InstanceInfo( + uuid="d5b1090c-9e00-4fa4-9504-4b1494857970", + name="instance-000004da", + project_id="2f37d7f6-e51a-4a1f-8b6e-b0917ffc8390") + + self.assertObjEqual(info, expect) + + def test_nova_to_osvif_instance_minimal(self): + inst = objects.Instance( + id="1242", + uuid="d5b1090c-9e00-4fa4-9504-4b1494857970") + + actual = os_vif_util.nova_to_osvif_instance(inst) + + expect = osv_objects.instance_info.InstanceInfo( + uuid=inst.uuid, + name=inst.name) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_ips(self): + ips = [ + model.FixedIP( + address="192.168.122.24", + floating_ips=[ + model.IP(address="192.168.122.100", + type="floating"), + model.IP(address="192.168.122.101", + type="floating"), + model.IP(address="192.168.122.102", + type="floating"), + ], + version=4), + model.FixedIP( + address="2001::beef", + version=6), + ] + + actual = os_vif_util._nova_to_osvif_ips(ips) + + expect = osv_objects.fixed_ip.FixedIPList( + objects=[ + osv_objects.fixed_ip.FixedIP( + address="192.168.122.24", + floating_ips=[ + "192.168.122.100", + "192.168.122.101", + "192.168.122.102", + ]), + osv_objects.fixed_ip.FixedIP( + address="2001::beef", + floating_ips=[]), + ], + ) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_routes(self): + routes = [ + model.Route(cidr="192.168.1.0/24", + gateway=model.IP( + address="192.168.1.254", + type='gateway'), + interface="eth0"), + model.Route(cidr="10.0.0.0/8", + gateway=model.IP( + address="10.0.0.1", + type='gateway'), + interface="eth1"), + ] + + expect = osv_objects.route.RouteList( + objects=[ + osv_objects.route.Route( + cidr="192.168.1.0/24", + gateway="192.168.1.254", + interface="eth0"), + osv_objects.route.Route( + cidr="10.0.0.0/8", + gateway="10.0.0.1", + interface="eth1"), + ]) + + actual = os_vif_util._nova_to_osvif_routes(routes) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_subnets(self): + subnets = [ + model.Subnet(cidr="192.168.1.0/24", + dns=[ + model.IP( + address="192.168.1.1", + type="dns"), + model.IP( + address="192.168.1.2", + type="dns"), + ], + gateway=model.IP( + address="192.168.1.254", + type='gateway'), + ips=[ + model.FixedIP( + address="192.168.1.100", + ), + model.FixedIP( + address="192.168.1.101", + ), + ], + routes=[ + model.Route( + cidr="10.0.0.1/24", + gateway=model.IP( + address="192.168.1.254", + type="gateway"), + interface="eth0"), + ]), + model.Subnet(dns=[ + model.IP( + address="192.168.1.1", + type="dns"), + model.IP( + address="192.168.1.2", + type="dns"), + ], + ips=[ + model.FixedIP( + address="192.168.1.100", + ), + model.FixedIP( + address="192.168.1.101", + ), + ], + routes=[ + model.Route( + cidr="10.0.0.1/24", + gateway=model.IP( + address="192.168.1.254", + type="gateway"), + interface="eth0"), + ]), + model.Subnet(dns=[ + model.IP( + address="192.168.1.1", + type="dns"), + model.IP( + address="192.168.1.2", + type="dns"), + ], + gateway=model.IP( + type='gateway'), + ips=[ + model.FixedIP( + address="192.168.1.100", + ), + model.FixedIP( + address="192.168.1.101", + ), + ], + routes=[ + model.Route( + cidr="10.0.0.1/24", + gateway=model.IP( + address="192.168.1.254", + type="gateway"), + interface="eth0"), + ]), + ] + + expect = osv_objects.subnet.SubnetList( + objects=[ + osv_objects.subnet.Subnet( + cidr="192.168.1.0/24", + dns=["192.168.1.1", + "192.168.1.2"], + gateway="192.168.1.254", + ips=osv_objects.fixed_ip.FixedIPList( + objects=[ + osv_objects.fixed_ip.FixedIP( + address="192.168.1.100", + floating_ips=[]), + osv_objects.fixed_ip.FixedIP( + address="192.168.1.101", + floating_ips=[]), + ]), + routes=osv_objects.route.RouteList( + objects=[ + osv_objects.route.Route( + cidr="10.0.0.1/24", + gateway="192.168.1.254", + interface="eth0") + ]), + ), + osv_objects.subnet.Subnet( + dns=["192.168.1.1", + "192.168.1.2"], + ips=osv_objects.fixed_ip.FixedIPList( + objects=[ + osv_objects.fixed_ip.FixedIP( + address="192.168.1.100", + floating_ips=[]), + osv_objects.fixed_ip.FixedIP( + address="192.168.1.101", + floating_ips=[]), + ]), + routes=osv_objects.route.RouteList( + objects=[ + osv_objects.route.Route( + cidr="10.0.0.1/24", + gateway="192.168.1.254", + interface="eth0") + ]), + ), + osv_objects.subnet.Subnet( + dns=["192.168.1.1", + "192.168.1.2"], + ips=osv_objects.fixed_ip.FixedIPList( + objects=[ + osv_objects.fixed_ip.FixedIP( + address="192.168.1.100", + floating_ips=[]), + osv_objects.fixed_ip.FixedIP( + address="192.168.1.101", + floating_ips=[]), + ]), + routes=osv_objects.route.RouteList( + objects=[ + osv_objects.route.Route( + cidr="10.0.0.1/24", + gateway="192.168.1.254", + interface="eth0") + ]), + ), + ]) + + actual = os_vif_util._nova_to_osvif_subnets(subnets) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_network(self): + network = model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + bridge="br0", + subnets=[ + model.Subnet(cidr="192.168.1.0/24", + gateway=model.IP( + address="192.168.1.254", + type='gateway')), + ]) + + expect = osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + bridge="br0", + subnets=osv_objects.subnet.SubnetList( + objects=[ + osv_objects.subnet.Subnet( + cidr="192.168.1.0/24", + dns=[], + gateway="192.168.1.254", + ips=osv_objects.fixed_ip.FixedIPList( + objects=[]), + routes=osv_objects.route.RouteList( + objects=[]), + ) + ])) + + actual = os_vif_util._nova_to_osvif_network(network) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_network_extra(self): + network = model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + bridge="br0", + multi_host=True, + should_provide_bridge=True, + should_provide_vlan=True, + bridge_interface="eth0", + vlan=1729, + subnets=[ + model.Subnet(cidr="192.168.1.0/24", + gateway=model.IP( + address="192.168.1.254", + type='gateway')), + ]) + + expect = osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + bridge="br0", + multi_host=True, + should_provide_bridge=True, + should_provide_vlan=True, + bridge_interface="eth0", + vlan=1729, + subnets=osv_objects.subnet.SubnetList( + objects=[ + osv_objects.subnet.Subnet( + cidr="192.168.1.0/24", + dns=[], + gateway="192.168.1.254", + ips=osv_objects.fixed_ip.FixedIPList( + objects=[]), + routes=osv_objects.route.RouteList( + objects=[]), + ) + ])) + + actual = os_vif_util._nova_to_osvif_network(network) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_network_labeled_no_bridge(self): + network = model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[ + model.Subnet(cidr="192.168.1.0/24", + gateway=model.IP( + address="192.168.1.254", + type='gateway')), + ]) + + expect = osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=osv_objects.subnet.SubnetList( + objects=[ + osv_objects.subnet.Subnet( + cidr="192.168.1.0/24", + dns=[], + gateway="192.168.1.254", + ips=osv_objects.fixed_ip.FixedIPList( + objects=[]), + routes=osv_objects.route.RouteList( + objects=[]), + ) + ])) + + actual = os_vif_util._nova_to_osvif_network(network) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_vif_linux_bridge(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_BRIDGE, + address="22:52:25:62:e2:aa", + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + details={ + model.VIF_DETAILS_PORT_FILTER: True, + } + ) + + actual = os_vif_util.nova_to_osvif_vif(vif) + + expect = osv_objects.vif.VIFBridge( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + active=False, + address="22:52:25:62:e2:aa", + has_traffic_filtering=True, + plugin="linux_bridge", + preserve_on_delete=False, + vif_name="nicdc065497-3c", + network=osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=osv_objects.subnet.SubnetList( + objects=[]))) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_vif_ovs_plain(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_OVS, + address="22:52:25:62:e2:aa", + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + details={ + model.VIF_DETAILS_PORT_FILTER: True, + } + ) + + actual = os_vif_util.nova_to_osvif_vif(vif) + + expect = osv_objects.vif.VIFOpenVSwitch( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + active=False, + address="22:52:25:62:e2:aa", + has_traffic_filtering=True, + plugin="ovs", + port_profile=osv_objects.vif.VIFPortProfileOpenVSwitch( + interface_id="dc065497-3c8d-4f44-8fb4-e1d33c16a536"), + preserve_on_delete=False, + vif_name="nicdc065497-3c", + network=osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=osv_objects.subnet.SubnetList( + objects=[]))) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_vif_ovs_hybrid(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_OVS, + address="22:52:25:62:e2:aa", + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + details={ + model.VIF_DETAILS_PORT_FILTER: False, + } + ) + + actual = os_vif_util.nova_to_osvif_vif(vif) + + expect = osv_objects.vif.VIFBridge( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + active=False, + address="22:52:25:62:e2:aa", + has_traffic_filtering=False, + plugin="ovs", + bridge_name="qbrdc065497-3c", + port_profile=osv_objects.vif.VIFPortProfileOpenVSwitch( + interface_id="dc065497-3c8d-4f44-8fb4-e1d33c16a536"), + preserve_on_delete=False, + vif_name="nicdc065497-3c", + network=osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=osv_objects.subnet.SubnetList( + objects=[]))) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_vif_ivs_plain(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_IVS, + address="22:52:25:62:e2:aa", + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + details={ + model.VIF_DETAILS_PORT_FILTER: True, + } + ) + + actual = os_vif_util.nova_to_osvif_vif(vif) + + self.assertIsNone(actual) + + def test_nova_to_osvif_vif_unknown(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type="wibble", + address="22:52:25:62:e2:aa", + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + ) + + self.assertRaises(exception.NovaException, + os_vif_util.nova_to_osvif_vif, + vif) diff --git a/nova/tests/unit/notifications/objects/test_notification.py b/nova/tests/unit/notifications/objects/test_notification.py index 2f9e2afced4f..7a0a920daa82 100644 --- a/nova/tests/unit/notifications/objects/test_notification.py +++ b/nova/tests/unit/notifications/objects/test_notification.py @@ -281,7 +281,7 @@ class TestNotificationObjectVersions(test.NoDBTestCase): def test_versions(self): checker = fixture.ObjectVersionChecker( - base.NovaObjectRegistry.obj_classes()) + test_objects.get_nova_objects()) notification_object_data.update(test_objects.object_data) expected, actual = checker.test_hashes(notification_object_data) self.assertEqual(expected, actual, diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index d8c76d204ada..db0dccb2f844 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1206,10 +1206,31 @@ object_data = { } +def get_nova_objects(): + """Get Nova versioned objects + + This returns a dict of versioned objects which are + in the Nova project namespace only. ie excludes + objects from os-vif and other 3rd party modules + + :return: a dict mapping class names to lists of versioned objects + """ + + all_classes = base.NovaObjectRegistry.obj_classes() + nova_classes = {} + for name in all_classes: + objclasses = all_classes[name] + if (objclasses[0].OBJ_PROJECT_NAMESPACE != + base.NovaObject.OBJ_PROJECT_NAMESPACE): + continue + nova_classes[name] = objclasses + return nova_classes + + class TestObjectVersions(test.NoDBTestCase): def test_versions(self): checker = fixture.ObjectVersionChecker( - base.NovaObjectRegistry.obj_classes()) + get_nova_objects()) fingerprints = checker.get_hashes() if os.getenv('GENERATE_HASHES'): diff --git a/requirements.txt b/requirements.txt index a059d4c20f87..afa562107c65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -54,6 +54,7 @@ oslo.middleware>=3.0.0 # Apache-2.0 psutil<2.0.0,>=1.1.1 # BSD oslo.versionedobjects>=1.13.0 # Apache-2.0 os-brick>=1.3.0 # Apache-2.0 +os-vif>=1.0.0 # Apache-2.0 os-win>=0.2.3 # Apache-2.0 castellan>=0.4.0 # Apache-2.0 microversion-parse>=0.1.2 # Apache-2.0