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