Change in vdsm[master]: net: native ovs [7]: configure basic ovs nets and bonds

phoracek at redhat.com phoracek at redhat.com
Sun Mar 27 14:18:35 UTC 2016


Petr Horáček has uploaded a new change for review.

Change subject: net: native ovs [7]: configure basic ovs nets and bonds
......................................................................

net: native ovs [7]: configure basic ovs nets and bonds

First part of ovs_switch.setup() implementation. We handle
basic nets and bonds configuration.

This patch also introduces OVS configurator, its API and
ovs-vsctl implementation.

Change-Id: Ic779d8798e02b5e5eae514e111ab2a7e2ad2e786
Bug-Url: https://bugzilla.redhat.com/1195208
Signed-off-by: Petr Horáček <phoracek at redhat.com>
---
M configure.ac
M lib/vdsm/network/ovs/Makefile.am
A lib/vdsm/network/ovs/configurators/Makefile.am
A lib/vdsm/network/ovs/configurators/__init__.py
A lib/vdsm/network/ovs/configurators/api.py
A lib/vdsm/network/ovs/configurators/vsctl.py
A lib/vdsm/network/ovs/ovs.py
M lib/vdsm/network/ovs/switch.py
M lib/vdsm/network/ovs/utils.py
M tests/network/ovs_test.py
M vdsm.spec.in
11 files changed, 459 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/07/55307/1

diff --git a/configure.ac b/configure.ac
index 7343132..730f91c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -358,6 +358,7 @@
 	lib/vdsm/network/Makefile
 	lib/vdsm/network/configurators/Makefile
 	lib/vdsm/network/ovs/Makefile
+	lib/vdsm/network/ovs/configurators/Makefile
 	lib/vdsm/rpc/Makefile
 	lib/vdsm/storage/Makefile
 	lib/vdsm/tc/Makefile
diff --git a/lib/vdsm/network/ovs/Makefile.am b/lib/vdsm/network/ovs/Makefile.am
index 814c797..01b72c9 100644
--- a/lib/vdsm/network/ovs/Makefile.am
+++ b/lib/vdsm/network/ovs/Makefile.am
@@ -17,12 +17,15 @@
 # Refer to the README and COPYING files for full details of the license
 #
 
+SUBDIRS = configurators
+
 include $(top_srcdir)/build-aux/Makefile.subs
 
 vdsmnetworkovsdir = $(vdsmpylibdir)/network/ovs
 
 dist_vdsmnetworkovs_PYTHON = \
 	__init__.py \
+	ovs.py \
 	switch.py \
 	utils.py \
 	validator.py \
