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 <phora...@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 <phora...@redhat.com> _______________________________________________ vdsm-patches mailing list vdsm-patches@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/vdsm-patches