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

Reply via email to