diff --git a/lib/vdsm/network/ovs/configurators/Makefile.am b/lib/vdsm/network/ovs/configurators/Makefile.am
new file mode 100644
index 0000000..3bc50e4
--- /dev/null
+++ b/lib/vdsm/network/ovs/configurators/Makefile.am
@@ -0,0 +1,28 @@
+# 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
+#
+
+include $(top_srcdir)/build-aux/Makefile.subs
+
+vdsmnetworkovsconfiguratorsdir = $(vdsmpylibdir)/network/ovs/configurators
+
+dist_vdsmnetworkovsconfigurators_PYTHON = \
+	__init__.py \
+	api.py \
+	vsctl.py \
+	$(NULL)
diff --git a/lib/vdsm/network/ovs/configurators/__init__.py b/lib/vdsm/network/ovs/configurators/__init__.py
new file mode 100644
index 0000000..4a67f47
--- /dev/null
+++ b/lib/vdsm/network/ovs/configurators/__init__.py
@@ -0,0 +1,19 @@
+# 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
diff --git a/lib/vdsm/network/ovs/configurators/api.py b/lib/vdsm/network/ovs/configurators/api.py
new file mode 100644
index 0000000..afb0997
--- /dev/null
+++ b/lib/vdsm/network/ovs/configurators/api.py
@@ -0,0 +1,81 @@
+# 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
+
+
+class Transaction(object):
+    """Keep and execute commands."""
+
+    def commit(self):
+        raise NotImplemented
+
+    def add(self, *commands):
+        raise NotImplemented
+
+
+class Command(object):
+    """An OVS command which is to be executed in Transaction."""
+
+
+class API(object):
+    """Abstact class for configurator implementations.
+
+    Each method returns Command instance.
+    """
+
+    @staticmethod
+    def add_br(bridge, may_exist=False):
+        raise NotImplemented
+
+    @staticmethod
+    def add_fake_br(bridge, parent, vlan, may_exist=False):
+        raise NotImplemented
+
+    @staticmethod
+    def del_br(bridge, if_exists=False):
+        raise NotImplemented
+
+    @staticmethod
+    def add_bond(bond, bridge, nics, fake_iface=False, may_exist=False):
+        raise NotImplemented
+
+    @staticmethod
+    def attach_bond_slave(bond, slave):
+        raise NotImplemented
+
+    @staticmethod
+    def detach_bond_slave(bond, slave):
+        raise NotImplemented
+
+    @staticmethod
+    def add_port(port, bridge, may_exist=False):
+        raise NotImplemented
+
+    @staticmethod
+    def del_port(port, bridge, if_exists=False):
+        raise NotImplemented
+
+    @staticmethod
+    def set_port_attr(port, key, value):
+        raise NotImplemented
+
+    @staticmethod
+    def do_nothing():
+        """None equivalent for Command."""
+        raise NotImplemented
diff --git a/lib/vdsm/network/ovs/configurators/vsctl.py b/lib/vdsm/network/ovs/configurators/vsctl.py
new file mode 100644
index 0000000..98992ce
--- /dev/null
+++ b/lib/vdsm/network/ovs/configurators/vsctl.py
@@ -0,0 +1,132 @@
+# 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
+
+import logging
+
+from vdsm.commands import execCmd
+from vdsm.network import errors as ne
+from vdsm.network.errors import ConfigNetworkError
+from vdsm.utils import CommandPath
+
+from . import api
+
+_EXT_OVS_VSCTL = CommandPath('ovs-vsctl',
+                             '/usr/sbin/ovs-vsctl',
+                             '/usr/bin/ovs-vsctl').cmd
+
+
+class Transaction(api.Transaction):
+
+    def __init__(self):
+        self.commands = []
+
+    def commit(self):
+        commands = [_EXT_OVS_VSCTL] + self.commands
+        logging.debug('Executing commands: %s' % ' '.join(commands))
+        rc, _, err = execCmd(commands)
+        if rc != 0:
+            raise ConfigNetworkError(
+                ne.ERR_BAD_PARAMS,
+                'Executing commands failed: %s' % '\n'.join(err))
+
+    def add(self, *commands):
+        for command in commands:
+            self.commands.extend(['--'] + command.cli)
+
+
+class Command(api.Command):
+
+    def __init__(self, cli):
+        self.cli = cli
+
+
+class Vsctl(api.API):
+
+    @staticmethod
+    def add_br(bridge, may_exist=False):
+        command = []
+        if may_exist:
+            command.append('--may-exist')
+        command.extend(['add-br', bridge])
+        return Command(command)
+
+    @staticmethod
+    def add_fake_br(bridge, parent, vlan, may_exist=False):
+        command = []
+        if may_exist:
+            command.append('--may-exist')
+        command.extend(['add-br', bridge, parent, str(vlan)])
+        return Command(command)
+
+    @staticmethod
+    def del_br(bridge, if_exists=False):
+        command = []
+        if if_exists:
+            command.append('--if-exists')
+        command.extend(['del-br', bridge])
+        return Command(command)
+
+    @staticmethod
+    def add_bond(bond, bridge, nics, fake_iface=False, may_exist=False):
+        command = []
+        if may_exist:
+            command.append('--may-exist')
+        if fake_iface:
+            command.append('--fake-iface')
+        command.extend(['add-bond', bridge, bond] + nics)
+        return Command(command)
+
+    @staticmethod
+    def attach_bond_slave(bond, slave):
+        command = [
+            '--id=@%s' % slave, 'create', 'Interface', 'name=%s' % slave,
+            '--', 'add', 'Port', bond, 'interfaces', '@%s' % slave]
+        return Command(command)
+
+    @staticmethod
+    def detach_bond_slave(bond, slave):
+        command = ['--', '--id=@%s' % slave, 'get', 'Interface', slave, '--',
+                   'remove', 'Port', bond, 'interfaces', '@%s' % slave]
+        return Command(command)
+
+    @staticmethod
+    def add_port(port, bridge, may_exist=False):
+        command = []
+        if may_exist:
+            command.append('--may-exist')
+        command.extend(['add-port', bridge, port])
+        return Command(command)
+
+    @staticmethod
+    def del_port(port, bridge, if_exists=False):
+        command = []
+        if if_exists:
+            command.append('--if-exists')
+        command.extend(['del-port', bridge, port])
+        return Command(command)
+
+    @staticmethod
+    def set_port_attr(port, key, value):
+        command = ['set', 'port', port, '%s=%s' % (key, value)]
+        return Command(command)
+
+    @staticmethod
+    def do_nothing():
+        return Command([])
diff --git a/lib/vdsm/network/ovs/ovs.py b/lib/vdsm/network/ovs/ovs.py
new file mode 100644
index 0000000..e0c4c1f
--- /dev/null
+++ b/lib/vdsm/network/ovs/ovs.py
@@ -0,0 +1,157 @@
+# 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
+
+import six
+
+from vdsm.utils import rget
+
+from .utils import get_bond_options, BRIDGE_NAME
+from .configurators import vsctl
+
+
+_Commands = vsctl.Vsctl
+_Transaction = vsctl.Transaction
+
+
+def _get_nets_by_nics(running_config):
+    """Transform running config into {nic: set(networks)}."""
+    nets_by_nic = {}
+    for net, attrs in six.iteritems(running_config.networks):
+        nic = attrs.get('nic')
+        if nic is not None:
+            nets_by_nic.setdefault(nic, set()).add(net)
+    return nets_by_nic
+
+
+def _setup_ovs_net(net, attrs, running_config, nets_by_nic):
+    commands = []
+    nic = attrs.get('nic')
+    vlan = attrs.get('vlan')
+
+    if vlan is not None:
+        commands.append(_Commands.add_fake_br(net, BRIDGE_NAME, vlan))
+    if nic is not None:
+        commands.append(_add_nic_port(net, nic, nets_by_nic))
+
+    running_config.setNetwork(net, attrs)
+    return commands
+
+
+def _remove_ovs_net(net, running_config, nets_by_nic):
+    commands = []
+    net_conf = running_config.networks.get(net)
+    if 'vlan' in net_conf:
+        commands.append(_Commands.del_br(net))
+    if 'nic' in net_conf:
+        commands.append(_del_nic_port(net, net_conf.get('nic'), nets_by_nic))
+    running_config.removeNetwork(net)
+    return commands
+
+
+def _add_nic_port(net, nic, nets_by_nic):
+    if nic in nets_by_nic:
+        nets_by_nic[nic].add(net)
+        return _Commands.do_nothing()
+    else:
+        nets_by_nic[nic] = set([net])
+        return _Commands.add_port(nic, BRIDGE_NAME, may_exist=True)
+
+
+def _del_nic_port(net, nic, nets_by_nic):
+    nets_by_nic[nic].remove(net)
+    if len(nets_by_nic[nic]) == 0:
+        return _Commands.del_port(nic, BRIDGE_NAME, if_exists=True)
+    else:
+        return _Commands.do_nothing()
+
+
+def _setup_ovs_bond(bond, attrs, running_config):
+    """
+    Add OVS bonding and set it requested mode and lacp options.
+    As we use custom entry, these values are not validated in network api,
+    so we check correct values here.
+    """
+    commands = []
+    commands.append(_Commands.add_bond(bond, BRIDGE_NAME, attrs.get('nics'),
+                                       fake_iface=True, may_exist=True))
+
+    bond_options = get_bond_options(attrs.get('options'))
+    mode = rget(bond_options, ('custom', 'ovs_mode')) or 'active-backup'
+    lacp = rget(bond_options, ('custom', 'ovs_lacp')) or 'off'
+    commands.append(_Commands.set_port_attr(bond, 'bond_mode', mode))
+    commands.append(_Commands.set_port_attr(bond, 'lacp', lacp))
+
+    running_config.setBonding(bond, {'nics': attrs.get('nics'),
+                                     'options': attrs.get('options')})
+    return commands
+
+
+def _edit_ovs_bond(bond, attrs, running_config):
+    """
+    Use OVS database commands to change nics under a bonding. Then we can
+    continue with _setup_ovs_bond().
+    """
+    commands = []
+    current = set(rget(running_config.bonds, (bond, 'nics')))
+    new = set(attrs.get('nics'))
+    add = new - current
+    remove = current - new
+    for nic in add:
+        commands.append(_Commands.attach_bond_slave(bond, nic))
+    for nic in remove:
+        commands.append(_Commands.dettach_bond_slave(bond, nic))
+
+    commands.extend(_setup_ovs_bond(bond, attrs, running_config))
+    return commands
+
+
+def _prepare_addition(nets, bonds, running_config):
+    commands = []
+    nets_by_nic = _get_nets_by_nics(running_config)
+    for bond, attrs in six.iteritems(bonds):
+        if bond in running_config.bonds:
+            commands.extend(_edit_ovs_bond(bond, attrs, running_config))
+        else:
+            commands.extend(_setup_ovs_bond(bond, attrs, running_config))
+    for net, attrs in six.iteritems(nets):
+        commands.extend(_setup_ovs_net(net, attrs, running_config,
+                        nets_by_nic))
+    return commands
+
+
+def _prepare_removal(nets, bonds, running_config):
+    commands = []
+    nets_by_nic = _get_nets_by_nics(running_config)
+    for net in nets:
+        commands.extend(_remove_ovs_net(net, running_config, nets_by_nic))
+    for bond in bonds:
+        commands.append(_Commands.del_port(bond, BRIDGE_NAME))
+        running_config.removeBonding(bond)
+    return commands
+
+
+def add(nets, bonds, running_config):
+    commands = _prepare_addition(nets, bonds, running_config)
+    _Transaction(*commands).commit()
+
+
+def remove(nets, bonds, running_config):
+    commands = _prepare_removal(nets, bonds, running_config)
+    _Transaction(*commands).commit()
diff --git a/lib/vdsm/network/ovs/switch.py b/lib/vdsm/network/ovs/switch.py
index d1a4b20..c393543 100644
--- a/lib/vdsm/network/ovs/switch.py
+++ b/lib/vdsm/network/ovs/switch.py
@@ -28,6 +28,7 @@
 from vdsm.netinfo.cache import CachingNetInfo
 from vdsm.network.api import RollbackIncomplete
 
