From 7bfb01bcb151087d70df115ba01f789a8a7f161f Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez <“ralonso@redhat.com”> Date: Thu, 20 Sep 2018 16:22:00 +0100 Subject: [PATCH] Add abstract OVSDB API Abstract all existing 'ovs-vsctl' calls to an abstract OVSDB API. This will allow the future addition of a native OVSDB protocol implementation of the API without breaking backwards compatibility. Partial-Bug: #1666917 Change-Id: I9ec36be390d713a928a673191008612f3fddad8f --- lower-constraints.txt | 1 + ...d-abstract-ovsdb-api-8f04df58d4ed5b73.yaml | 9 + requirements.txt | 1 + vif_plug_ovs/linux_net.py | 126 +----- vif_plug_ovs/ovs.py | 64 ++- vif_plug_ovs/ovsdb/__init__.py | 0 vif_plug_ovs/ovsdb/api.py | 27 ++ vif_plug_ovs/ovsdb/impl_vsctl.py | 378 ++++++++++++++++++ vif_plug_ovs/ovsdb/ovsdb_lib.py | 90 +++++ vif_plug_ovs/tests/unit/ovsdb/__init__.py | 0 .../tests/unit/ovsdb/test_ovsdb_lib.py | 138 +++++++ vif_plug_ovs/tests/unit/test_linux_net.py | 252 ------------ vif_plug_ovs/tests/unit/test_plugin.py | 59 ++- 13 files changed, 706 insertions(+), 439 deletions(-) create mode 100644 releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml create mode 100644 vif_plug_ovs/ovsdb/__init__.py create mode 100644 vif_plug_ovs/ovsdb/api.py create mode 100644 vif_plug_ovs/ovsdb/impl_vsctl.py create mode 100644 vif_plug_ovs/ovsdb/ovsdb_lib.py create mode 100644 vif_plug_ovs/tests/unit/ovsdb/__init__.py create mode 100644 vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py diff --git a/lower-constraints.txt b/lower-constraints.txt index a67fe4fb..7b0e4c16 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -49,6 +49,7 @@ oslo.service==1.30.0 oslo.utils==3.36.0 oslo.versionedobjects==1.28.0 oslotest==1.10.0 +ovsdbapp==0.12.1 Paste==2.0.3 PasteDeploy==1.5.2 pbr==2.0.0 diff --git a/releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml b/releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml new file mode 100644 index 00000000..4dc2b62b --- /dev/null +++ b/releasenotes/notes/add-abstract-ovsdb-api-8f04df58d4ed5b73.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Added an abstract OVSDB API in ``vif_plug_ovs``. All calls to OVS database + will de done using this unique API. + Command line implementation using ``ovs-vsctl`` was refactored as a + backend for this abstract API. + A new configuration variable, ``ovsdb_interface``, is added to select + the interface for interacting with the OVS database. diff --git a/requirements.txt b/requirements.txt index 6b2af3bd..b3dfaff3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ oslo.log>=3.30.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.privsep>=1.23.0 # Apache-2.0 oslo.versionedobjects>=1.28.0 # Apache-2.0 +ovsdbapp>=0.12.1 # Apache-2.0 pyroute2>=0.4.21;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 diff --git a/vif_plug_ovs/linux_net.py b/vif_plug_ovs/linux_net.py index 9bef005f..48c395bf 100644 --- a/vif_plug_ovs/linux_net.py +++ b/vif_plug_ovs/linux_net.py @@ -49,68 +49,15 @@ PF_FUNC_RE = re.compile("\.(\d+)", 0) _SRIOV_TOTALVFS = "sriov_totalvfs" -def _ovs_vsctl(args, timeout=None, ovsdb_connection=None): - full_args = ['ovs-vsctl'] - if timeout is not None: - full_args += ['--timeout=%s' % timeout] - if ovsdb_connection is not None: - full_args += ['--db=%s' % ovsdb_connection] - full_args += args - try: - return processutils.execute(*full_args) - except Exception as e: - LOG.error("Unable to execute %(cmd)s. Exception: %(exception)s", - {'cmd': full_args, 'exception': e}) - raise exception.AgentError(method=full_args) - - -def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, - instance_id, interface_type=None, - vhost_server_path=None): - cmd = ['--', '--may-exist', 'add-port', bridge, dev, - '--', 'set', 'Interface', dev, - 'external-ids:iface-id=%s' % iface_id, - 'external-ids:iface-status=active', - 'external-ids:attached-mac=%s' % mac, - 'external-ids:vm-uuid=%s' % instance_id] - if interface_type: - cmd += ['type=%s' % interface_type] - if vhost_server_path: - cmd += ['options:vhost-server-path=%s' % vhost_server_path] - return cmd - - -def _create_ovs_bridge_cmd(bridge, datapath_type): - return ['--', '--may-exist', 'add-br', bridge, - '--', 'set', 'Bridge', bridge, 'datapath_type=%s' % datapath_type] - - -@privsep.vif_plug.entrypoint -def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, - mtu=None, interface_type=None, timeout=None, - vhost_server_path=None, ovsdb_connection=None): - _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id, - mac, instance_id, interface_type, - vhost_server_path), timeout=timeout, - ovsdb_connection=ovsdb_connection) - _update_device_mtu(dev, mtu, interface_type, timeout=timeout, - ovsdb_connection=ovsdb_connection) - - -@privsep.vif_plug.entrypoint -def update_ovs_vif_port(dev, mtu=None, interface_type=None, timeout=None, - ovsdb_connection=None): - _update_device_mtu(dev, mtu, interface_type, timeout=timeout, - ovsdb_connection=ovsdb_connection) - - -@privsep.vif_plug.entrypoint -def delete_ovs_vif_port(bridge, dev, timeout=None, - ovsdb_connection=None, delete_netdev=True): - _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev], - timeout=timeout, ovsdb_connection=ovsdb_connection) - if delete_netdev: - _delete_net_dev(dev) +def _update_device_mtu(dev, mtu): + if not mtu: + return + if sys.platform != constants.PLATFORM_WIN32: + # Hyper-V with OVS does not support external programming of + # virtual interface MTUs via netsh or other Windows tools. + # When plugging an interface on Windows, we therefore skip + # programming the MTU and fallback to DHCP advertisement. + set_device_mtu(dev, mtu) def interface_in_bridge(bridge, device): @@ -119,7 +66,7 @@ def interface_in_bridge(bridge, device): {'bridge': bridge, 'device': device}) -def _delete_net_dev(dev): +def delete_net_dev(dev): """Delete a network device only if it exists.""" if ip_lib.exists(dev): try: @@ -136,7 +83,7 @@ def create_veth_pair(dev1_name, dev2_name, mtu): deleting any previous devices with those names. """ for dev in [dev1_name, dev2_name]: - _delete_net_dev(dev) + delete_net_dev(dev) ip_lib.add(dev1_name, 'veth', peer=dev2_name) for dev in [dev1_name, dev2_name]: @@ -152,13 +99,6 @@ def update_veth_pair(dev1_name, dev2_name, mtu): _update_device_mtu(dev, mtu) -@privsep.vif_plug.entrypoint -def ensure_ovs_bridge(bridge, datapath_type, timeout=None, - ovsdb_connection=None): - _ovs_vsctl(_create_ovs_bridge_cmd(bridge, datapath_type), timeout=timeout, - ovsdb_connection=ovsdb_connection) - - @privsep.vif_plug.entrypoint def ensure_bridge(bridge): if not ip_lib.exists(bridge): @@ -196,32 +136,8 @@ def add_bridge_port(bridge, dev): processutils.execute('brctl', 'addif', bridge, dev) -def _update_device_mtu(dev, mtu, interface_type=None, timeout=120, - ovsdb_connection=None): - if not mtu: - return - if interface_type not in [ - constants.OVS_VHOSTUSER_INTERFACE_TYPE, - constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE]: - if sys.platform != constants.PLATFORM_WIN32: - # Hyper-V with OVS does not support external programming of virtual - # interface MTUs via netsh or other Windows tools. - # When plugging an interface on Windows, we therefore skip - # programming the MTU and fallback to DHCP advertisement. - _set_device_mtu(dev, mtu) - elif _ovs_supports_mtu_requests(timeout=timeout, - ovsdb_connection=ovsdb_connection): - _set_mtu_request(dev, mtu, timeout=timeout, - ovsdb_connection=ovsdb_connection) - else: - LOG.debug("MTU not set on %(interface_name)s interface " - "of type %(interface_type)s.", - {'interface_name': dev, - 'interface_type': interface_type}) - - @privsep.vif_plug.entrypoint -def _set_device_mtu(dev, mtu): +def set_device_mtu(dev, mtu): """Set the device MTU.""" ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254]) @@ -231,24 +147,6 @@ def set_interface_state(interface_name, port_state): ip_lib.set(interface_name, state=port_state, check_exit_code=[0, 2, 254]) -@privsep.vif_plug.entrypoint -def _set_mtu_request(dev, mtu, timeout=None, ovsdb_connection=None): - args = ['--', 'set', 'interface', dev, - 'mtu_request=%s' % mtu] - _ovs_vsctl(args, timeout=timeout, ovsdb_connection=ovsdb_connection) - - -@privsep.vif_plug.entrypoint -def _ovs_supports_mtu_requests(timeout=None, ovsdb_connection=None): - args = ['--columns=mtu_request', 'list', 'interface'] - _, error = _ovs_vsctl(args, timeout=timeout, - ovsdb_connection=ovsdb_connection) - if (error == 'ovs-vsctl: Interface does not contain' + - ' a column whose name matches "mtu_request"'): - return False - return True - - def _parse_vf_number(phys_port_name): """Parses phys_port_name and returns VF number or None. diff --git a/vif_plug_ovs/ovs.py b/vif_plug_ovs/ovs.py index 0cfe8b87..3d7352fe 100644 --- a/vif_plug_ovs/ovs.py +++ b/vif_plug_ovs/ovs.py @@ -27,6 +27,8 @@ from oslo_config import cfg from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs import linux_net +from vif_plug_ovs.ovsdb import api as ovsdb_api +from vif_plug_ovs.ovsdb import ovsdb_lib class OvsPlugin(plugin.PluginBase): @@ -62,8 +64,16 @@ class OvsPlugin(plugin.PluginBase): 'tcp:127.0.0.1:6640, ovs-vsctl commands will use the ' 'tcp:IP:PORT parameter for communicating with OVSDB over ' 'a TCP socket.'), + cfg.StrOpt('ovsdb_interface', + choices=list(ovsdb_api.interface_map), + default='vsctl', + help='The interface for interacting with the OVSDB'), ) + def __init__(self, config): + super(OvsPlugin, self).__init__(config) + self.ovsdb = ovsdb_lib.BaseOVS(self.config) + @staticmethod def gen_port_name(prefix, id): return ("%s%s" % (prefix, id))[:OvsPlugin.NIC_NAME_LEN] @@ -118,21 +128,17 @@ class OvsPlugin(plugin.PluginBase): def _create_vif_port(self, vif, vif_name, instance_info, **kwargs): mtu = self._get_mtu(vif) - linux_net.create_ovs_vif_port( + self.ovsdb.create_ovs_vif_port( vif.network.bridge, vif_name, vif.port_profile.interface_id, vif.address, instance_info.uuid, mtu, - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection, **kwargs) def _update_vif_port(self, vif, vif_name): mtu = self._get_mtu(vif) - linux_net.update_ovs_vif_port(vif_name, mtu, - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection) + self.ovsdb.update_ovs_vif_port(vif_name, mtu) @staticmethod def _get_vif_datapath_type(vif, datapath=constants.OVS_DATAPATH_SYSTEM): @@ -142,7 +148,7 @@ class OvsPlugin(plugin.PluginBase): return profile.datapath_type def _plug_vhostuser(self, vif, instance_info): - linux_net.ensure_ovs_bridge( + self.ovsdb.ensure_ovs_bridge( vif.network.bridge, self._get_vif_datapath_type( vif, datapath=constants.OVS_DATAPATH_NETDEV)) vif_name = OvsPlugin.gen_port_name( @@ -176,10 +182,8 @@ class OvsPlugin(plugin.PluginBase): if not ip_lib.exists(v2_name): linux_net.create_veth_pair(v1_name, v2_name, mtu) linux_net.add_bridge_port(vif.bridge_name, v1_name) - linux_net.ensure_ovs_bridge(vif.network.bridge, - self._get_vif_datapath_type(vif), - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection) + self.ovsdb.ensure_ovs_bridge(vif.network.bridge, + self._get_vif_datapath_type(vif)) self._create_vif_port(vif, v2_name, instance_info) else: linux_net.update_veth_pair(v1_name, v2_name, mtu) @@ -189,15 +193,13 @@ class OvsPlugin(plugin.PluginBase): """Create a per-VIF OVS port.""" if not ip_lib.exists(vif.id): - linux_net.ensure_ovs_bridge(vif.network.bridge, - self._get_vif_datapath_type(vif)) + self.ovsdb.ensure_ovs_bridge(vif.network.bridge, + self._get_vif_datapath_type(vif)) self._create_vif_port(vif, vif.id, instance_info) def _plug_vf_passthrough(self, vif, instance_info): - linux_net.ensure_ovs_bridge( - vif.network.bridge, constants.OVS_DATAPATH_SYSTEM, - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection) + self.ovsdb.ensure_ovs_bridge( + vif.network.bridge, constants.OVS_DATAPATH_SYSTEM) pci_slot = vif.dev_address pf_ifname = linux_net.get_ifname_by_pci_address( pci_slot, pf_interface=True, switchdev=True) @@ -216,10 +218,8 @@ class OvsPlugin(plugin.PluginBase): if isinstance(vif, objects.vif.VIFOpenVSwitch): if sys.platform != constants.PLATFORM_WIN32: - linux_net.ensure_ovs_bridge(vif.network.bridge, - self._get_vif_datapath_type(vif), - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection) + self.ovsdb.ensure_ovs_bridge(vif.network.bridge, + self._get_vif_datapath_type(vif)) else: self._plug_vif_windows(vif, instance_info) elif isinstance(vif, objects.vif.VIFBridge): @@ -233,12 +233,10 @@ class OvsPlugin(plugin.PluginBase): self._plug_vf_passthrough(vif, instance_info) def _unplug_vhostuser(self, vif, instance_info): - linux_net.delete_ovs_vif_port(vif.network.bridge, + self.ovsdb.delete_ovs_vif_port(vif.network.bridge, OvsPlugin.gen_port_name( constants.OVS_VHOSTUSER_PREFIX, - vif.id), - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection) + vif.id)) def _unplug_bridge(self, vif, instance_info): """UnPlug using hybrid strategy @@ -251,16 +249,11 @@ class OvsPlugin(plugin.PluginBase): linux_net.delete_bridge(vif.bridge_name, v1_name) - linux_net.delete_ovs_vif_port(vif.network.bridge, v2_name, - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection) + self.ovsdb.delete_ovs_vif_port(vif.network.bridge, v2_name) def _unplug_vif_windows(self, vif, instance_info): """Remove port from OVS.""" - - linux_net.delete_ovs_vif_port(vif.network.bridge, vif.id, - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection) + self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.id) def _unplug_vf_passthrough(self, vif, instance_info): """Remove port from OVS.""" @@ -272,11 +265,8 @@ class OvsPlugin(plugin.PluginBase): # The representor interface can't be deleted because it bind the # SR-IOV VF, therefore we just need to remove it from the ovs bridge # and set the status to down - linux_net.delete_ovs_vif_port( - vif.network.bridge, representor, - timeout=self.config.ovs_vsctl_timeout, - ovsdb_connection=self.config.ovsdb_connection, - delete_netdev=False) + self.ovsdb.delete_ovs_vif_port( + vif.network.bridge, representor, delete_netdev=False) linux_net.set_interface_state(representor, 'down') def unplug(self, vif, instance_info): diff --git a/vif_plug_ovs/ovsdb/__init__.py b/vif_plug_ovs/ovsdb/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vif_plug_ovs/ovsdb/api.py b/vif_plug_ovs/ovsdb/api.py new file mode 100644 index 00000000..c0be34e9 --- /dev/null +++ b/vif_plug_ovs/ovsdb/api.py @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_utils import importutils + + +interface_map = { + 'vsctl': 'vif_plug_ovs.ovsdb.impl_vsctl', + # NOTE(ralonsoh): to be implemented in following patches. + # 'native': 'vif_plug_ovs.ovsdb.impl_idl', +} + + +def get_instance(context, iface_name=None): + """Return the configured OVSDB API implementation""" + iface = importutils.import_module( + interface_map[iface_name or context.interface]) + return iface.api_factory(context) diff --git a/vif_plug_ovs/ovsdb/impl_vsctl.py b/vif_plug_ovs/ovsdb/impl_vsctl.py new file mode 100644 index 00000000..9396e6fc --- /dev/null +++ b/vif_plug_ovs/ovsdb/impl_vsctl.py @@ -0,0 +1,378 @@ +# Derived from neutron/agent/ovsdb/impl_vsctl.py +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import collections +import itertools +import uuid + +from oslo_concurrency import processutils +from oslo_log import log as logging +from oslo_serialization import jsonutils +from oslo_utils import excutils +from oslo_utils import uuidutils +from ovsdbapp import api as ovsdb_api +import six + +from vif_plug_ovs import privsep + + +LOG = logging.getLogger(__name__) + + +def _val_to_py(val): + """Convert a json ovsdb return value to native python object""" + if isinstance(val, collections.Sequence) and len(val) == 2: + if val[0] == "uuid": + return uuid.UUID(val[1]) + elif val[0] == "set": + return [_val_to_py(x) for x in val[1]] + elif val[0] == "map": + return {_val_to_py(x): _val_to_py(y) for x, y in val[1]} + return val + + +def _py_to_val(pyval): + """Convert python value to ovs-vsctl value argument""" + if isinstance(pyval, bool): + return 'true' if pyval is True else 'false' + elif pyval == '': + return '""' + else: + # NOTE(twilson) If a Command object, return its record_id as a value + return getattr(pyval, "record_id", pyval) + + +def api_factory(context): + return OvsdbVsctl(context) + + +@privsep.vif_plug.entrypoint +def _run_vsctl(full_args): + # NOTE(ralonsoh): this function is defined outside the class Transaction + # to allow oslo_privsep.PrivContext.entrypoint to wrap + # the function correctly. + return processutils.execute(*full_args)[0].rstrip() + + +class Transaction(ovsdb_api.Transaction): + def __init__(self, context, check_error=False, log_errors=True, opts=None): + self.context = context + self.check_error = check_error + self.log_errors = log_errors + self.opts = ['--timeout=%d' % self.context.timeout, + '--oneline', '--format=json'] + if self.context.connection: + self.opts += ['--db=%s' % self.context.connection] + if opts: + self.opts += opts + self.commands = [] + + def add(self, command): + self.commands.append(command) + return command + + def commit(self): + args = [] + for cmd in self.commands: + cmd.result = None + args += cmd.vsctl_args() + res = self.run_vsctl(args) + if res is None: + return + res = res.replace(r'\\', '\\').splitlines() + for i, record in enumerate(res): + self.commands[i].result = record + return [cmd.result for cmd in self.commands] + + def run_vsctl(self, args): + full_args = ["ovs-vsctl"] + self.opts + args + try: + # We log our own errors, so never have utils.execute do it + return _run_vsctl(full_args) + except Exception as e: + with excutils.save_and_reraise_exception() as ctxt: + if self.log_errors: + LOG.error("Unable to execute %(cmd)s. Exception: " + "%(exception)s", + {'cmd': full_args, 'exception': e}) + if not self.check_error: + ctxt.reraise = False + + +class BaseCommand(ovsdb_api.Command): + def __init__(self, context, cmd, opts=None, args=None): + self.context = context + self.cmd = cmd + self.opts = [] if opts is None else opts + self.args = [] if args is None else args + + def execute(self, check_error=False, log_errors=True): + with Transaction(self.context, check_error=check_error, + log_errors=log_errors) as txn: + txn.add(self) + return self.result + + def vsctl_args(self): + return itertools.chain(('--',), self.opts, (self.cmd,), self.args) + + +class MultiLineCommand(BaseCommand): + """Command for ovs-vsctl commands that return multiple lines""" + @property + def result(self): + return self._result + + @result.setter + def result(self, raw_result): + self._result = raw_result.split(r'\n') if raw_result else [] + + +class DbCommand(BaseCommand): + def __init__(self, context, cmd, opts=None, args=None, columns=None): + if opts is None: + opts = [] + if columns: + opts += ['--columns=%s' % ",".join(columns)] + super(DbCommand, self).__init__(context, cmd, opts, args) + + @property + def result(self): + return self._result + + @result.setter + def result(self, raw_result): + # If check_error=False, run_vsctl can return None + if not raw_result: + self._result = None + return + + try: + json = jsonutils.loads(raw_result) + except (ValueError, TypeError) as e: + # This shouldn't happen, but if it does and we check_errors + # log and raise. + with excutils.save_and_reraise_exception(): + LOG.error("Could not parse: %(raw_result)s. Exception: " + "%(exception)s", + {'raw_result': raw_result, 'exception': e}) + + headings = json['headings'] + data = json['data'] + results = [] + for record in data: + obj = {} + for pos, heading in enumerate(headings): + obj[heading] = _val_to_py(record[pos]) + results.append(obj) + self._result = results + + +class DbGetCommand(DbCommand): + @DbCommand.result.setter + def result(self, val): + # super()'s never worked for setters http://bugs.python.org/issue14965 + DbCommand.result.fset(self, val) + # DbCommand will return [{'column': value}] and we just want value. + if self._result: + self._result = list(self._result[0].values())[0] + + +class DbCreateCommand(BaseCommand): + def __init__(self, context, opts=None, args=None): + super(DbCreateCommand, self).__init__(context, "create", opts, args) + # NOTE(twilson) pre-commit result used for intra-transaction reference + self.record_id = "@%s" % uuidutils.generate_uuid() + self.opts.append("--id=%s" % self.record_id) + + @property + def result(self): + return self._result + + @result.setter + def result(self, val): + self._result = uuid.UUID(val) if val else val + + +class BrExistsCommand(DbCommand): + @DbCommand.result.setter + def result(self, val): + self._result = val is not None + + def execute(self): + return super(BrExistsCommand, self).execute(check_error=False, + log_errors=False) + + +class OvsdbVsctl(ovsdb_api.API): + def __init__(self, context): + super(OvsdbVsctl, self).__init__() + self.context = context + + def create_transaction(self, check_error=False, log_errors=True, **kwargs): + return Transaction(self.context, check_error, log_errors, **kwargs) + + def add_manager(self, connection_uri): + # This will add a new manager without overriding existing ones. + conn_uri = 'target="%s"' % connection_uri + args = ['create', 'Manager', conn_uri, '--', 'add', 'Open_vSwitch', + '.', 'manager_options', '@manager'] + return BaseCommand(self.context, '--id=@manager', args=args) + + def get_manager(self): + return MultiLineCommand(self.context, 'get-manager') + + def remove_manager(self, connection_uri): + args = ['get', 'Manager', connection_uri, '--', 'remove', + 'Open_vSwitch', '.', 'manager_options', '@manager'] + return BaseCommand(self.context, '--id=@manager', args=args) + + def add_br(self, name, may_exist=True, datapath_type=None): + opts = ['--may-exist'] if may_exist else None + params = [name] + if datapath_type: + params += ['--', 'set', 'Bridge', name, + 'datapath_type=%s' % datapath_type] + return BaseCommand(self.context, 'add-br', opts, params) + + def del_br(self, name, if_exists=True): + opts = ['--if-exists'] if if_exists else None + return BaseCommand(self.context, 'del-br', opts, [name]) + + def br_exists(self, name): + return BrExistsCommand(self.context, 'list', args=['Bridge', name]) + + def port_to_br(self, name): + return BaseCommand(self.context, 'port-to-br', args=[name]) + + def iface_to_br(self, name): + return BaseCommand(self.context, 'iface-to-br', args=[name]) + + def list_br(self): + return MultiLineCommand(self.context, 'list-br') + + def br_get_external_id(self, name, field): + return BaseCommand(self.context, 'br-get-external-id', + args=[name, field]) + + def db_create(self, table, **col_values): + args = [table] + args += _set_colval_args(*col_values.items()) + return DbCreateCommand(self.context, args=args) + + def db_destroy(self, table, record): + args = [table, record] + return BaseCommand(self.context, 'destroy', args=args) + + def db_set(self, table, record, *col_values): + args = [table, record] + args += _set_colval_args(*col_values) + return BaseCommand(self.context, 'set', args=args) + + def db_add(self, table, record, column, *values): + args = [table, record, column] + for value in values: + if isinstance(value, collections.Mapping): + args += ["{}={}".format(_py_to_val(k), _py_to_val(v)) + for k, v in value.items()] + else: + args.append(_py_to_val(value)) + return BaseCommand(self.context, 'add', args=args) + + def db_clear(self, table, record, column): + return BaseCommand(self.context, 'clear', args=[table, record, + column]) + + def db_get(self, table, record, column): + # Use the 'list' command as it can return json and 'get' cannot so that + # we can get real return types instead of treating everything as string + # NOTE: openvswitch can return a single atomic value for fields that + # are sets, but only have one value. This makes directly iterating over + # the result of a db_get() call unsafe. + return DbGetCommand(self.context, 'list', args=[table, record], + columns=[column]) + + def db_list(self, table, records=None, columns=None, if_exists=False): + opts = ['--if-exists'] if if_exists else None + args = [table] + if records: + args += records + return DbCommand(self.context, 'list', opts=opts, args=args, + columns=columns) + + def db_find(self, table, *conditions, **kwargs): + columns = kwargs.pop('columns', None) + args = itertools.chain([table], + *[_set_colval_args(c) for c in conditions]) + return DbCommand(self.context, 'find', args=args, columns=columns) + + def set_controller(self, bridge, controllers): + return BaseCommand(self.context, 'set-controller', + args=[bridge] + list(controllers)) + + def del_controller(self, bridge): + return BaseCommand(self.context, 'del-controller', args=[bridge]) + + def get_controller(self, bridge): + return MultiLineCommand(self.context, 'get-controller', args=[bridge]) + + def set_fail_mode(self, bridge, mode): + return BaseCommand(self.context, 'set-fail-mode', args=[bridge, mode]) + + def add_port(self, bridge, port, may_exist=True): + opts = ['--may-exist'] if may_exist else None + return BaseCommand(self.context, 'add-port', opts, [bridge, port]) + + def del_port(self, port, bridge=None, if_exists=True): + opts = ['--if-exists'] if if_exists else None + args = filter(None, [bridge, port]) + return BaseCommand(self.context, 'del-port', opts, args) + + def list_ports(self, bridge): + return MultiLineCommand(self.context, 'list-ports', args=[bridge]) + + def list_ifaces(self, bridge): + return MultiLineCommand(self.context, 'list-ifaces', args=[bridge]) + + def db_list_rows(self, table, record=None, if_exists=False): + raise NotImplementedError() + + def db_find_rows(self, table, *conditions, **kwargs): + raise NotImplementedError() + + def db_remove(self, table, record, column, *values, **keyvalues): + raise NotImplementedError() + + +def _set_colval_args(*col_values): + args = [] + # TODO(twilson) This is ugly, but set/find args are very similar except for + # op. Will try to find a better way to default this op to '=' + for entry in col_values: + if len(entry) == 2: + col, op, val = entry[0], '=', entry[1] + else: + col, op, val = entry + if isinstance(val, collections.Mapping): + args += ["%s:%s%s%s" % ( + col, k, op, _py_to_val(v)) for k, v in val.items()] + elif (isinstance(val, collections.Sequence) + and not isinstance(val, six.string_types)): + if len(val) == 0: + args.append("%s%s%s" % (col, op, "[]")) + else: + args.append( + "%s%s%s" % (col, op, ",".join(map(_py_to_val, val)))) + else: + args.append("%s%s%s" % (col, op, _py_to_val(val))) + return args diff --git a/vif_plug_ovs/ovsdb/ovsdb_lib.py b/vif_plug_ovs/ovsdb/ovsdb_lib.py new file mode 100644 index 00000000..cb30a88e --- /dev/null +++ b/vif_plug_ovs/ovsdb/ovsdb_lib.py @@ -0,0 +1,90 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from oslo_log import log as logging + +from vif_plug_ovs import constants +from vif_plug_ovs import linux_net +from vif_plug_ovs.ovsdb import api as ovsdb_api + + +LOG = logging.getLogger(__name__) + + +class BaseOVS(object): + + def __init__(self, config): + self.timeout = config.ovs_vsctl_timeout + self.connection = config.ovsdb_connection + self.interface = config.ovsdb_interface + self.ovsdb = ovsdb_api.get_instance(self) + + def _ovs_supports_mtu_requests(self): + return bool(self.ovsdb.db_list( + 'Interface', columns=['mtu_request']).execute(check_error=True, + log_errors=True)) + + def _set_mtu_request(self, dev, mtu): + self.ovsdb.db_set('Interface', dev, ('mtu_request', mtu)).execute() + + def update_device_mtu(self, dev, mtu, interface_type=None): + if not mtu: + return + if interface_type not in [ + constants.OVS_VHOSTUSER_INTERFACE_TYPE, + constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE]: + if sys.platform != constants.PLATFORM_WIN32: + # Hyper-V with OVS does not support external programming of + # virtual interface MTUs via netsh or other Windows tools. + # When plugging an interface on Windows, we therefore skip + # programming the MTU and fallback to DHCP advertisement. + linux_net.set_device_mtu(dev, mtu) + elif self._ovs_supports_mtu_requests(): + self._set_mtu_request(dev, mtu) + else: + LOG.debug("MTU not set on %(interface_name)s interface " + "of type %(interface_type)s.", + {'interface_name': dev, + 'interface_type': interface_type}) + + def ensure_ovs_bridge(self, bridge, datapath_type): + return self.ovsdb.add_br(bridge, may_exist=True, + datapath_type=datapath_type).execute() + + def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id, + mtu=None, interface_type=None, + vhost_server_path=None): + external_ids = {'iface-id': iface_id, + 'iface-status': 'active', + 'attached-mac': mac, + 'vm-uuid': instance_id} + col_values = [('external_ids', external_ids)] + if interface_type: + col_values.append(('type', interface_type)) + if vhost_server_path: + col_values.append(('options', + {'vhost-server-path': vhost_server_path})) + + with self.ovsdb.transaction() as txn: + txn.add(self.ovsdb.add_port(bridge, dev)) + txn.add(self.ovsdb.db_set('Interface', dev, *col_values)) + self.update_device_mtu(dev, mtu, interface_type=interface_type) + + def update_ovs_vif_port(self, dev, mtu=None, interface_type=None): + self.update_device_mtu(dev, mtu, interface_type=interface_type) + + def delete_ovs_vif_port(self, bridge, dev, delete_netdev=True): + self.ovsdb.del_port(dev, bridge=bridge, if_exists=True).execute() + if delete_netdev: + linux_net.delete_net_dev(dev) diff --git a/vif_plug_ovs/tests/unit/ovsdb/__init__.py b/vif_plug_ovs/tests/unit/ovsdb/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py b/vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py new file mode 100644 index 00000000..b340c432 --- /dev/null +++ b/vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py @@ -0,0 +1,138 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import sys +import testtools + +from oslo_config import cfg +from oslo_utils import uuidutils + +from vif_plug_ovs import constants +from vif_plug_ovs import linux_net +from vif_plug_ovs.ovsdb import ovsdb_lib + + +CONF = cfg.CONF + + +class BaseOVSTest(testtools.TestCase): + + def setUp(self): + super(BaseOVSTest, self).setUp() + test_vif_plug_ovs_group = cfg.OptGroup('test_vif_plug_ovs') + CONF.register_group(test_vif_plug_ovs_group) + CONF.register_opt(cfg.IntOpt('ovs_vsctl_timeout', default=1500), + test_vif_plug_ovs_group) + CONF.register_opt(cfg.StrOpt('ovsdb_interface', default='vsctl'), + test_vif_plug_ovs_group) + CONF.register_opt(cfg.StrOpt('ovsdb_connection', default=None), + test_vif_plug_ovs_group) + self.br = ovsdb_lib.BaseOVS(cfg.CONF.test_vif_plug_ovs) + self.mock_db_set = mock.patch.object(self.br.ovsdb, 'db_set').start() + self.mock_del_port = mock.patch.object(self.br.ovsdb, + 'del_port').start() + self.mock_add_port = mock.patch.object(self.br.ovsdb, + 'add_port').start() + self.mock_add_br = mock.patch.object(self.br.ovsdb, 'add_br').start() + self.mock_transaction = mock.patch.object(self.br.ovsdb, + 'transaction').start() + + def test__set_mtu_request(self): + self.br._set_mtu_request('device', 1500) + calls = [mock.call('Interface', 'device', ('mtu_request', 1500))] + self.mock_db_set.assert_has_calls(calls) + + @mock.patch.object(sys, 'platform', return_value='linux') + @mock.patch.object(linux_net, 'set_device_mtu') + def test__update_device_mtu_interface_not_vhostuser_linux(self, + mock_set_device_mtu, mock_platform): + self.br.update_device_mtu('device', 1500, 'not_vhost') + mock_set_device_mtu.assert_has_calls([mock.call('device', 1500)]) + + @mock.patch.object(linux_net, 'set_device_mtu') + def test__update_device_mtu_interface_not_vhostuser_windows(self, + mock_set_device_mtu): + sys.platform = constants.PLATFORM_WIN32 + self.br.update_device_mtu('device', 1500, 'not_vhost') + mock_set_device_mtu.assert_not_called() + + def test__update_device_mtu_interface_vhostuser_supports_mtu_req(self): + with mock.patch.object(self.br, '_ovs_supports_mtu_requests', + return_value=True), \ + mock.patch.object(self.br, '_set_mtu_request') as \ + mock_set_mtu_request: + self.br.update_device_mtu('device', 1500, + constants.OVS_VHOSTUSER_INTERFACE_TYPE) + mock_set_mtu_request.assert_has_calls([mock.call('device', 1500)]) + + def test__update_device_mtu_interface_vhostuser_not_supports_mtu_req(self): + with mock.patch.object(self.br, '_ovs_supports_mtu_requests', + return_value=False), \ + mock.patch.object(self.br, '_set_mtu_request') as \ + mock_set_mtu_request: + self.br.update_device_mtu('device', 1500, + constants.OVS_VHOSTUSER_INTERFACE_TYPE) + mock_set_mtu_request.assert_not_called() + + def test_create_ovs_vif_port(self): + iface_id = 'iface_id' + mac = 'ca:fe:ca:fe:ca:fe' + instance_id = uuidutils.generate_uuid() + interface_type = constants.OVS_VHOSTUSER_INTERFACE_TYPE + vhost_server_path = '/fake/path' + device = 'device' + bridge = 'bridge' + mtu = 1500 + external_ids = {'iface-id': iface_id, + 'iface-status': 'active', + 'attached-mac': mac, + 'vm-uuid': instance_id} + values = [('external_ids', external_ids), + ('type', interface_type), + ('options', {'vhost-server-path': vhost_server_path}) + ] + with mock.patch.object(self.br, 'update_device_mtu', + return_value=True) as mock_update_device_mtu, \ + mock.patch.object(self.br, '_ovs_supports_mtu_requests', + return_value=True): + self.br.create_ovs_vif_port(bridge, device, iface_id, mac, + instance_id, mtu=mtu, + interface_type=interface_type, + vhost_server_path=vhost_server_path) + self.mock_add_port.assert_has_calls([mock.call(bridge, device)]) + self.mock_db_set.assert_has_calls( + [mock.call('Interface', device, *values)]) + mock_update_device_mtu.assert_has_calls( + [mock.call(device, mtu, interface_type=interface_type)]) + + def test_update_ovs_vif_port(self): + with mock.patch.object(self.br, 'update_device_mtu') as \ + mock_update_device_mtu: + self.br.update_ovs_vif_port('device', mtu=1500, + interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) + mock_update_device_mtu.assert_has_calls( + [mock.call( + 'device', 1500, + interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE)]) + + @mock.patch.object(linux_net, 'delete_net_dev') + def test_delete_ovs_vif_port(self, mock_delete_net_dev): + self.br.delete_ovs_vif_port('bridge', 'device') + self.mock_del_port.assert_has_calls( + [mock.call('device', bridge='bridge', if_exists=True)]) + mock_delete_net_dev.assert_has_calls([mock.call('device')]) + + def test_ensure_ovs_bridge(self): + self.br.ensure_ovs_bridge('bridge', constants.OVS_DATAPATH_SYSTEM) + self.mock_add_br('bridge', may_exist=True, + datapath_type=constants.OVS_DATAPATH_SYSTEM) diff --git a/vif_plug_ovs/tests/unit/test_linux_net.py b/vif_plug_ovs/tests/unit/test_linux_net.py index 47ed9c08..b375338b 100644 --- a/vif_plug_ovs/tests/unit/test_linux_net.py +++ b/vif_plug_ovs/tests/unit/test_linux_net.py @@ -18,7 +18,6 @@ import testtools from os_vif.internal.command import ip as ip_lib from oslo_concurrency import processutils -from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs import linux_net from vif_plug_ovs import privsep @@ -131,257 +130,6 @@ class LinuxNetTest(testtools.TestCase): mock_execute.assert_has_calls([ mock.call('brctl', 'addif', 'br0', 'vnet1')]) - def test_ovs_vif_port_cmd(self): - expected = ['--', '--may-exist', 'add-port', - 'fake-bridge', 'fake-dev', - '--', 'set', 'Interface', 'fake-dev', - 'external-ids:iface-id=fake-iface-id', - 'external-ids:iface-status=active', - 'external-ids:attached-mac=fake-mac', - 'external-ids:vm-uuid=fake-instance-uuid'] - cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', - 'fake-iface-id', 'fake-mac', - 'fake-instance-uuid') - - self.assertEqual(expected, cmd) - - expected += ['type=fake-type'] - cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', - 'fake-iface-id', 'fake-mac', - 'fake-instance-uuid', - 'fake-type') - self.assertEqual(expected, cmd) - - expected += ['options:vhost-server-path=/fake/path'] - cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', - 'fake-iface-id', 'fake-mac', - 'fake-instance-uuid', - 'fake-type', - vhost_server_path='/fake/path') - self.assertEqual(expected, cmd) - - @mock.patch.object(linux_net, '_create_ovs_bridge_cmd') - @mock.patch.object(linux_net, '_ovs_vsctl') - def test_ensure_ovs_bridge(self, mock_vsctl, mock_create_ovs_bridge): - bridge = 'fake-bridge' - dp_type = 'fake-type' - linux_net.ensure_ovs_bridge(bridge, dp_type) - mock_create_ovs_bridge.assert_called_once_with(bridge, dp_type) - self.assertTrue(mock_vsctl.called) - - def test_create_ovs_bridge_cmd(self): - bridge = 'fake-bridge' - dp_type = 'fake-type' - expected = ['--', '--may-exist', 'add-br', bridge, - '--', 'set', 'Bridge', bridge, - 'datapath_type=%s' % dp_type] - actual = linux_net._create_ovs_bridge_cmd(bridge, dp_type) - self.assertEqual(expected, actual) - - @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') - @mock.patch.object(linux_net, '_ovs_vsctl') - @mock.patch.object(linux_net, '_create_ovs_vif_cmd') - @mock.patch.object(linux_net, '_set_device_mtu') - def test_ovs_vif_port_with_type_vhostuser(self, mock_set_device_mtu, - mock_create_cmd, mock_vsctl, - mock_ovs_supports_mtu_requests): - mock_ovs_supports_mtu_requests.return_value = True - linux_net.create_ovs_vif_port( - 'fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", mtu=1500, - interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) - mock_create_cmd.assert_called_once_with('fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", constants.OVS_VHOSTUSER_INTERFACE_TYPE, - None) - self.assertFalse(mock_set_device_mtu.called) - self.assertTrue(mock_vsctl.called) - - @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') - @mock.patch.object(linux_net, '_ovs_vsctl') - @mock.patch.object(linux_net, '_create_ovs_vif_cmd') - @mock.patch.object(linux_net, '_set_device_mtu') - def test_ovs_vif_port_with_type_vhostuserclient(self, - mock_set_device_mtu, mock_create_cmd, - mock_vsctl, mock_ovs_supports_mtu_requests): - mock_ovs_supports_mtu_requests.return_value = True - linux_net.create_ovs_vif_port( - 'fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", mtu=1500, - interface_type=constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE, - vhost_server_path="/fake/path") - mock_create_cmd.assert_called_once_with('fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", - constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE, - "/fake/path") - self.assertFalse(mock_set_device_mtu.called) - self.assertTrue(mock_vsctl.called) - - @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') - @mock.patch.object(linux_net, '_ovs_vsctl') - @mock.patch.object(linux_net, '_create_ovs_vif_cmd') - @mock.patch.object(linux_net, '_set_device_mtu') - def test_ovs_vif_port_with_no_mtu(self, mock_set_device_mtu, - mock_create_cmd, mock_vsctl, - mock_ovs_supports_mtu_requests): - mock_ovs_supports_mtu_requests.return_value = True - linux_net.create_ovs_vif_port( - 'fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid") - mock_create_cmd.assert_called_once_with('fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", None, None) - self.assertFalse(mock_set_device_mtu.called) - self.assertTrue(mock_vsctl.called) - - @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') - @mock.patch.object(linux_net, '_set_mtu_request') - @mock.patch.object(linux_net, '_ovs_vsctl') - @mock.patch.object(linux_net, '_create_ovs_vif_cmd', - return_value='ovs_command') - @mock.patch.object(linux_net, '_set_device_mtu') - def test_ovs_vif_port_with_timeout(self, mock_set_device_mtu, - mock_create_cmd, mock_vsctl, - mock_set_mtu_request, - mock_ovs_supports_mtu_requests): - mock_ovs_supports_mtu_requests.return_value = True - linux_net.create_ovs_vif_port( - 'fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", ovsdb_connection=None, - timeout=42) - self.assertTrue(mock_create_cmd.called) - self.assertFalse(mock_set_device_mtu.called) - mock_vsctl.assert_called_with('ovs_command', ovsdb_connection=None, - timeout=42) - - @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') - @mock.patch.object(linux_net, '_set_mtu_request') - @mock.patch.object(linux_net, '_ovs_vsctl') - @mock.patch.object(linux_net, '_create_ovs_vif_cmd', - return_value='ovs_command') - @mock.patch.object(linux_net, '_set_device_mtu') - def test_ovs_vif_port_with_no_timeout(self, mock_set_device_mtu, - mock_create_cmd, mock_vsctl, - mock_set_mtu_request, - mock_ovs_supports_mtu_requests): - mock_ovs_supports_mtu_requests.return_value = True - linux_net.create_ovs_vif_port( - 'fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid") - self.assertTrue(mock_create_cmd.called) - self.assertFalse(mock_set_device_mtu.called) - mock_vsctl.assert_called_with('ovs_command', ovsdb_connection=None, - timeout=None) - - @mock.patch.object(linux_net, '_ovs_supports_mtu_requests') - @mock.patch.object(linux_net, '_set_mtu_request') - @mock.patch.object(linux_net, '_ovs_vsctl') - @mock.patch.object(linux_net, '_create_ovs_vif_cmd', - return_value='ovs_command') - @mock.patch.object(linux_net, '_set_device_mtu') - def test_ovs_vif_port_with_ovsdb_connection(self, mock_set_device_mtu, - mock_create_cmd, mock_vsctl, - mock_set_mtu_request, - mock_ovs_supports_mtu_requests): - mock_ovs_supports_mtu_requests.return_value = True - linux_net.create_ovs_vif_port( - 'fake-bridge', - 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", ovsdb_connection='tcp:127.0.0.1:6640', - timeout=42) - self.assertTrue(mock_create_cmd.called) - self.assertFalse(mock_set_device_mtu.called) - mock_vsctl.assert_called_with('ovs_command', - ovsdb_connection='tcp:127.0.0.1:6640', - timeout=42) - - @mock.patch.object(processutils, "execute") - def test_ovs_vsctl(self, mock_execute): - args = ['fake-args', 42] - ovsdb_connection = 'tcp:127.0.0.1:6640' - timeout = 42 - linux_net._ovs_vsctl(args) - linux_net._ovs_vsctl(args, ovsdb_connection=ovsdb_connection, - timeout=timeout) - mock_execute.assert_has_calls([ - mock.call('ovs-vsctl', *args), - mock.call('ovs-vsctl', '--timeout=%s' % timeout, - '--db=%s' % ovsdb_connection, *args)]) - - @mock.patch.object(linux_net, '_ovs_vsctl') - def test_set_mtu_request(self, mock_vsctl): - dev = 'fake-dev' - mtu = 'fake-mtu' - ovsdb_connection = None - timeout = 120 - linux_net._set_mtu_request(dev, mtu, ovsdb_connection=ovsdb_connection, - timeout=timeout) - args = ['--', 'set', 'interface', dev, - 'mtu_request=%s' % mtu] - mock_vsctl.assert_called_with(args, ovsdb_connection=ovsdb_connection, - timeout=timeout) - - @mock.patch.object(linux_net, '_delete_net_dev') - @mock.patch.object(linux_net, '_ovs_vsctl') - def test_delete_ovs_vif_port_delete_netdev( - self, mock_vsctl, mock_delete_net_dev): - bridge = 'fake-bridge' - dev = 'fake-dev' - ovsdb_connection = None - timeout = 120 - linux_net.delete_ovs_vif_port(bridge, dev, - ovsdb_connection=ovsdb_connection, - timeout=timeout) - args = ['--', '--if-exists', 'del-port', bridge, dev] - mock_vsctl.assert_called_with(args, ovsdb_connection=ovsdb_connection, - timeout=timeout) - mock_delete_net_dev.assert_called() - - @mock.patch.object(linux_net, '_delete_net_dev') - @mock.patch.object(linux_net, '_ovs_vsctl') - def test_delete_ovs_vif_port(self, mock_vsctl, mock_delete_net_dev): - bridge = 'fake-bridge' - dev = 'fake-dev' - ovsdb_connection = None - timeout = 120 - linux_net.delete_ovs_vif_port( - bridge, dev, ovsdb_connection=ovsdb_connection, timeout=timeout, - delete_netdev=False) - args = ['--', '--if-exists', 'del-port', bridge, dev] - mock_vsctl.assert_called_with(args, ovsdb_connection=ovsdb_connection, - timeout=timeout) - mock_delete_net_dev.assert_not_called() - - @mock.patch.object(linux_net, '_ovs_vsctl') - def test_ovs_supports_mtu_requests(self, mock_vsctl): - args = ['--columns=mtu_request', 'list', 'interface'] - ovsdb_connection = None - timeout = 120 - msg = 'ovs-vsctl: Interface does not contain' + \ - ' a column whose name matches "mtu_request"' - - mock_vsctl.return_value = (None, msg) - result = linux_net._ovs_supports_mtu_requests( - ovsdb_connection=ovsdb_connection, - timeout=timeout) - mock_vsctl.assert_called_with(args, ovsdb_connection=ovsdb_connection, - timeout=timeout) - self.assertFalse(result) - - mock_vsctl.return_value = (None, None) - result = linux_net._ovs_supports_mtu_requests( - ovsdb_connection=ovsdb_connection, - timeout=timeout) - mock_vsctl.assert_called_with(args, ovsdb_connection=ovsdb_connection, - timeout=timeout) - self.assertTrue(result) - @mock.patch('six.moves.builtins.open') @mock.patch.object(os.path, 'isfile') def test_is_switchdev_ioerror(self, mock_isfile, mock_open): diff --git a/vif_plug_ovs/tests/unit/test_plugin.py b/vif_plug_ovs/tests/unit/test_plugin.py index dc6cff04..82fb0f6b 100644 --- a/vif_plug_ovs/tests/unit/test_plugin.py +++ b/vif_plug_ovs/tests/unit/test_plugin.py @@ -20,6 +20,7 @@ from os_vif.objects import fields from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs import ovs +from vif_plug_ovs.ovsdb import ovsdb_lib class PluginTest(testtools.TestCase): @@ -118,7 +119,7 @@ class PluginTest(testtools.TestCase): self.vif_ovs_hybrid, datapath=constants.OVS_DATAPATH_SYSTEM) self.assertEqual(constants.OVS_DATAPATH_SYSTEM, dp_type) - @mock.patch.object(linux_net, 'create_ovs_vif_port') + @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_create_vif_port(self, mock_create_ovs_vif_port): plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin._create_vif_port( @@ -129,11 +130,9 @@ class PluginTest(testtools.TestCase): self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, plugin.config.network_device_mtu, - ovsdb_connection=plugin.config.ovsdb_connection, - timeout=plugin.config.ovs_vsctl_timeout, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) - @mock.patch.object(linux_net, 'create_ovs_vif_port') + @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_create_vif_port_mtu_in_model(self, mock_create_ovs_vif_port): self.vif_ovs.network = self.network_ovs_mtu plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) @@ -145,12 +144,10 @@ class PluginTest(testtools.TestCase): self.vif_ovs.port_profile.interface_id, self.vif_ovs.address, self.instance.uuid, self.network_ovs_mtu.mtu, - ovsdb_connection=plugin.config.ovsdb_connection, - timeout=plugin.config.ovs_vsctl_timeout, interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) @mock.patch.object(ovs, 'sys') - @mock.patch.object(linux_net, 'ensure_ovs_bridge') + @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') def test_plug_ovs(self, ensure_ovs_bridge, mock_sys): mock_sys.platform = 'linux' plug_bridge_mock = mock.Mock() @@ -159,10 +156,10 @@ class PluginTest(testtools.TestCase): plugin.plug(self.vif_ovs, self.instance) dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_ovs) ensure_ovs_bridge.assert_called_once_with(self.vif_ovs.network.bridge, - dp_type, ovsdb_connection=None, timeout=120) + dp_type) @mock.patch.object(linux_net, 'set_interface_state') - @mock.patch.object(linux_net, 'ensure_ovs_bridge') + @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_update_vif_port') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') @mock.patch.object(linux_net, 'add_bridge_port') @@ -195,9 +192,7 @@ class PluginTest(testtools.TestCase): '_create_vif_port': [mock.call(self.vif_ovs_hybrid, 'qvob679325f-ca', self.instance)], - 'ensure_ovs_bridge': [mock.call('br0', dp_type, - ovsdb_connection=None, - timeout=120)] + 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } # plugging new devices should result in devices being created @@ -229,7 +224,7 @@ class PluginTest(testtools.TestCase): update_veth_pair.assert_has_calls(calls['update_veth_pair']) _update_vif_port.assert_has_calls(calls['_update_vif_port']) - @mock.patch.object(linux_net, 'ensure_ovs_bridge') + @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') @mock.patch.object(ip_lib, 'exists', return_value=False) @mock.patch.object(ovs, 'sys') @@ -262,15 +257,14 @@ class PluginTest(testtools.TestCase): plugin.unplug(self.vif_ovs, self.instance) unplug_bridge_mock.assert_not_called() - @mock.patch.object(linux_net, 'delete_ovs_vif_port') + @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(linux_net, 'delete_bridge') @mock.patch.object(ovs, 'sys') def test_unplug_ovs_bridge(self, mock_sys, delete_bridge, delete_ovs_vif_port): calls = { 'delete_bridge': [mock.call('qbrvif-xxx-yyy', 'qvbb679325f-ca')], - 'delete_ovs_vif_port': [mock.call('br0', 'qvob679325f-ca', - ovsdb_connection=None, timeout=120)] + 'delete_ovs_vif_port': [mock.call('br0', 'qvob679325f-ca')] } mock_sys.platform = 'linux' plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) @@ -278,14 +272,13 @@ class PluginTest(testtools.TestCase): delete_bridge.assert_has_calls(calls['delete_bridge']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) - @mock.patch.object(linux_net, 'delete_ovs_vif_port') + @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') @mock.patch.object(ovs, 'sys') def _check_unplug_ovs_windows(self, vif, mock_sys, delete_ovs_vif_port): mock_sys.platform = constants.PLATFORM_WIN32 plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(vif, self.instance) - delete_ovs_vif_port.assert_called_once_with('br0', vif.id, - ovsdb_connection=None, timeout=120) + delete_ovs_vif_port.assert_called_once_with('br0', vif.id) def test_unplug_ovs_windows(self): self._check_unplug_ovs_windows(self.vif_ovs) @@ -293,7 +286,7 @@ class PluginTest(testtools.TestCase): def test_unplug_ovs_bridge_windows(self): self._check_unplug_ovs_windows(self.vif_ovs_hybrid) - @mock.patch.object(linux_net, 'ensure_ovs_bridge') + @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') def test_plug_ovs_vhostuser(self, _create_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_vhostuser) @@ -311,8 +304,8 @@ class PluginTest(testtools.TestCase): _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) - @mock.patch.object(linux_net, 'ensure_ovs_bridge') - @mock.patch.object(linux_net, 'create_ovs_vif_port') + @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') + @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port') def test_plug_ovs_vhostuser_client(self, create_ovs_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type( @@ -325,9 +318,7 @@ class PluginTest(testtools.TestCase): 'ca:fe:de:ad:be:ef', 'f0000000-0000-0000-0000-000000000001', 1500, interface_type='dpdkvhostuserclient', - vhost_server_path='/var/run/openvswitch/vhub679325f-ca', - ovsdb_connection=None, - timeout=120)], + vhost_server_path='/var/run/openvswitch/vhub679325f-ca')], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } @@ -336,17 +327,16 @@ class PluginTest(testtools.TestCase): create_ovs_vif_port.assert_has_calls(calls['create_ovs_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) - @mock.patch.object(linux_net, 'delete_ovs_vif_port') + @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vhostuser(self, delete_ovs_vif_port): calls = { - 'delete_ovs_vif_port': [mock.call('br0', 'vhub679325f-ca', - ovsdb_connection=None, timeout=120)] + 'delete_ovs_vif_port': [mock.call('br0', 'vhub679325f-ca')] } plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.unplug(self.vif_vhostuser, self.instance) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) - @mock.patch.object(linux_net, 'ensure_ovs_bridge') + @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge') @mock.patch.object(linux_net, 'get_ifname_by_pci_address') @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_representor_port') @@ -365,9 +355,7 @@ class PluginTest(testtools.TestCase): calls = { 'ensure_ovs_bridge': [mock.call('br0', - constants.OVS_DATAPATH_SYSTEM, - ovsdb_connection=None, - timeout=120)], + constants.OVS_DATAPATH_SYSTEM)], 'get_ifname_by_pci_address': [mock.call('0002:24:12.3', pf_interface=True, switchdev=True)], @@ -395,7 +383,7 @@ class PluginTest(testtools.TestCase): @mock.patch.object(linux_net, 'get_vf_num_by_pci_address') @mock.patch.object(linux_net, 'get_representor_port') @mock.patch.object(linux_net, 'set_interface_state') - @mock.patch.object(linux_net, 'delete_ovs_vif_port') + @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port') def test_unplug_ovs_vf_passthrough(self, delete_ovs_vif_port, set_interface_state, get_representor_port, @@ -409,9 +397,8 @@ class PluginTest(testtools.TestCase): 'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')], 'get_representor_port': [mock.call('eth0', '2')], 'set_interface_state': [mock.call('eth0_2', 'down')], - 'delete_ovs_vif_port': [mock.call('br0', 'eth0_2', timeout=120, - ovsdb_connection=None, - delete_netdev=False)] + 'delete_ovs_vif_port': [mock.call('br0', 'eth0_2', + delete_netdev=False)] } get_ifname_by_pci_address.return_value = 'eth0'