Edward Haas has uploaded a new change for review. Change subject: net: Libvirt hook that enables ovs-legacy migration ......................................................................
net: Libvirt hook that enables ovs-legacy migration This hook allows VM/s to be migrated between OVS based hosts and legacy based hosts, in both directions. The hook is added to the OVS hook package. Change-Id: I29cf441cc365d3679382e44410dad0906d9be3ec Signed-off-by: Edward Haas <edwa...@redhat.com> --- A tests/network/hook_ovs_test.py M vdsm.spec.in M vdsm/virt/libvirt-hook.sh M vdsm_hooks/ovs/Makefile.am A vdsm_hooks/ovs/ovs_migrate.py 5 files changed, 277 insertions(+), 1 deletion(-) git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/97/55497/1 diff --git a/tests/network/hook_ovs_test.py b/tests/network/hook_ovs_test.py new file mode 100644 index 0000000..9f6318e --- /dev/null +++ b/tests/network/hook_ovs_test.py @@ -0,0 +1,173 @@ +# Copyright 2016 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Refer to the README and COPYING files for full details of the license +# +from __future__ import absolute_import + +from StringIO import StringIO +import sys +import re + +from nose.plugins.attrib import attr + +from testlib import VdsmTestCase +from monkeypatch import MonkeyPatchScope + +from vdsm import netconfpersistence + +sys.path.append('../vdsm_hooks/ovs') # Source Location +sys.path.append('../../vdsm_hooks/ovs') # Source Location +sys.path.append('/usr/libexec/vdsm/') # Deploy Location +try: + import ovs_utils +except: + # Assuming that the failure is due to ovs-vsctl missing (ovs not installed) + from vdsm.utils import CommandPath + with MonkeyPatchScope([(CommandPath, 'cmd', None)]): + import ovs_utils +import ovs_migrate + + +@attr(type='unit') +class TestOvsHookMigration(VdsmTestCase): + + class MockRunningConfigNoVlan: + def __init__(self): + self.networks = {'testnet0': {'vlan': None}} + + class MockRunningConfigVlan: + def __init__(self): + self.networks = {'testnet0': {'vlan': 101}} + + def test_legacy_to_legacy_non_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_LEGACY, + to_xml=LIBVIRT_XML_DESCR_LEGACY, + is_destibation_ovs=False, + is_vlan_net=False, + normalize=False) + + def test_legacy_to_legacy_with_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_LEGACY, + to_xml=LIBVIRT_XML_DESCR_LEGACY, + is_destibation_ovs=False, + is_vlan_net=True, + normalize=False) + + def test_ovs_to_ovs_non_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_OVS, + to_xml=LIBVIRT_XML_DESCR_OVS, + is_destibation_ovs=True, + is_vlan_net=False, + normalize=False) + + def test_ovs_to_ovs_with_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_OVS_VLAN, + to_xml=LIBVIRT_XML_DESCR_OVS_VLAN, + is_destibation_ovs=True, + is_vlan_net=True, + normalize=False) + + def test_legacy_to_ovs_non_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_LEGACY, + to_xml=LIBVIRT_XML_DESCR_OVS, + is_destibation_ovs=True, + is_vlan_net=False, + normalize=True) + + def test_legacy_to_ovs_with_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_LEGACY, + to_xml=LIBVIRT_XML_DESCR_OVS_VLAN, + is_destibation_ovs=True, + is_vlan_net=True, + normalize=True) + + def test_ovs_to_legacy_non_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_OVS, + to_xml=LIBVIRT_XML_DESCR_LEGACY, + is_destibation_ovs=False, + is_vlan_net=False, + normalize=True) + + def test_ovs_to_legacy_with_vlan(self): + self._assert_device_migration(from_xml=LIBVIRT_XML_DESCR_OVS_VLAN, + to_xml=LIBVIRT_XML_DESCR_LEGACY, + is_destibation_ovs=False, + is_vlan_net=True, + normalize=True) + + def _assert_device_migration(self, from_xml, to_xml, is_destibation_ovs, + is_vlan_net, normalize): + stdin = StringIO(from_xml) + stdout = StringIO() + + MockRunningConfig = (self.MockRunningConfigVlan if is_vlan_net else + self.MockRunningConfigNoVlan) + with MonkeyPatchScope([(netconfpersistence, 'RunningConfig', + MockRunningConfig), + (ovs_utils, 'is_ovs_network', + lambda x: is_destibation_ovs)]): + ovs_migrate.main('do', 'migrate', 'ph', stdin=stdin, stdout=stdout) + + # normalize input and output for the comparison + expect = _xml_normalize(to_xml) if normalize else to_xml + actual = (_xml_normalize(stdout.getvalue()) + if normalize else stdout.getvalue()) + self.assertEqual(expect, actual) + + +def _xml_normalize(xml): + return re.sub('\n\s*', ' ', xml.strip(' ')) + +LIBVIRT_XML_DESCR_LEGACY = """<domain type="kvm"> + <devices> + <interface type="bridge"> + <mac address="00:1a:4a:16:01:54" /> + <source bridge="testnet0" /> + <model type="virtio" /> + <filterref filter="vdsm-no-mac-spoofing" /> + <link state="up" /> + <boot order="1" /> + <address bus="0x00" domain="0x0" function="0x0" slot="0x03" type="pci" /> + </interface> + </devices> +</domain>""" + +# Note: +# libvirt automatically generates the element 'parameters' under 'virtualport' +# <virtualport type="openvswitch"> +# <parameters interfaceid="8b44132f-301f-4af5-bc16-f18f0c4c39c1" /> +# </virtualport> +# When converting from legacy to ovs, the hook does not add it and leaves it up +# to libvirt to add its own defaults. +LIBVIRT_XML_DESCR_OVS = """<domain type="kvm"> + <devices> + <interface type="bridge"> + <mac address="00:1a:4a:16:01:54" /> + <source bridge="ovsbr0" /> + <model type="virtio" /> + <filterref filter="vdsm-no-mac-spoofing" /> + <link state="up" /> + <boot order="1" /> + <address bus="0x00" domain="0x0" function="0x0" slot="0x03" type="pci" /> + <virtualport type="openvswitch" /> + </interface> + </devices> +</domain>""" + +# Device bound to an OVS vlan network (bridge name is not OVS default) +LIBVIRT_XML_DESCR_OVS_VLAN = LIBVIRT_XML_DESCR_OVS.replace('ovsbr0', + 'testnet0', 1) diff --git a/vdsm.spec.in b/vdsm.spec.in index 13fa74f..a3b8b44 100644 --- a/vdsm.spec.in +++ b/vdsm.spec.in @@ -1300,6 +1300,8 @@ %files hook-ovs %defattr(-, root, root, -) %{_sysconfdir}/sudoers.d/50_vdsm_hook_ovs +%{_libexecdir}/%{vdsm_name}/ovs_migrate.py* +%{_libexecdir}/%{vdsm_name}/ovs_utils.py* %{_libexecdir}/%{vdsm_name}/hooks/after_get_caps/50_ovs %{_libexecdir}/%{vdsm_name}/hooks/after_get_caps/ovs_utils.py* %{_libexecdir}/%{vdsm_name}/hooks/after_get_stats/50_ovs diff --git a/vdsm/virt/libvirt-hook.sh b/vdsm/virt/libvirt-hook.sh index 963ba02..880b5fb 100644 --- a/vdsm/virt/libvirt-hook.sh +++ b/vdsm/virt/libvirt-hook.sh @@ -17,5 +17,5 @@ # Fix VMs migrating to host with libvirt >= 1.2.8 # See https://bugzilla.redhat.com/show_bug.cgi?id=1138340 -exec sed -e 's|<min_guarantee[^>]*>[0-9 ]*</min_guarantee>||g' +sed -e 's|<min_guarantee[^>]*>[0-9 ]*</min_guarantee>||g' | /usr/libexec/vdsm/ovs_migrate.py $DOMAIN $EVENT $PHASE diff --git a/vdsm_hooks/ovs/Makefile.am b/vdsm_hooks/ovs/Makefile.am index 152be56..0218fd1 100644 --- a/vdsm_hooks/ovs/Makefile.am +++ b/vdsm_hooks/ovs/Makefile.am @@ -25,6 +25,11 @@ nodist_noinst_DATA = \ sudoers +dist_vdsmexec_SCRIPTS = \ + ovs_migrate.py \ + ovs_utils.py \ + $(NULL) + CLEANFILES = \ $(nodist_noinst_DATA) diff --git a/vdsm_hooks/ovs/ovs_migrate.py b/vdsm_hooks/ovs/ovs_migrate.py new file mode 100755 index 0000000..f7f2ad4 --- /dev/null +++ b/vdsm_hooks/ovs/ovs_migrate.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# Copyright 2015 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Refer to the README and COPYING files for full details of the license +# +from __future__ import print_function + +import six +import sys +import xml.etree.ElementTree as ET + +from vdsm import netconfpersistence + +import ovs_utils + +# LOG_FILE = '/tmp/libvirthook_ovs_migrate.log' +# log = open(LOG_FILE, 'w') + + +def main(domain, event, phase, *args, **gwargs): + if event not in ('migrate', 'restore'): + sys.exit(0) + + # print('Hook input args are: ', domain, event, phase, file=log) + + stdin = gwargs.get('stdin', sys.stdin) + tree = ET.parse(stdin) + root = tree.getroot() + devices = root.find('devices') + running_config = netconfpersistence.RunningConfig() + for interface in devices.findall('interface'): + if interface.get('type') == 'bridge': + + # 'source bridge' element must exist + elem_source = interface.find('source') + source_bridge = elem_source.get('bridge') + + elem_virtualport = interface.find('virtualport') + + # If the source bridge is the OVS switch name (its default bridge) + # it cannot be validated against the running config, as it is not a + # net name. Instead, a non vlan network is looked for and if found, + # it is assumed to be the target network (raising the limitation of + # having a single non-vlan network on a host). + if source_bridge == ovs_utils.BRIDGE_NAME: + nets = [net for net, attrs in six.iteritems( + running_config.networks) if attrs.get('vlan') is None] + if len(nets) == 1: + source_bridge = nets[0] + + network = running_config.networks.get(source_bridge) + if network is None: + print('Network', source_bridge, 'does not exist', + file=sys.stderr) + sys.exit(1) + + if ovs_utils.is_ovs_network(network): + if elem_virtualport is None: + # OVS based host with a legacy VM iface binding + elem_virtualport = ET.SubElement(interface, 'virtualport') + elem_virtualport.set('type', 'openvswitch') + elem_virtualport.tail = ' ' + if network.get('vlan') is None: + elem_source.set('bridge', ovs_utils.BRIDGE_NAME) + else: + if elem_virtualport is not None: + # Legacy based host with an OVS VM iface binding + interface.remove(elem_virtualport) + if (network.get('vlan') is None and + elem_source.get('bridge') == + ovs_utils.BRIDGE_NAME): + elem_source.set('bridge', source_bridge) + + stdout = gwargs.get('stdout', sys.stdout) + tree.write(stdout) + + # tree.write(log) + # print('\nEnd of hook', file=log) + + +if __name__ == '__main__': + main(*sys.argv[1:]) -- To view, visit https://gerrit.ovirt.org/55497 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I29cf441cc365d3679382e44410dad0906d9be3ec Gerrit-PatchSet: 1 Gerrit-Project: vdsm Gerrit-Branch: master Gerrit-Owner: Edward Haas <edwa...@redhat.com> _______________________________________________ vdsm-patches mailing list vdsm-patches@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/vdsm-patches