+from . import ovs as _ovs
 from . import utils
 from . import validator
 
@@ -76,7 +77,8 @@
     bonds_to_be_added, bonds_to_be_removed = _filter_bonds(bonds,
                                                            running_config)
 
-    # TODO: remove and add filtered networks
+    _ovs.remove(nets_to_be_removed, bonds_to_be_removed, running_config)
+    _ovs.add(nets_to_be_added, bonds_to_be_added, running_config)
 
 
 def _filter_nets(nets, running_config):
diff --git a/lib/vdsm/network/ovs/utils.py b/lib/vdsm/network/ovs/utils.py
index 19e65e7..40c9d2e 100644
--- a/lib/vdsm/network/ovs/utils.py
+++ b/lib/vdsm/network/ovs/utils.py
@@ -24,6 +24,9 @@
 from vdsm.utils import rget
 
 
+BRIDGE_NAME = 'ovsbr0'
+
+
 def is_ovs_net(attrs):
     return get_bool(rget(attrs, ('custom', 'ovs')))
 
diff --git a/tests/network/ovs_test.py b/tests/network/ovs_test.py
index ff53491..f666faa 100644
--- a/tests/network/ovs_test.py
+++ b/tests/network/ovs_test.py
@@ -163,3 +163,30 @@
             bonds_query, running_config)
         self.assertEquals(bonds_to_be_added.keys(), {'bond2', 'bond3'})
         self.assertEquals(bonds_to_be_removed, {'bond4'})
