aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/__init__.py21
-rw-r--r--src/dpdk/__init__.py21
-rw-r--r--src/dpdk/dpdk.py377
-rw-r--r--src/ovs/__init__.py25
-rw-r--r--src/ovs/daemon.py142
-rw-r--r--src/ovs/ofctl.py316
6 files changed, 902 insertions, 0 deletions
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 00000000..9293b4f8
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,21 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""High level package for VSPERF dependencies
+
+No functionality is expected for this package and its purpose is just to
+keep Python package structure intact without extra requirements for
+PYTHONPATH.
+"""
+
diff --git a/src/dpdk/__init__.py b/src/dpdk/__init__.py
new file mode 100644
index 00000000..4be1e215
--- /dev/null
+++ b/src/dpdk/__init__.py
@@ -0,0 +1,21 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""A collection of functions for automating the DPDK setup and teardown.
+
+These automation tasks include mounting/unmounting hugepages, inserting
+and removing drivers and binding/unbinding NICs.
+"""
+
+from src.dpdk.dpdk import *
diff --git a/src/dpdk/dpdk.py b/src/dpdk/dpdk.py
new file mode 100644
index 00000000..9b3d1385
--- /dev/null
+++ b/src/dpdk/dpdk.py
@@ -0,0 +1,377 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""Automation of system configuration for DPDK use.
+
+Parts of this based on ``tools/pci_unbind.py`` script from Intel(R) DPDK.
+"""
+
+from sys import platform as _platform
+
+import os
+import re
+import subprocess
+import logging
+import locale
+
+from tools import tasks
+from conf import settings
+
+_LOGGER = logging.getLogger(__name__)
+RTE_PCI_TOOL = os.path.join(
+ settings.getValue('RTE_SDK'), 'tools', 'dpdk_nic_bind.py')
+
+#
+# system management
+#
+
+
+def init():
+ """Setup system for DPDK.
+ """
+ if not _is_linux():
+ _LOGGER.error('Not running on a compatible Linux version. Exiting...')
+ return
+
+ _mount_hugepages()
+ _insert_modules()
+ _remove_vhost_net()
+ _bind_nics()
+ _copy_dpdk_for_guest()
+
+
+def cleanup():
+ """Setup system for DPDK.
+ """
+ if not _is_linux():
+ _LOGGER.error('Not running on a compatible Linux version. Exiting...')
+ return
+
+ _unbind_nics()
+ _remove_modules()
+ _umount_hugepages()
+ _vhost_user_cleanup()
+
+
+#
+# vhost specific modules management
+#
+
+
+def insert_vhost_modules():
+ """Inserts VHOST related kernel modules
+ """
+ mod_path_prefix = os.path.join(settings.getValue('RTE_SDK'),
+ 'lib',
+ 'librte_vhost')
+ _insert_module_group('VHOST_MODULE', mod_path_prefix)
+
+
+def remove_vhost_modules():
+ """Removes all VHOST related kernel modules
+ """
+ _remove_module_group('VHOST_MODULE')
+
+#
+# basic compatibility test
+#
+
+
+def _is_linux():
+ """Check if running on Linux.
+
+ Many of the functions in this file rely on features commonly found
+ only on Linux (i.e. ``/proc`` is not present on FreeBSD). Hence, this
+ check is important to ensure someone doesn't run this on an incompatible
+ OS or distro.
+ """
+ return _platform.startswith('linux') and os.path.isdir('/proc')
+
+#
+# hugepage management
+#
+
+
+def _is_hugepage_available():
+ """Check if hugepages are available on the system.
+ """
+ hugepage_re = re.compile(r'^HugePages_Free:\s+(?P<num_hp>\d+)$')
+
+ # read in meminfo
+ with open('/proc/meminfo') as mem_file:
+ mem_info = mem_file.readlines()
+
+ # first check if module is loaded
+ for line in mem_info:
+ result = hugepage_re.match(line)
+ if not result:
+ continue
+
+ num_huge = result.group('num_hp')
+ if not num_huge:
+ _LOGGER.info('No free hugepages.')
+ else:
+ _LOGGER.info('Found \'%s\' free hugepage(s).', num_huge)
+ return True
+
+ return False
+
+
+def _is_hugepage_mounted():
+ """Check if hugepages are mounted.
+ """
+ output = subprocess.check_output(['mount'], shell=True)
+ my_encoding = locale.getdefaultlocale()[1]
+ for line in output.decode(my_encoding).split('\n'):
+ if 'hugetlbfs' in line:
+ return True
+
+ return False
+
+
+def _mount_hugepages():
+ """Ensure hugepages are mounted.
+ """
+ if not _is_hugepage_available():
+ return
+
+ if _is_hugepage_mounted():
+ return
+
+ if not os.path.exists(settings.getValue('HUGEPAGE_DIR')):
+ os.makedirs(settings.getValue('HUGEPAGE_DIR'))
+ try:
+ tasks.run_task(['sudo', 'mount', '-t', 'hugetlbfs', 'nodev',
+ settings.getValue('HUGEPAGE_DIR')],
+ _LOGGER, 'Mounting hugepages...', True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to mount hugepages.')
+
+
+def _umount_hugepages():
+ """Ensure hugepages are unmounted.
+ """
+ if not _is_hugepage_mounted():
+ return
+
+ try:
+ tasks.run_task(['sudo', 'umount', settings.getValue('HUGEPAGE_DIR')],
+ _LOGGER, 'Unmounting hugepages...', True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to umount hugepages.')
+
+#
+# module management
+#
+
+
+def _is_module_inserted(module):
+ """Check if a module is inserted on system.
+ """
+ with open('/proc/modules') as mod_file:
+ loaded_mods = mod_file.readlines()
+
+ # first check if module is loaded
+ for line in loaded_mods:
+ if line.startswith(module):
+ return True
+ return False
+
+
+def _insert_modules():
+ """Ensure required modules are inserted on system.
+ """
+ for module in settings.getValue('SYS_MODULES'):
+ if _is_module_inserted(module):
+ continue
+
+ try:
+ tasks.run_task(['sudo', 'modprobe', module], _LOGGER,
+ 'Inserting module \'%s\'...' % module, True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to insert module \'%s\'.', module)
+ raise # fail catastrophically
+
+ mod_path_prefix = settings.getValue('OVS_DIR')
+ _insert_module_group('OVS_MODULES', mod_path_prefix)
+ mod_path_prefix = os.path.join(settings.getValue('RTE_SDK'),
+ settings.getValue('RTE_TARGET'))
+ _insert_module_group('DPDK_MODULES', mod_path_prefix)
+
+
+def _insert_module_group(module_group, group_path_prefix):
+ """Ensure all modules in a group are inserted into the system.
+
+ :param module_group: A name of configuration item containing a list
+ of module names
+ """
+ for module in settings.getValue(module_group):
+ # first check if module is loaded
+ if _is_module_inserted(module[1]):
+ continue
+
+ try:
+ mod_path = os.path.join(group_path_prefix, module[0],
+ '%s.ko' % module[1])
+ tasks.run_task(['sudo', 'insmod', mod_path], _LOGGER,
+ 'Inserting module \'%s\'...' % module[1], True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to insert module \'%s\'.', module[1])
+ raise # fail catastrophically
+
+
+def _remove_modules():
+ """Ensure required modules are removed from system.
+ """
+ _remove_module_group('OVS_MODULES')
+ _remove_module_group('DPDK_MODULES')
+
+ for module in settings.getValue('SYS_MODULES'):
+ # first check if module is loaded
+ if not _is_module_inserted(module):
+ continue
+
+ try:
+ tasks.run_task(['sudo', 'rmmod', module], _LOGGER,
+ 'Removing module \'%s\'...' % module, True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to remove module \'%s\'.', module)
+ continue
+
+
+def _remove_module_group(module_group):
+ """Ensure all modules in a group are removed from the system.
+
+ :param module_group: A name of configuration item containing a list
+ of module names
+ """
+ for module in settings.getValue(module_group):
+ # first check if module is loaded
+ if not _is_module_inserted(module[1]):
+ continue
+
+ try:
+ tasks.run_task(['sudo', 'rmmod', module[1]], _LOGGER,
+ 'Removing module \'%s\'...' % module[1], True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to remove module \'%s\'.', module[1])
+ continue
+
+
+#
+# 'vhost-net' module management
+#
+
+def _remove_vhost_net():
+ """Remove vhost-net driver and file.
+ """
+ if _is_module_inserted('vhost_net'):
+ try:
+ tasks.run_task(['sudo', 'rmmod', 'vhost_net'], _LOGGER,
+ 'Removing \'/dev/vhost-net\' directory...', True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to remove module \'vhost_net\'.')
+
+ try:
+ tasks.run_task(['sudo', 'rm', '-f', '/dev/vhost-net'], _LOGGER,
+ 'Removing \'/dev/vhost-net\' directory...', True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to remove directory \'/dev/vhost-net\'.')
+
+#
+# NIC management
+#
+
+
+def _bind_nics():
+ """Bind NICs using the Intel DPDK ``pci_unbind.py`` tool.
+ """
+ try:
+ tasks.run_task(['sudo', RTE_PCI_TOOL, '--bind', 'igb_uio'] +
+ settings.getValue('WHITELIST_NICS'), _LOGGER,
+ 'Binding NICs %s...' %
+ settings.getValue('WHITELIST_NICS'),
+ True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to bind NICs %s',
+ str(settings.getValue('WHITELIST_NICS')))
+
+
+def _unbind_nics():
+ """Unbind NICs using the Intel DPDK ``pci_unbind.py`` tool.
+ """
+ try:
+ tasks.run_task(['sudo', RTE_PCI_TOOL, '--unbind'] +
+ settings.getValue('WHITELIST_NICS'), _LOGGER,
+ 'Unbinding NICs %s...' %
+ str(settings.getValue('WHITELIST_NICS')),
+ True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to unbind NICs %s',
+ str(settings.getValue('WHITELIST_NICS')))
+
+
+def _copy_dpdk_for_guest():
+ """Copy dpdk code to GUEST_SHARE_DIR for use by guests.
+ """
+ guest_share_dir = os.path.join(
+ settings.getValue('GUEST_SHARE_DIR'), 'DPDK')
+
+ if not os.path.exists(guest_share_dir):
+ os.makedirs(guest_share_dir)
+
+ try:
+ tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
+ os.path.join(settings.getValue('RTE_SDK'), ''),
+ guest_share_dir],
+ _LOGGER,
+ 'Copying DPDK to shared directory...',
+ True)
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to copy DPDK to shared directory')
+
+
+#
+# Vhost-user cleanup
+#
+
+def _vhost_user_cleanup():
+ """Remove files created by vhost-user tests.
+ """
+ for sock in settings.getValue('VHOST_USER_SOCKS'):
+ if os.path.exists(sock):
+ try:
+ tasks.run_task(['sudo', 'rm', sock],
+ _LOGGER,
+ 'Deleting vhost-user socket \'%s\'...' %
+ sock,
+ True)
+
+ except subprocess.CalledProcessError:
+ _LOGGER.error('Unable to delete vhost-user socket \'%s\'.',
+ sock)
+ continue
+
+
+class Dpdk(object):
+ """A context manager for the system init/cleanup.
+ """
+ def __enter__(self):
+ _LOGGER.info('Setting up DPDK')
+ init()
+ return self
+
+ def __exit__(self, type_, value, traceback):
+ _LOGGER.info('Cleaning up DPDK')
+ cleanup()
diff --git a/src/ovs/__init__.py b/src/ovs/__init__.py
new file mode 100644
index 00000000..1a31ea2e
--- /dev/null
+++ b/src/ovs/__init__.py
@@ -0,0 +1,25 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""
+A package for controlling Open vSwitch
+
+This package is intended to stay gneneric enough to support using any data
+path of OVS (linux kernel, DPDK userspace, etc.) by different parameterization
+and external setup of vswitchd-external process, kernel modules etc.
+
+"""
+
+from src.ovs.daemon import *
+from src.ovs.ofctl import *
diff --git a/src/ovs/daemon.py b/src/ovs/daemon.py
new file mode 100644
index 00000000..ee3446d5
--- /dev/null
+++ b/src/ovs/daemon.py
@@ -0,0 +1,142 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""Class wrapper for controlling an OVS instance.
+
+Wraps a pair of ``ovs-vswitchd`` and ``ovsdb-server`` processes.
+"""
+
+import os
+import logging
+import pexpect
+
+from conf import settings
+from tools import tasks
+
+_OVS_VSWITCHD_BIN = os.path.join(
+ settings.getValue('OVS_DIR'), 'vswitchd', 'ovs-vswitchd')
+_OVSDB_TOOL_BIN = os.path.join(
+ settings.getValue('OVS_DIR'), 'ovsdb', 'ovsdb-tool')
+_OVSDB_SERVER_BIN = os.path.join(
+ settings.getValue('OVS_DIR'), 'ovsdb', 'ovsdb-server')
+
+_OVS_VAR_DIR = '/usr/local/var/run/openvswitch/'
+_OVS_ETC_DIR = '/usr/local/etc/openvswitch/'
+
+_LOG_FILE_VSWITCHD = os.path.join(
+ settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_VSWITCHD'))
+
+class VSwitchd(tasks.Process):
+ """Class wrapper for controlling an OVS instance.
+
+ Wraps a pair of ``ovs-vswitchd`` and ``ovsdb-server`` processes.
+ """
+ _ovsdb_pid = None
+ _logfile = _LOG_FILE_VSWITCHD
+
+
+ _expect = r'EAL: Master l*core \d+ is ready'
+ _proc_name = 'ovs-vswitchd'
+
+ def __init__(self, timeout=30, vswitchd_args=None):
+ """Initialise the wrapper with a specific start timeout and extra
+ parameters.
+
+ :param timeout: Timeout to wait for application to start.
+ :param vswitchd_args: Command line parameters for vswitchd.
+
+ :returns: None
+ """
+ self._logger = logging.getLogger(__name__)
+ self._timeout = timeout
+ vswitchd_args = vswitchd_args or []
+
+ self._cmd = ['sudo', '-E', _OVS_VSWITCHD_BIN] + vswitchd_args
+
+ # startup/shutdown
+
+ def start(self):
+ """ Start ``ovsdb-server`` and ``ovs-vswitchd`` instance.
+
+ :returns: None
+ :raises: pexpect.EOF, pexpect.TIMEOUT
+ """
+ self._reset_ovsdb()
+ self._start_ovsdb() # this has to be started first
+
+ try:
+ super(VSwitchd, self).start()
+ self.relinquish()
+ except (pexpect.EOF, pexpect.TIMEOUT) as exc:
+ self._kill_ovsdb()
+ raise exc
+
+ def kill(self):
+ """Kill ``ovs-vswitchd`` instance if it is alive.
+
+ :returns: None
+ """
+ self._logger.info('Killing ovs-vswitchd...')
+
+ self._kill_ovsdb()
+
+ super(VSwitchd, self).kill()
+
+ # helper functions
+
+ def _reset_ovsdb(self):
+ """Reset system for 'ovsdb'.
+
+ :returns: None
+ """
+ self._logger.info('Resetting system after last run...')
+
+ tasks.run_task(['sudo', 'rm', '-rf', _OVS_VAR_DIR], self._logger)
+ tasks.run_task(['sudo', 'mkdir', '-p', _OVS_VAR_DIR], self._logger)
+ tasks.run_task(['sudo', 'rm', '-rf', _OVS_ETC_DIR], self._logger)
+ tasks.run_task(['sudo', 'mkdir', '-p', _OVS_ETC_DIR], self._logger)
+
+ tasks.run_task(['sudo', 'rm', '-f',
+ os.path.join(_OVS_ETC_DIR, 'conf.db')],
+ self._logger)
+
+ self._logger.info('System reset after last run.')
+
+ def _start_ovsdb(self):
+ """Start ``ovsdb-server`` instance.
+
+ :returns: None
+ """
+ tasks.run_task(['sudo', _OVSDB_TOOL_BIN, 'create',
+ os.path.join(_OVS_ETC_DIR, 'conf.db'),
+ os.path.join(settings.getValue('OVS_DIR'), 'vswitchd',
+ 'vswitch.ovsschema')],
+ self._logger,
+ 'Creating ovsdb configuration database...')
+
+ self._ovsdb_pid = tasks.run_background_task(
+ ['sudo', _OVSDB_SERVER_BIN,
+ '--remote=punix:%s' % os.path.join(_OVS_VAR_DIR, 'db.sock'),
+ '--remote=db:Open_vSwitch,Open_vSwitch,manager_options'],
+ self._logger,
+ 'Starting ovsdb-server...')
+
+ def _kill_ovsdb(self):
+ """Kill ``ovsdb-server`` instance.
+
+ :returns: None
+ """
+ if self._ovsdb_pid:
+ tasks.run_task(['sudo', 'kill', '-2', str(self._ovsdb_pid)],
+ self._logger, 'Killing ovsdb-server...')
diff --git a/src/ovs/ofctl.py b/src/ovs/ofctl.py
new file mode 100644
index 00000000..c6aaddc9
--- /dev/null
+++ b/src/ovs/ofctl.py
@@ -0,0 +1,316 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""Wrapper for an OVS bridge for convenient use of ``ovs-vsctl`` and
+``ovs-ofctl`` on it.
+
+Much of this code is based on ``ovs-lib.py`` from Open Stack:
+
+https://github.com/openstack/neutron/blob/6eac1dc99124ca024d6a69b3abfa3bc69c735667/neutron/agent/linux/ovs_lib.py
+"""
+
+import os
+import logging
+import string
+
+from tools import tasks
+from conf import settings
+
+_OVS_VSCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
+ 'ovs-vsctl')
+_OVS_OFCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
+ 'ovs-ofctl')
+
+class OFBase(object):
+ """Add/remove/show datapaths using ``ovs-ofctl``.
+ """
+ def __init__(self, timeout=10):
+ """Initialise logger.
+
+ :param timeout: Timeout to be used for each command
+
+ :returns: None
+ """
+ self.logger = logging.getLogger(__name__)
+ self.timeout = timeout
+
+ # helpers
+
+ def run_vsctl(self, args, check_error=False):
+ """Run ``ovs-vsctl`` with supplied arguments.
+
+ :param args: Arguments to pass to ``ovs-vsctl``
+ :param check_error: Throw exception on error
+
+ :return: None
+ """
+ cmd = ['sudo', _OVS_VSCTL_BIN, '--timeout', str(self.timeout)] + args
+ return tasks.run_task(
+ cmd, self.logger, 'Running ovs-vsctl...', check_error)
+
+ # datapath management
+
+ def add_br(self, br_name='br0'):
+ """Add datapath.
+
+ :param br_name: Name of bridge
+
+ :return: Instance of :class OFBridge:
+ """
+ self.logger.debug('add bridge')
+ self.run_vsctl(['add-br', br_name])
+
+ return OFBridge(br_name, self.timeout)
+
+ def del_br(self, br_name='br0'):
+ """Delete datapath.
+
+ :param br_name: Name of bridge
+
+ :return: None
+ """
+ self.logger.debug('delete bridge')
+ self.run_vsctl(['del-br', br_name])
+
+
+class OFBridge(OFBase):
+ """Control a bridge instance using ``ovs-vsctl`` and ``ovs-ofctl``.
+ """
+ def __init__(self, br_name='br0', timeout=10):
+ """Initialise bridge.
+
+ :param br_name: Bridge name
+ :param timeout: Timeout to be used for each command
+
+ :returns: None
+ """
+ super(OFBridge, self).__init__(timeout)
+ self.br_name = br_name
+ self._ports = {}
+
+ # context manager
+
+ def __enter__(self):
+ """Create datapath
+
+ :returns: self
+ """
+ return self
+
+ def __exit__(self, type_, value, traceback):
+ """Remove datapath.
+ """
+ if not traceback:
+ self.destroy()
+
+ # helpers
+
+ def run_ofctl(self, args, check_error=False):
+ """Run ``ovs-ofctl`` with supplied arguments.
+
+ :param args: Arguments to pass to ``ovs-ofctl``
+ :param check_error: Throw exception on error
+
+ :return: None
+ """
+ cmd = ['sudo', _OVS_OFCTL_BIN, '--timeout', str(self.timeout)] + args
+ return tasks.run_task(
+ cmd, self.logger, 'Running ovs-ofctl...', check_error)
+
+ def create(self):
+ """Create bridge.
+ """
+ self.logger.debug('create bridge')
+ self.add_br(self.br_name)
+
+ def destroy(self):
+ """Destroy bridge.
+ """
+ self.logger.debug('destroy bridge')
+ self.del_br(self.br_name)
+
+ def reset(self):
+ """Reset bridge.
+ """
+ self.logger.debug('reset bridge')
+ self.destroy()
+ self.create()
+
+ # port management
+
+ def add_port(self, port_name, params):
+ """Add port to bridge.
+
+ :param port_name: Name of port
+ :param params: Additional list of parameters to add-port
+
+ :return: OpenFlow port number for the port
+ """
+ self.logger.debug('add port')
+ self.run_vsctl(['add-port', self.br_name, port_name]+params)
+
+ # This is how port number allocation works currently
+ # This possibly will not work correctly if there are port deletions
+ # in between
+ of_port = len(self._ports) + 1
+ self._ports[port_name] = (of_port, params)
+ return of_port
+
+ def del_port(self, port_name):
+ """Remove port from bridge.
+
+ :param port_name: Name of port
+
+ :return: None
+ """
+ self.logger.debug('delete port')
+ self.run_vsctl(['del-port', self.br_name, port_name])
+ self._ports.pop(port_name)
+
+ def set_db_attribute(self, table_name, record, column, value):
+ """Set database attribute.
+
+ :param table_name: Name of table
+ :param record: Name of record
+ :param column: Name of column
+ :param value: Value to set
+
+ :return: None
+ """
+ self.logger.debug('set attribute')
+ self.run_vsctl(['set', table_name, record, '%s=%s' % (column, value)])
+
+ def get_ports(self):
+ """Get the ports of this bridge
+
+ Structure of the returned ports dictionary is
+ 'portname': (openflow_port_number, extra_parameters)
+
+ Example:
+ ports = {
+ 'dpdkport0':
+ (1, ['--', 'set', 'Interface', 'dpdkport0', 'type=dpdk']),
+ 'dpdkvhostport0':
+ (2, ['--', 'set', 'Interface', 'dpdkvhostport0',
+ 'type=dpdkvhost'])
+ }
+
+ :return: Dictionary of ports
+ """
+ return self._ports
+
+ def clear_db_attribute(self, table_name, record, column):
+ """Clear database attribute.
+
+ :param table_name: Name of table
+ :param record: Name of record
+ :param column: Name of column
+
+ :return: None
+ """
+ self.logger.debug('clear attribute')
+ self.run_vsctl(['clear', table_name, record, column])
+
+ # flow mangement
+
+ def add_flow(self, flow):
+ """Add flow to bridge.
+
+ :param flow: Flow description as a dictionary
+ For flow dictionary structure, see function flow_key
+
+ :return: None
+ """
+ if not flow.get('actions'):
+ self.logger.error('add flow requires actions')
+ return
+
+ self.logger.debug('add flow')
+ _flow_key = flow_key(flow)
+ self.logger.debug('key : %s', _flow_key)
+ self.run_ofctl(['add-flow', self.br_name, _flow_key])
+
+ def del_flow(self, flow):
+ """Delete flow from bridge.
+
+ :param flow: Flow description as a dictionary
+ For flow dictionary structure, see function flow_key
+ flow=None will delete all flows
+
+ :return: None
+ """
+ self.logger.debug('delete flow')
+ _flow_key = flow_key(flow)
+ self.logger.debug('key : %s', _flow_key)
+ self.run_ofctl(['del-flows', self.br_name, _flow_key])
+
+ def del_flows(self):
+ """Delete all flows from bridge.
+ """
+ self.logger.debug('delete flows')
+ self.run_ofctl(['del-flows', self.br_name])
+
+ def dump_flows(self):
+ """Dump all flows from bridge.
+ """
+ self.logger.debug('dump flows')
+ self.run_ofctl(['dump-flows', self.br_name])
+
+#
+# helper functions
+#
+
+def flow_key(flow):
+ """Model a flow key string for ``ovs-ofctl``.
+
+ Syntax taken from ``ovs-ofctl`` manpages:
+ http://openvswitch.org/cgi-bin/ovsman.cgi?page=utilities%2Fovs-ofctl.8
+
+ Example flow dictionary:
+ flow = {
+ 'in_port': '1',
+ 'idle_timeout': '0',
+ 'actions': ['output:3']
+ }
+
+ :param flow: Flow description as a dictionary
+
+ :return: String
+ :rtype: str
+ """
+ _flow_add_key = string.Template('${fields},action=${actions}')
+ _flow_del_key = string.Template('${fields}')
+
+ field_params = []
+
+ user_params = (x for x in list(flow.items()) if x[0] != 'actions')
+ for (key, default) in user_params:
+ field_params.append('%(field)s=%(value)s' %
+ {'field': key, 'value': default})
+
+ field_params = ','.join(field_params)
+
+ _flow_key_param = {
+ 'fields': field_params,
+ }
+
+ # no actions == delete key
+ if 'actions' in flow:
+ _flow_key_param['actions'] = ','.join(flow['actions'])
+
+ flow_str = _flow_add_key.substitute(_flow_key_param)
+ else:
+ flow_str = _flow_del_key.substitute(_flow_key_param)
+
+ return flow_str