+
+
+class Ovs(TestCaseBase):
+
+    class FakeRunningConfig(netconfpersistence.BaseConfig):
+        def __init__(self, *args, **kwargs):
+            self.networks = {'net1': {'nic': 'eth0'},
+                             'net2': {'nic': 'eth1'},
+                             'net3': {'nic': 'eth1', 'vlan': 20}}
+            self.bonds = {}
+
+    def test_get_nets_by_nics(self):
+        expected_nets_by_nics = {'eth0': {'net1'},
+                                 'eth1': {'net2', 'net3'}}
+        nets_by_nics = ovs_switch._get_nets_by_nics(self.FakeRunningConfig())
+        self.assertEquals(nets_by_nics, expected_nets_by_nics)
+
+
+class Vsctl(TestCaseBase):
+
+    """TODO
+
+    test commands
+
+    create helper functions for ovs show, list ports etc, this will be added to
+    the configurator when needed
+    """
diff --git a/vdsm.spec.in b/vdsm.spec.in
index 6b5244c..73902fc 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -1063,6 +1063,7 @@
 %dir %{python_sitelib}/%{vdsm_name}/network
 %dir %{python_sitelib}/%{vdsm_name}/network/configurators
 %dir %{python_sitelib}/%{vdsm_name}/network/ovs
+%dir %{python_sitelib}/%{vdsm_name}/network/ovs/configurators
 %dir %{python_sitelib}/%{vdsm_name}/tool
 %dir %{python_sitelib}/%{vdsm_name}/tool/configurators
 %dir %{python_sitelib}/%{vdsm_name}/profiling
@@ -1128,6 +1129,10 @@
 %{python_sitelib}/%{vdsm_name}/network/connectivity.py*
 %{python_sitelib}/%{vdsm_name}/network/legacy_switch.py*
 %{python_sitelib}/%{vdsm_name}/network/ovs/__init__.py*
+%{python_sitelib}/%{vdsm_name}/network/ovs/configurators/__init__.py*
+%{python_sitelib}/%{vdsm_name}/network/ovs/configurators/api.py*
+%{python_sitelib}/%{vdsm_name}/network/ovs/configurators/vsctl.py*
+%{python_sitelib}/%{vdsm_name}/network/ovs/ovs.py*
 %{python_sitelib}/%{vdsm_name}/network/ovs/switch.py*
 %{python_sitelib}/%{vdsm_name}/network/ovs/utils.py*
 %{python_sitelib}/%{vdsm_name}/network/ovs/validator.py*


-- 
To view, visit https://gerrit.ovirt.org/55307
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic779d8798e02b5e5eae514e111ab2a7e2ad2e786
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Petr Horáček <phoracek at redhat.com>


More information about the vdsm-patches mailing list