ovsdbapp-0.9.1/0000775000175100017510000000000013236157776013404 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/PKG-INFO0000664000175100017510000000310113236157776014474 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: ovsdbapp Version: 0.9.1 Summary: A library for creating OVSDB applications Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: =============================== ovsdbapp =============================== A library for creating OVSDB applications The ovdsbapp library is useful for creating applications that communicate via Open_vSwitch's OVSDB protocol (https://tools.ietf.org/html/rfc7047). It wraps the Python 'ovs' and adds an event loop and friendly transactions. * Free software: Apache license * Source: http://git.openstack.org/cgit/openstack/ovsdbapp * Bugs: http://bugs.launchpad.net/ovsdbapp Features -------- * An thread-based event loop for using ovs.db.Idl * Transaction support * Native OVSDB communication Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.5 ovsdbapp-0.9.1/setup.cfg0000666000175100017510000000237313236157776015234 0ustar zuulzuul00000000000000[metadata] name = ovsdbapp summary = A library for creating OVSDB applications description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.5 [files] packages = ovsdbapp [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = ovsdbapp/locale domain = ovsdbapp [update_catalog] domain = ovsdbapp output_dir = ovsdbapp/locale input_file = ovsdbapp/locale/ovsdbapp.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = ovsdbapp/locale/ovsdbapp.pot [build_releasenotes] all_files = 1 build-dir = releasenotes/build source-dir = releasenotes/source [egg_info] tag_build = tag_date = 0 ovsdbapp-0.9.1/tools/0000775000175100017510000000000013236157776014544 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/tools/debug_venv.py0000777000175100017510000000270713236157620017241 0ustar zuulzuul00000000000000#!/usr/bin/env python # 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 __future__ import print_function import atexit import os import subprocess import sys from fixtures import fixture from ovsdbapp import venv if len(sys.argv) != 3: print("Requires two arguments: venvdir ovsdir", file=sys.stderr) sys.exit(1) for d in sys.argv[1:]: if not os.path.isdir(d): print("%s is not a directory" % d, file=sys.stderr) sys.exit(1) venvdir = os.path.abspath(sys.argv[1]) ovsdir = os.path.abspath(sys.argv[2]) v = venv.OvsOvnVenvFixture(venvdir, ovsdir) try: atexit.register(v.cleanUp) v.setUp() except fixture.MultipleExceptions as e: raise e.args[0][0], e.args[0][1], e.args[0][2] try: print("*** Exit the shell when finished debugging ***") subprocess.call([os.getenv('SHELL'), '-i'], env=v.env) except Exception: print("*** Could not start shell, don't type 'exit'***", file=sys.stderr) raise ovsdbapp-0.9.1/tools/tox_install.sh0000777000175100017510000000415613236157633017443 0ustar zuulzuul00000000000000#!/usr/bin/env bash # Library constraint file contains this library version pin that is in conflict # with installing the library from source. We should replace the version pin in # the constraints file before applying it for from-source installation. ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner BRANCH_NAME=master LIB_NAME=ovsdbapp requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?) set -e CONSTRAINTS_FILE=$1 shift install_cmd="pip install" mydir=$(mktemp -dt "$LIB_NAME-tox_install-XXXXXXX") trap "rm -rf $mydir" EXIT localfile=$mydir/upper-constraints.txt if [[ $CONSTRAINTS_FILE != http* ]]; then CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE fi curl $CONSTRAINTS_FILE -k -o $localfile install_cmd="$install_cmd -c$localfile" if [ $requirements_installed -eq 0 ]; then echo "Requirements already installed; using existing package" elif [ -x "$ZUUL_CLONER" ]; then pushd $mydir $ZUUL_CLONER --cache-dir \ /opt/git \ --branch $BRANCH_NAME \ git://git.openstack.org \ openstack/requirements cd openstack/requirements $install_cmd -e . popd else if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements" fi $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION} fi # This is the main purpose of the script: Allow local installation of # the current repo. It is listed in constraints file and thus any # install will be constrained and we need to unconstrain it. edit-constraints $localfile -- $LIB_NAME "-e file://$PWD#egg=$LIB_NAME" # We require at least OVS 2.7. Testing infrastructure doesn't support it yet, # so build it. Eventually, we should run some checks to see what is actually # installed and see if we can use it instead. if [ "$OVS_SRCDIR" -a ! -d "$OVS_SRCDIR" ]; then echo "Building OVS in $OVS_SRCDIR" mkdir -p $OVS_SRCDIR git clone git://github.com/openvswitch/ovs.git $OVS_SRCDIR (cd $OVS_SRCDIR && ./boot.sh && PYTHON=/usr/bin/python ./configure && make -j$(($(nproc) + 1))) fi $install_cmd -U $* exit $? ovsdbapp-0.9.1/tools/debug_venv0000777000175100017510000000333113236157620016604 0ustar zuulzuul00000000000000#!/bin/sh # Use a shell script to launch tools/debub_venv.py so that we can enter a # virtual environment if we are not already in one for option;do if test -n "$prev";then eval $prev=\$option prev= continue fi case $option in -v) if [ -n "$VIRTUAL_ENV" ]; then echo "Already in a virtual environment" 1>&2 exit 1 fi prev=venv ;; -o) prev=ovsvenv ;; -h|--help) cat << EOF debug_venv: debug a test OVS virtual environment usage: debug_venv [-v virtualenv] [-o ovsvenv | test_regex]" Options: -v The Python virtualenv to enter (defaults to 'functional') -o The OVS virtual environment directory (precludes test_regex) test_regex An optionsal regular expression matching the test name to debug EOF exit ;; *) if test -z "$regex";then regex=$option else echo "Only one regex" 1>&2 exit 1 fi esac done if [ -z "$regex" -a -z "$ovsvenv" ]; then echo "Need regex or ovsvenv" 1>&2 exit 1 fi if [ -z "$VIRTUAL_ENV" ]; then . .tox/${venv:-functional}/bin/activate trap deactivate EXIT fi if [ -n "$regex" -a -z "$ovsvenv" ]; then # Just do the first match for now lookup=$(grep $regex $VIRTUAL_ENV/ovsvenv.*|head -1) if [ -z "$lookup" ]; then echo "Could not match $regex" 1>&2 exit 1 fi test_file=$(echo $lookup|cut -d: -f1) test_match=", matched $(echo $lookup|rev|cut -d: -f1|rev)" ovsvenv=$(head -1 $test_file) fi echo "Debugging OVS virtual environment: $ovsvenv$test_match" tools/debug_venv.py $ovsvenv $VIRTUAL_ENV/src/ovs ovsdbapp-0.9.1/tools/test-setup.sh0000777000175100017510000000042613236157620017210 0ustar zuulzuul00000000000000#!/bin/bash -xe # This script is triggered by extra-test-setup macro from project-config # repository. # Set manager for native interface sudo ovs-vsctl --timeout=10 --id=@manager -- create Manager target=\"ptcp:6640:127.0.0.1\" -- add Open_vSwitch . manager_options @manager ovsdbapp-0.9.1/tools/coding-checks.sh0000777000175100017510000000301113236157620017565 0ustar zuulzuul00000000000000#!/bin/sh # This script is copied from neutron and adapted for ovsdbapp. set -eu usage () { echo "Usage: $0 [OPTION]..." echo "Run ovsdbapp's coding check(s)" echo "" echo " -Y, --pylint [] Run pylint check on the entire ovsdbapp module or just files changed in basecommit (e.g. HEAD~1)" echo " -h, --help Print this usage message" echo exit 0 } join_args() { if [ -z "$scriptargs" ]; then scriptargs="$opt" else scriptargs="$scriptargs $opt" fi } process_options () { i=1 while [ $i -le $# ]; do eval opt=\$$i case $opt in -h|--help) usage;; -Y|--pylint) pylint=1;; *) join_args;; esac i=$((i+1)) done } run_pylint () { local target="${scriptargs:-all}" if [ "$target" = "all" ]; then files="ovsdbapp" else case "$target" in *HEAD~[0-9]*) files=$(git diff --diff-filter=AM --name-only $target -- "*.py");; *) echo "$target is an unrecognized basecommit"; exit 1;; esac fi echo "Running pylint..." echo "You can speed this up by running it on 'HEAD~[0-9]' (e.g. HEAD~1, this change only)..." if [ -n "${files}" ]; then pylint --rcfile=.pylintrc --output-format=colorized ${files} else echo "No python changes in this commit, pylint check not required." exit 0 fi } scriptargs= pylint=1 process_options $@ if [ $pylint -eq 1 ]; then run_pylint exit 0 fi ovsdbapp-0.9.1/setup.py0000666000175100017510000000200613236157620015102 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ovsdbapp-0.9.1/TESTING.rst0000666000175100017510000000377213236157620015252 0ustar zuulzuul00000000000000ovsdbapp testing ================ Test preferences ---------------- Most ovsdbapp tests will be functional tests. Unit tests are reserved primarily for functions that produce a given output for their inputs, regardless of externalities. Unit tests using mock are acceptable if something is hard to test without it. BUT, please think carefully before writing a test that makes heavy use of mock.assert_called_once_with() as those tests *very* often tend to test what a function *currently does* and not what a function *should do*. Running tests ------------- Tests are run with tox. Generally in the form of: .. code-block:: shell tox -e Functional tests ---------------- Run the functional tests with: .. code-block:: shell tox -e functional The ovsdbapp functional tests create an OVS virtual environment similar to a Python virtualenv. OVS will be checked out from git and placed in .tox/functional/src/ovs and a virtual environment directory will be created. Various OVS servers will be launched and will store their runtime files in the virtual environment directory. The tests will then be run against these servers. Upon test completion, the servers will be killed and the virtual environment directory deleted. Note that one environment is created per test process, by default one per-core. In the event that debugging tests is necessary, it is possible to keep the virtual environment directories by running: .. code-block:: shell KEEP_VENV=1 tox -e functional This will also write an informational file .tox/functional/ovsvenv.$pid for each process. The first line of this file is the virtual environment directory and additional lines are the tests run by this process. To load an OVS virtualenv for debugging for a particular test (e.g. test_ls_add_name), call: .. code-block:: shell tools/debug_venv test_ls_add_name This will spawn a shell where you can run ovs-vsctl, ovn-nbctl, etc. on the db used by the test. When finished, type 'exit'. See the debug_venv help for more options. ovsdbapp-0.9.1/test-requirements.txt0000666000175100017510000000113013236157633017632 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking<0.13,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD sphinx>=1.6.2 # BSD openstackdocstheme>=1.17.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 pylint==1.4.5 # GPLv2 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT # releasenotes reno>=2.5.0 # Apache-2.0 ovsdbapp-0.9.1/tox.ini0000666000175100017510000000250013236157633014706 0ustar zuulzuul00000000000000[tox] minversion = 2.0 envlist = py35,py27,pypy,pep8 skipsdist = True [testenv] usedevelop = True setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning OS_TEST_PATH=./ovsdbapp/tests/unit CLIENT_NAME=ovsdbapp install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/queens} {opts} {packages} deps = -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 {posargs} {toxinidir}/tools/coding-checks.sh --pylint '{posargs}' [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py test --coverage --testr-args='{posargs}' [testenv:docs] commands = python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:debug] commands = oslo_debug_helper {posargs} [testenv:functional] setenv = {[testenv]setenv} OS_TEST_PATH=./ovsdbapp/tests/functional OVS_SRCDIR={envdir}/src/ovs passenv = KEEP_VENV [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build ovsdbapp-0.9.1/CONTRIBUTING.rst0000666000175100017510000000121413236157620016031 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/ovsdbapp ovsdbapp-0.9.1/.pylintrc0000666000175100017510000000442613236157620015245 0ustar zuulzuul00000000000000# The format of this file isn't really documented; just use --generate-rcfile [MASTER] # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. ignore=.git,tests [MESSAGES CONTROL] # NOTE: This list is copied from neutron, the options which do not need to be # suppressed have been removed. disable= # "F" Fatal errors that prevent further processing # "I" Informational noise # "E" Error for important programming issues (likely bugs) no-member, # "W" Warnings for stylistic problems or minor programming issues arguments-differ, attribute-defined-outside-init, broad-except, fixme, protected-access, redefined-outer-name, unused-argument, useless-super-delegation, # "C" Coding convention violations bad-continuation, invalid-name, missing-docstring, # "R" Refactor recommendations duplicate-code, no-self-use, too-few-public-methods, too-many-ancestors, too-many-arguments, too-many-branches, too-many-instance-attributes, too-many-lines, too-many-locals, too-many-public-methods, too-many-return-statements, too-many-statements [BASIC] # Variable names can be 1 to 31 characters long, with lowercase and underscores variable-rgx=[a-z_][a-z0-9_]{0,30}$ # Argument names can be 2 to 31 characters long, with lowercase and underscores argument-rgx=[a-z_][a-z0-9_]{1,30}$ # Method names should be at least 3 characters long # and be lowercased with underscores method-rgx=([a-z_][a-z0-9_]{2,}|setUp|tearDown)$ # Module names matching module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Don't require docstrings on tests. no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$ [FORMAT] # Maximum number of characters on a single line. max-line-length=79 [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= [CLASSES] # List of interface methods to ignore, separated by a comma. ignore-iface-methods= [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules= [TYPECHECK] # List of module names for which member attributes should not be checked ignored-modules=six.moves,_MovedItems [REPORTS] # Tells whether to display a full report or only the messages reports=no ovsdbapp-0.9.1/ovsdbapp/0000775000175100017510000000000013236157776015222 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/CHANGES0000666000175100017510000000063013236157620016202 0ustar zuulzuul00000000000000Changes for Neutron migration * Neutron needs to keep its interface mapp and specify its own API.get() * The context passed to an API should have an ovsdb_connection attribute * The helpers.enable_connection_uri function now takes a timeout and an execute function as arguments, any kwargs will be passed to that function * Neutron will need to subclass OvsdbIdl and call vlog.use_python_logger() itself. ovsdbapp-0.9.1/ovsdbapp/venv.py0000666000175100017510000002156213236157620016546 0ustar zuulzuul00000000000000# 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 glob import os import shutil import signal import subprocess import time import fixtures # These are the valid dummy values for ovs-vswitchd process. They are here just # to get user enumeration. See man ovs-vswitchd(8) for more information. DUMMY_OVERRIDE_ALL = 'override' DUMMY_OVERRIDE_SYSTEM = 'system' DUMMY_OVERRIDE_NONE = '' class OvsVenvFixture(fixtures.Fixture): PATH_VAR_TEMPLATE = "{0}/ovsdb:{0}/vswitchd:{0}/utilities" def __init__(self, venv, ovsdir, dummy=DUMMY_OVERRIDE_ALL, remove=False): """Initialize fixture :param venv: Path to venv directory. :param ovsdir: Path to directory containing ovs source codes. :param dummy: One of following: an empty string, 'override' or 'system'. :param remove: Boolean value whether venv directory should be removed at the fixture cleanup. """ self.venv = venv self.env = {'OVS_RUNDIR': self.venv, 'OVS_LOGDIR': self.venv, 'OVS_DBDIR': self.venv, 'OVS_SYSCONFDIR': self.venv} if ovsdir: self.ovsdir = ovsdir self.env['PATH'] = (self.PATH_VAR_TEMPLATE.format(self.ovsdir) + ":%s" % os.getenv('PATH')) else: self.env['PATH'] = os.getenv('PATH') self.ovsdir = os.path.join('/usr', 'local', 'share', 'openvswitch') if not os.path.isdir(self.ovsdir): self.ovsdir = os.path.join('/usr', 'share', 'openvswitch') if not os.path.isdir(self.ovsdir): raise Exception("%s is not a directory" % self.ovsdir) self._dummy = dummy self.remove = remove self.ovsdb_server_dbs = [] @property def ovs_schema(self): path = os.path.join(self.ovsdir, 'vswitchd', 'vswitch.ovsschema') if os.path.isfile(path): return path return os.path.join(self.ovsdir, 'vswitch.ovsschema') @property def dummy_arg(self): return "--enable-dummy=%s" % self._dummy @property def ovs_connection(self): return 'unix:' + os.path.join(self.venv, 'db.sock') def _setUp(self): super(OvsVenvFixture, self)._setUp() self.addCleanup(self.deactivate) if not os.path.isdir(self.venv): os.mkdir(self.venv) self.setup_dbs() self.start_ovsdb_processes() time.sleep(1) # wait_until_true(os.path.isfile(db_sock) self.init_processes() def setup_dbs(self): db_filename = 'conf.db' self.create_db(db_filename, self.ovs_schema) self.ovsdb_server_dbs.append(db_filename) def start_ovsdb_processes(self): self.call([ 'ovsdb-server', '--remote=p' + self.ovs_connection, '--detach', '--no-chdir', '--pidfile', '-vconsole:off', '--log-file'] + self.ovsdb_server_dbs) def init_processes(self): self.call(['ovs-vsctl', '--no-wait', '--', 'init']) self.call(['ovs-vswitchd', '--detach', '--no-chdir', '--pidfile', '-vconsole:off', '-vvconn', '-vnetdev_dummy', '--log-file', self.dummy_arg, self.ovs_connection]) def deactivate(self): self.kill_processes() if self.remove: shutil.rmtree(self.venv, ignore_errors=True) def create_db(self, name, schema): filename = os.path.join(self.venv, name) if not os.path.isfile(filename): return self.call(['ovsdb-tool', '-v', 'create', name, schema]) def call(self, cmd, *args, **kwargs): cwd = kwargs.pop('cwd', self.venv) return subprocess.check_call( cmd, *args, env=self.env, cwd=cwd, **kwargs) def get_pids(self): files = glob.glob(os.path.join(self.venv, "*.pid")) result = [] for fname in files: with open(fname, 'r') as f: result.append(int(f.read().strip())) return result def kill_processes(self): for pid in self.get_pids(): os.kill(pid, signal.SIGTERM) class OvsOvnVenvFixture(OvsVenvFixture): PATH_VAR_TEMPLATE = OvsVenvFixture.PATH_VAR_TEMPLATE + ( ":{0}/vtep" ":{0}/ovn/controller:{0}/ovn/controller-vtep" ":{0}/ovn/northd:{0}/ovn/utilities") def __init__(self, *args, **kwargs): self.add_chassis = kwargs.pop('add_chassis', False) super(OvsOvnVenvFixture, self).__init__(*args, **kwargs) @property def vtep_schema(self): path = os.path.join(self.ovsdir, 'vtep', 'vtep.ovsschema') if os.path.isfile(path): return path return os.path.join(self.ovsdir, 'vtep.ovsschema') @property def ovnsb_schema(self): path = os.path.join(self.ovsdir, 'ovn', 'ovn-sb.ovsschema') if os.path.isfile(path): return path return os.path.join(self.ovsdir, 'ovn-sb.ovsschema') @property def ovnnb_schema(self): path = os.path.join(self.ovsdir, 'ovn', 'ovn-nb.ovsschema') if os.path.isfile(path): return path return os.path.join(self.ovsdir, 'ovn-nb.ovsschema') @property def ovnnb_connection(self): return 'unix:' + os.path.join(self.venv, 'ovnnb_db.sock') @property def ovnsb_connection(self): return 'unix:' + os.path.join(self.venv, 'ovnsb_db.sock') def setup_dbs(self): super(OvsOvnVenvFixture, self).setup_dbs() self.create_db('vtep.db', self.vtep_schema) self.ovsdb_server_dbs.append('vtep.db') self.create_db('ovnsb.db', self.ovnsb_schema) self.create_db('ovnnb.db', self.ovnnb_schema) def start_ovsdb_processes(self): super(OvsOvnVenvFixture, self).start_ovsdb_processes() self.call(['ovsdb-server', '--detach', '--no-chdir', '-vconsole:off', '--pidfile=%s' % os.path.join(self.venv, 'ovnnb_db.pid'), '--log-file=%s' % os.path.join(self.venv, 'ovnnb_db.log'), '--remote=db:OVN_Northbound,NB_Global,connections', '--private-key=db:OVN_Northbound,SSL,private_key', '--certificate=db:OVN_Northbound,SSL,certificate', '--ca-cert=db:OVN_Northbound,SSL,ca_cert', '--ssl-protocols=db:OVN_Northbound,SSL,ssl_protocols', '--ssl-ciphers=db:OVN_Northbound,SSL,ssl_ciphers', '--remote=p' + self.ovnnb_connection, 'ovnnb.db']) self.call(['ovsdb-server', '--detach', '--no-chdir', '-vconsole:off', '--pidfile=%s' % os.path.join(self.venv, 'ovnsb_db.pid'), '--log-file=%s' % os.path.join(self.venv, 'ovnsb_db.log'), '--remote=db:OVN_Southbound,SB_Global,connections', '--private-key=db:OVN_Southbound,SSL,private_key', '--certificate=db:OVN_Southbound,SSL,certificate', '--ca-cert=db:OVN_Southbound,SSL,ca_cert', '--ssl-protocols=db:OVN_Southbound,SSL,ssl_protocols', '--ssl-ciphers=db:OVN_Southbound,SSL,ssl_ciphers', '--remote=p' + self.ovnsb_connection, 'ovnsb.db']) def init_processes(self): super(OvsOvnVenvFixture, self).init_processes() self.call(['ovn-nbctl', 'init']) self.call(['ovn-sbctl', 'init']) if self.add_chassis: self.call([ 'ovs-vsctl', 'set', 'open', '.', 'external_ids:system-id=56b18105-5706-46ef-80c4-ff20979ab068', 'external_ids:hostname=sandbox', 'external_ids:ovn-encap-type=geneve', 'external_ids:ovn-encap-ip=127.0.0.1']) # TODO(twilson) SSL stuff if False: pass else: self.call(['ovs-vsctl', 'set', 'open', '.', 'external_ids:ovn-remote=' + self.ovnsb_connection]) self.call(['ovn-northd', '--detach', '--no-chdir', '--pidfile', '-vconsole:off', '--log-file', '--ovnsb-db=' + self.ovnsb_connection, '--ovnnb-db=' + self.ovnnb_connection]) self.call(['ovn-controller', '--detach', '--no-chdir', '--pidfile', '-vconsole:off', '--log-file']) self.call(['ovn-controller-vtep', '--detach', '--no-chdir', '--pidfile', '-vconsole:off', '--log-file', '--ovnsb-db=' + self.ovnsb_connection]) ovsdbapp-0.9.1/ovsdbapp/backend/0000775000175100017510000000000013236157776016611 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/__init__.py0000666000175100017510000000000013236157620020676 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/0000775000175100017510000000000013236157776020250 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/windows/0000775000175100017510000000000013236157776021742 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/windows/utils.py0000666000175100017510000000324113236157620023442 0ustar zuulzuul00000000000000# Copyright 2017 Cloudbase Solutions Srl # # 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. try: import eventlet from eventlet import tpool except ImportError: eventlet = None def avoid_blocking_call(f, *args, **kwargs): """Ensure that the method "f" will not block other greenthreads. Performs the call to the function "f" received as parameter in a different thread using tpool.execute when called from a greenthread. This will ensure that the function "f" will not block other greenthreads. If not called from a greenthread, it will invoke the function "f" directly. The function "f" will receive as parameters the arguments "args" and keyword arguments "kwargs". If eventlet is not installed on the system then this will call directly the function "f". """ if eventlet is None: return f(*args, **kwargs) # Note that eventlet.getcurrent will always return a greenlet object. # In case of a greenthread, the parent greenlet will always be the hub # loop greenlet. if eventlet.getcurrent().parent: return tpool.execute(f, *args, **kwargs) else: return f(*args, **kwargs) ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/windows/connection_utils.py0000666000175100017510000000416213236157620025664 0ustar zuulzuul00000000000000# Copyright 2017 Cloudbase Solutions Srl # # 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 ovs import poller from ovs import winutils from ovsdbapp.backend.ovs_idl.common import base_connection_utils from ovsdbapp.backend.ovs_idl.windows import utils class WaitQueue(base_connection_utils.WaitQueue): def init_alert_notification(self): # We will use an event to get signaled when there is something in # the queue. The OVS poller can wait on events on Windows. # NOTE(abalutoiu) The assumption made is that the queue has # length 1, otherwise we will need to have a list of events with # the size of the queue. self.alert_event = winutils.get_new_event(bManualReset=True, bInitialState=False) def alert_notification_consume(self): # Set the event object to the nonsignaled state to indicate that # the queue is empty. winutils.win32event.ResetEvent(self.alert_event) def alert_notify(self): # Set the event object to the signaled state to indicate that # we have something in the queue. winutils.win32event.SetEvent(self.alert_event) @property def alert_fileno(self): return self.alert_event def monkey_patch_poller_support(): # Ensure that WaitForMultipleObjects will not block other greenthreads. # poller.block uses WaitForMultipleObjects on Windows old_block = poller.Poller.block def new_block(self): return utils.avoid_blocking_call(old_block, self) poller.Poller.block = new_block monkey_patch_poller_support() ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/windows/__init__.py0000666000175100017510000000000013236157620024027 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/fixtures.py0000666000175100017510000000216113236157620022461 0ustar zuulzuul00000000000000# 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 __future__ import absolute_import import fixtures from ovsdbapp.backend.ovs_idl import vlog class OvsdbVlogFixture(fixtures.Fixture): def __init__(self, *args, **kwargs): """Constructor for the OvsdbVlogVixture The OvsdbVlogFixture will call vlog.use_python_logger with any args or kwargs passed and call vlog.reset_logger() on cleanup """ self.args = args self.kwargs = kwargs def _setUp(self): vlog.use_python_logger(*self.args, **self.kwargs) self.addCleanup(vlog.reset_logger) ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/connection.py0000666000175100017510000001106013236157620022745 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, Inc. # # 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 os import threading import traceback from ovs.db import idl from ovs import poller from six.moves import queue as Queue from ovsdbapp.backend.ovs_idl import idlutils if os.name == 'nt': from ovsdbapp.backend.ovs_idl.windows import connection_utils else: from ovsdbapp.backend.ovs_idl.linux import connection_utils class TransactionQueue(Queue.Queue, object): def __init__(self, *args, **kwargs): super(TransactionQueue, self).__init__(*args, **kwargs) self._wait_queue = connection_utils.WaitQueue( max_queue_size=self.maxsize) def get_nowait(self, *args, **kwargs): try: result = super(TransactionQueue, self).get_nowait(*args, **kwargs) except Queue.Empty: return None self._wait_queue.alert_notification_consume() return result def put(self, *args, **kwargs): super(TransactionQueue, self).put(*args, **kwargs) self._wait_queue.alert_notify() @property def alert_fileno(self): return self._wait_queue.alert_fileno class Connection(object): def __init__(self, idl, timeout): """Create a connection to an OVSDB server using the OVS IDL :param timeout: The timeout value for OVSDB operations :param idl: A newly created ovs.db.Idl instance (run never called) """ self.timeout = timeout self.txns = TransactionQueue(1) self.lock = threading.Lock() self.idl = idl self.thread = None self._is_running = None def start(self): """Start the connection.""" with self.lock: if self.thread is not None: return False if not self.idl.has_ever_connected(): idlutils.wait_for_change(self.idl, self.timeout) try: self.idl.post_connect() except AttributeError: # An ovs.db.Idl class has no post_connect pass self.poller = poller.Poller() self._is_running = True self.thread = threading.Thread(target=self.run) self.thread.setDaemon(True) self.thread.start() def run(self): while self._is_running: self.idl.wait(self.poller) self.poller.fd_wait(self.txns.alert_fileno, poller.POLLIN) # TODO(jlibosva): Remove next line once losing connection to ovsdb # is solved. self.poller.timer_wait(self.timeout * 1000) self.poller.block() self.idl.run() txn = self.txns.get_nowait() if txn is not None: try: txn.results.put(txn.do_commit()) except Exception as ex: er = idlutils.ExceptionResult(ex=ex, tb=traceback.format_exc()) txn.results.put(er) self.txns.task_done() def stop(self, timeout=None): if not self._is_running: return True self._is_running = False self.txns.put(None) self.thread.join(timeout) if self.thread.is_alive(): return False self.thread = None return True def queue_txn(self, txn): # Even if we aren't started, we can queue a transaction and it will # run when we are started self.txns.put(txn) class OvsdbIdl(idl.Idl): @classmethod def from_server(cls, connection_string, schema_name): """Create the Idl instance by pulling the schema from OVSDB server""" helper = idlutils.get_schema_helper(connection_string, schema_name) helper.register_all() return cls(connection_string, helper) def post_connect(self): """Operations to execute after the Idl has connected to the server An example would be to set up Idl notification handling for watching and unwatching certain OVSDB change events """ ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/linux/0000775000175100017510000000000013236157776021407 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/linux/connection_utils.py0000666000175100017510000000244513236157620025333 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, Inc. # # 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 os import six from ovsdbapp.backend.ovs_idl.common import base_connection_utils class WaitQueue(base_connection_utils.WaitQueue): def init_alert_notification(self): alertpipe = os.pipe() # NOTE(ivasilevskaya) python 3 doesn't allow unbuffered I/O. # Will get around this constraint by using binary mode. self.alertin = os.fdopen(alertpipe[0], 'rb', 0) self.alertout = os.fdopen(alertpipe[1], 'wb', 0) def alert_notification_consume(self): self.alertin.read(1) def alert_notify(self): self.alertout.write(six.b('X')) self.alertout.flush() @property def alert_fileno(self): return self.alertin.fileno() ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/linux/__init__.py0000666000175100017510000000000013236157620023474 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/idlutils.py0000666000175100017510000002334613236157620022451 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, Inc. # # 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 os import sys import time import uuid from ovs.db import idl from ovs import jsonrpc from ovs import poller from ovs import stream import six from ovsdbapp import api from ovsdbapp import exceptions RowLookup = collections.namedtuple('RowLookup', ['table', 'column', 'uuid_column']) # Tables with no index in OVSDB and special record lookup rules _LOOKUP_TABLE = { 'Controller': RowLookup('Bridge', 'name', 'controller'), 'Flow_Table': RowLookup('Flow_Table', 'name', None), 'IPFIX': RowLookup('Bridge', 'name', 'ipfix'), 'Mirror': RowLookup('Mirror', 'name', None), 'NetFlow': RowLookup('Bridge', 'name', 'netflow'), 'Open_vSwitch': RowLookup('Open_vSwitch', None, None), 'QoS': RowLookup('Port', 'name', 'qos'), 'Queue': RowLookup(None, None, None), 'sFlow': RowLookup('Bridge', 'name', 'sflow'), 'SSL': RowLookup('Open_vSwitch', None, 'ssl'), } _NO_DEFAULT = object() class RowNotFound(exceptions.OvsdbAppException): message = "Cannot find %(table)s with %(col)s=%(match)s" def row_by_value(idl_, table, column, match, default=_NO_DEFAULT): """Lookup an IDL row in a table by column/value""" tab = idl_.tables[table] for r in tab.rows.values(): if getattr(r, column) == match: return r if default is not _NO_DEFAULT: return default raise RowNotFound(table=table, col=column, match=match) def row_by_record(idl_, table, record): t = idl_.tables[table] try: if isinstance(record, uuid.UUID): return t.rows[record] uuid_ = uuid.UUID(record) return t.rows[uuid_] except ValueError: # Not a UUID string, continue lookup by other means pass except KeyError: if sys.platform != 'win32': # On Windows the name of the ports is described by the OVS schema: # https://tinyurl.com/zk8skhx # Is a UUID. (This is due to the fact on Windows port names don't # have the 16 chars length limitation as for Linux). Because of # this uuid.UUID(record) will not raise a ValueError exception # as it happens on Linux and will try to fetch the directly # the column instead of using the lookup table. This will raise # a KeyError exception on Windows. raise RowNotFound(table=table, col='uuid', match=record) rl = _LOOKUP_TABLE.get(table, RowLookup(table, get_index_column(t), None)) # no table means uuid only, no column means lookup table only has one row if rl.table is None: raise ValueError("Table %s can only be queried by UUID") % table if rl.column is None: return next(iter(t.rows.values())) row = row_by_value(idl_, rl.table, rl.column, record) if rl.uuid_column: rows = getattr(row, rl.uuid_column) if len(rows) != 1: raise RowNotFound(table=table, col='record', match=record) row = rows[0] return row class ExceptionResult(object): def __init__(self, ex, tb): self.ex = ex self.tb = tb def get_schema_helper(connection, schema_name): """Create a schema helper object by querying an ovsdb-server :param connection: The ovsdb-server connection string :type connection: string :param schema_name: The schema on the server to pull :type schema_name: string """ err, strm = stream.Stream.open_block( stream.Stream.open(connection)) if err: raise Exception("Could not connect to %s" % connection) rpc = jsonrpc.Connection(strm) req = jsonrpc.Message.create_request('get_schema', [schema_name]) err, resp = rpc.transact_block(req) rpc.close() if err: raise Exception("Could not retrieve schema from %(conn)s: " "%(err)s" % {'conn': connection, 'err': os.strerror(err)}) elif resp.error: raise Exception(resp.error) return idl.SchemaHelper(None, resp.result) def wait_for_change(_idl, timeout, seqno=None): if seqno is None: seqno = _idl.change_seqno stop = time.time() + timeout while _idl.change_seqno == seqno and not _idl.run(): ovs_poller = poller.Poller() _idl.wait(ovs_poller) ovs_poller.timer_wait(timeout * 1000) ovs_poller.block() if time.time() > stop: raise Exception("Timeout") # TODO(twilson) use TimeoutException? def get_column_value(row, col): """Retrieve column value from the given row. If column's type is optional, the value will be returned as a single element instead of a list of length 1. """ if col == '_uuid': val = row.uuid else: val = getattr(row, col) # Idl returns lists of Rows where ovs-vsctl returns lists of UUIDs if isinstance(val, list) and val: if isinstance(val[0], idl.Row): val = [v.uuid for v in val] col_type = row._table.columns[col].type # ovs-vsctl treats lists of 1 as single results if col_type.is_optional(): val = val[0] return val def condition_match(row, condition): """Return whether a condition matches a row :param row: An OVSDB Row :param condition: A 3-tuple containing (column, operation, match) """ col, op, match = condition val = get_column_value(row, col) # both match and val are primitive types, so type can be used for type # equality here. # NOTE (twilson) the above is a lie--not all string types are the same # I haven't investigated the reason for the patch that # added this code, but for now I check string_types if type(match) is not type(val) and not all( isinstance(x, six.string_types) for x in (match, val)): # Types of 'val' and 'match' arguments MUST match in all cases with 2 # exceptions: # - 'match' is an empty list and column's type is optional; # - 'value' is an empty and column's type is optional if (not all([match, val]) and row._table.columns[col].type.is_optional()): # utilize the single elements comparison logic if match == []: match = None elif val == []: val = None else: # no need to process any further raise ValueError( "Column type and condition operand do not match") matched = True # TODO(twilson) Implement other operators and type comparisons # ovs_lib only uses dict '=' and '!=' searches for now if isinstance(match, dict): for key in match: if op == '=': if key not in val or match[key] != val[key]: matched = False break elif op == '!=': if key not in val or match[key] == val[key]: matched = False break else: raise NotImplementedError() elif isinstance(match, list): # According to rfc7047, lists support '=' and '!=' # (both strict and relaxed). Will follow twilson's dict comparison # and implement relaxed version (excludes/includes as per standard) if op == "=": if not all([val, match]): return val == match for elem in set(match): if elem not in val: matched = False break elif op == '!=': if not all([val, match]): return val != match for elem in set(match): if elem in val: matched = False break else: raise NotImplementedError() else: if op == '=': if val != match: matched = False elif op == '!=': if val == match: matched = False else: raise NotImplementedError() return matched def row_match(row, conditions): """Return whether the row matches the list of conditions""" return all(condition_match(row, cond) for cond in conditions) def get_index_column(table): if len(table.indexes) == 1: idx = table.indexes[0] if len(idx) == 1: return idx[0].name def db_replace_record(obj): """Replace any api.Command objects with their results This method should leave obj untouched unless the object contains an api.Command object. """ if isinstance(obj, collections.Mapping): for k, v in six.iteritems(obj): if isinstance(v, api.Command): obj[k] = v.result elif (isinstance(obj, collections.Sequence) and not isinstance(obj, six.string_types)): for i, v in enumerate(obj): if isinstance(v, api.Command): try: obj[i] = v.result except TypeError: # NOTE(twilson) If someone passes a tuple, then just return # a tuple with the Commands replaced with their results return type(obj)(getattr(v, "result", v) for v in obj) elif isinstance(obj, api.Command): obj = obj.result return obj ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/__init__.py0000666000175100017510000001273013236157620022352 0ustar zuulzuul00000000000000# 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 logging import uuid from ovsdbapp.backend.ovs_idl import command as cmd from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import transaction from ovsdbapp import exceptions LOG = logging.getLogger(__name__) _NO_DEFAULT = object() class Backend(object): lookup_table = {} ovsdb_connection = None def __init__(self, connection): super(Backend, self).__init__() self.start_connection(connection) @classmethod def start_connection(cls, connection): try: if cls.ovsdb_connection is None: cls.ovsdb_connection = connection cls.ovsdb_connection.start() except Exception as e: connection_exception = exceptions.OvsdbConnectionUnavailable( db_schema=cls.schema, error=e) LOG.exception(connection_exception) raise connection_exception @classmethod def restart_connection(cls): cls.ovsdb_connection.stop() cls.ovsdb_connection.start() @property def idl(self): return self.__class__.ovsdb_connection.idl @property def tables(self): return self.idl.tables _tables = tables def create_transaction(self, check_error=False, log_errors=True, **kwargs): return transaction.Transaction( self, self.__class__.ovsdb_connection, self.__class__.ovsdb_connection.timeout, check_error, log_errors) def db_create(self, table, **col_values): return cmd.DbCreateCommand(self, table, **col_values) def db_destroy(self, table, record): return cmd.DbDestroyCommand(self, table, record) def db_set(self, table, record, *col_values): return cmd.DbSetCommand(self, table, record, *col_values) def db_add(self, table, record, column, *values): return cmd.DbAddCommand(self, table, record, column, *values) def db_clear(self, table, record, column): return cmd.DbClearCommand(self, table, record, column) def db_get(self, table, record, column): return cmd.DbGetCommand(self, table, record, column) def db_list(self, table, records=None, columns=None, if_exists=False): return cmd.DbListCommand(self, table, records, columns, if_exists) def db_list_rows(self, table, records=None, if_exists=False): return cmd.DbListCommand(self, table, records, columns=None, row=True, if_exists=if_exists) def db_find(self, table, *conditions, **kwargs): return cmd.DbFindCommand(self, table, *conditions, **kwargs) def db_find_rows(self, table, *conditions, **kwargs): return cmd.DbFindCommand(self, table, *conditions, row=True, **kwargs) def db_remove(self, table, record, column, *values, **keyvalues): return cmd.DbRemoveCommand(self, table, record, column, *values, **keyvalues) def lookup(self, table, record, default=_NO_DEFAULT): try: return self._lookup(table, record) except idlutils.RowNotFound: if default is not _NO_DEFAULT: return default raise def _lookup(self, table, record): if record == "": raise TypeError("Cannot look up record by empty string") t = self.tables[table] try: if isinstance(record, uuid.UUID): return t.rows[record] try: uuid_ = uuid.UUID(record) return t.rows[uuid_] except ValueError: # Not a UUID string, continue lookup by other means pass except KeyError: # If record isn't found by UUID , go ahead and look up by the table pass if not self.lookup_table: raise idlutils.RowNotFound(table=table, col='record', match=record) # NOTE (twilson) This is an approximation of the db-ctl implementation # that allows a partial table, assuming that if a table has a single # index, that we should be able to do a lookup by it. rl = self.lookup_table.get( table, idlutils.RowLookup(table, idlutils.get_index_column(t), None)) # no table means uuid only, no column means lookup table has one row if rl.table is None: raise idlutils.RowNotFound(table=table, col='uuid', match=record) if rl.column is None: if t.max_rows == 1: return next(iter(t.rows.values())) raise idlutils.RowNotFound(table=table, col='uuid', match=record) row = idlutils.row_by_value(self, rl.table, rl.column, record) if rl.uuid_column: rows = getattr(row, rl.uuid_column) if len(rows) != 1: raise idlutils.RowNotFound(table=table, col='record', match=record) row = rows[0] return row ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/rowview.py0000666000175100017510000000177113236157620022320 0ustar zuulzuul00000000000000# 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 RowView(object): def __init__(self, row): self._row = row def __getattr__(self, column_name): return getattr(self._row, column_name) def __eq__(self, other): # use other's == since it is likely to be a Row object try: return other == self._row except NotImplemented: return other._row == self._row def __hash__(self): return self._row.__hash__() ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/transaction.py0000666000175100017510000001076713236157633023154 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat Inc # # 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 logging import time from ovs.db import idl from six.moves import queue as Queue from ovsdbapp import api from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp import exceptions LOG = logging.getLogger(__name__) class Transaction(api.Transaction): def __init__(self, api, ovsdb_connection, timeout=None, check_error=False, log_errors=True): self.api = api self.check_error = check_error self.log_errors = log_errors self.commands = [] self.results = Queue.Queue(1) self.ovsdb_connection = ovsdb_connection self.timeout = timeout or ovsdb_connection.timeout def __str__(self): return ", ".join(str(cmd) for cmd in self.commands) def add(self, command): """Add a command to the transaction returns The command passed as a convenience """ self.commands.append(command) return command def commit(self): self.ovsdb_connection.queue_txn(self) try: result = self.results.get(timeout=self.timeout) except Queue.Empty: raise exceptions.TimeoutException(commands=self.commands, timeout=self.timeout) if isinstance(result, idlutils.ExceptionResult): if self.log_errors: LOG.error(result.tb) if self.check_error: raise result.ex return result def pre_commit(self, txn): pass def post_commit(self, txn): for command in self.commands: command.post_commit(txn) def do_commit(self): self.start_time = time.time() attempts = 0 while True: if attempts > 0 and self.timeout_exceeded(): raise RuntimeError("OVS transaction timed out") attempts += 1 # TODO(twilson) Make sure we don't loop longer than vsctl_timeout txn = idl.Transaction(self.api.idl) self.pre_commit(txn) for i, command in enumerate(self.commands): LOG.debug("Running txn command(idx=%(idx)s): %(cmd)s", {'idx': i, 'cmd': command}) try: command.run_idl(txn) except Exception: txn.abort() if self.check_error: raise status = txn.commit_block() if status == txn.TRY_AGAIN: LOG.debug("OVSDB transaction returned TRY_AGAIN, retrying") # In the case that there is a reconnection after # Connection.run() calls self.idl.run() but before do_commit() # is called, commit_block() can loop w/o calling idl.run() # which does the reconnect logic. It will then always return # TRY_AGAIN until we time out and Connection.run() calls # idl.run() again. So, call idl.run() here just in case. self.api.idl.run() continue elif status == txn.ERROR: msg = "OVSDB Error: %s" % txn.get_error() if self.log_errors: LOG.error(msg) if self.check_error: # For now, raise similar error to vsctl/utils.execute() raise RuntimeError(msg) return elif status == txn.ABORTED: LOG.debug("Transaction aborted") return elif status == txn.UNCHANGED: LOG.debug("Transaction caused no change") elif status == txn.SUCCESS: self.post_commit(txn) return [cmd.result for cmd in self.commands] def elapsed_time(self): return time.time() - self.start_time def time_remaining(self): return self.timeout - self.elapsed_time() def timeout_exceeded(self): return self.elapsed_time() > self.timeout ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/event.py0000666000175100017510000000306113236157620021731 0ustar zuulzuul00000000000000# 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 logging from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp import event as ovsdb_event LOG = logging.getLogger(__name__) class RowEvent(ovsdb_event.RowEvent): # pylint: disable=abstract-method def matches(self, event, row, old=None): if event not in self.events: return False if row._table.name != self.table: return False if self.conditions and not idlutils.row_match(row, self.conditions): return False if self.old_conditions: if not old: return False try: if not idlutils.row_match(old, self.old_conditions): return False except (KeyError, AttributeError): # Its possible that old row may not have all columns in it return False LOG.debug("%s : Matched %s, %s, %s %s", self.event_name, self.table, self.events, self.conditions, self.old_conditions) return True ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/common/0000775000175100017510000000000013236157776021540 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/common/base_connection_utils.py0000666000175100017510000000205113236157620026447 0ustar zuulzuul00000000000000# Copyright 2017 Cloudbase Solutions Srl # # 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 WaitQueue(object): def __init__(self, max_queue_size): self.max_queue_size = max_queue_size self.init_alert_notification() def init_alert_notification(self): raise NotImplementedError() def alert_notification_consume(self): raise NotImplementedError() def alert_notify(self): raise NotImplementedError() @property def alert_fileno(self): raise NotImplementedError() ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/common/__init__.py0000666000175100017510000000000013236157620023625 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/command.py0000666000175100017510000002373013236157620022233 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat Inc. # # 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 logging import six from ovsdbapp import api from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import rowview LOG = logging.getLogger(__name__) class BaseCommand(api.Command): def __init__(self, api): self.api = api self.result = None def execute(self, check_error=False, log_errors=True): try: with self.api.transaction(check_error, log_errors) as txn: txn.add(self) return self.result except Exception: if log_errors: LOG.exception("Error executing command") if check_error: raise @classmethod def set_column(cls, row, col, val): setattr(row, col, idlutils.db_replace_record(val)) @classmethod def set_columns(cls, row, **columns): for col, val in columns.items(): cls.set_column(row, col, val) def post_commit(self, txn): pass def __str__(self): command_info = self.__dict__ return "%s(%s)" % ( self.__class__.__name__, ", ".join("%s=%s" % (k, v) for k, v in command_info.items() if k not in ['api', 'result'])) class AddCommand(BaseCommand): table_name = [] # unhashable, won't be looked up def post_commit(self, txn): # If get_insert_uuid fails, self.result was not a result of a # recent insert. Most likely we are post_commit after a lookup() real_uuid = txn.get_insert_uuid(self.result) or self.result row = self.api.tables[self.table_name].rows[real_uuid] self.result = rowview.RowView(row) class DbCreateCommand(BaseCommand): def __init__(self, api, table, **columns): super(DbCreateCommand, self).__init__(api) self.table = table self.columns = columns def run_idl(self, txn): row = txn.insert(self.api._tables[self.table]) self.set_columns(row, **self.columns) # This is a temporary row to be used within the transaction self.result = row def post_commit(self, txn): # Replace the temporary row with the post-commit UUID to match vsctl self.result = txn.get_insert_uuid(self.result.uuid) class DbDestroyCommand(BaseCommand): def __init__(self, api, table, record): super(DbDestroyCommand, self).__init__(api) self.table = table self.record = record def run_idl(self, txn): record = idlutils.row_by_record(self.api.idl, self.table, self.record) record.delete() class DbSetCommand(BaseCommand): def __init__(self, api, table, record, *col_values): super(DbSetCommand, self).__init__(api) self.table = table self.record = record self.col_values = col_values def run_idl(self, txn): record = idlutils.row_by_record(self.api.idl, self.table, self.record) for col, val in self.col_values: # TODO(twilson) Ugh, the OVS library doesn't like OrderedDict # We're only using it to make a unit test work, so we should fix # this soon. if isinstance(val, collections.OrderedDict): val = dict(val) if isinstance(val, dict): # NOTE(twilson) OVS 2.6's Python IDL has mutate methods that # would make this cleaner, but it's too early to rely on them. existing = getattr(record, col, {}) existing.update(val) val = existing self.set_column(record, col, val) class DbAddCommand(BaseCommand): def __init__(self, api, table, record, column, *values): super(DbAddCommand, self).__init__(api) self.table = table self.record = record self.column = column self.values = values def run_idl(self, txn): record = idlutils.row_by_record(self.api.idl, self.table, self.record) for value in self.values: if isinstance(value, collections.Mapping): # We should be doing an add on a 'map' column. If the key is # already set, do nothing, otherwise set the key to the value # Since this operation depends on the previous value, verify() # must be called. field = getattr(record, self.column, {}) for k, v in six.iteritems(value): if k in field: continue field[k] = v else: # We should be appending to a 'set' column. try: record.addvalue(self.column, idlutils.db_replace_record(value)) continue except AttributeError: # OVS < 2.6 field = getattr(record, self.column, []) field.append(value) record.verify(self.column) self.set_column(record, self.column, field) class DbClearCommand(BaseCommand): def __init__(self, api, table, record, column): super(DbClearCommand, self).__init__(api) self.table = table self.record = record self.column = column def run_idl(self, txn): record = idlutils.row_by_record(self.api.idl, self.table, self.record) # Create an empty value of the column type value = type(getattr(record, self.column))() setattr(record, self.column, value) class DbGetCommand(BaseCommand): def __init__(self, api, table, record, column): super(DbGetCommand, self).__init__(api) self.table = table self.record = record self.column = column def run_idl(self, txn): record = idlutils.row_by_record(self.api.idl, self.table, self.record) # TODO(twilson) This feels wrong, but ovs-vsctl returns single results # on set types without the list. The IDL is returning them as lists, # even if the set has the maximum number of items set to 1. Might be # able to inspect the Schema and just do this conversion for that case. result = idlutils.get_column_value(record, self.column) if isinstance(result, list) and len(result) == 1: self.result = result[0] else: self.result = result class DbListCommand(BaseCommand): def __init__(self, api, table, records, columns, if_exists, row=False): super(DbListCommand, self).__init__(api) self.table = table self.columns = columns self.if_exists = if_exists self.records = records self.row = row def run_idl(self, txn): table_schema = self.api._tables[self.table] columns = self.columns or list(table_schema.columns.keys()) + ['_uuid'] if self.records: rows = [] for record in self.records: try: rows.append(idlutils.row_by_record( self.api.idl, self.table, record)) except idlutils.RowNotFound: if self.if_exists: continue raise else: rows = table_schema.rows.values() self.result = [ rowview.RowView(row) if self.row else { c: idlutils.get_column_value(row, c) for c in columns } for row in rows ] class DbFindCommand(BaseCommand): def __init__(self, api, table, *conditions, **kwargs): super(DbFindCommand, self).__init__(api) self.table = self.api._tables[table] self.conditions = conditions self.row = kwargs.get('row', False) self.columns = (kwargs.get('columns') or list(self.table.columns.keys()) + ['_uuid']) def run_idl(self, txn): self.result = [ rowview.RowView(r) if self.row else { c: idlutils.get_column_value(r, c) for c in self.columns } for r in self.table.rows.values() if idlutils.row_match(r, self.conditions) ] class BaseGetRowCommand(BaseCommand): def __init__(self, api, record): super(BaseGetRowCommand, self).__init__(api) self.record = record def run_idl(self, txn): self.result = self.api.lookup(self.table, self.record) class DbRemoveCommand(BaseCommand): def __init__(self, api, table, record, column, *values, **keyvalues): super(DbRemoveCommand, self).__init__(api) self.table = table self.record = record self.column = column self.values = values self.keyvalues = keyvalues self.if_exists = keyvalues.pop('if_exists', False) def run_idl(self, txn): try: record = self.api.lookup(self.table, self.record) if isinstance(getattr(record, self.column), dict): for value in self.values: record.delkey(self.column, value) for key, value in self.keyvalues.items(): record.delkey(self.column, key, value) elif isinstance(getattr(record, self.column), list): for value in self.values: record.delvalue(self.column, value) else: value = type(getattr(record, self.column))() setattr(record, self.column, value) except idlutils.RowNotFound: if self.if_exists: return else: raise ovsdbapp-0.9.1/ovsdbapp/backend/ovs_idl/vlog.py0000666000175100017510000000637013236157620021565 0ustar zuulzuul00000000000000# Copyright (c) 2016 Red Hat, Inc. # # 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 logging import sys from ovs import vlog try: from eventlet import patcher # If eventlet is installed and the 'thread' module is patched, we will # skip setting up the python logger on Windows. EVENTLET_NONBLOCKING_MODE_ENABLED = patcher.is_monkey_patched('thread') except ImportError: EVENTLET_NONBLOCKING_MODE_ENABLED = False _LOG = logging.getLogger(__name__) # Map local log LEVELS to local LOG functions CRITICAL = _LOG.critical ERROR = _LOG.error WARN = _LOG.warn INFO = _LOG.info DEBUG = _LOG.debug _LOG_MAPPING = collections.OrderedDict(( (CRITICAL, vlog.Vlog.emer), (ERROR, vlog.Vlog.err), (WARN, vlog.Vlog.warn), (INFO, vlog.Vlog.info), (DEBUG, vlog.Vlog.dbg), )) ALL_LEVELS = tuple(_LOG_MAPPING.keys()) def _original_vlog_fn(level): """Get the original unpatched OVS vlog function for level""" return _LOG_MAPPING[level] def _current_vlog_fn(level): """Get the currently used OVS vlog function mapped to level""" return getattr(vlog.Vlog, _LOG_MAPPING[level].__name__) def use_python_logger(levels=ALL_LEVELS, max_level=None): """Replace the OVS vlog functions with our logger :param: levels: log levels *from this module* e.g. [vlog.WARN] :type: levels: iterable :param: max_level: the maximum level to log :type: max_level: vlog level, CRITICAL, ERROR, WARN, INFO, or DEBUG """ if sys.platform == 'win32' and EVENTLET_NONBLOCKING_MODE_ENABLED: # NOTE(abalutoiu) When using oslo logging we need to keep in mind that # it does not work well with native threads. We need to be careful when # we call eventlet.tpool.execute, and make sure that it will not use # the oslo logging, since it might cause unexpected hangs if # greenthreads are used. On Windows we have to use # eventlet.tpool.execute for a call to the ovs lib which will use # vlog to log messages. We will skip replacing the OVS IDL logger # functions on Windows to avoid unexpected hangs with oslo logging return if max_level: levels = levels[:levels.index(max_level) + 1] # NOTE(twilson) Replace functions directly instead of subclassing so that # debug messages contain the correct function/filename/line information for log in levels: setattr(vlog.Vlog, _LOG_MAPPING[log].__name__, log) def reset_logger(): """Reset the OVS vlog functions to their original values""" for log in ALL_LEVELS: setattr(vlog.Vlog, _LOG_MAPPING[log].__name__, _LOG_MAPPING[log]) def is_patched(level): """Test if the vlog level is patched""" return _current_vlog_fn(level) != _original_vlog_fn(level) ovsdbapp-0.9.1/ovsdbapp/exceptions.py0000666000175100017510000000411213236157633017745 0ustar zuulzuul00000000000000# Copyright (c) 2017 OpenStack Foundation # # 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 six class OvsdbAppException(RuntimeError): """Base OvsdbApp Exception. To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd with the keyword arguments provided to the constructor. """ message = "An unknown exception occurred." def __init__(self, **kwargs): try: super(OvsdbAppException, self).__init__(self.message % kwargs) self.msg = self.message % kwargs except Exception: if self.use_fatal_exceptions(): raise else: # at least get the core message out if something happened super(OvsdbAppException, self).__init__(self.message) if six.PY2: def __unicode__(self): return unicode(self.msg) # noqa def __str__(self): return self.msg def use_fatal_exceptions(self): """Is the instance using fatal exceptions. :returns: Always returns False. """ return False class TimeoutException(OvsdbAppException): message = "Commands %(commands)s exceeded timeout %(timeout)d seconds" class OvsdbConnectionUnavailable(OvsdbAppException): message = ("OVS database connection to %(db_schema)s failed with error: " "'%(error)s'. Verify that the OVS and OVN services are " "available and that the 'ovn_nb_connection' and " "'ovn_sb_connection' configuration options are correct.") ovsdbapp-0.9.1/ovsdbapp/utils.py0000666000175100017510000000301313236157620016717 0ustar zuulzuul00000000000000# 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 netaddr # NOTE(twilson) Clearly these are silly, but they are good enough for now # I'm happy for someone to replace them with better parsing def normalize_ip(ip): return str(netaddr.IPAddress(ip)) def normalize_ip_port(ipport): try: return normalize_ip(ipport) except netaddr.AddrFormatError: # maybe we have a port if ipport[0] == '[': # Should be an IPv6 w/ port try: ip, port = ipport[1:].split(']:') except ValueError: raise netaddr.AddrFormatError("Invalid Port") ip = "[%s]" % normalize_ip(ip) else: try: ip, port = ipport.split(':') except ValueError: raise netaddr.AddrFormatError("Invalid Port") ip = normalize_ip(ip) if int(port) <= 0 or int(port) > 65535: raise netaddr.AddrFormatError("Invalid port") return "%s:%s" % (ip, port) ovsdbapp-0.9.1/ovsdbapp/__init__.py0000666000175100017510000000123013236157620017315 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # 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 pbr.version __version__ = pbr.version.VersionInfo( 'ovsdbapp').version_string() ovsdbapp-0.9.1/ovsdbapp/schema/0000775000175100017510000000000013236157776016462 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/ovn_southbound/0000775000175100017510000000000013236157776021536 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/ovn_southbound/impl_idl.py0000666000175100017510000000323513236157620023672 0ustar zuulzuul00000000000000# 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 ovsdbapp.backend import ovs_idl from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.schema.ovn_southbound import api from ovsdbapp.schema.ovn_southbound import commands as cmd class OvnSbApiIdlImpl(ovs_idl.Backend, api.API): schema = 'OVN_Southbound' lookup_table = { 'Chassis': idlutils.RowLookup('Chassis', 'name', None), } def __init__(self, connection): super(OvnSbApiIdlImpl, self).__init__(connection) def chassis_add(self, chassis, encap_types, encap_ip, may_exist=False, **columns): return cmd.ChassisAddCommand(self, chassis, encap_types, encap_ip, may_exist, **columns) def chassis_del(self, chassis, if_exists=False): return cmd.ChassisDelCommand(self, chassis, if_exists) def chassis_list(self): return cmd.ChassisListCommand(self) def lsp_bind(self, chassis, port, may_exist=False): return cmd.LspBindCommand(self, chassis, port, may_exist) def lsp_unbind(self, port, if_exists=False): return cmd.LspUnbindCommand(self, port, if_exists) ovsdbapp-0.9.1/ovsdbapp/schema/ovn_southbound/commands.py0000666000175100017510000001070413236157620023701 0ustar zuulzuul00000000000000# 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 ovsdbapp.backend.ovs_idl import command as cmd from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import rowview class ChassisAddCommand(cmd.AddCommand): table_name = 'Chassis' def __init__(self, api, chassis, encap_types, encap_ip, may_exist=False, **columns): super(ChassisAddCommand, self).__init__(api) self.chassis = chassis self.encap_types = encap_types self.encap_ip = encap_ip self.may_exist = may_exist self.columns = columns def run_idl(self, txn): # ovn-sbctl does a client-side check for duplicate entry, but since # there is an index on "name", it will fail if we try to insert a # duplicate, so I'm not doing the check unless may_exist is set if self.may_exist: chassis = idlutils.row_by_value(self.api.idl, self.table_name, 'name', self.chassis) if chassis: self.result = rowview.RowView(chassis) return chassis = txn.insert(self.api.tables[self.table_name]) chassis.name = self.chassis encaps = [] for encap_type in self.encap_types: encap = txn.insert(self.api.tables['Encap']) encap.type = encap_type encap.ip = self.encap_ip encap.options = {'csum': 'True'} # ovn-sbctl silently does this... # NOTE(twilson) addvalue seems like it should work, but fails with # Chassis table col encaps references nonexistent row error # chassis.addvalue('encaps', encap) encaps.append(encap) chassis.encaps = encaps for col, val in self.columns.items(): setattr(chassis, col, val) self.result = chassis.uuid class ChassisDelCommand(cmd.BaseCommand): def __init__(self, api, chassis, if_exists=False): super(ChassisDelCommand, self).__init__(api) self.chassis = chassis self.if_exists = if_exists def run_idl(self, txn): # ovn-sbctl, unlike ovn-nbctl, only looks up by name and not UUI; going # to allow UUID because the lookup is cheaper and should be encouraged try: chassis = self.api.lookup('Chassis', self.chassis) except idlutils.RowNotFound: if self.if_exists: return raise for encap in getattr(chassis, 'encaps', []): encap.delete() chassis.delete() class ChassisListCommand(cmd.BaseCommand): def run_idl(self, txn): self.result = [rowview.RowView(r) for r in self.api.tables['Chassis'].rows.values()] class LspBindCommand(cmd.BaseCommand): def __init__(self, api, port, chassis, may_exist=False): super(LspBindCommand, self).__init__(api) self.port = port self.chassis = chassis self.may_exist = may_exist def run_idl(self, txn): chassis = self.api.lookup('Chassis', self.chassis) binding = idlutils.row_by_value(self.api.idl, 'Port_Binding', 'logical_port', self.port) if binding.chassis: if self.may_exist: return raise RuntimeError("Port %s already bound to %s" % (self.port, self.chassis)) binding.chassis = chassis class LspUnbindCommand(cmd.BaseCommand): def __init__(self, api, port, if_exists=False): super(LspUnbindCommand, self).__init__(api) self.port = port self.if_exists = if_exists def run_idl(self, txn): try: binding = idlutils.row_by_value(self.api.idl, 'Port_Binding', 'logical_port', self.port) except idlutils.RowNotFound: if self.if_exists: return raise binding.chassis = [] ovsdbapp-0.9.1/ovsdbapp/schema/ovn_southbound/__init__.py0000666000175100017510000000000013236157620023623 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/ovn_southbound/api.py0000666000175100017510000000574413236157620022661 0ustar zuulzuul00000000000000# 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 abc import six from ovsdbapp import api @six.add_metaclass(abc.ABCMeta) class API(api.API): """An API based off of the ovn-sbctl CLI interface This API basically mirrors the ovn-nbctl operations with these changes: 1. Methods that create objects will return a read-only view of the object 2. Methods which list objects will return a list of read-only view objects """ @abc.abstractmethod def chassis_add(self, chassis, encap_types, encap_ip, may_exist=False, **columns): """Creates a new chassis :param chassis: The name of the chassis to create :type chassis: string :param encap_types: Tunnel types for the chassis :type encap_types: list of strings :encap_ip: The destination IP for each tunnel :type encap_ip: string :param may_exist: Don't fail if chassis named `chassis` exists :type may_exist: boolean :param columns: Additional column values to set :type columns: key/value pairs :returns: :class:`Command` with RowView result """ @abc.abstractmethod def chassis_del(self, chassis, if_exists=False): """Deletes chassis and its encaps and gateway_ports :param chassis: The name of the chassis to create :type chassis: string :param if_exsits: Don't fail if `chassis` doesn't exist :param if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def chassis_list(self): """Retrieve all chassis :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def lsp_bind(self, port, chassis, may_exist=False): """Bind a logical port to a chassis :param port: The name of the logical port to bind :type port: chassis :param chassis: The name of the chassis :type chassis: string :param may_exist: Don't fail if port is already bound to a chassis :type may_exist: boolean """ @abc.abstractmethod def lsp_unbind(self, port, if_exists=False): """Unbind a logical port from its chassis :param port: The name of the port to unbind :type port: string :param if_exists: Don't fail if the port binding doesn't exist :type if_exists: boolean """ ovsdbapp-0.9.1/ovsdbapp/schema/ovn_northbound/0000775000175100017510000000000013236157776021526 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/ovn_northbound/impl_idl.py0000666000175100017510000002227313236157633023671 0ustar zuulzuul00000000000000# 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 ovsdbapp.backend import ovs_idl from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp import constants as const from ovsdbapp.schema.ovn_northbound import api from ovsdbapp.schema.ovn_northbound import commands as cmd from ovsdbapp import utils class OvnNbApiIdlImpl(ovs_idl.Backend, api.API): schema = 'OVN_Northbound' lookup_table = { 'Logical_Switch': idlutils.RowLookup('Logical_Switch', 'name', None), 'Logical_Router': idlutils.RowLookup('Logical_Router', 'name', None), 'Load_Balancer': idlutils.RowLookup('Load_Balancer', 'name', None), } def ls_add(self, switch=None, may_exist=False, **columns): return cmd.LsAddCommand(self, switch, may_exist, **columns) def ls_del(self, switch, if_exists=False): return cmd.LsDelCommand(self, switch, if_exists) def ls_list(self): return cmd.LsListCommand(self) def ls_get(self, switch): return cmd.LsGetCommand(self, switch) def ls_set_dns_records(self, switch_uuid, dns_uuids): return self.db_set('Logical_Switch', switch_uuid, ('dns_records', dns_uuids)) def ls_clear_dns_records(self, switch_uuid): return self.db_clear('Logical_Switch', switch_uuid, 'dns_records') def ls_add_dns_record(self, switch_uuid, dns_uuid): return self.db_add('Logical_Switch', switch_uuid, 'dns_records', dns_uuid) def ls_remove_dns_record(self, switch_uuid, dns_uuid): return self.db_remove('Logical_Switch', switch_uuid, 'dns_records', dns_uuid) def acl_add(self, switch, direction, priority, match, action, log=False, may_exist=False, **external_ids): return cmd.AclAddCommand(self, switch, direction, priority, match, action, log, may_exist, **external_ids) def acl_del(self, switch, direction=None, priority=None, match=None): return cmd.AclDelCommand(self, switch, direction, priority, match) def acl_list(self, switch): return cmd.AclListCommand(self, switch) def lsp_add(self, switch, port, parent_name=None, tag=None, may_exist=False, **columns): return cmd.LspAddCommand(self, switch, port, parent_name, tag, may_exist, **columns) def lsp_del(self, port, switch=None, if_exists=False): return cmd.LspDelCommand(self, port, switch, if_exists) def lsp_list(self, switch=None): return cmd.LspListCommand(self, switch) def lsp_get(self, port): return cmd.LspGetCommand(self, port) def lsp_get_parent(self, port): return cmd.LspGetParentCommand(self, port) def lsp_get_tag(self, port): # NOTE (twilson) tag can be unassigned for a while after setting return cmd.LspGetTagCommand(self, port) def lsp_set_addresses(self, port, addresses): return cmd.LspSetAddressesCommand(self, port, addresses) def lsp_get_addresses(self, port): return cmd.LspGetAddressesCommand(self, port) def lsp_set_port_security(self, port, addresses): return cmd.LspSetPortSecurityCommand(self, port, addresses) def lsp_get_port_security(self, port): return cmd.LspGetPortSecurityCommand(self, port) def lsp_get_up(self, port): return cmd.LspGetUpCommand(self, port) def lsp_set_enabled(self, port, is_enabled): return cmd.LspSetEnabledCommand(self, port, is_enabled) def lsp_get_enabled(self, port): return cmd.LspGetEnabledCommand(self, port) def lsp_set_type(self, port, port_type): return cmd.LspSetTypeCommand(self, port, port_type) def lsp_get_type(self, port): return cmd.LspGetTypeCommand(self, port) def lsp_set_options(self, port, **options): return cmd.LspSetOptionsCommand(self, port, **options) def lsp_get_options(self, port): return cmd.LspGetOptionsCommand(self, port) def lsp_set_dhcpv4_options(self, port, dhcpopt_uuids): return cmd.LspSetDhcpV4OptionsCommand(self, port, dhcpopt_uuids) def lsp_get_dhcpv4_options(self, port): return cmd.LspGetDhcpV4OptionsCommand(self, port) def lr_add(self, router=None, may_exist=False, **columns): return cmd.LrAddCommand(self, router, may_exist, **columns) def lr_del(self, router, if_exists=False): return cmd.LrDelCommand(self, router, if_exists) def lr_list(self): return cmd.LrListCommand(self) def lrp_add(self, router, port, mac, networks, peer=None, may_exist=False, **columns): return cmd.LrpAddCommand(self, router, port, mac, networks, peer, may_exist, **columns) def lrp_del(self, port, router=None, if_exists=False): return cmd.LrpDelCommand(self, port, router, if_exists) def lrp_list(self, router): return cmd.LrpListCommand(self, router) def lrp_set_enabled(self, port, is_enabled): return cmd.LrpSetEnabledCommand(self, port, is_enabled) def lrp_get_enabled(self, port): return cmd.LrpGetEnabledCommand(self, port) def lr_route_add(self, router, prefix, nexthop, port=None, policy='dst-ip', may_exist=False): return cmd.LrRouteAddCommand(self, router, prefix, nexthop, port, policy, may_exist) def lr_route_del(self, router, prefix=None, if_exists=False): return cmd.LrRouteDelCommand(self, router, prefix, if_exists) def lr_route_list(self, router): return cmd.LrRouteListCommand(self, router) def lr_nat_add(self, router, nat_type, external_ip, logical_ip, logical_port=None, external_mac=None, may_exist=False): return cmd.LrNatAddCommand( self, router, nat_type, external_ip, logical_ip, logical_port, external_mac, may_exist) def lr_nat_del(self, router, nat_type=None, match_ip=None, if_exists=False): return cmd.LrNatDelCommand(self, router, nat_type, match_ip, if_exists) def lr_nat_list(self, router): return cmd.LrNatListCommand(self, router) def lb_add(self, lb, vip, ips, protocol=const.PROTO_TCP, may_exist=False, **columns): return cmd.LbAddCommand(self, lb, vip, ips, protocol, may_exist, **columns) def lb_del(self, lb, vip=None, if_exists=False): return cmd.LbDelCommand(self, lb, vip, if_exists) def lb_list(self): return cmd.LbListCommand(self) def lr_lb_add(self, router, lb, may_exist=False): return cmd.LrLbAddCommand(self, router, lb, may_exist) def lr_lb_del(self, router, lb=None, if_exists=False): return cmd.LrLbDelCommand(self, router, lb, if_exists) def lr_lb_list(self, router): return cmd.LrLbListCommand(self, router) def ls_lb_add(self, switch, lb, may_exist=False): return cmd.LsLbAddCommand(self, switch, lb, may_exist) def ls_lb_del(self, switch, lb=None, if_exists=False): return cmd.LsLbDelCommand(self, switch, lb, if_exists) def ls_lb_list(self, switch): return cmd.LsLbListCommand(self, switch) def dhcp_options_add(self, cidr, **external_ids): return cmd.DhcpOptionsAddCommand(self, cidr, **external_ids) def dhcp_options_del(self, dhcpopt_uuid): return cmd.DhcpOptionsDelCommand(self, dhcpopt_uuid) def dhcp_options_list(self): return cmd.DhcpOptionsListCommand(self) def dhcp_options_get(self, dhcpopt_uuid): return cmd.DhcpOptionsGetCommand(self, dhcpopt_uuid) def dhcp_options_set_options(self, dhcpopt_uuid, **options): return cmd.DhcpOptionsSetOptionsCommand(self, dhcpopt_uuid, **options) def dhcp_options_get_options(self, dhcpopt_uuid): return cmd.DhcpOptionsGetOptionsCommand(self, dhcpopt_uuid) def dns_add(self, **columns): return cmd.DnsAddCommand(self, **columns) def dns_del(self, uuid): return cmd.DnsDelCommand(self, uuid) def dns_get(self, uuid): return cmd.DnsGetCommand(self, uuid) def dns_list(self): return cmd.DnsListCommand(self) def dns_set_records(self, uuid, **records): return cmd.DnsSetRecordsCommand(self, uuid, **records) def dns_add_record(self, uuid, hostname, ips): if isinstance(ips, list): ips = " ".join(utils.normalize_ip_port(ip) for ip in ips) return self.db_add('DNS', uuid, 'records', {hostname: ips}) def dns_remove_record(self, uuid, hostname): return self.db_remove('DNS', uuid, 'records', hostname) def dns_set_external_ids(self, uuid, **external_ids): return cmd.DnsSetExternalIdsCommand(self, uuid, **external_ids) ovsdbapp-0.9.1/ovsdbapp/schema/ovn_northbound/commands.py0000666000175100017510000011076613236157633023706 0ustar zuulzuul00000000000000# 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 re import netaddr from ovsdbapp.backend.ovs_idl import command as cmd from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import rowview from ovsdbapp import constants as const from ovsdbapp import utils class LsAddCommand(cmd.AddCommand): table_name = 'Logical_Switch' def __init__(self, api, switch=None, may_exist=False, **columns): super(LsAddCommand, self).__init__(api) self.switch = switch self.columns = columns self.may_exist = may_exist def run_idl(self, txn): # There is no requirement for name to be unique, so if a name is # specified, we always have to do a lookup since adding it won't # fail. If may_exist is set, we just don't do anything when dup'd if self.switch: sw = idlutils.row_by_value(self.api.idl, self.table_name, 'name', self.switch, None) if sw: if self.may_exist: self.result = rowview.RowView(sw) return raise RuntimeError("Switch %s exists" % self.switch) elif self.may_exist: raise RuntimeError("may_exist requires name") sw = txn.insert(self.api.tables[self.table_name]) if self.switch: sw.name = self.switch else: # because ovs.db.idl brokenly requires a changed column sw.name = "" self.set_columns(sw, **self.columns) self.result = sw.uuid class LsDelCommand(cmd.BaseCommand): def __init__(self, api, switch, if_exists=False): super(LsDelCommand, self).__init__(api) self.switch = switch self.if_exists = if_exists def run_idl(self, txn): try: lswitch = self.api.lookup('Logical_Switch', self.switch) lswitch.delete() except idlutils.RowNotFound: if self.if_exists: return msg = "Logical Switch %s does not exist" % self.switch raise RuntimeError(msg) class LsListCommand(cmd.BaseCommand): def run_idl(self, txn): table = self.api.tables['Logical_Switch'] self.result = [rowview.RowView(r) for r in table.rows.values()] class LsGetCommand(cmd.BaseGetRowCommand): table = 'Logical_Switch' class AclAddCommand(cmd.AddCommand): table_name = 'ACL' def __init__(self, api, switch, direction, priority, match, action, log=False, may_exist=False, **external_ids): if direction not in ('from-lport', 'to-lport'): raise TypeError("direction must be either from-lport or to-lport") if not 0 <= priority <= const.ACL_PRIORITY_MAX: raise ValueError("priority must be beween 0 and %s, inclusive" % ( const.ACL_PRIORITY_MAX)) if action not in ('allow', 'allow-related', 'drop', 'reject'): raise TypeError("action must be allow/allow-related/drop/reject") super(AclAddCommand, self).__init__(api) self.switch = switch self.direction = direction self.priority = priority self.match = match self.action = action self.log = log self.may_exist = may_exist self.external_ids = external_ids def acl_match(self, row): return (self.direction == row.direction and self.priority == row.priority and self.match == row.match) def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) acls = [acl for acl in ls.acls if self.acl_match(acl)] if acls: if self.may_exist: self.result = rowview.RowView(acls[0]) return raise RuntimeError("ACL (%s, %s, %s) already exists" % ( self.direction, self.priority, self.match)) acl = txn.insert(self.api.tables[self.table_name]) acl.direction = self.direction acl.priority = self.priority acl.match = self.match acl.action = self.action acl.log = self.log ls.addvalue('acls', acl) for col, value in self.external_ids.items(): acl.setkey('external_ids', col, value) self.result = acl.uuid class AclDelCommand(cmd.BaseCommand): def __init__(self, api, switch, direction=None, priority=None, match=None): if (priority is None) != (match is None): raise TypeError("Must specify priority and match together") if priority is not None and not direction: raise TypeError("Cannot specify priority/match without direction") super(AclDelCommand, self).__init__(api) self.switch = switch self.conditions = [] if direction: self.conditions.append(('direction', '=', direction)) # priority can be 0 if match: # and therefor prioroity due to the above check self.conditions += [('priority', '=', priority), ('match', '=', match)] def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) for acl in [a for a in ls.acls if idlutils.row_match(a, self.conditions)]: ls.delvalue('acls', acl) acl.delete() class AclListCommand(cmd.BaseCommand): def __init__(self, api, switch): super(AclListCommand, self).__init__(api) self.switch = switch def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) self.result = [rowview.RowView(acl) for acl in ls.acls] class LspAddCommand(cmd.AddCommand): table_name = 'Logical_Switch_Port' def __init__(self, api, switch, port, parent_name=None, tag=None, may_exist=False, **columns): if tag and not 0 <= tag <= 4095: raise TypeError("tag must be 0 to 4095, inclusive") if (parent_name is None) != (tag is None): raise TypeError("parent_name and tag must be passed together") super(LspAddCommand, self).__init__(api) self.switch = switch self.port = port self.parent = parent_name self.tag = tag self.may_exist = may_exist self.columns = columns def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) try: lsp = self.api.lookup(self.table_name, self.port) if self.may_exist: msg = None if lsp not in ls.ports: msg = "%s exists, but is not in %s" % ( self.port, self.switch) if self.parent: if not lsp.parent_name: msg = "%s exists, but has no parent" % self.port # parent_name, being optional, is stored as list if self.parent not in lsp.parent_name: msg = "%s exists with different parent" % self.port if self.tag not in lsp.tag_request: msg = "%s exists with different tag request" % ( self.port,) elif lsp.parent_name: msg = "%s exists, but with a parent" % self.port if msg: raise RuntimeError(msg) self.result = rowview.RowView(lsp) return except idlutils.RowNotFound: # This is what we want pass lsp = txn.insert(self.api.tables[self.table_name]) lsp.name = self.port if self.tag is not None: lsp.parent_name = self.parent lsp.tag_request = self.tag ls.addvalue('ports', lsp) self.set_columns(lsp, **self.columns) self.result = lsp.uuid class PortDelCommand(cmd.BaseCommand): def __init__(self, api, table, port, parent_table, parent=None, if_exists=False): super(PortDelCommand, self).__init__(api) self.table = table self.port = port self.parent_table = parent_table self.parent = parent self.if_exists = if_exists def run_idl(self, txn): try: row = self.api.lookup(self.table, self.port) except idlutils.RowNotFound: if self.if_exists: return raise RuntimeError("%s does not exist" % self.port) # We need to delete the port from its parent if self.parent: parent = self.api.lookup(self.parent_table, self.parent) else: parent = next(iter( p for p in self.api.tables[self.parent_table].rows.values() if row in p.ports), None) if not (parent and row in parent.ports): raise RuntimeError("%s does not exist in %s" % ( self.port, self.parent)) parent.delvalue('ports', row) row.delete() class LspDelCommand(PortDelCommand): def __init__(self, api, port, switch=None, if_exists=False): super(LspDelCommand, self).__init__( api, 'Logical_Switch_Port', port, 'Logical_Switch', switch, if_exists) class LspListCommand(cmd.BaseCommand): def __init__(self, api, switch=None): super(LspListCommand, self).__init__(api) self.switch = switch def run_idl(self, txn): if self.switch: ports = self.api.lookup('Logical_Switch', self.switch).ports else: ports = self.api.tables['Logical_Switch_Port'].rows.values() self.result = [rowview.RowView(r) for r in ports] class LspGetCommand(cmd.BaseGetRowCommand): table = 'Logical_Switch_Port' class LspGetParentCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetParentCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) self.result = next(iter(lsp.parent_name), "") class LspGetTagCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetTagCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) self.result = next(iter(lsp.tag), -1) class LspSetAddressesCommand(cmd.BaseCommand): addr_re = re.compile( r'^(router|unknown|dynamic|([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2} .+)$') def __init__(self, api, port, addresses): for addr in addresses: if not self.addr_re.match(addr): raise TypeError( "address must be router/unknown/dynamic/ethaddr ipaddr...") super(LspSetAddressesCommand, self).__init__(api) self.port = port self.addresses = addresses def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) lsp.addresses = self.addresses class LspGetAddressesCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetAddressesCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) self.result = lsp.addresses class LspSetPortSecurityCommand(cmd.BaseCommand): def __init__(self, api, port, addresses): # NOTE(twilson) ovn-nbctl.c does not do any checking of addresses # so neither do we super(LspSetPortSecurityCommand, self).__init__(api) self.port = port self.addresses = addresses def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) lsp.port_security = self.addresses class LspGetPortSecurityCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetPortSecurityCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) self.result = lsp.port_security class LspGetUpCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetUpCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) # 'up' is optional, but if not up, it's not up :p self.result = next(iter(lsp.up), False) class LspSetEnabledCommand(cmd.BaseCommand): def __init__(self, api, port, is_enabled): super(LspSetEnabledCommand, self).__init__(api) self.port = port self.is_enabled = is_enabled def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) lsp.enabled = self.is_enabled class LspGetEnabledCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetEnabledCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) # enabled is optional, but if not disabled then enabled self.result = next(iter(lsp.enabled), True) class LspSetTypeCommand(cmd.BaseCommand): def __init__(self, api, port, port_type): super(LspSetTypeCommand, self).__init__(api) self.port = port self.port_type = port_type def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) lsp.type = self.port_type class LspGetTypeCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetTypeCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) self.result = lsp.type class LspSetOptionsCommand(cmd.BaseCommand): def __init__(self, api, port, **options): super(LspSetOptionsCommand, self).__init__(api) self.port = port self.options = options def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) lsp.options = self.options class LspGetOptionsCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetOptionsCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) self.result = lsp.options class LspSetDhcpV4OptionsCommand(cmd.BaseCommand): def __init__(self, api, port, dhcpopt_uuid): super(LspSetDhcpV4OptionsCommand, self).__init__(api) self.port = port self.dhcpopt_uuid = dhcpopt_uuid def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) lsp.dhcpv4_options = self.dhcpopt_uuid class LspGetDhcpV4OptionsCommand(cmd.BaseCommand): def __init__(self, api, port): super(LspGetDhcpV4OptionsCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup('Logical_Switch_Port', self.port) self.result = next((rowview.RowView(d) for d in lsp.dhcpv4_options), []) class DhcpOptionsAddCommand(cmd.AddCommand): table_name = 'DHCP_Options' def __init__(self, api, cidr, **external_ids): cidr = netaddr.IPNetwork(cidr) super(DhcpOptionsAddCommand, self).__init__(api) self.cidr = str(cidr) self.external_ids = external_ids def run_idl(self, txn): dhcpopt = txn.insert(self.api.tables[self.table_name]) dhcpopt.cidr = self.cidr dhcpopt.external_ids = self.external_ids self.result = dhcpopt.uuid class DhcpOptionsDelCommand(cmd.BaseCommand): def __init__(self, api, dhcpopt_uuid): super(DhcpOptionsDelCommand, self).__init__(api) self.dhcpopt_uuid = dhcpopt_uuid def run_idl(self, txn): dhcpopt = self.api.lookup('DHCP_Options', self.dhcpopt_uuid) dhcpopt.delete() class DhcpOptionsListCommand(cmd.BaseCommand): def run_idl(self, txn): self.result = [rowview.RowView(r) for r in self.api.tables['DHCP_Options'].rows.values()] class DhcpOptionsGetCommand(cmd.BaseGetRowCommand): table = 'DHCP_Options' class DhcpOptionsSetOptionsCommand(cmd.BaseCommand): def __init__(self, api, dhcpopt_uuid, **options): super(DhcpOptionsSetOptionsCommand, self).__init__(api) self.dhcpopt_uuid = dhcpopt_uuid self.options = options def run_idl(self, txn): dhcpopt = self.api.lookup('DHCP_Options', self.dhcpopt_uuid) dhcpopt.options = self.options class DhcpOptionsGetOptionsCommand(cmd.BaseCommand): def __init__(self, api, dhcpopt_uuid): super(DhcpOptionsGetOptionsCommand, self).__init__(api) self.dhcpopt_uuid = dhcpopt_uuid def run_idl(self, txn): dhcpopt = self.api.lookup('DHCP_Options', self.dhcpopt_uuid) self.result = dhcpopt.options class LrAddCommand(cmd.BaseCommand): def __init__(self, api, router=None, may_exist=False, **columns): super(LrAddCommand, self).__init__(api) self.router = router self.may_exist = may_exist self.columns = columns def run_idl(self, txn): if self.router: try: lr = self.api.lookup('Logical_Router', self.router) if self.may_exist: self.result = rowview.RowView(lr) return except idlutils.RowNotFound: pass lr = txn.insert(self.api.tables['Logical_Router']) lr.name = self.router if self.router else "" self.set_columns(lr, **self.columns) self.result = lr.uuid def post_commit(self, txn): real_uuid = txn.get_insert_uuid(self.result) if real_uuid: row = self.api.tables['Logical_Router'].rows[real_uuid] self.result = rowview.RowView(row) class LrDelCommand(cmd.BaseCommand): def __init__(self, api, router, if_exists=False): super(LrDelCommand, self).__init__(api) self.router = router self.if_exists = if_exists def run_idl(self, txn): try: lr = self.api.lookup('Logical_Router', self.router) lr.delete() except idlutils.RowNotFound: if self.if_exists: return msg = "Logical Router %s does not exist" % self.router raise RuntimeError(msg) class LrListCommand(cmd.BaseCommand): def run_idl(self, txn): self.result = [rowview.RowView(r) for r in self.api.tables['Logical_Router'].rows.values()] class LrpAddCommand(cmd.BaseCommand): def __init__(self, api, router, port, mac, networks, peer=None, may_exist=False, **columns): self.mac = str(netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded)) self.networks = [str(netaddr.IPNetwork(net)) for net in networks] self.router = router self.port = port self.peer = peer self.may_exist = may_exist self.columns = columns super(LrpAddCommand, self).__init__(api) def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) try: lrp = self.api.lookup('Logical_Router_Port', self.port) if self.may_exist: msg = None if lrp not in lr.ports: msg = "Port %s exists, but is not in router %s" % ( self.port, self.router) elif netaddr.EUI(lrp.mac) != netaddr.EUI(self.mac): msg = "Port %s exists with different mac" % (self.port) elif set(self.networks) != set(lrp.networks): msg = "Port %s exists with different networks" % ( self.port) elif (not self.peer) != (not lrp.peer) or ( self.peer != lrp.peer): msg = "Port %s exists with different peer" % (self.port) if msg: raise RuntimeError(msg) self.result = rowview.RowView(lrp) return except idlutils.RowNotFound: pass lrp = txn.insert(self.api.tables['Logical_Router_Port']) # This is what ovn-nbctl does, though the lookup is by uuid or name lrp.name = self.port lrp.mac = self.mac lrp.networks = self.networks if self.peer: lrp.peer = self.peer lr.addvalue('ports', lrp) self.set_columns(lrp, **self.columns) self.result = lrp.uuid def post_commit(self, txn): real_uuid = txn.get_insert_uuid(self.result) if real_uuid: row = self.api.tables['Logical_Router_Port'].rows[real_uuid] self.result = rowview.RowView(row) class LrpDelCommand(PortDelCommand): def __init__(self, api, port, router=None, if_exists=False): super(LrpDelCommand, self).__init__( api, 'Logical_Router_Port', port, 'Logical_Router', router, if_exists) class LrpListCommand(cmd.BaseCommand): def __init__(self, api, router): super(LrpListCommand, self).__init__(api) self.router = router def run_idl(self, txn): router = self.api.lookup('Logical_Router', self.router) self.result = [rowview.RowView(r) for r in router.ports] class LrpSetEnabledCommand(cmd.BaseCommand): def __init__(self, api, port, is_enabled): super(LrpSetEnabledCommand, self).__init__(api) self.port = port self.is_enabled = is_enabled def run_idl(self, txn): lrp = self.api.lookup('Logical_Router_Port', self.port) lrp.enabled = self.is_enabled class LrpGetEnabledCommand(cmd.BaseCommand): def __init__(self, api, port): super(LrpGetEnabledCommand, self).__init__(api) self.port = port def run_idl(self, txn): lrp = self.api.lookup('Logical_Router_Port', self.port) # enabled is optional, but if not disabled then enabled self.result = next(iter(lrp.enabled), True) class LrRouteAddCommand(cmd.BaseCommand): def __init__(self, api, router, prefix, nexthop, port=None, policy='dst-ip', may_exist=False): prefix = str(netaddr.IPNetwork(prefix)) nexthop = str(netaddr.IPAddress(nexthop)) super(LrRouteAddCommand, self).__init__(api) self.router = router self.prefix = prefix self.nexthop = nexthop self.port = port self.policy = policy self.may_exist = may_exist def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) for route in lr.static_routes: if self.prefix == route.ip_prefix: if not self.may_exist: msg = "Route %s already exists on router %s" % ( self.prefix, self.router) raise RuntimeError(msg) route.nexthop = self.nexthop route.policy = self.policy if self.port: route.port = self.port self.result = rowview.RowView(route) return route = txn.insert(self.api.tables['Logical_Router_Static_Route']) route.ip_prefix = self.prefix route.nexthop = self.nexthop route.policy = self.policy if self.port: route.port = self.port lr.addvalue('static_routes', route) self.result = route.uuid def post_commit(self, txn): real_uuid = txn.get_insert_uuid(self.result) if real_uuid: table = self.api.tables['Logical_Router_Static_Route'] row = table.rows[real_uuid] self.result = rowview.RowView(row) class LrRouteDelCommand(cmd.BaseCommand): def __init__(self, api, router, prefix=None, if_exists=False): if prefix is not None: prefix = str(netaddr.IPNetwork(prefix)) super(LrRouteDelCommand, self).__init__(api) self.router = router self.prefix = prefix self.if_exists = if_exists def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) if not self.prefix: lr.static_routes = [] return for route in lr.static_routes: if self.prefix == route.ip_prefix: lr.delvalue('static_routes', route) # There should only be one possible match return if not self.if_exists: msg = "Route for %s in router %s does not exist" % ( self.prefix, self.router) raise RuntimeError(msg) class LrRouteListCommand(cmd.BaseCommand): def __init__(self, api, router): super(LrRouteListCommand, self).__init__(api) self.router = router def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) self.result = [rowview.RowView(r) for r in lr.static_routes] class LrNatAddCommand(cmd.BaseCommand): def __init__(self, api, router, nat_type, external_ip, logical_ip, logical_port=None, external_mac=None, may_exist=False): if nat_type not in const.NAT_TYPES: raise TypeError("nat_type not in %s" % str(const.NAT_TYPES)) external_ip = str(netaddr.IPAddress(external_ip)) if nat_type == const.NAT_DNAT: logical_ip = str(netaddr.IPAddress(logical_ip)) else: net = netaddr.IPNetwork(logical_ip) logical_ip = str(net.ip if net.prefixlen == 32 else net) if (logical_port is None) != (external_mac is None): msg = "logical_port and external_mac must be passed together" raise TypeError(msg) if logical_port and nat_type != const.NAT_BOTH: msg = "logical_port/external_mac only valid for %s" % ( const.NAT_BOTH,) raise TypeError(msg) if external_mac: external_mac = str( netaddr.EUI(external_mac, dialect=netaddr.mac_unix_expanded)) super(LrNatAddCommand, self).__init__(api) self.router = router self.nat_type = nat_type self.external_ip = external_ip self.logical_ip = logical_ip self.logical_port = logical_port or [] self.external_mac = external_mac or [] self.may_exist = may_exist def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) if self.logical_port: lp = self.api.lookup('Logical_Switch_Port', self.logical_port) for nat in lr.nat: if ((self.nat_type, self.external_ip, self.logical_ip) == (nat.type, nat.external_ip, nat.logical_ip)): if self.may_exist: nat.logical_port = self.logical_port nat.external_mac = self.external_mac self.result = rowview.RowView(nat) return raise RuntimeError("NAT already exists") nat = txn.insert(self.api.tables['NAT']) nat.type = self.nat_type nat.external_ip = self.external_ip nat.logical_ip = self.logical_ip if self.logical_port: # It seems kind of weird that ovn uses a name string instead of # a ref to a LSP, especially when ovn-nbctl looks the value up by # either name or uuid (and discards the result and store the name). nat.logical_port = lp.name nat.external_mac = self.external_mac lr.addvalue('nat', nat) self.result = nat.uuid def post_commit(self, txn): real_uuid = txn.get_insert_uuid(self.result) if real_uuid: row = self.api.tables['NAT'].rows[real_uuid] self.result = rowview.RowView(row) class LrNatDelCommand(cmd.BaseCommand): def __init__(self, api, router, nat_type=None, match_ip=None, if_exists=False): super(LrNatDelCommand, self).__init__(api) self.conditions = [] if nat_type: if nat_type not in const.NAT_TYPES: raise TypeError("nat_type not in %s" % str(const.NAT_TYPES)) self.conditions += [('type', '=', nat_type)] if match_ip: match_ip = str(netaddr.IPAddress(match_ip)) self.col = ('logical_ip' if nat_type == const.NAT_SNAT else 'external_ip') self.conditions += [(self.col, '=', match_ip)] elif match_ip: raise TypeError("must specify nat_type with match_ip") self.router = router self.nat_type = nat_type self.match_ip = match_ip self.if_exists = if_exists def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) found = False for nat in [r for r in lr.nat if idlutils.row_match(r, self.conditions)]: found = True lr.delvalue('nat', nat) nat.delete() if self.match_ip: break if self.match_ip and not (found or self.if_exists): raise idlutils.RowNotFound(table='NAT', col=self.col, match=self.match_ip) class LrNatListCommand(cmd.BaseCommand): def __init__(self, api, router): super(LrNatListCommand, self).__init__(api) self.router = router def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) self.result = [rowview.RowView(r) for r in lr.nat] class LbAddCommand(cmd.BaseCommand): def __init__(self, api, lb, vip, ips, protocol=const.PROTO_TCP, may_exist=False, **columns): super(LbAddCommand, self).__init__(api) self.lb = lb self.vip = utils.normalize_ip_port(vip) self.ips = ",".join(utils.normalize_ip_port(ip) for ip in ips) self.protocol = protocol self.may_exist = may_exist self.columns = columns def run_idl(self, txn): try: lb = self.api.lookup('Load_Balancer', self.lb) if lb.vips.get(self.vip): if not self.may_exist: raise RuntimeError("Load Balancer %s exists" % lb.name) # Update load balancer vip lb.setkey('vips', self.vip, self.ips) lb.protocol = self.protocol except idlutils.RowNotFound: # New load balancer lb = txn.insert(self.api.tables['Load_Balancer']) lb.name = self.lb lb.protocol = self.protocol lb.vips = {self.vip: self.ips} self.set_columns(lb, **self.columns) self.result = lb.uuid def post_commit(self, txn): real_uuid = txn.get_insert_uuid(self.result) or self.result row = self.api.tables['Load_Balancer'].rows[real_uuid] self.result = rowview.RowView(row) class LbDelCommand(cmd.BaseCommand): def __init__(self, api, lb, vip=None, if_exists=False): super(LbDelCommand, self).__init__(api) self.lb = lb self.vip = utils.normalize_ip_port(vip) if vip else vip self.if_exists = if_exists def run_idl(self, txn): try: lb = self.api.lookup('Load_Balancer', self.lb) if self.vip: if self.vip in lb.vips: if self.if_exists: return lb.delkey('vips', self.vip) else: lb.delete() except idlutils.RowNotFound: if not self.if_exists: raise class LbListCommand(cmd.BaseCommand): def run_idl(self, txn): self.result = [rowview.RowView(r) for r in self.api.tables['Load_Balancer'].rows.values()] class LrLbAddCommand(cmd.BaseCommand): def __init__(self, api, router, lb, may_exist=False): super(LrLbAddCommand, self).__init__(api) self.router = router self.lb = lb self.may_exist = may_exist def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) lb = self.api.lookup('Load_Balancer', self.lb) if lb in lr.load_balancer: if self.may_exist: return raise RuntimeError("LB %s already exist in router %s" % ( lb.uuid, lr.uuid)) lr.addvalue('load_balancer', lb) class LrLbDelCommand(cmd.BaseCommand): def __init__(self, api, router, lb=None, if_exists=False): super(LrLbDelCommand, self).__init__(api) self.router = router self.lb = lb self.if_exists = if_exists def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) if not self.lb: lr.load_balancer = [] return try: lb = self.api.lookup('Load_Balancer', self.lb) lr.delvalue('load_balancer', lb) except idlutils.RowNotFound: if self.if_exists: return raise class LrLbListCommand(cmd.BaseCommand): def __init__(self, api, router): super(LrLbListCommand, self).__init__(api) self.router = router def run_idl(self, txn): lr = self.api.lookup('Logical_Router', self.router) self.result = [rowview.RowView(r) for r in lr.load_balancer] class LsLbAddCommand(cmd.BaseCommand): def __init__(self, api, switch, lb, may_exist=False): super(LsLbAddCommand, self).__init__(api) self.switch = switch self.lb = lb self.may_exist = may_exist def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) lb = self.api.lookup('Load_Balancer', self.lb) if lb in ls.load_balancer: if self.may_exist: return raise RuntimeError("LB %s alseady exist in switch %s" % ( lb.uuid, ls.uuid)) ls.addvalue('load_balancer', lb) class LsLbDelCommand(cmd.BaseCommand): def __init__(self, api, switch, lb=None, if_exists=False): super(LsLbDelCommand, self).__init__(api) self.switch = switch self.lb = lb self.if_exists = if_exists def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) if not self.lb: ls.load_balancer = [] return try: lb = self.api.lookup('Load_Balancer', self.lb) ls.delvalue('load_balancer', lb) except idlutils.RowNotFound: if self.if_exists: return raise class LsLbListCommand(cmd.BaseCommand): def __init__(self, api, switch): super(LsLbListCommand, self).__init__(api) self.switch = switch def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) self.result = [rowview.RowView(r) for r in ls.load_balancer] class DnsAddCommand(cmd.AddCommand): table_name = 'DNS' def __init__(self, api, **columns): super(DnsAddCommand, self).__init__(api) self.columns = columns def run_idl(self, txn): dns = txn.insert(self.api.tables[self.table_name]) # Transaction will not be commited if the row is not initialized with # any columns. dns.external_ids = {} self.set_columns(dns, **self.columns) self.result = dns.uuid class DnsDelCommand(cmd.DbDestroyCommand): def __init__(self, api, uuid): super(DnsDelCommand, self).__init__(api, 'DNS', uuid) class DnsGetCommand(cmd.BaseGetRowCommand): table = 'DNS' class DnsListCommand(cmd.BaseCommand): def run_idl(self, txn): table = self.api.tables['DNS'] self.result = [rowview.RowView(r) for r in table.rows.values()] class DnsSetRecordsCommand(cmd.BaseCommand): def __init__(self, api, row_uuid, **records): super(DnsSetRecordsCommand, self).__init__(api) self.row_uuid = row_uuid self.records = records def run_idl(self, txn): try: dns = self.api.lookup('DNS', self.row_uuid) dns.records = self.records except idlutils.RowNotFound: msg = "DNS %s does not exist" % self.row_uuid raise RuntimeError(msg) class DnsSetExternalIdsCommand(cmd.BaseCommand): def __init__(self, api, row_uuid, **external_ids): super(DnsSetExternalIdsCommand, self).__init__(api) self.row_uuid = row_uuid self.external_ids = external_ids def run_idl(self, txn): try: dns = self.api.lookup('DNS', self.row_uuid) dns.external_ids = self.external_ids except idlutils.RowNotFound: msg = "DNS %s does not exist" % self.row_uuid raise RuntimeError(msg) ovsdbapp-0.9.1/ovsdbapp/schema/ovn_northbound/__init__.py0000666000175100017510000000000013236157620023613 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/ovn_northbound/api.py0000666000175100017510000006766313236157633022665 0ustar zuulzuul00000000000000# 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 abc import six from ovsdbapp import api from ovsdbapp import constants as const @six.add_metaclass(abc.ABCMeta) class API(api.API): """An API based off of the ovn-nbctl CLI interface This API basically mirrors the ovn-nbctl operations with these changes: 1. Methods that create objects will return a read-only view of the object 2. Methods which list objects will return a list of read-only view objects """ @abc.abstractmethod def ls_add(self, switch=None, may_exist=False, **columns): """Create a logical switch named 'switch' :param switch: The name of the switch (optional) :type switch: string or uuid.UUID :param may_exist: If True, don't fail if the switch already exists :type may_exist: boolean :param columns: Additional columns to directly set on the switch :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ls_del(self, switch, if_exists=False): """Delete logical switch 'switch' and all its ports :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :type if_exists: If True, don't fail if the switch doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def ls_list(self): """Get all logical switches :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def ls_get(self, switch): """Get logical switch for 'switch' :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ls_set_dns_records(self, switch_uuid, dns_uuids): """Sets 'dns_records' column on the switch with uuid 'switch_uuid' :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ls_clear_dns_records(self, switch): """Clears 'dns_records' from the switch with uuid 'switch_uuid' :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ls_add_dns_record(self, switch_uuid, dns_uuid): """Add the 'dns_record' to the switch's 'dns_records' list :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ls_remove_dns_record(self, switch_uuid, dns_uuid): """Remove the 'dns_record' from the switch's 'dns_records' list :returns: :class:`Command` with RowView result """ @abc.abstractmethod def acl_add(self, switch, direction, priority, match, action, log=False): """Add an ACL to 'switch' :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :param direction: The traffic direction to match :type direction: 'from-lport' or 'to-lport' :param priority: The priority field of the ACL :type priority: int :param match: The match rule :type match: string :param action: The action to take upon match :type action: 'allow', 'allow-related', 'drop', or 'reject' :param log: If True, enable packet logging for the ACL :type log: boolean :returns: :class:`Command` with RowView result """ @abc.abstractmethod def acl_del(self, switch, direction=None, priority=None, match=None): """Remove ACLs from 'switch' If only switch is supplied, all the ACLs from the logical switch are deleted. If direction is also specified, then all the flows in that direction will be deleted from the logical switch. If all the fields are given, then only flows that match all fields will be deleted. :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :param direction: The traffic direction to match :type direction: 'from-lport' or 'to-lport' :param priority: The priority field of the ACL :type priority: int :param match: The match rule :type match: string :returns: :class:`Command` with no result """ @abc.abstractmethod def acl_list(self, switch): """Get the ACLs for 'switch' :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def lsp_add(self, switch, port, parent_name=None, tag=None, may_exist=False, **columns): """Add logical port 'port' on 'switch' NOTE: for the purposes of testing the existence of the 'port', 'port' is treated as either a name or a uuid, as in ovn-nbctl. :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :param port: The name of the port :type port: string or uuid.UUID :param parent_name: The name of the parent port (requires tag) :type parent_name: string :param tag: The tag_request field of the port. 0 causes ovn-northd to assign a unique tag :type tag: int [0, 4095] :param may_exist: If True, don't fail if the switch already exists :type may_exist: boolean :param columns: Additional columns to directly set on the switch :returns: :class:`Command` with RowView result """ @abc.abstractmethod def lsp_del(self, port, if_exists=False): """Delete 'port' from its attached switch :param port: The name or uuid of the port :type port: string or uuid.UUID :type if_exists: If True, don't fail if the switch doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lsp_list(self, switch=None): """Get the logical ports on switch or all ports if switch is None :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def lsp_get(self, port): """Get logical switch port for 'port' :returns: :class:`Command` with RowView result """ @abc.abstractmethod def lsp_get_parent(self, port): """Get the parent of 'port' if set :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: :class:`Command` with port parent string result or "" if not set """ @abc.abstractmethod def lsp_set_addresses(self, port, addresses): """Set addresses for 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :param addresses: One or more addresses in the format: 'unknown', 'router', 'dynamic', or 'ethaddr [ipaddr]...' :type addresses: string :returns: :class:`Command` with no result """ @abc.abstractmethod def lsp_get_addresses(self, port): """Return the list of addresses assigned to port :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: A list of string representations of addresses in the format referenced in lsp_set_addresses """ @abc.abstractmethod def lsp_set_port_security(self, port, addresses): """Set port security addresses for 'port' Sets the port security addresses associated with port to addrs. Multiple sets of addresses may be set by using multiple addrs arguments. If no addrs argument is given, port will not have port security enabled. Port security limits the addresses from which a logical port may send packets and to which it may receive packets. :param port: The name or uuid of the port :type port: string or uuid.UUID :param addresses: The addresses in the format 'ethaddr [ipaddr...]' See `man ovn-nb` and port_security column for details :type addresses: string :returns: :class:`Command` with no result """ @abc.abstractmethod def lsp_get_port_security(self, port): """Get port security addresses for 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: :class:`Command` with list of strings described by lsp_set_port_security result """ @abc.abstractmethod def lsp_get_up(self, port): """Get state of port. :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: :class:`Command` with boolean result """ @abc.abstractmethod def lsp_set_enabled(self, port, is_enabled): """Set administrative state of 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :param is_enabled: Whether the port should be enabled :type is_enabled: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lsp_get_enabled(self, port): """Get administrative state of 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: :class:`Command` with boolean result """ @abc.abstractmethod def lsp_set_type(self, port, port_type): """Set the type for 'port :param port: The name or uuid of the port :type port: string or uuid.UUID :param port_type: The type of the port :type port_type: string :returns: :class:`Command` with no result """ @abc.abstractmethod def lsp_get_type(self, port): """Get the type for 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: :class:`Command` with string result """ @abc.abstractmethod def lsp_set_options(self, port, **options): """Set options related to the type of 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :param options: keys and values for the port 'options' dict :type options: key: string, value: string :returns: :class:`Command` with no result """ @abc.abstractmethod def lsp_get_options(self, port): """Get the type-specific options for 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: :class:`Command` with dict result """ @abc.abstractmethod def lsp_set_dhcpv4_options(self, port, dhcp_options_uuid): """Set the dhcp4 options for 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :param dhcp_options_uuid: The uuid of the dhcp_options row :type dhcp_options_uuid: uuid.UUID :returns: :class:`Command` with no result """ @abc.abstractmethod def lr_add(self, router=None, may_exist=False, **columns): """Create a logical router named `router` :param router: The optional name or uuid of the router :type router: string or uuid.UUID :param may_exist: If True, don't fail if the router already exists :type may_exist: boolean :param **columns: Additional columns to directly set on the router :returns: :class:`Command` with RowView result """ @abc.abstractmethod def lr_del(self, router, if_exists=False): """Delete 'router' and all its ports :param router: The name or uuid of the router :type router: string or uuid.UUID :param if_exists: If True, don't fail if the router doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lr_list(self): """Get the UUIDs of all logical routers :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def lrp_add(self, router, port, mac, networks, peer=None, may_exist=False, **columns): """Add logical port 'port' on 'router' :param router: The name or uuid of the router to attach the port :type router: string or uuid.UUID :param mac: The MAC address of the port :type mac: string :param networks: One or more IP address/netmask to assign to the port :type networks: list of strings :param peer: Optional logical router port connected to this one :param may_exist: If True, don't fail if the port already exists :type may_exist: boolean :param **columns: Additional column values to directly set on the port :returns: :class:`Command` with RowView result """ @abc.abstractmethod def lrp_del(self, port, router=None, if_exists=None): """Delete 'port' from its attached router :param port: The name or uuid of the port :type port: string or uuid.UUID :param router: Only delete router if attached to `router` :type router: string or uuiwhd.UUID :param if_exists: If True, don't fail if the port doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lrp_list(self, router): """Get the UUIDs of all ports on 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def lrp_set_enabled(self, port, is_enabled): """Set administrative state of 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :param is_enabled: True for enabled, False for disabled :type is_enabled: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lrp_get_enabled(self, port): """Get administrative state of 'port' :param port: The name or uuid of the port :type port: string or uuid.UUID :returns: """ @abc.abstractmethod def lr_route_add(self, router, prefix, nexthop, port=None, policy='dst-ip', may_exist=False): """Add a route to 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :param prefix: an IPv4/6 prefix for this route, e.g. 192.168.1.0/24 :type prefix: type string :parm nexthop: The gateway to use for this route, which should be the IP address of one of `router`'s logical router ports or the IP address of a logical port :type nexthop: string :param port: If specified, packets that match this route will be sent out this port. Otherwise OVN infers the output port based on nexthop. :type port: string :param policy: the policy used to make routing decisions :type policy: string, 'dst-ip' or 'src-ip' :param may_exist: If True, don't fail if the route already exists :type may_exist: boolean returns: :class:`Command` with RowView result """ @abc.abstractmethod def lr_route_del(self, router, prefix=None, if_exists=False): """Remove routes from 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :param prefix: an IPv4/6 prefix to match, e.g. 192.168.1.0/24 :type prefix: type string :param if_exists: If True, don't fail if the port doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lr_route_list(self, router): """Get the UUIDs of static logical routes from 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def lr_nat_add(self, router, nat_type, external_ip, logical_ip, logical_port=None, external_mac=None, may_exist=False): """Add a NAT to 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :param nat_type: The type of NAT to be done :type nat_type: NAT_SNAT, NAT_DNAT, or NAT_BOTH :param external_ip: Externally visible Ipv4 address :type external_ip: string :param logical_ip: The logical IPv4 network or address with which `external_ip` is NATted :type logical_ip: string :param logical_port: The name of an existing logical switch port where the logical_ip resides :type logical_port: string :param external_mac: ARP replies for the external_ip return the value of `external_mac`. Packets transmitted with source IP address equal to `external_ip` will be sent using `external_mac`. :type external_mac: string :param may_exist: If True, don't fail if the route already exists and if `logical_port` and `external_mac` are specified, they will be updated :type may_exist: boolean :returns: :class:`Command` with RowView result """ @abc.abstractmethod def lr_nat_del(self, router, nat_type=None, match_ip=None, if_exists=None): """Remove NATs from 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :param nat_type: The type of NAT to match :type nat_type: NAT_SNAT, NAT_DNAT, or NAT_BOTH :param match_ip: The IPv4 address to match on. If `nat_type` is specified and is NAT_SNAT, the IP should be the logical ip, otherwise the IP should be the external IP. :type match_ip: string :param if_exists: If True, don't fail if the port doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lr_nat_list(self, router): """Get the NATs on 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def lb_add(self, vip, ips, protocol=const.PROTO_TCP, may_exist=False): """Create a load-balancer or add a VIP to an existing load balancer :param lb: The name or uuid of the load-balancer :type lb: string or uuid.UUID :param vip: A virtual IP in the format IP[:PORT] :type vip: string :param ips: A list of ips in the form IP[:PORT] :type ips: string :param protocol: The IP protocol for load balancing :type protocol: PROTO_TCP or PROTO_UDP :param may_exist: If True, don't fail if a LB w/ `vip` exists, and instead, replace the vips on the LB :type may_exist: boolean :returns: :class:`Command` with RowView result """ @abc.abstractmethod def lb_del(self, lb, vip=None, if_exists=False): """Remove a load balancer or just the VIP from a load balancer :param lb: The name or uuid of a load balancer :type lb: string or uuid.UUID :param vip: The VIP on the load balancer to match :type: string :param if_exists: If True, don't fail if the port doesn't exist :type if_exists: boolean """ @abc.abstractmethod def lb_list(self): """Get the UUIDs of all load balanacers""" @abc.abstractmethod def lr_lb_add(self, router, lb, may_exist=False): """Add a load-balancer to 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :param lb: The name or uuid of the load balancer :type lb: string or uuid.UUID :param may_exist: If True, don't fail if lb already assigned to lr :type may_exist: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lr_lb_del(self, router, lb=None, if_exists=False): """Remove load-balancers from 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :param lb: The name or uuid of the load balancer to remove. None to remove all load balancers from the router :type lb: string or uuid.UUID :type if_exists: If True, don't fail if the switch doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def lr_lb_list(self, router): """Get UUIDs of load-balancers on 'router' :param router: The name or uuid of the router :type router: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def ls_lb_add(self, switch, lb, may_exist=False): """Add a load-balancer to 'switch' :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :param lb: The name or uuid of the load balancer :type lb: string or uuid.UUID :param may_exist: If True, don't fail if lb already assigned to lr :type may_exist: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def ls_lb_del(self, switch, lb=None, if_exists=False): """Remove load-balancers from 'switch' :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :param lb: The name or uuid of the load balancer to remove. None to remove all load balancers from the switch :type lb: string or uuid.UUID :type if_exists: If True, don't fail if the switch doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def ls_lb_list(self, switch): """Get UUIDs of load-balancers on 'switch' :param switch: The name or uuid of the switch :type switch: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def dhcp_options_add(self, cidr, **external_ids): """Create a DHCP options row with CIDR This is equivalent to ovn-nbctl's dhcp-options-create, but renamed to be consistent with other object creation methods :param cidr: An IP network in CIDR format :type cidr: string :param external_ids: external_id field key/value mapping :type external_ids: key: string, value: string :returns: :class:`Command` with RowView result """ @abc.abstractmethod def dhcp_options_del(self, uuid): """Delete DHCP options row with 'uuid' :param uuid: The uuid of the DHCP Options row to delete :type uuid: string or uuid.UUID :returns: :class:`Command` with no result """ @abc.abstractmethod def dhcp_options_list(self): """Get all DHCP_Options :returns: :class:`Command with RowView list result """ @abc.abstractmethod def dhcp_options_get(self, dhcpopt_uuid): """Get dhcp options for 'dhcpopt_uuid' :returns: :class:`Command` with RowView result """ @abc.abstractmethod def dhcp_options_set_options(self, uuid, **options): """Set the DHCP options for 'uuid' :param uuid: The uuid of the DHCP Options row :type uuid: string or uuid.UUID :returns: :class:`Command` with no result """ @abc.abstractmethod def dhcp_options_get_options(self, uuid): """Get the DHCP options for 'uuid' :param uuid: The uuid of the DHCP Options row :type uuid: string or uuid.UUID :returns: :class:`Command` with dict result """ @abc.abstractmethod def dns_add(self, **external_ids): """Create a DNS row with external_ids :param external_ids: external_id field key/value mapping :type external_ids: key: string, value: string :returns: :class:`Command` with RowView result """ @abc.abstractmethod def dns_del(self, uuid): """Delete DNS row with 'uuid' :param uuid: The uuid of the DNS row to delete :type uuid: string or uuid.UUID :returns: :class:`Command` with no result """ @abc.abstractmethod def dns_get(self, uuid): """Get DNS row with 'uuid' :returns: :class:`Command` with RowView result """ @abc.abstractmethod def dns_list(self): """Get all DNS rows :returns: :class:`Command with RowView list result """ @abc.abstractmethod def dns_set_records(self, uuid, **records): """Sets the 'records' field of the DNS row :param uuid: The uuid of the DNS row to set the records with :type uuid: string or uuid.UUID :param records: keys and values for the DNS 'records' dict :type records: key: string, value: string :returns: :class:`Command` with no result """ @abc.abstractmethod def dns_add_record(self, uuid, hostname, ips): """Add the record 'hostname: ips' into the records column of the DNS :param uuid: The uuid of the DNS row to add the record :type uuid: string or uuid.UUID :param hostname: hostname as the key to the record dict :type ips: IPs as the value to the hostname key in the 'records' :returns: :class:`Command` with no result """ @abc.abstractmethod def dns_remove_record(self, uuid, hostname): """Remove the 'hostname' from the 'records' field of the DNS row :param uuid: The uuid of the DNS row to set the records with :type uuid: string or uuid.UUID :param hostname: hostname as the key to the record dict :returns: :class:`Command` with no result """ @abc.abstractmethod def dns_set_external_ids(self, uuid, **external_ids): """Sets the 'external_ids' field of the DNS row :param uuid: The uuid of the DNS row to set the external_ids with :type uuid: string or uuid.UUID :param external_ids: keys and values for the DNS 'external_ids' dict :type external_ids: key: string, value: string :returns: :class:`Command` with no result """ ovsdbapp-0.9.1/ovsdbapp/schema/__init__.py0000666000175100017510000000000013236157620020547 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/open_vswitch/0000775000175100017510000000000013236157776021172 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/open_vswitch/helpers.py0000666000175100017510000000370513236157620023201 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, Inc. # # 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 logging import subprocess LOG = logging.getLogger(__name__) def _connection_to_manager_uri(conn_uri): proto, addr = conn_uri.split(':', 1) if ':' in addr: ip, port = addr.split(':', 1) return 'p%s:%s:%s' % (proto, port, ip) return 'p%s:%s' % (proto, addr) # TODO(jlibosva): Get rid of this runtime configuration and raise a message to # set Manager outside of ovsdbapp. def enable_connection_uri(conn_uri, execute=None, **kwargs): timeout = kwargs.get('timeout', 5) probe = timeout if kwargs.get('set_timeout') else None man_uri = _connection_to_manager_uri(conn_uri) cmd = ['ovs-vsctl', '--timeout=%d' % timeout, '--id=@manager', '--', 'create', 'Manager', 'target="%s"' % man_uri, '--', 'add', 'Open_vSwitch', '.', 'manager_options', '@manager'] if probe: cmd += ['--', 'set', 'Manager', man_uri, 'inactivity_probe=%s' % probe] if execute: return execute(cmd, **kwargs).rstrip() else: obj = subprocess.Popen(['sudo'] + cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = obj.communicate() if err: LOG.debug(err) # will fail if target already exists return out.rstrip() ovsdbapp-0.9.1/ovsdbapp/schema/open_vswitch/impl_idl.py0000666000175100017510000001213113236157633023325 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat Inc. # # 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 logging from ovsdbapp.backend import ovs_idl from ovsdbapp.backend.ovs_idl import transaction from ovsdbapp import exceptions from ovsdbapp.schema.open_vswitch import api from ovsdbapp.schema.open_vswitch import commands as cmd LOG = logging.getLogger(__name__) class VswitchdInterfaceAddException(exceptions.OvsdbAppException): message = "Failed to add interfaces: %(ifaces)s" class OvsVsctlTransaction(transaction.Transaction): def pre_commit(self, txn): self.api._ovs.increment('next_cfg') txn.expected_ifaces = set() def post_commit(self, txn): super(OvsVsctlTransaction, self).post_commit(txn) # ovs-vsctl only logs these failures and does not return nonzero try: self.do_post_commit(txn) except Exception: LOG.exception("Post-commit checks failed") def do_post_commit(self, txn): next_cfg = txn.get_increment_new_value() while not self.timeout_exceeded(): self.api.idl.run() if self.vswitchd_has_completed(next_cfg): failed = self.post_commit_failed_interfaces(txn) if failed: raise VswitchdInterfaceAddException( ifaces=", ".join(failed)) break self.ovsdb_connection.poller.timer_wait( self.time_remaining() * 1000) self.api.idl.wait(self.ovsdb_connection.poller) self.ovsdb_connection.poller.block() else: raise exceptions.TimeoutException(commands=self.commands, timeout=self.timeout) def post_commit_failed_interfaces(self, txn): failed = [] for iface_uuid in txn.expected_ifaces: uuid = txn.get_insert_uuid(iface_uuid) if uuid: ifaces = self.api.idl.tables['Interface'] iface = ifaces.rows.get(uuid) if iface and (not iface.ofport or iface.ofport == -1): failed.append(iface.name) return failed def vswitchd_has_completed(self, next_cfg): return self.api._ovs.cur_cfg >= next_cfg class OvsdbIdl(ovs_idl.Backend, api.API): schema = 'Open_vSwitch' @property def connection(self): return self.ovsdb_connection @property def _ovs(self): return list(self._tables['Open_vSwitch'].rows.values())[0] def create_transaction(self, check_error=False, log_errors=True, **kwargs): return OvsVsctlTransaction(self, self.ovsdb_connection, check_error=check_error, log_errors=log_errors) def add_manager(self, connection_uri): return cmd.AddManagerCommand(self, connection_uri) def get_manager(self): return cmd.GetManagerCommand(self) def remove_manager(self, connection_uri): return cmd.RemoveManagerCommand(self, connection_uri) def add_br(self, name, may_exist=True, datapath_type=None): return cmd.AddBridgeCommand(self, name, may_exist, datapath_type) def del_br(self, name, if_exists=True): return cmd.DelBridgeCommand(self, name, if_exists) def br_exists(self, name): return cmd.BridgeExistsCommand(self, name) def port_to_br(self, name): return cmd.PortToBridgeCommand(self, name) def iface_to_br(self, name): return cmd.InterfaceToBridgeCommand(self, name) def list_br(self): return cmd.ListBridgesCommand(self) def br_get_external_id(self, name, field): return cmd.BrGetExternalIdCommand(self, name, field) def br_set_external_id(self, name, field, value): return cmd.BrSetExternalIdCommand(self, name, field, value) def set_controller(self, bridge, controllers): return cmd.SetControllerCommand(self, bridge, controllers) def del_controller(self, bridge): return cmd.DelControllerCommand(self, bridge) def get_controller(self, bridge): return cmd.GetControllerCommand(self, bridge) def set_fail_mode(self, bridge, mode): return cmd.SetFailModeCommand(self, bridge, mode) def add_port(self, bridge, port, may_exist=True): return cmd.AddPortCommand(self, bridge, port, may_exist) def del_port(self, port, bridge=None, if_exists=True): return cmd.DelPortCommand(self, port, bridge, if_exists) def list_ports(self, bridge): return cmd.ListPortsCommand(self, bridge) def list_ifaces(self, bridge): return cmd.ListIfacesCommand(self, bridge) ovsdbapp-0.9.1/ovsdbapp/schema/open_vswitch/commands.py0000666000175100017510000003110013236157633023332 0ustar zuulzuul00000000000000# Copyright (c) 2015 OpenStack Foundation # # 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 logging from ovsdbapp.backend.ovs_idl import command from ovsdbapp.backend.ovs_idl import idlutils LOG = logging.getLogger(__name__) BaseCommand = command.BaseCommand class AddManagerCommand(command.AddCommand): table_name = 'Manager' def __init__(self, api, target): super(AddManagerCommand, self).__init__(api) self.target = target def run_idl(self, txn): row = txn.insert(self.api._tables['Manager']) row.target = self.target try: self.api._ovs.addvalue('manager_options', row) except AttributeError: # OVS < 2.6 self.api._ovs.verify('manager_options') self.api._ovs.manager_options = ( self.api._ovs.manager_options + [row]) self.result = row.uuid class GetManagerCommand(BaseCommand): def __init__(self, api): super(GetManagerCommand, self).__init__(api) def run_idl(self, txn): self.result = [m.target for m in self.api._tables['Manager'].rows.values()] class RemoveManagerCommand(BaseCommand): def __init__(self, api, target): super(RemoveManagerCommand, self).__init__(api) self.target = target def run_idl(self, txn): try: manager = idlutils.row_by_value(self.api.idl, 'Manager', 'target', self.target) except idlutils.RowNotFound: msg = "Manager with target %s does not exist" % self.target LOG.error(msg) raise RuntimeError(msg) try: self.api._ovs.delvalue('manager_options', manager) except AttributeError: # OVS < 2.6 self.api._ovs.verify('manager_options') manager_list = self.api._ovs.manager_options manager_list.remove(manager) self.api._ovs.manager_options = manager_list manager.delete() class AddBridgeCommand(command.AddCommand): table_name = 'Bridge' def __init__(self, api, name, may_exist, datapath_type): super(AddBridgeCommand, self).__init__(api) self.name = name self.may_exist = may_exist self.datapath_type = datapath_type def run_idl(self, txn): if self.may_exist: br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.name, None) if br: if self.datapath_type: br.datapath_type = self.datapath_type self.result = br.uuid return row = txn.insert(self.api._tables['Bridge']) row.name = self.name if self.datapath_type: row.datapath_type = self.datapath_type try: self.api._ovs.addvalue('bridges', row) except AttributeError: # OVS < 2.6 self.api._ovs.verify('bridges') self.api._ovs.bridges = self.api._ovs.bridges + [row] # Add the internal bridge port cmd = AddPortCommand(self.api, self.name, self.name, self.may_exist) cmd.run_idl(txn) cmd = command.DbSetCommand(self.api, 'Interface', self.name, ('type', 'internal')) cmd.run_idl(txn) self.result = row.uuid class DelBridgeCommand(BaseCommand): def __init__(self, api, name, if_exists): super(DelBridgeCommand, self).__init__(api) self.name = name self.if_exists = if_exists def run_idl(self, txn): try: br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.name) except idlutils.RowNotFound: if self.if_exists: return else: msg = "Bridge %s does not exist" % self.name LOG.error(msg) raise RuntimeError(msg) # Clean up cached ports/interfaces for port in br.ports: for interface in port.interfaces: interface.delete() port.delete() try: self.api._ovs.delvalue('bridges', br) except AttributeError: # OVS < 2.6 self.api._ovs.verify('bridges') bridges = self.api._ovs.bridges bridges.remove(br) self.api._ovs.bridges = bridges br.delete() class BridgeExistsCommand(BaseCommand): def __init__(self, api, name): super(BridgeExistsCommand, self).__init__(api) self.name = name def run_idl(self, txn): self.result = bool(idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.name, None)) class ListBridgesCommand(BaseCommand): def __init__(self, api): super(ListBridgesCommand, self).__init__(api) def run_idl(self, txn): # NOTE (twilson) [x.name for x in rows.values()] if no index self.result = [x.name for x in self.api._tables['Bridge'].rows.values()] class BrGetExternalIdCommand(BaseCommand): def __init__(self, api, name, field): super(BrGetExternalIdCommand, self).__init__(api) self.name = name self.field = field def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.name) self.result = br.external_ids[self.field] class BrSetExternalIdCommand(BaseCommand): def __init__(self, api, name, field, value): super(BrSetExternalIdCommand, self).__init__(api) self.name = name self.field = field self.value = value def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.name) external_ids = getattr(br, 'external_ids', {}) external_ids[self.field] = self.value br.external_ids = external_ids class SetControllerCommand(BaseCommand): def __init__(self, api, bridge, targets): super(SetControllerCommand, self).__init__(api) self.bridge = bridge self.targets = targets def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) controllers = [] for target in self.targets: controller = txn.insert(self.api._tables['Controller']) controller.target = target controllers.append(controller) # Don't need to verify because we unconditionally overwrite br.controller = controllers class DelControllerCommand(BaseCommand): def __init__(self, api, bridge): super(DelControllerCommand, self).__init__(api) self.bridge = bridge def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) br.controller = [] class GetControllerCommand(BaseCommand): def __init__(self, api, bridge): super(GetControllerCommand, self).__init__(api) self.bridge = bridge def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) self.result = [c.target for c in br.controller] class SetFailModeCommand(BaseCommand): def __init__(self, api, bridge, mode): super(SetFailModeCommand, self).__init__(api) self.bridge = bridge self.mode = mode def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) br.fail_mode = self.mode class AddPortCommand(command.AddCommand): table_name = 'Port' def __init__(self, api, bridge, port, may_exist): super(AddPortCommand, self).__init__(api) self.bridge = bridge self.port = port self.may_exist = may_exist def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) if self.may_exist: port = idlutils.row_by_value(self.api.idl, 'Port', 'name', self.port, None) if port: self.result = port.uuid return port = txn.insert(self.api._tables['Port']) port.name = self.port try: br.addvalue('ports', port) except AttributeError: # OVS < 2.6 br.verify('ports') ports = getattr(br, 'ports', []) ports.append(port) br.ports = ports iface = txn.insert(self.api._tables['Interface']) txn.expected_ifaces.add(iface.uuid) iface.name = self.port # This is a new port, so it won't have any existing interfaces port.interfaces = [iface] self.result = port.uuid class DelPortCommand(BaseCommand): def __init__(self, api, port, bridge, if_exists): super(DelPortCommand, self).__init__(api) self.port = port self.bridge = bridge self.if_exists = if_exists def run_idl(self, txn): try: port = idlutils.row_by_value(self.api.idl, 'Port', 'name', self.port) except idlutils.RowNotFound: if self.if_exists: return msg = "Port %s does not exist" % self.port raise RuntimeError(msg) if self.bridge: br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) else: br = next(b for b in self.api._tables['Bridge'].rows.values() if port in b.ports) if port not in br.ports and not self.if_exists: # TODO(twilson) Make real errors across both implementations msg = "Port %(port)s does not exist on %(bridge)s!" % { 'port': self.port, 'bridge': self.bridge } LOG.error(msg) raise RuntimeError(msg) try: br.delvalue('ports', port) except AttributeError: # OVS < 2.6 br.verify('ports') ports = br.ports ports.remove(port) br.ports = ports # The interface on the port will be cleaned up by ovsdb-server for interface in port.interfaces: interface.delete() port.delete() class ListPortsCommand(BaseCommand): def __init__(self, api, bridge): super(ListPortsCommand, self).__init__(api) self.bridge = bridge def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) self.result = [p.name for p in br.ports if p.name != self.bridge] class ListIfacesCommand(BaseCommand): def __init__(self, api, bridge): super(ListIfacesCommand, self).__init__(api) self.bridge = bridge def run_idl(self, txn): br = idlutils.row_by_value(self.api.idl, 'Bridge', 'name', self.bridge) self.result = [i.name for p in br.ports if p.name != self.bridge for i in p.interfaces] class PortToBridgeCommand(BaseCommand): def __init__(self, api, name): super(PortToBridgeCommand, self).__init__(api) self.name = name def run_idl(self, txn): # TODO(twilson) This is expensive! # This traversal of all ports could be eliminated by caching the bridge # name on the Port's external_id field # In fact, if we did that, the only place that uses to_br functions # could just add the external_id field to the conditions passed to find port = idlutils.row_by_value(self.api.idl, 'Port', 'name', self.name) bridges = self.api._tables['Bridge'].rows.values() self.result = next(br.name for br in bridges if port in br.ports) class InterfaceToBridgeCommand(BaseCommand): def __init__(self, api, name): super(InterfaceToBridgeCommand, self).__init__(api) self.name = name def run_idl(self, txn): interface = idlutils.row_by_value(self.api.idl, 'Interface', 'name', self.name) ports = self.api._tables['Port'].rows.values() pname = next( port for port in ports if interface in port.interfaces) bridges = self.api._tables['Bridge'].rows.values() self.result = next(br.name for br in bridges if pname in br.ports) ovsdbapp-0.9.1/ovsdbapp/schema/open_vswitch/__init__.py0000666000175100017510000000000013236157620023257 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/schema/open_vswitch/api.py0000666000175100017510000001564413236157633022321 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat Inc. # # 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 abc import six from ovsdbapp import api @six.add_metaclass(abc.ABCMeta) class API(api.API): @abc.abstractmethod def add_manager(self, connection_uri): """Create a command to add a Manager to the OVS switch This API will add a new manager without overriding the existing ones. :param connection_uri: target to which manager needs to be set :type connection_uri: string, see ovs-vsctl manpage for format :returns: :class:`Command` with RowView result """ @abc.abstractmethod def get_manager(self): """Create a command to get Manager list from the OVS switch :returns: :class:`Command` with list of Manager names result """ @abc.abstractmethod def remove_manager(self, connection_uri): """Create a command to remove a Manager from the OVS switch This API will remove the manager configured on the OVS switch. :param connection_uri: target identifying the manager uri that needs to be removed. :type connection_uri: string, see ovs-vsctl manpage for format :returns: :class:`Command` with no result """ @abc.abstractmethod def add_br(self, name, may_exist=True, datapath_type=None): """Create a command to add an OVS bridge :param name: The name of the bridge :type name: string :param may_exist: Do not fail if bridge already exists :type may_exist: bool :param datapath_type: The datapath_type of the bridge :type datapath_type: string :returns: :class:`Command` with RowView result """ @abc.abstractmethod def del_br(self, name, if_exists=True): """Create a command to delete an OVS bridge :param name: The name of the bridge :type name: string :param if_exists: Do not fail if the bridge does not exist :type if_exists: bool :returns: :class:`Command` with no result """ @abc.abstractmethod def br_exists(self, name): """Create a command to check if an OVS bridge exists :param name: The name of the bridge :type name: string :returns: :class:`Command` with bool result """ @abc.abstractmethod def port_to_br(self, name): """Create a command to return the name of the bridge with the port :param name: The name of the OVS port :type name: string :returns: :class:`Command` with bridge name result """ @abc.abstractmethod def iface_to_br(self, name): """Create a command to return the name of the bridge with the interface :param name: The name of the OVS interface :type name: string :returns: :class:`Command` with bridge name result """ @abc.abstractmethod def list_br(self): """Create a command to return the current list of OVS bridge names :returns: :class:`Command` with list of bridge names result """ @abc.abstractmethod def br_get_external_id(self, name, field): """Create a command to return a field from the Bridge's external_ids :param name: The name of the OVS Bridge :type name: string :param field: The external_ids field to return :type field: string :returns: :class:`Command` with field value result """ @abc.abstractmethod def set_controller(self, bridge, controllers): """Create a command to set an OVS bridge's OpenFlow controllers :param bridge: The name of the bridge :type bridge: string :param controllers: The controller strings :type controllers: list of strings, see ovs-vsctl manpage for format :returns: :class:`Command` with no result """ @abc.abstractmethod def del_controller(self, bridge): """Create a command to clear an OVS bridge's OpenFlow controllers :param bridge: The name of the bridge :type bridge: string :returns: :class:`Command` with no result """ @abc.abstractmethod def get_controller(self, bridge): """Create a command to return an OVS bridge's OpenFlow controllers :param bridge: The name of the bridge :type bridge: string :returns: :class:`Command` with list of controller strings result """ @abc.abstractmethod def set_fail_mode(self, bridge, mode): """Create a command to set an OVS bridge's failure mode :param bridge: The name of the bridge :type bridge: string :param mode: The failure mode :type mode: "secure" or "standalone" :returns: :class:`Command` with no result """ @abc.abstractmethod def add_port(self, bridge, port, may_exist=True): """Create a command to add a port to an OVS bridge :param bridge: The name of the bridge :type bridge: string :param port: The name of the port :type port: string :param may_exist: Do not fail if the port already exists :type may_exist: bool :returns: :class:`Command` with RowView result """ @abc.abstractmethod def del_port(self, port, bridge=None, if_exists=True): """Create a command to delete a port an OVS port :param port: The name of the port :type port: string :param bridge: Only delete port if it is attached to this bridge :type bridge: string :param if_exists: Do not fail if the port does not exist :type if_exists: bool :returns: :class:`Command` with no result """ @abc.abstractmethod def list_ports(self, bridge): """Create a command to list the names of ports on a bridge :param bridge: The name of the bridge :type bridge: string :returns: :class:`Command` with list of port names result """ @abc.abstractmethod def list_ifaces(self, bridge): """Create a command to list the names of interfaces on a bridge :param bridge: The name of the bridge :type bridge: string :returns: :class:`Command` with list of interfaces names result """ ovsdbapp-0.9.1/ovsdbapp/api.py0000666000175100017510000002377613236157633016356 0ustar zuulzuul00000000000000# Copyright (c) 2014 OpenStack Foundation # # 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 abc import contextlib import six @six.add_metaclass(abc.ABCMeta) class Command(object): """An OVSDB command that can be executed in a transaction :attr result: The result of executing the command in a transaction """ @abc.abstractmethod def execute(self, **transaction_options): """Immediately execute an OVSDB command This implicitly creates a transaction with the passed options and then executes it, returning the value of the executed transaction :param transaction_options: Options to pass to the transaction """ @six.add_metaclass(abc.ABCMeta) class Transaction(object): @abc.abstractmethod def commit(self): """Commit the transaction to OVSDB""" @abc.abstractmethod def add(self, command): """Append an OVSDB operation to the transaction""" def __enter__(self): return self def __exit__(self, exc_type, exc_val, tb): if exc_type is None: self.result = self.commit() @six.add_metaclass(abc.ABCMeta) class API(object): def __init__(self): self._nested_txn = None @abc.abstractmethod def create_transaction(self, check_error=False, log_errors=True, **kwargs): """Create a transaction :param check_error: Allow the transaction to raise an exception? :type check_error: bool :param log_errors: Log an error if the transaction fails? :type log_errors: bool :returns: A new transaction :rtype: :class:`Transaction` """ @contextlib.contextmanager def transaction(self, check_error=False, log_errors=True, **kwargs): """Create a transaction context. :param check_error: Allow the transaction to raise an exception? :type check_error: bool :param log_errors: Log an error if the transaction fails? :type log_errors: bool :returns: Either a new transaction or an existing one. :rtype: :class:`Transaction` """ if self._nested_txn: yield self._nested_txn else: with self.create_transaction( check_error, log_errors, **kwargs) as txn: self._nested_txn = txn try: yield txn finally: self._nested_txn = None @abc.abstractmethod def db_create(self, table, **col_values): """Create a command to create new record :param table: The OVS table containing the record to be created :type table: string :param col_values: The columns and their associated values to be set after create :type col_values: Dictionary of columns id's and values :returns: :class:`Command` with no result """ @abc.abstractmethod def db_destroy(self, table, record): """Create a command to destroy a record :param table: The OVS table containing the record to be destroyed :type table: string :param record: The record id (name/uuid) to be destroyed :type record: uuid/string :returns: :class:`Command` with no result """ @abc.abstractmethod def db_set(self, table, record, *col_values): """Create a command to set fields in a record :param table: The OVS table containing the record to be modified :type table: string :param record: The record id (name/uuid) to be modified :type table: string :param col_values: The columns and their associated values :type col_values: Tuples of (column, value). Values may be atomic values or unnested sequences/mappings :returns: :class:`Command` with no result """ # TODO(twilson) Consider handling kwargs for arguments where order # doesn't matter. Though that would break the assert_called_once_with # unit tests @abc.abstractmethod def db_add(self, table, record, column, *values): """Create a command to add a value to a record Adds each value or key-value pair to column in record in table. If column is a map, then each value will be a dict, otherwise a base type. If key already exists in a map column, then the current value is not replaced (use the set command to replace an existing value). :param table: The OVS table containing the record to be modified :type table: string :param record: The record id (name/uuid) to modified :type record: string :param column: The column name to be modified :type column: string :param values: The values to be added to the column :type values: The base type of the column. If column is a map, then a dict containing the key name and the map's value type :returns: :class:`Command` with no result """ @abc.abstractmethod def db_clear(self, table, record, column): """Create a command to clear a field's value in a record :param table: The OVS table containing the record to be modified :type table: string :param record: The record id (name/uuid) to be modified :type record: string :param column: The column whose value should be cleared :type column: string :returns: :class:`Command` with no result """ @abc.abstractmethod def db_get(self, table, record, column): """Create a command to return a field's value in a record :param table: The OVS table containing the record to be queried :type table: string :param record: The record id (name/uuid) to be queried :type record: string :param column: The column whose value should be returned :type column: string :returns: :class:`Command` with the field's value result """ @abc.abstractmethod def db_list(self, table, records=None, columns=None, if_exists=False): """Create a command to return a list of OVSDB records :param table: The OVS table to query :type table: string :param records: The records to return values from :type records: list of record ids (names/uuids) :param columns: Limit results to only columns, None means all columns :type columns: list of column names or None :param if_exists: Do not fail if the record does not exist :type if_exists: bool :returns: :class:`Command` with [{'column', value}, ...] result """ @abc.abstractmethod def db_list_rows(self, table, record=None, if_exists=False): """Create a command to return a list of OVSDB records Identical to db_list, but returns a RowView list result :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def db_find(self, table, *conditions, **kwargs): """Create a command to return find OVSDB records matching conditions :param table: The OVS table to query :type table: string :param conditions:The conditions to satisfy the query :type conditions: 3-tuples containing (column, operation, match) Type of 'match' parameter MUST be identical to column type Examples: atomic: ('tag', '=', 7) map: ('external_ids' '=', {'iface-id': 'xxx'}) field exists? ('external_ids', '!=', {'iface-id', ''}) set contains?: ('protocols', '{>=}', 'OpenFlow13') See the ovs-vsctl man page for more operations :param columns: Limit results to only columns, None means all columns :type columns: list of column names or None :returns: :class:`Command` with [{'column', value}, ...] result """ @abc.abstractmethod def db_find_rows(self, table, *conditions, **kwargs): """Create a command to return OVSDB records matching conditions Identical to db_find, but returns a list of RowView objects :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def db_remove(self, table, record, column, *values, **keyvalues): """Create a command to delete fields or key-value pairs in a record :param table: The OVS table to query :type table: string :param record: The record id (name/uuid) :type record: string :param column: The column whose value should be deleted :type column: string :param values: In case of list columns, the values to be deleted from the list of values In case of dict columns, the keys to delete regardless of their value :type value: varies depending on column :param keyvalues: For dict columns, the keys to delete when the key's value matches the argument value :type keyvalues: values vary depending on column :param if_exists: Do not fail if the record does not exist :type if_exists: bool :returns: :class:`Command` with no result """ ovsdbapp-0.9.1/ovsdbapp/event.py0000666000175100017510000000766613236157620016722 0ustar zuulzuul00000000000000# 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 abc import atexit import logging import threading import six from six.moves import queue as Queue LOG = logging.getLogger(__name__) STOP_EVENT = ("STOP", None, None, None) @six.add_metaclass(abc.ABCMeta) class RowEvent(object): ROW_CREATE = "create" ROW_UPDATE = "update" ROW_DELETE = "delete" ONETIME = False event_name = 'RowEvent' def __init__(self, events, table, conditions, old_conditions=None): self.table = table self.events = events self.conditions = conditions self.old_conditions = old_conditions @property def key(self): return (self.__class__, self.table, self.events, self.conditions) def __hash__(self): return hash(self.key) def __eq__(self, other): try: return self.key == other.key except AttributeError: return False def __ne__(self, other): return not self == other @abc.abstractmethod def matches(self, event, row, old=None): """Test that `event` on `row` matches watched events :param event: event type :type event: ROW_CREATE, ROW_UPDATE, or ROW_DELETE :param row: :param old: :returns: boolean, True if match else False """ @abc.abstractmethod def run(self, event, row, old): """Method to run when the event matches""" class RowEventHandler(object): def __init__(self): self.__watched_events = set() self.__lock = threading.Lock() self.notifications = Queue.Queue() self.notify_thread = threading.Thread(target=self.notify_loop) self.notify_thread.daemon = True atexit.register(self.shutdown) self.start() def start(self): self.notify_thread.start() def matching_events(self, event, row, updates): with self.__lock: return tuple(t for t in self.__watched_events if t.matches(event, row, updates)) def watch_event(self, event): with self.__lock: self.__watched_events.add(event) def watch_events(self, events): with self.__lock: for event in events: self.__watched_events.add(event) def unwatch_event(self, event): with self.__lock: self.__watched_events.discard(event) def unwatch_events(self, events): with self.__lock: for event in events: self.__watched_events.discard(event) def shutdown(self): self.notifications.put(STOP_EVENT) def notify_loop(self): while True: try: match, event, row, updates = self.notifications.get() if (match, event, row, updates) == STOP_EVENT: self.notifications.task_done() break match.run(event, row, updates) if match.ONETIME: self.unwatch_event(match) self.notifications.task_done() except Exception: # If any unexpected exception happens we don't want the # notify_loop to exit. LOG.exception('Unexpected exception in notify_loop') def notify(self, event, row, updates=None): matching = self.matching_events( event, row, updates) for match in matching: self.notifications.put((match, event, row, updates)) ovsdbapp-0.9.1/ovsdbapp/constants.py0000666000175100017510000000163213236157620017600 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat, Inc. # # 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. DEFAULT_OVSDB_CONNECTION = 'tcp:127.0.0.1:6640' DEFAULT_OVNNB_CONNECTION = 'tcp:127.0.0.1:6641' DEFAULT_TIMEOUT = 5 DEVICE_NAME_MAX_LEN = 14 ACL_PRIORITY_MAX = 32767 NAT_SNAT = 'snat' NAT_DNAT = 'dnat' NAT_BOTH = 'dnat_and_snat' NAT_TYPES = (NAT_SNAT, NAT_DNAT, NAT_BOTH) PROTO_TCP = 'tcp' PROTO_UDP = 'udp' ovsdbapp-0.9.1/ovsdbapp/tests/0000775000175100017510000000000013236157776016364 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/utils.py0000666000175100017510000000444513236157620020073 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # 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 random from ovsdbapp import constants # NOTE(twilson) This code was copied from the neutron/neutron-lib trees def get_random_string(length): """Get a random hex string of the specified length. :param length: The length for the hex string. :returns: A random hex string of the said length. """ return "{0:0{1}x}".format(random.getrandbits(length * 4), length) def get_rand_name(max_length=None, prefix='test'): """Return a random string. The string will start with 'prefix' and will be exactly 'max_length'. If 'max_length' is None, then exactly 8 random characters, each hexadecimal, will be added. In case len(prefix) <= len(max_length), ValueError will be raised to indicate the problem. """ return get_related_rand_names([prefix], max_length)[0] def get_rand_device_name(prefix='test'): return get_rand_name( max_length=constants.DEVICE_NAME_MAX_LEN, prefix=prefix) def get_related_rand_names(prefixes, max_length=None): """Returns a list of the prefixes with the same random characters appended :param prefixes: A list of prefix strings :param max_length: The maximum length of each returned string :returns: A list with each prefix appended with the same random characters """ if max_length: length = max_length - max(len(p) for p in prefixes) if length <= 0: raise ValueError("'max_length' must be longer than all prefixes") else: length = 8 rndchrs = get_random_string(length) return [p + rndchrs for p in prefixes] def get_related_rand_device_names(prefixes): return get_related_rand_names(prefixes, max_length=constants.DEVICE_NAME_MAX_LEN) ovsdbapp-0.9.1/ovsdbapp/tests/functional/0000775000175100017510000000000013236157776020526 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/__init__.py0000666000175100017510000000000013236157620022613 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/0000775000175100017510000000000013236157776021766 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_southbound/0000775000175100017510000000000013236157776025042 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_southbound/test_impl_idl.py0000666000175100017510000001506713236157620030243 0ustar zuulzuul00000000000000# 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 threading from ovsdbapp.backend.ovs_idl import event from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp import event as ovsdb_event from ovsdbapp.schema.ovn_northbound import impl_idl as nbidl from ovsdbapp.schema.ovn_southbound import impl_idl from ovsdbapp.tests.functional import base from ovsdbapp.tests.functional.schema.ovn_southbound import fixtures from ovsdbapp.tests import utils class WaitForPortBindingEvent(event.RowEvent): event_name = 'WaitForPortBindingEvent' ONETIME = True def __init__(self, port, timeout=5): self.event = threading.Event() self.timeout = timeout super(WaitForPortBindingEvent, self).__init__( (self.ROW_CREATE,), 'Port_Binding', (('logical_port', '=', port),)) def run(self, event, row, old): self.event.set() def wait(self): self.event.wait(self.timeout) class OvnSouthboundTest(base.FunctionalTestCase): schemas = ['OVN_Southbound', 'OVN_Northbound'] def setUp(self): super(OvnSouthboundTest, self).setUp() self.api = impl_idl.OvnSbApiIdlImpl(self.connection['OVN_Southbound']) self.nbapi = nbidl.OvnNbApiIdlImpl(self.connection['OVN_Northbound']) self.handler = ovsdb_event.RowEventHandler() self.api.idl.notify = self.handler.notify def _chassis_add(self, encap_types, encap_ip, *args, **kwargs): chassis = kwargs.pop('chassis', utils.get_rand_device_name()) c = self.useFixture(fixtures.ChassisFixture( chassis=chassis, encap_types=encap_types, encap_ip=encap_ip, *args, **kwargs)).obj self.assertIn(c, self.api.chassis_list().execute(check_error=True)) self.assertEqual(c.name, chassis) self.assertEqual(set(encap_types), {e.type for e in c.encaps}) self.assertTrue(all(encap_ip == e.ip for e in c.encaps)) return c def test_chassis_add(self): self._chassis_add(['vxlan', 'geneve'], '192.0.2.1') def test_chassis_add_exists(self): chassis = utils.get_rand_device_name() self._chassis_add(['vxlan'], '192.0.2.1', chassis=chassis) cmd = self.api.chassis_add(chassis, ['vxlan'], '192.0.2.1') self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_chassis_add_may_exist(self): chassis = utils.get_rand_device_name() self._chassis_add(['vxlan'], '192.0.2.1', chassis=chassis) self._chassis_add(['vxlan'], '192.0.2.1', chassis=chassis, may_exist=True) def test_chassis_add_columns(self): chassis = utils.get_rand_device_name() hostname = "testhostname" extids = {'my': 'external_id', 'is': 'set'} ch = self._chassis_add(['vxlan'], '192.0.2.1', chassis=chassis, hostname=hostname, external_ids=extids) self.assertEqual(hostname, ch.hostname) self.assertEqual(extids, ch.external_ids) def test_chassis_del(self): name = utils.get_rand_device_name() chassis = self._chassis_add(['vxlan'], '192.0.2.1', chassis=name) self.api.chassis_del(chassis.name).execute(check_error=True) self.assertNotIn(chassis, self.api.chassis_list().execute()) def test_chass_del_no_exist(self): name = utils.get_rand_device_name() cmd = self.api.chassis_del(name) self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) def test_chassis_del_if_exists(self): name = utils.get_rand_device_name() self.api.chassis_del(name, if_exists=True).execute(check_error=True) def _add_chassis_switch_port(self): cname, sname, pname = (utils.get_rand_device_name(prefix=p) for p in ("chassis", "switch", "port")) chassis = self._chassis_add(['vxlan'], '192.0.2.1', chassis=cname) row_event = WaitForPortBindingEvent(pname) # We have to wait for ovn-northd to actually create the port binding self.handler.watch_event(row_event) with self.nbapi.transaction(check_error=True) as txn: switch = txn.add(self.nbapi.ls_add(sname)) port = txn.add(self.nbapi.lsp_add(sname, pname)) row_event.wait() return chassis, switch.result, port.result def test_lsp_bind(self): chassis, switch, port = self._add_chassis_switch_port() self.api.lsp_bind(port.name, chassis.name).execute(check_error=True) binding = idlutils.row_by_value(self.api.idl, 'Port_Binding', 'logical_port', port.name) self.assertIn(chassis, binding.chassis) return chassis, switch, port def test_lsp_bind_exists(self): chassis, switch, port = self.test_lsp_bind() cmd = self.api.lsp_bind(port.name, chassis.name) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lsp_bind_may_exist(self): chassis, switch, port = self.test_lsp_bind() other = self._chassis_add(['vxlan'], '192.0.2.2', chassis=utils.get_rand_device_name()) self.api.lsp_bind(port.name, other.name, may_exist=True).execute( check_error=True) binding = idlutils.row_by_value(self.api.idl, 'Port_Binding', 'logical_port', port.name) self.assertNotIn(other, binding.chassis) self.assertIn(chassis, binding.chassis) def test_lsp_unbind(self): chassis, switch, port = self.test_lsp_bind() self.api.lsp_unbind(port.name).execute(check_error=True) binding = idlutils.row_by_value(self.api.idl, 'Port_Binding', 'logical_port', port.name) self.assertEqual([], binding.chassis) def test_lsp_unbind_no_exist(self): cmd = self.api.lsp_unbind(utils.get_rand_device_name()) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lsp_unbind_if_exists(self): pname = utils.get_rand_device_name() self.api.lsp_unbind(pname, if_exists=True).execute(check_error=True) ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_southbound/fixtures.py0000666000175100017510000000146113236157620027255 0ustar zuulzuul00000000000000# 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 ovsdbapp.schema.ovn_southbound import impl_idl from ovsdbapp.tests.functional.schema import fixtures class ChassisFixture(fixtures.ImplIdlFixture): api = impl_idl.OvnSbApiIdlImpl create = 'chassis_add' delete = 'chassis_del' ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_southbound/__init__.py0000666000175100017510000000000013236157620027127 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/fixtures.py0000666000175100017510000000246113236157620024202 0ustar zuulzuul00000000000000# 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 __future__ import absolute_import import fixtures class ImplIdlFixture(fixtures.Fixture): api, create, delete = (None, None, None) delete_args = {'if_exists': True} delete_id = 'uuid' def __init__(self, *args, **kwargs): super(ImplIdlFixture, self).__init__() self.args = args self.kwargs = kwargs def _setUp(self): api = self.api(None) create_fn = getattr(api, self.create) delete_fn = getattr(api, self.delete) self.obj = create_fn(*self.args, **self.kwargs).execute( check_error=True) del_value = getattr(self.obj, self.delete_id) self.addCleanup(delete_fn(del_value, **self.delete_args).execute, check_error=True) ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_northbound/0000775000175100017510000000000013236157776025032 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py0000666000175100017510000013746313236157633030244 0ustar zuulzuul00000000000000# 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 netaddr import testscenarios from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp import constants as const from ovsdbapp.schema.ovn_northbound import impl_idl from ovsdbapp.tests.functional import base from ovsdbapp.tests.functional.schema.ovn_northbound import fixtures from ovsdbapp.tests import utils from ovsdbapp import utils as ovsdb_utils class OvnNorthboundTest(base.FunctionalTestCase): schemas = ['OVN_Northbound'] def setUp(self): super(OvnNorthboundTest, self).setUp() self.api = impl_idl.OvnNbApiIdlImpl(self.connection) class TestLogicalSwitchOps(OvnNorthboundTest): def setUp(self): super(TestLogicalSwitchOps, self).setUp() self.table = self.api.tables['Logical_Switch'] def _ls_add(self, *args, **kwargs): fix = self.useFixture(fixtures.LogicalSwitchFixture(*args, **kwargs)) self.assertIn(fix.obj.uuid, self.table.rows) return fix.obj def _test_ls_get(self, col): ls = self._ls_add(switch=utils.get_rand_device_name()) val = getattr(ls, col) found = self.api.ls_get(val).execute(check_error=True) self.assertEqual(ls, found) def test_ls_get_uuid(self): self._test_ls_get('uuid') def test_ls_get_name(self): self._test_ls_get('name') def test_ls_add_no_name(self): self._ls_add() def test_ls_add_name(self): name = utils.get_rand_device_name() sw = self._ls_add(name) self.assertEqual(name, sw.name) def test_ls_add_exists(self): name = utils.get_rand_device_name() self._ls_add(name) cmd = self.api.ls_add(name) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_ls_add_may_exist(self): name = utils.get_rand_device_name() sw = self._ls_add(name) sw2 = self.api.ls_add(name, may_exist=True).execute(check_error=True) self.assertEqual(sw, sw2) def test_ls_add_columns(self): external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'} ls = self._ls_add(external_ids=external_ids) self.assertEqual(external_ids, ls.external_ids) def test_ls_del(self): sw = self._ls_add() self.api.ls_del(sw.uuid).execute(check_error=True) self.assertNotIn(sw.uuid, self.table.rows) def test_ls_del_by_name(self): name = utils.get_rand_device_name() self._ls_add(name) self.api.ls_del(name).execute(check_error=True) def test_ls_del_no_exist(self): name = utils.get_rand_device_name() cmd = self.api.ls_del(name) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_ls_del_if_exists(self): name = utils.get_rand_device_name() self.api.ls_del(name, if_exists=True).execute(check_error=True) def test_ls_list(self): with self.api.transaction(check_error=True): switches = {self._ls_add() for _ in range(3)} switch_set = set(self.api.ls_list().execute(check_error=True)) self.assertTrue(switches.issubset(switch_set)) class TestAclOps(OvnNorthboundTest): def setUp(self): super(TestAclOps, self).setUp() self.switch = self.useFixture(fixtures.LogicalSwitchFixture()).obj def _acl_add(self, *args, **kwargs): cmd = self.api.acl_add(self.switch.uuid, *args, **kwargs) aclrow = cmd.execute(check_error=True) self.assertIn(aclrow._row, self.switch.acls) self.assertEqual(cmd.direction, aclrow.direction) self.assertEqual(cmd.priority, aclrow.priority) self.assertEqual(cmd.match, aclrow.match) self.assertEqual(cmd.action, aclrow.action) return aclrow def test_acl_add(self): self._acl_add('from-lport', 0, 'output == "fake_port" && ip', 'drop') def test_acl_add_exists(self): args = ('from-lport', 0, 'output == "fake_port" && ip', 'drop') self._acl_add(*args) self.assertRaises(RuntimeError, self._acl_add, *args) def test_acl_add_may_exist(self): args = ('from-lport', 0, 'output == "fake_port" && ip', 'drop') row = self._acl_add(*args) row2 = self._acl_add(*args, may_exist=True) self.assertEqual(row, row2) def test_acl_add_extids(self): external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'} acl = self._acl_add('from-lport', 0, 'output == "fake_port" && ip', 'drop', **external_ids) self.assertEqual(external_ids, acl.external_ids) def test_acl_del_all(self): r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop') self.api.acl_del(self.switch.uuid).execute(check_error=True) self.assertNotIn(r1.uuid, self.api.tables['ACL'].rows) self.assertEqual([], self.switch.acls) def test_acl_del_direction(self): r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop') r2 = self._acl_add('to-lport', 0, 'output == "fake_port"', 'allow') self.api.acl_del(self.switch.uuid, 'from-lport').execute( check_error=True) self.assertNotIn(r1, self.switch.acls) self.assertIn(r2, self.switch.acls) def test_acl_del_direction_priority_match(self): r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop') r2 = self._acl_add('from-lport', 1, 'output == "fake_port"', 'allow') cmd = self.api.acl_del(self.switch.uuid, 'from-lport', 0, 'output == "fake_port"') cmd.execute(check_error=True) self.assertNotIn(r1, self.switch.acls) self.assertIn(r2, self.switch.acls) def test_acl_del_priority_without_match(self): self.assertRaises(TypeError, self.api.acl_del, self.switch.uuid, 'from-lport', 0) def test_acl_del_priority_without_direction(self): self.assertRaises(TypeError, self.api.acl_del, self.switch.uuid, priority=0) def test_acl_list(self): r1 = self._acl_add('from-lport', 0, 'output == "fake_port"', 'drop') r2 = self._acl_add('from-lport', 1, 'output == "fake_port2"', 'allow') acls = self.api.acl_list(self.switch.uuid).execute(check_error=True) self.assertIn(r1, acls) self.assertIn(r2, acls) class TestLspOps(OvnNorthboundTest): def setUp(self): super(TestLspOps, self).setUp() name = utils.get_rand_device_name() self.switch = self.useFixture( fixtures.LogicalSwitchFixture(name)).obj def _lsp_add(self, switch, name, *args, **kwargs): name = utils.get_rand_device_name() if name is None else name lsp = self.api.lsp_add(switch.uuid, name, *args, **kwargs).execute( check_error=True) self.assertIn(lsp, switch.ports) return lsp def _test_lsp_get(self, col): lsp = self._lsp_add(self.switch, None) val = getattr(lsp, col) found = self.api.lsp_get(val).execute(check_error=True) self.assertEqual(lsp, found) def test_lsp_get_uuid(self): self._test_lsp_get('uuid') def test_ls_get_name(self): self._test_lsp_get('name') def test_lsp_add(self): self._lsp_add(self.switch, None) def test_lsp_add_exists(self): lsp = self._lsp_add(self.switch, None) self.assertRaises(RuntimeError, self._lsp_add, self.switch, lsp.name) def test_lsp_add_may_exist(self): lsp1 = self._lsp_add(self.switch, None) lsp2 = self._lsp_add(self.switch, lsp1.name, may_exist=True) self.assertEqual(lsp1, lsp2) def test_lsp_add_may_exist_wrong_switch(self): sw = self.useFixture(fixtures.LogicalSwitchFixture()).obj lsp = self._lsp_add(self.switch, None) self.assertRaises(RuntimeError, self._lsp_add, sw, lsp.name, may_exist=True) def test_lsp_add_parent(self): lsp1 = self._lsp_add(self.switch, None) lsp2 = self._lsp_add(self.switch, None, parent_name=lsp1.name, tag=0) # parent_name, being optional, is stored as a list self.assertIn(lsp1.name, lsp2.parent_name) def test_lsp_add_parent_no_tag(self): self.assertRaises(TypeError, self._lsp_add, self.switch, None, parent_name="fake_parent") def test_lsp_add_parent_may_exist(self): lsp1 = self._lsp_add(self.switch, None) lsp2 = self._lsp_add(self.switch, None, parent_name=lsp1.name, tag=0) lsp3 = self._lsp_add(self.switch, lsp2.name, parent_name=lsp1.name, tag=0, may_exist=True) self.assertEqual(lsp2, lsp3) def test_lsp_add_parent_may_exist_no_parent(self): lsp1 = self._lsp_add(self.switch, None) self.assertRaises(RuntimeError, self._lsp_add, self.switch, lsp1.name, parent_name="fake_parent", tag=0, may_exist=True) def test_lsp_add_parent_may_exist_different_parent(self): lsp1 = self._lsp_add(self.switch, None) lsp2 = self._lsp_add(self.switch, None, parent_name=lsp1.name, tag=0) self.assertRaises(RuntimeError, self._lsp_add, self.switch, lsp2.name, parent_name="fake_parent", tag=0, may_exist=True) def test_lsp_add_parent_may_exist_different_tag(self): lsp1 = self._lsp_add(self.switch, None) lsp2 = self._lsp_add(self.switch, None, parent_name=lsp1.name, tag=0) self.assertRaises(RuntimeError, self._lsp_add, self.switch, lsp2.name, parent_name=lsp1.name, tag=1, may_exist=True) def test_lsp_add_may_exist_existing_parent(self): lsp1 = self._lsp_add(self.switch, None) lsp2 = self._lsp_add(self.switch, None, parent_name=lsp1.name, tag=0) self.assertRaises(RuntimeError, self._lsp_add, self.switch, lsp2.name, may_exist=True) def test_lsp_add_columns(self): options = {'myside': 'yourside'} external_ids = {'myside': 'yourside'} lsp = self._lsp_add(self.switch, None, options=options, external_ids=external_ids) self.assertEqual(options, lsp.options) self.assertEqual(external_ids, lsp.external_ids) def test_lsp_del_uuid(self): lsp = self._lsp_add(self.switch, None) self.api.lsp_del(lsp.uuid).execute(check_error=True) self.assertNotIn(lsp, self.switch.ports) def test_lsp_del_name(self): lsp = self._lsp_add(self.switch, None) self.api.lsp_del(lsp.name).execute(check_error=True) self.assertNotIn(lsp, self.switch.ports) def test_lsp_del_switch(self): lsp = self._lsp_add(self.switch, None) self.api.lsp_del(lsp.uuid, self.switch.uuid).execute(check_error=True) self.assertNotIn(lsp, self.switch.ports) def test_lsp_del_switch_name(self): lsp = self._lsp_add(self.switch, None) self.api.lsp_del(lsp.uuid, self.switch.name).execute(check_error=True) self.assertNotIn(lsp, self.switch.ports) def test_lsp_del_wrong_switch(self): lsp = self._lsp_add(self.switch, None) sw_id = self.useFixture(fixtures.LogicalSwitchFixture()).obj cmd = self.api.lsp_del(lsp.uuid, sw_id) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lsp_del_switch_no_exist(self): lsp = self._lsp_add(self.switch, None) cmd = self.api.lsp_del(lsp.uuid, utils.get_rand_device_name()) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lsp_del_no_exist(self): cmd = self.api.lsp_del("fake_port") self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lsp_del_if_exist(self): self.api.lsp_del("fake_port", if_exists=True).execute(check_error=True) def test_lsp_list(self): ports = {self._lsp_add(self.switch, None) for _ in range(3)} port_set = set(self.api.lsp_list(self.switch.uuid).execute( check_error=True)) self.assertTrue(ports.issubset(port_set)) def test_lsp_list_no_switch(self): ports = {self._lsp_add(self.switch, None) for _ in range(3)} other_switch = self.useFixture(fixtures.LogicalSwitchFixture( name=utils.get_rand_device_name())).obj other_port = self._lsp_add(other_switch, None) all_ports = set(self.api.lsp_list().execute(check_error=True)) self.assertTrue((ports.union(set([other_port]))).issubset(all_ports)) def test_lsp_get_parent(self): ls1 = self._lsp_add(self.switch, None) ls2 = self._lsp_add(self.switch, None, parent_name=ls1.name, tag=0) self.assertEqual( ls1.name, self.api.lsp_get_parent(ls2.name).execute( check_error=True)) def test_lsp_get_tag(self): ls1 = self._lsp_add(self.switch, None) ls2 = self._lsp_add(self.switch, None, parent_name=ls1.name, tag=0) self.assertIsInstance(self.api.lsp_get_tag(ls2.uuid).execute( check_error=True), int) def test_lsp_set_addresses(self): lsp = self._lsp_add(self.switch, None) for addr in ('dynamic', 'unknown', 'router', 'de:ad:be:ef:4d:ad 192.0.2.1'): self.api.lsp_set_addresses(lsp.name, [addr]).execute( check_error=True) self.assertEqual([addr], lsp.addresses) def test_lsp_set_addresses_invalid(self): self.assertRaises( TypeError, self.api.lsp_set_addresses, 'fake', '01:02:03:04:05:06') def test_lsp_get_addresses(self): addresses = [ '01:02:03:04:05:06 192.0.2.1', 'de:ad:be:ef:4d:ad 192.0.2.2'] lsp = self._lsp_add(self.switch, None) self.api.lsp_set_addresses( lsp.name, addresses).execute(check_error=True) self.assertEqual(set(addresses), set(self.api.lsp_get_addresses( lsp.name).execute(check_error=True))) def test_lsp_get_set_port_security(self): port_security = [ '01:02:03:04:05:06 192.0.2.1', 'de:ad:be:ef:4d:ad 192.0.2.2'] lsp = self._lsp_add(self.switch, None) self.api.lsp_set_port_security(lsp.name, port_security).execute( check_error=True) ps = self.api.lsp_get_port_security(lsp.name).execute( check_error=True) self.assertEqual(port_security, ps) def test_lsp_get_up(self): lsp = self._lsp_add(self.switch, None) self.assertFalse(self.api.lsp_get_up(lsp.name).execute( check_error=True)) def test_lsp_get_set_enabled(self): lsp = self._lsp_add(self.switch, None) # default is True self.assertTrue(self.api.lsp_get_enabled(lsp.name).execute( check_error=True)) self.api.lsp_set_enabled(lsp.name, False).execute(check_error=True) self.assertFalse(self.api.lsp_get_enabled(lsp.name).execute( check_error=True)) self.api.lsp_set_enabled(lsp.name, True).execute(check_error=True) self.assertTrue(self.api.lsp_get_enabled(lsp.name).execute( check_error=True)) def test_lsp_get_set_type(self): type_ = 'router' lsp = self._lsp_add(self.switch, None) self.api.lsp_set_type(lsp.uuid, type_).execute(check_error=True) self.assertEqual(type_, self.api.lsp_get_type(lsp.uuid).execute( check_error=True)) def test_lsp_get_set_options(self): options = {'one': 'two', 'three': 'four'} lsp = self._lsp_add(self.switch, None) self.api.lsp_set_options(lsp.uuid, **options).execute( check_error=True) self.assertEqual(options, self.api.lsp_get_options(lsp.uuid).execute( check_error=True)) def test_lsp_set_get_dhcpv4_options(self): lsp = self._lsp_add(self.switch, None) dhcpopt = self.useFixture( fixtures.DhcpOptionsFixture('192.0.2.1/24')).obj self.api.lsp_set_dhcpv4_options( lsp.name, dhcpopt.uuid).execute(check_error=True) options = self.api.lsp_get_dhcpv4_options( lsp.uuid).execute(check_error=True) self.assertEqual(dhcpopt, options) class TestDhcpOptionsOps(OvnNorthboundTest): def _dhcpopt_add(self, cidr, *args, **kwargs): dhcpopt = self.useFixture(fixtures.DhcpOptionsFixture( cidr, *args, **kwargs)).obj self.assertEqual(cidr, dhcpopt.cidr) return dhcpopt def test_dhcp_options_get(self): dhcpopt = self._dhcpopt_add('192.0.2.1/24') found = self.api.dhcp_options_get(dhcpopt.uuid).execute( check_error=True) self.assertEqual(dhcpopt, found) def test_dhcp_options_get_no_exist(self): cmd = self.api.dhcp_options_get("noexist") self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) def test_dhcp_options_add(self): self._dhcpopt_add('192.0.2.1/24') def test_dhcp_options_add_v6(self): self._dhcpopt_add('2001:db8::1/32') def test_dhcp_options_invalid_cidr(self): self.assertRaises(netaddr.AddrFormatError, self.api.dhcp_options_add, '256.0.0.1/24') def test_dhcp_options_add_ext_ids(self): ext_ids = {'subnet-id': '1', 'other-id': '2'} dhcpopt = self._dhcpopt_add('192.0.2.1/24', **ext_ids) self.assertEqual(ext_ids, dhcpopt.external_ids) def test_dhcp_options_list(self): dhcpopts = {self._dhcpopt_add('192.0.2.1/24') for d in range(3)} dhcpopts_set = set( self.api.dhcp_options_list().execute(check_error=True)) self.assertTrue(dhcpopts.issubset(dhcpopts_set)) def test_dhcp_options_get_set_options(self): dhcpopt = self._dhcpopt_add('192.0.2.1/24') options = {'a': 'one', 'b': 'two'} self.api.dhcp_options_set_options( dhcpopt.uuid, **options).execute(check_error=True) cmd = self.api.dhcp_options_get_options(dhcpopt.uuid) self.assertEqual(options, cmd.execute(check_error=True)) class TestLogicalRouterOps(OvnNorthboundTest): def _lr_add(self, *args, **kwargs): lr = self.useFixture( fixtures.LogicalRouterFixture(*args, **kwargs)).obj self.assertIn(lr.uuid, self.api.tables['Logical_Router'].rows) return lr def test_lr_add(self): self._lr_add() def test_lr_add_name(self): name = utils.get_rand_device_name() lr = self._lr_add(name) self.assertEqual(name, lr.name) def test_lr_add_columns(self): external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'} lr = self._lr_add(external_ids=external_ids) self.assertEqual(external_ids, lr.external_ids) def test_lr_del(self): lr = self._lr_add() self.api.lr_del(lr.uuid).execute(check_error=True) self.assertNotIn(lr.uuid, self.api.tables['Logical_Router'].rows.keys()) def test_lr_del_name(self): lr = self._lr_add(utils.get_rand_device_name()) self.api.lr_del(lr.name).execute(check_error=True) self.assertNotIn(lr.uuid, self.api.tables['Logical_Router'].rows.keys()) def test_lr_list(self): lrs = {self._lr_add() for _ in range(3)} lr_set = set(self.api.lr_list().execute(check_error=True)) self.assertTrue(lrs.issubset(lr_set), "%s vs %s" % (lrs, lr_set)) def _lr_add_route(self, router=None, prefix=None, nexthop=None, port=None, **kwargs): lr = self._lr_add(router or utils.get_rand_device_name(), may_exist=True) prefix = prefix or '192.0.2.0/25' nexthop = nexthop or '192.0.2.254' sr = self.api.lr_route_add(lr.uuid, prefix, nexthop, port, **kwargs).execute(check_error=True) self.assertIn(sr, lr.static_routes) self.assertEqual(prefix, sr.ip_prefix) self.assertEqual(nexthop, sr.nexthop) sr.router = lr return sr def test_lr_route_add(self): self._lr_add_route() def test_lr_route_add_invalid_prefix(self): self.assertRaises(netaddr.AddrFormatError, self._lr_add_route, prefix='192.168.1.1/40') def test_lr_route_add_invalid_nexthop(self): self.assertRaises(netaddr.AddrFormatError, self._lr_add_route, nexthop='256.0.1.3') def test_lr_route_add_exist(self): router_name = utils.get_rand_device_name() self._lr_add_route(router_name) self.assertRaises(RuntimeError, self._lr_add_route, router=router_name) def test_lr_route_add_may_exist(self): router_name = utils.get_rand_device_name() self._lr_add_route(router_name) self._lr_add_route(router_name, may_exist=True) def test_lr_route_del(self): prefix = "192.0.2.0/25" route = self._lr_add_route(prefix=prefix) self.api.lr_route_del(route.router.uuid, prefix).execute( check_error=True) self.assertNotIn(route, route.router.static_routes) def test_lr_route_del_all(self): router = self._lr_add() for p in range(3): self._lr_add_route(router.uuid, prefix="192.0.%s.0/24" % p) self.api.lr_route_del(router.uuid).execute(check_error=True) self.assertEqual([], router.static_routes) def test_lr_route_del_no_router(self): cmd = self.api.lr_route_del("fake_router", '192.0.2.0/25') self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lr_route_del_no_exist(self): lr = self._lr_add() cmd = self.api.lr_route_del(lr.uuid, '192.0.2.0/25') self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lr_route_del_if_exist(self): lr = self._lr_add() self.api.lr_route_del(lr.uuid, '192.0.2.0/25', if_exists=True).execute( check_error=True) def test_lr_route_list(self): lr = self._lr_add() routes = {self._lr_add_route(lr.uuid, prefix="192.0.%s.0/25" % p) for p in range(3)} route_set = set(self.api.lr_route_list(lr.uuid).execute( check_error=True)) self.assertTrue(routes.issubset(route_set)) def _lr_nat_add(self, *args, **kwargs): lr = kwargs.pop('router', self._lr_add(utils.get_rand_device_name())) nat = self.api.lr_nat_add( lr.uuid, *args, **kwargs).execute( check_error=True) self.assertIn(nat, lr.nat) nat.router = lr return nat def test_lr_nat_add_dnat(self): ext, log = ('10.172.4.1', '192.0.2.1') nat = self._lr_nat_add(const.NAT_DNAT, ext, log) self.assertEqual(ext, nat.external_ip) self.assertEqual(log, nat.logical_ip) def test_lr_nat_add_snat(self): ext, log = ('10.172.4.1', '192.0.2.0/24') nat = self._lr_nat_add(const.NAT_SNAT, ext, log) self.assertEqual(ext, nat.external_ip) self.assertEqual(log, nat.logical_ip) def test_lr_nat_add_port(self): sw = self.useFixture( fixtures.LogicalSwitchFixture()).obj lsp = self.api.lsp_add(sw.uuid, utils.get_rand_device_name()).execute( check_error=True) lport, mac = (lsp.name, 'de:ad:be:ef:4d:ad') nat = self._lr_nat_add(const.NAT_BOTH, '10.172.4.1', '192.0.2.1', lport, mac) self.assertIn(lport, nat.logical_port) # because optional self.assertIn(mac, nat.external_mac) def test_lr_nat_add_port_no_mac(self): # yes, this and other TypeError tests are technically unit tests self.assertRaises(TypeError, self.api.lr_nat_add, 'faker', const.NAT_DNAT, '10.17.4.1', '192.0.2.1', 'fake') def test_lr_nat_add_port_wrong_type(self): for nat_type in (const.NAT_DNAT, const.NAT_SNAT): self.assertRaises( TypeError, self.api.lr_nat_add, 'faker', nat_type, '10.17.4.1', '192.0.2.1', 'fake', 'de:ad:be:ef:4d:ad') def test_lr_nat_add_exists(self): args = (const.NAT_SNAT, '10.17.4.1', '192.0.2.0/24') nat1 = self._lr_nat_add(*args) cmd = self.api.lr_nat_add(nat1.router.uuid, *args) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lr_nat_add_may_exist(self): sw = self.useFixture( fixtures.LogicalSwitchFixture()).obj lsp = self.api.lsp_add(sw.uuid, utils.get_rand_device_name()).execute( check_error=True) args = (const.NAT_BOTH, '10.17.4.1', '192.0.2.1') nat1 = self._lr_nat_add(*args) lp, mac = (lsp.name, 'de:ad:be:ef:4d:ad') nat2 = self.api.lr_nat_add( nat1.router.uuid, *args, logical_port=lp, external_mac=mac, may_exist=True).execute(check_error=True) self.assertEqual(nat1, nat2) self.assertIn(lp, nat2.logical_port) # because optional self.assertIn(mac, nat2.external_mac) def test_lr_nat_add_may_exist_remove_port(self): sw = self.useFixture( fixtures.LogicalSwitchFixture()).obj lsp = self.api.lsp_add(sw.uuid, utils.get_rand_device_name()).execute( check_error=True) args = (const.NAT_BOTH, '10.17.4.1', '192.0.2.1') lp, mac = (lsp.name, 'de:ad:be:ef:4d:ad') nat1 = self._lr_nat_add(*args, logical_port=lp, external_mac=mac) nat2 = self.api.lr_nat_add( nat1.router.uuid, *args, may_exist=True).execute(check_error=True) self.assertEqual(nat1, nat2) self.assertEqual([], nat2.logical_port) # because optional self.assertEqual([], nat2.external_mac) def _three_nats(self): lr = self._lr_add(utils.get_rand_device_name()) for n, nat_type in enumerate((const.NAT_DNAT, const.NAT_SNAT, const.NAT_BOTH)): nat_kwargs = {'router': lr, 'nat_type': nat_type, 'logical_ip': '10.17.4.%s' % (n + 1), 'external_ip': '192.0.2.%s' % (n + 1)} self._lr_nat_add(**nat_kwargs) return lr def _lr_nat_del(self, *args, **kwargs): lr = self._three_nats() self.api.lr_nat_del(lr.name, *args, **kwargs).execute(check_error=True) return lr def test_lr_nat_del_all(self): lr = self._lr_nat_del() self.assertEqual([], lr.nat) def test_lr_nat_del_type(self): lr = self._lr_nat_del(nat_type=const.NAT_SNAT) types = tuple(nat.type for nat in lr.nat) self.assertNotIn(const.NAT_SNAT, types) self.assertEqual(len(types), len(const.NAT_TYPES) - 1) def test_lr_nat_del_specific_dnat(self): lr = self._lr_nat_del(nat_type=const.NAT_DNAT, match_ip='192.0.2.1') self.assertEqual(len(lr.nat), len(const.NAT_TYPES) - 1) for nat in lr.nat: self.assertNotEqual('192.0.2.1', nat.external_ip) self.assertNotEqual(const.NAT_DNAT, nat.type) def test_lr_nat_del_specific_snat(self): lr = self._lr_nat_del(nat_type=const.NAT_SNAT, match_ip='10.17.4.2') self.assertEqual(len(lr.nat), len(const.NAT_TYPES) - 1) for nat in lr.nat: self.assertNotEqual('10.17.4.2', nat.external_ip) self.assertNotEqual(const.NAT_SNAT, nat.type) def test_lr_nat_del_specific_both(self): lr = self._lr_nat_del(nat_type=const.NAT_BOTH, match_ip='192.0.2.3') self.assertEqual(len(lr.nat), len(const.NAT_TYPES) - 1) for nat in lr.nat: self.assertNotEqual('192.0.2.3', nat.external_ip) self.assertNotEqual(const.NAT_BOTH, nat.type) def test_lr_nat_del_specific_not_found(self): self.assertRaises(idlutils.RowNotFound, self._lr_nat_del, nat_type=const.NAT_BOTH, match_ip='10.17.4.2') def test_lr_nat_del_specific_if_exists(self): lr = self._lr_nat_del(nat_type=const.NAT_BOTH, match_ip='10.17.4.2', if_exists=True) self.assertEqual(len(lr.nat), len(const.NAT_TYPES)) def test_lr_nat_list(self): lr = self._three_nats() nats = self.api.lr_nat_list(lr.uuid).execute(check_error=True) self.assertEqual(lr.nat, nats) class TestLogicalRouterPortOps(OvnNorthboundTest): def setUp(self): super(TestLogicalRouterPortOps, self).setUp() self.lr = self.useFixture(fixtures.LogicalRouterFixture()).obj def _lrp_add(self, port, mac='de:ad:be:ef:4d:ad', networks=None, *args, **kwargs): if port is None: port = utils.get_rand_device_name() if networks is None: networks = ['192.0.2.0/24'] lrp = self.api.lrp_add(self.lr.uuid, port, mac, networks, *args, **kwargs).execute(check_error=True) self.assertIn(lrp, self.lr.ports) self.assertEqual(mac, lrp.mac) self.assertEqual(set(networks), set(lrp.networks)) return lrp def test_lrp_add(self): self._lrp_add(None, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24']) def test_lpr_add_peer(self): lrp = self._lrp_add(None, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24'], peer='fake_peer') self.assertIn('fake_peer', lrp.peer) def test_lpr_add_multiple_networks(self): networks = ['192.0.2.0/24', '192.2.1.0/24'] self._lrp_add(None, 'de:ad:be:ef:4d:ad', networks) def test_lrp_add_invalid_mac(self): self.assertRaises( netaddr.AddrFormatError, self.api.lrp_add, "fake", "fake", "000:11:22:33:44:55", ['192.0.2.0/24']) def test_lrp_add_invalid_network(self): self.assertRaises( netaddr.AddrFormatError, self.api.lrp_add, "fake", "fake", "01:02:03:04:05:06", ['256.2.0.1/24']) def test_lrp_add_exists(self): name = utils.get_rand_device_name() args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24']) self._lrp_add(*args) self.assertRaises(RuntimeError, self._lrp_add, *args) def test_lrp_add_may_exist(self): name = utils.get_rand_device_name() args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24']) self._lrp_add(*args) self.assertRaises(RuntimeError, self._lrp_add, *args, may_exist=True) def test_lrp_add_may_exist_different_router(self): name = utils.get_rand_device_name() args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24']) lr2 = self.useFixture(fixtures.LogicalRouterFixture()).obj self._lrp_add(*args) cmd = self.api.lrp_add(lr2.uuid, *args, may_exist=True) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lrp_add_may_exist_different_mac(self): name = utils.get_rand_device_name() args = {'port': name, 'mac': 'de:ad:be:ef:4d:ad', 'networks': ['192.0.2.0/24']} self._lrp_add(**args) args['mac'] = 'da:d4:de:ad:be:ef' self.assertRaises(RuntimeError, self._lrp_add, may_exist=True, **args) def test_lrp_add_may_exist_different_networks(self): name = utils.get_rand_device_name() args = (name, 'de:ad:be:ef:4d:ad') self._lrp_add(*args, networks=['192.0.2.0/24']) self.assertRaises(RuntimeError, self._lrp_add, *args, networks=['192.2.1.0/24'], may_exist=True) def test_lrp_add_may_exist_different_peer(self): name = utils.get_rand_device_name() args = (name, 'de:ad:be:ef:4d:ad', ['192.0.2.0/24']) self._lrp_add(*args) self.assertRaises(RuntimeError, self._lrp_add, *args, peer='fake', may_exist=True) def test_lrp_add_columns(self): options = {'myside': 'yourside'} external_ids = {'myside': 'yourside'} lrp = self._lrp_add(None, options=options, external_ids=external_ids) self.assertEqual(options, lrp.options) self.assertEqual(external_ids, lrp.external_ids) def test_lrp_del_uuid(self): lrp = self._lrp_add(None) self.api.lrp_del(lrp.uuid).execute(check_error=True) self.assertNotIn(lrp, self.lr.ports) def test_lrp_del_name(self): lrp = self._lrp_add(None) self.api.lrp_del(lrp.name).execute(check_error=True) self.assertNotIn(lrp, self.lr.ports) def test_lrp_del_router(self): lrp = self._lrp_add(None) self.api.lrp_del(lrp.uuid, self.lr.uuid).execute(check_error=True) self.assertNotIn(lrp, self.lr.ports) def test_lrp_del_router_name(self): lrp = self._lrp_add(None) self.api.lrp_del(lrp.uuid, self.lr.name).execute(check_error=True) self.assertNotIn(lrp, self.lr.ports) def test_lrp_del_wrong_router(self): lrp = self._lrp_add(None) sw_id = self.useFixture(fixtures.LogicalSwitchFixture()).obj cmd = self.api.lrp_del(lrp.uuid, sw_id) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lrp_del_router_no_exist(self): lrp = self._lrp_add(None) cmd = self.api.lrp_del(lrp.uuid, utils.get_rand_device_name()) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lrp_del_no_exist(self): cmd = self.api.lrp_del("fake_port") self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lrp_del_if_exist(self): self.api.lrp_del("fake_port", if_exists=True).execute(check_error=True) def test_lrp_list(self): ports = {self._lrp_add(None) for _ in range(3)} port_set = set(self.api.lrp_list(self.lr.uuid).execute( check_error=True)) self.assertTrue(ports.issubset(port_set)) def test_lrp_get_set_enabled(self): lrp = self._lrp_add(None) # default is True self.assertTrue(self.api.lrp_get_enabled(lrp.name).execute( check_error=True)) self.api.lrp_set_enabled(lrp.name, False).execute(check_error=True) self.assertFalse(self.api.lrp_get_enabled(lrp.name).execute( check_error=True)) self.api.lrp_set_enabled(lrp.name, True).execute(check_error=True) self.assertTrue(self.api.lrp_get_enabled(lrp.name).execute( check_error=True)) class TestLoadBalancerOps(OvnNorthboundTest): def _lb_add(self, lb, vip, ips, protocol=const.PROTO_TCP, may_exist=False, **columns): lbal = self.useFixture(fixtures.LoadBalancerFixture( lb, vip, ips, protocol, may_exist, **columns)).obj self.assertEqual(lb, lbal.name) norm_vip = ovsdb_utils.normalize_ip_port(vip) self.assertIn(norm_vip, lbal.vips) self.assertEqual(",".join(ovsdb_utils.normalize_ip(ip) for ip in ips), lbal.vips[norm_vip]) self.assertIn(protocol, lbal.protocol) # because optional return lbal def test_lb_add(self): vip = '192.0.2.1' ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3'] self._lb_add(utils.get_rand_device_name(), vip, ips) def test_lb_add_port(self): vip = '192.0.2.1:80' ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3'] self._lb_add(utils.get_rand_device_name(), vip, ips) def test_lb_add_protocol(self): vip = '192.0.2.1' ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3'] self._lb_add(utils.get_rand_device_name(), vip, ips, const.PROTO_UDP) def test_lb_add_new_vip(self): name = utils.get_rand_device_name() lb1 = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2']) lb2 = self._lb_add(name, '192.0.2.2', ['10.1.0.1', '10.1.0.2']) self.assertEqual(lb1, lb2) self.assertEqual(2, len(lb1.vips)) def test_lb_add_exists(self): name = utils.get_rand_device_name() vip = '192.0.2.1' ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3'] self._lb_add(name, vip, ips) cmd = self.api.lb_add(name, vip, ips) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_lb_add_may_exist(self): name = utils.get_rand_device_name() vip = '192.0.2.1' ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3'] lb1 = self._lb_add(name, vip, ips) ips += ['10.0.0.4'] lb2 = self.api.lb_add(name, vip, ips, may_exist=True).execute( check_error=True) self.assertEqual(lb1, lb2) self.assertEqual(",".join(ips), lb1.vips[vip]) def test_lb_add_columns(self): ext_ids = {'one': 'two'} name = utils.get_rand_device_name() lb = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2'], external_ids=ext_ids) self.assertEqual(ext_ids, lb.external_ids) def test_lb_del(self): name = utils.get_rand_device_name() lb = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2']).uuid self.api.lb_del(lb).execute(check_error=True) self.assertNotIn(lb, self.api.tables['Load_Balancer'].rows) def test_lb_del_vip(self): name = utils.get_rand_device_name() lb1 = self._lb_add(name, '192.0.2.1', ['10.0.0.1', '10.0.0.2']) lb2 = self._lb_add(name, '192.0.2.2', ['10.1.0.1', '10.1.0.2']) self.assertEqual(lb1, lb2) self.api.lb_del(lb1.name, '192.0.2.1').execute(check_error=True) self.assertNotIn('192.0.2.1', lb1.vips) self.assertIn('192.0.2.2', lb1.vips) def test_lb_del_no_exist(self): cmd = self.api.lb_del(utils.get_rand_device_name()) self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) def test_lb_del_if_exists(self): self.api.lb_del(utils.get_rand_device_name(), if_exists=True).execute( check_error=True) def test_lb_list(self): lbs = {self._lb_add(utils.get_rand_device_name(), '192.0.2.1', ['10.0.0.1', '10.0.0.2']) for _ in range(3)} lbset = self.api.lb_list().execute(check_error=True) self.assertTrue(lbs.issubset(lbset)) class TestObLbOps(testscenarios.TestWithScenarios, OvnNorthboundTest): scenarios = [ ('LrLbOps', dict(fixture=fixtures.LogicalRouterFixture, _add_fn='lr_lb_add', _del_fn='lr_lb_del', _list_fn='lr_lb_list')), ('LsLbOps', dict(fixture=fixtures.LogicalSwitchFixture, _add_fn='ls_lb_add', _del_fn='ls_lb_del', _list_fn='ls_lb_list')), ] def setUp(self): super(TestObLbOps, self).setUp() self.add_fn = getattr(self.api, self._add_fn) self.del_fn = getattr(self.api, self._del_fn) self.list_fn = getattr(self.api, self._list_fn) # They must be in this order because the load balancer # can't be deleted when there is a reference in the router self.lb = self.useFixture(fixtures.LoadBalancerFixture( utils.get_rand_device_name(), '192.0.2.1', ['10.0.0.1', '10.0.0.2'])).obj self.lb2 = self.useFixture(fixtures.LoadBalancerFixture( utils.get_rand_device_name(), '192.0.2.2', ['10.1.0.1', '10.1.0.2'])).obj self.lr = self.useFixture(self.fixture( utils.get_rand_device_name())).obj def test_ob_lb_add(self): self.add_fn(self.lr.name, self.lb.name).execute( check_error=True) self.assertIn(self.lb, self.lr.load_balancer) def test_ob_lb_add_exists(self): cmd = self.add_fn(self.lr.name, self.lb.name) cmd.execute(check_error=True) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_ob_lb_add_may_exist(self): cmd = self.add_fn(self.lr.name, self.lb.name, may_exist=True) lb1 = cmd.execute(check_error=True) lb2 = cmd.execute(check_error=True) self.assertEqual(lb1, lb2) def test_ob_lb_del(self): self.add_fn(self.lr.name, self.lb.name).execute( check_error=True) self.assertIn(self.lb, self.lr.load_balancer) self.del_fn(self.lr.name).execute(check_error=True) self.assertEqual(0, len(self.lr.load_balancer)) def test_ob_lb_del_lb(self): self.add_fn(self.lr.name, self.lb.name).execute( check_error=True) self.add_fn(self.lr.name, self.lb2.name).execute( check_error=True) self.del_fn(self.lr.name, self.lb2.name).execute( check_error=True) self.assertNotIn(self.lb2, self.lr.load_balancer) self.assertIn(self.lb, self.lr.load_balancer) def test_ob_lb_del_no_exist(self): cmd = self.del_fn(self.lr.name, 'fake') self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) def test_ob_lb_del_if_exists(self): self.del_fn(self.lr.name, 'fake', if_exists=True).execute( check_error=True) def test_ob_lb_list(self): self.add_fn(self.lr.name, self.lb.name).execute( check_error=True) self.add_fn(self.lr.name, self.lb2.name).execute( check_error=True) rows = self.list_fn(self.lr.name).execute(check_error=True) self.assertIn(self.lb, rows) self.assertIn(self.lb2, rows) class TestCommonDbOps(OvnNorthboundTest): def setUp(self): super(TestCommonDbOps, self).setUp() name = utils.get_rand_device_name() self.switch = self.useFixture(fixtures.LogicalSwitchFixture(name)).obj self.lsps = [ self.api.lsp_add( self.switch.uuid, utils.get_rand_device_name()).execute(check_error=True) for _ in range(3)] self.api.db_set('Logical_Switch', self.switch.uuid, ('external_ids', {'one': '1', 'two': '2'})).execute( check_error=True) def _ls_get_extids(self): return self.api.db_get('Logical_Switch', self.switch.uuid, 'external_ids').execute(check_error=True) def test_db_remove_map_key(self): ext_ids = self._ls_get_extids() removed = ext_ids.popitem() self.api.db_remove('Logical_Switch', self.switch.uuid, 'external_ids', removed[0]).execute( check_error=True) self.assertEqual(ext_ids, self.switch.external_ids) def test_db_remove_map_value(self): ext_ids = self._ls_get_extids() removed = dict([ext_ids.popitem()]) self.api.db_remove('Logical_Switch', self.switch.uuid, 'external_ids', **removed).execute( check_error=True) self.assertEqual(ext_ids, self.switch.external_ids) def test_db_remove_map_bad_key(self): # should be a NoOp, not fail self.api.db_remove('Logical_Switch', self.switch.uuid, 'external_ids', "badkey").execute(check_error=True) def test_db_remove_map_bad_value(self): ext_ids = self._ls_get_extids() removed = {ext_ids.popitem()[0]: "badvalue"} # should be a NoOp, not fail self.api.db_remove('Logical_Switch', self.switch.uuid, 'external_ids', **removed).execute(check_error=True) def test_db_remove_value(self): ports = self.api.db_get('Logical_Switch', self.switch.uuid, 'ports').execute(check_error=True) removed = ports.pop() self.api.db_remove('Logical_Switch', self.switch.uuid, 'ports', removed).execute(check_error=True) self.assertEqual(ports, [x.uuid for x in self.switch.ports]) def test_db_remove_bad_value(self): # should be a NoOp, not fail self.api.db_remove('Logical_Switch', self.switch.uuid, 'ports', "badvalue").execute(check_error=True) class TestDnsOps(OvnNorthboundTest): def _dns_add(self, *args, **kwargs): dns = self.useFixture(fixtures.DnsFixture(*args, **kwargs)).obj return dns def test_dns_get(self): dns = self._dns_add() found = self.api.dns_get(dns.uuid).execute( check_error=True) self.assertEqual(dns, found) def test_dns_get_no_exist(self): cmd = self.api.dns_get("noexist") self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) def test_dns_add(self): self._dns_add() def test_dns_add_ext_ids(self): ext_ids = {'net-id': '1', 'other-id': '2'} dns = self._dns_add(external_ids=ext_ids) self.assertEqual(ext_ids, dns.external_ids) def test_dns_list(self): dnses = {self._dns_add() for d in range(3)} dnses_set = set( self.api.dns_list().execute(check_error=True)) self.assertTrue(dnses.issubset(dnses_set)) def test_dns_set_records(self): dns = self._dns_add() records = {'a': 'one', 'b': 'two'} self.api.dns_set_records( dns.uuid, **records).execute(check_error=True) dns = self.api.dns_get(dns.uuid).execute( check_error=True) self.assertEqual(records, dns.records) self.api.dns_set_records( dns.uuid, **{}).execute(check_error=True) self.assertEqual({}, dns.records) def test_dns_set_external_ids(self): dns = self._dns_add() external_ids = {'a': 'one', 'b': 'two'} self.api.dns_set_external_ids( dns.uuid, **external_ids).execute(check_error=True) dns = self.api.dns_get(dns.uuid).execute( check_error=True) self.assertEqual(external_ids, dns.external_ids) self.api.dns_set_external_ids( dns.uuid, **{}).execute(check_error=True) self.assertEqual({}, dns.external_ids) def test_dns_add_remove_records(self): dns = self._dns_add() self.api.dns_add_record(dns.uuid, 'a', 'one').execute() self.api.dns_add_record(dns.uuid, 'b', 'two').execute() dns = self.api.dns_get(dns.uuid).execute( check_error=True) records = {'a': 'one', 'b': 'two'} self.assertEqual(records, dns.records) self.api.dns_remove_record(dns.uuid, 'a').execute() records.pop('a') self.assertEqual(records, dns.records) self.api.dns_remove_record(dns.uuid, 'b').execute() self.assertEqual({}, dns.records) class TestLsDnsOps(OvnNorthboundTest): def _dns_add(self, *args, **kwargs): dns = self.useFixture(fixtures.DnsFixture(*args, **kwargs)).obj return dns def _ls_add(self, *args, **kwargs): fix = self.useFixture(fixtures.LogicalSwitchFixture(*args, **kwargs)) return fix.obj def test_ls_dns_set_clear_records(self): dns1 = self._dns_add() dns2 = self._dns_add() ls1 = self._ls_add('ls1') self.api.ls_set_dns_records(ls1.uuid, [dns1.uuid, dns2.uuid]).execute() self.assertItemsEqual([dns1.uuid, dns2.uuid], [dns.uuid for dns in ls1.dns_records]) self.api.ls_clear_dns_records(ls1.uuid).execute() self.assertEqual([], ls1.dns_records) def test_ls_dns_add_remove_records(self): dns1 = self._dns_add() dns2 = self._dns_add() ls1 = self._ls_add('ls1') self.api.ls_add_dns_record(ls1.uuid, dns1.uuid).execute() self.assertItemsEqual([dns1.uuid], [dns.uuid for dns in ls1.dns_records]) self.api.ls_add_dns_record(ls1.uuid, dns2.uuid).execute() self.assertItemsEqual([dns1.uuid, dns2.uuid], [dns.uuid for dns in ls1.dns_records]) self.api.ls_remove_dns_record(ls1.uuid, dns2.uuid).execute() self.assertItemsEqual([dns1.uuid], [dns.uuid for dns in ls1.dns_records]) self.api.ls_remove_dns_record(ls1.uuid, dns1.uuid).execute() self.assertEqual([], ls1.dns_records) ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_northbound/fixtures.py0000666000175100017510000000257013236157620027247 0ustar zuulzuul00000000000000# 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 ovsdbapp.schema.ovn_northbound import impl_idl from ovsdbapp.tests.functional.schema import fixtures class LogicalSwitchFixture(fixtures.ImplIdlFixture): api = impl_idl.OvnNbApiIdlImpl create = 'ls_add' delete = 'ls_del' class DhcpOptionsFixture(fixtures.ImplIdlFixture): api = impl_idl.OvnNbApiIdlImpl create = 'dhcp_options_add' delete = 'dhcp_options_del' delete_args = {} class LogicalRouterFixture(fixtures.ImplIdlFixture): api = impl_idl.OvnNbApiIdlImpl create = 'lr_add' delete = 'lr_del' class LoadBalancerFixture(fixtures.ImplIdlFixture): api = impl_idl.OvnNbApiIdlImpl create = 'lb_add' delete = 'lb_del' class DnsFixture(fixtures.ImplIdlFixture): api = impl_idl.OvnNbApiIdlImpl create = 'dns_add' delete = 'dns_del' delete_args = {} ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/ovn_northbound/__init__.py0000666000175100017510000000000013236157620027117 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/__init__.py0000666000175100017510000000000013236157620024053 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/open_vswitch/0000775000175100017510000000000013236157776024476 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/open_vswitch/test_impl_idl.py0000666000175100017510000001520113236157633027671 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2017 Red Hat, Inc. # 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 from ovsdbapp import exceptions as exc from ovsdbapp.schema.open_vswitch import impl_idl from ovsdbapp.tests.functional import base from ovsdbapp.tests import utils # NOTE(twilson) functools.partial does not work for this def trpatch(*args, **kwargs): def wrapped(fn): return mock.patch.object(impl_idl.OvsVsctlTransaction, *args, **kwargs)(fn) return wrapped class TestOvsdbIdl(base.FunctionalTestCase): schemas = ["Open_vSwitch"] def setUp(self): super(TestOvsdbIdl, self).setUp() self.api = impl_idl.OvsdbIdl(self.connection) self.brname = utils.get_rand_device_name() # Destroying the bridge cleans up most things created by tests cleanup_cmd = self.api.del_br(self.brname) self.addCleanup(cleanup_cmd.execute) def test_br_exists_false(self): exists = self.api.br_exists(self.brname).execute(check_error=True) self.assertFalse(exists) def test_add_br_may_exist(self): self.api.add_br(self.brname).execute(check_error=True) with self.api.transaction(check_error=True) as txn: txn.add(self.api.add_br(self.brname, datapath_type="netdev")) exists = txn.add(self.api.br_exists(self.brname)) dpt = txn.add(self.api.db_get("Bridge", self.brname, "datapath_type")) self.assertTrue(exists) self.assertEqual("netdev", dpt.result) def test_add_br_may_not_exist(self): self.api.add_br(self.brname).execute(check_error=True) cmd = self.api.add_br(self.brname, may_exist=False) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_del_br_if_exists_false(self): cmd = self.api.del_br(self.brname, if_exists=False) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_del_br_if_exists_true(self): self.api.del_br(self.brname).execute(check_error=True) def test_del_br(self): self.api.add_br(self.brname).execute(check_error=True) self.api.del_br(self.brname).execute(check_error=True) exists = self.api.br_exists(self.brname).execute(check_error=True) self.assertFalse(exists) def _test_add_port(self): pname = utils.get_rand_device_name() with self.api.transaction(check_error=True) as txn: txn.add(self.api.add_br(self.brname)) txn.add(self.api.add_port(self.brname, pname)) return pname def test_add_port(self): pname = self._test_add_port() plist_cmd = self.api.list_ports(self.brname) ports = plist_cmd.execute(check_error=True) self.assertIn(pname, ports) def test_add_port_may_exist_false(self): pname = self._test_add_port() cmd = self.api.add_port(self.brname, pname, may_exist=False) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_del_port(self): pname = self._test_add_port() plist_cmd = self.api.list_ports(self.brname) self.assertIn(pname, plist_cmd.execute(check_error=True)) self.api.del_port(pname).execute(check_error=True) self.assertNotIn(pname, plist_cmd.execute(check_error=True)) def test_del_port_if_exists_false(self): cmd = self.api.del_port(utils.get_rand_device_name(), if_exists=False) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_connection_reconnect(self): self.api.ovsdb_connection.stop() existsCmd = self.api.br_exists(self.brname) txn = self.api.create_transaction(check_error=True) txn.add(existsCmd) self.api.ovsdb_connection.queue_txn(txn) self.api.ovsdb_connection.start() result = txn.results.get(timeout=self.api.ovsdb_connection.timeout) self.assertEqual(result, [False]) def test_connection_disconnect_timeout(self): _is_running_mock = mock.PropertyMock(return_value=True) connection = self.api.ovsdb_connection type(connection)._is_running = _is_running_mock self.addCleanup(delattr, type(connection), '_is_running') self.assertFalse(connection.stop(1)) class ImplIdlTestCase(base.FunctionalTestCase): schemas = ['Open_vSwitch'] def setUp(self): super(ImplIdlTestCase, self).setUp() self.api = impl_idl.OvsdbIdl(self.connection) self.brname = utils.get_rand_device_name() # Make sure exceptions pass through by calling do_post_commit directly mock.patch.object( impl_idl.OvsVsctlTransaction, "post_commit", side_effect=impl_idl.OvsVsctlTransaction.do_post_commit, autospec=True).start() def _add_br(self): # NOTE(twilson) we will be raising exceptions with add_br, so schedule # cleanup before that. cmd = self.api.del_br(self.brname) self.addCleanup(cmd.execute) with self.api.transaction(check_error=True) as tr: tr.add(self.api.add_br(self.brname)) return tr def _add_br_and_test(self): self._add_br() ofport = self.api.db_get("Interface", self.brname, "ofport").execute( check_error=True) self.assertTrue(int(ofport)) self.assertGreater(ofport, -1) def test_post_commit_vswitchd_completed_no_failures(self): self._add_br_and_test() @trpatch("vswitchd_has_completed", return_value=True) @trpatch("post_commit_failed_interfaces", return_value=["failed_if1"]) @trpatch("timeout_exceeded", return_value=False) def test_post_commit_vswitchd_completed_failures(self, *args): self.assertRaises(impl_idl.VswitchdInterfaceAddException, self._add_br) @trpatch("vswitchd_has_completed", return_value=False) def test_post_commit_vswitchd_incomplete_timeout(self, *args): # Due to timing issues we may rarely hit the global timeout, which # raises RuntimeError to match the vsctl implementation self.api.ovsdb_connection.timeout = 1 self.assertRaises((exc.TimeoutException, RuntimeError), self._add_br) ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/open_vswitch/fixtures.py0000666000175100017510000000146413236157620026714 0ustar zuulzuul00000000000000# 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 ovsdbapp.schema.open_vswitch import impl_idl from ovsdbapp.tests.functional.schema import fixtures class BridgeFixture(fixtures.ImplIdlFixture): api = impl_idl.OvsdbIdl create = 'add_br' delete = 'del_br' delete_id = 'name' ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/open_vswitch/__init__.py0000666000175100017510000000000013236157620026563 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/functional/schema/open_vswitch/test_common_db.py0000666000175100017510000000651313236157620030037 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat Inc. # # 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 ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.schema.open_vswitch import impl_idl from ovsdbapp.tests.functional import base from ovsdbapp.tests.functional.schema.open_vswitch import fixtures from ovsdbapp.tests import utils class TestBackendDb(base.FunctionalTestCase): schemas = ["Open_vSwitch"] def setUp(self): self.bridges = [ {'name': utils.get_rand_device_name(), 'datapath_type': 'fake1'}, {'name': utils.get_rand_device_name(), 'datapath_type': 'fake1'}, {'name': utils.get_rand_device_name(), 'datapath_type': 'fake2'} ] super(TestBackendDb, self).setUp() self.api = impl_idl.OvsdbIdl(self.connection) for bridge in self.bridges: self.useFixture(fixtures.BridgeFixture(bridge['name'])) for col, val in bridge.items(): if col == 'name': continue self.api.db_set( 'Bridge', bridge['name'], (col, val)).execute( check_error=True) def test_db_find(self): res = self.api.db_find( 'Bridge', ('datapath_type', '=', 'fake1'), columns=['name', 'datapath_type']).execute(check_error=True) self.assertItemsEqual(self.bridges[:2], res) def test_db_find_no_exist(self): res = self.api.db_find( 'Bridge', ('name', '=', 'unpossible')).execute(check_error=True) self.assertFalse(res) def test_db_find_rows(self): res = self.api.db_find_rows( 'Bridge', ('datapath_type', '=', 'fake1')).execute(check_error=True) self.assertItemsEqual( self.bridges[:2], [{'name': r.name, 'datapath_type': r.datapath_type} for r in res]) def test_db_list(self): res = self.api.db_list( 'Bridge', columns=('name', 'datapath_type')).execute(check_error=True) self.assertTrue(all(b in res for b in self.bridges)) def test_db_list_record(self): res = self.api.db_list( 'Bridge', [self.bridges[0]['name']], ('name', 'datapath_type')).execute(check_error=True) self.assertEqual(self.bridges[0], res[0]) def test_db_list_record_no_exist(self): cmd = self.api.db_list('Bridge', ['unpossible']) self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) def test_db_list_record_if_exsists(self): self.api.db_list('Bridge', ['unpossible']) def test_db_list_rows(self): res = self.api.db_list_rows('Bridge').execute(check_error=True) self.assertTrue( set(b['name'] for b in self.bridges).issubset( set(b.name for b in res))) ovsdbapp-0.9.1/ovsdbapp/tests/functional/base.py0000666000175100017510000000457313236157620022011 0ustar zuulzuul00000000000000# 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 atexit import os import tempfile from ovsdbapp.backend.ovs_idl import connection from ovsdbapp import constants from ovsdbapp.tests import base from ovsdbapp import venv class FunctionalTestCase(base.TestCase): _connections = None ovsvenv = venv.OvsOvnVenvFixture(tempfile.mkdtemp(), ovsdir=os.getenv('OVS_SRCDIR'), remove=not bool(os.getenv('KEEP_VENV'))) atexit.register(ovsvenv.cleanUp) ovsvenv.setUp() schema_map = {'Open_vSwitch': ovsvenv.ovs_connection, 'OVN_Northbound': ovsvenv.ovnnb_connection, 'OVN_Southbound': ovsvenv.ovnsb_connection} ovsvenvlog = None if os.getenv('KEEP_VENV') and os.getenv('VIRTUAL_ENV'): ovsvenvlog = open(os.path.join(os.getenv('VIRTUAL_ENV'), 'ovsvenv.%s' % os.getpid()), 'a+') atexit.register(ovsvenvlog.close) ovsvenvlog.write("%s\n" % ovsvenv.venv) @classmethod def venv_log(cls, val): if cls.ovsvenvlog: cls.ovsvenvlog.write("%s\n" % val) @property def connection(self): if len(self.schemas) == 1: return self.__class__._connections[self.schemas[0]] return self.__class__._connections @classmethod def set_connection(cls): if cls._connections is not None: return cls._connections = {} for schema in cls.schemas: cls._connections[schema] = cls.create_connection(schema) @classmethod def create_connection(cls, schema): idl = connection.OvsdbIdl.from_server(cls.schema_map[schema], schema) return connection.Connection(idl, constants.DEFAULT_TIMEOUT) def setUp(self): super(FunctionalTestCase, self).setUp() self.venv_log(self.id()) self.set_connection() ovsdbapp-0.9.1/ovsdbapp/tests/__init__.py0000666000175100017510000000000013236157620020451 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/base.py0000666000175100017510000000143213236157620017636 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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 oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" ovsdbapp-0.9.1/ovsdbapp/tests/unit/0000775000175100017510000000000013236157776017343 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/test_utils.py0000666000175100017510000000377413236157620022115 0ustar zuulzuul00000000000000# 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 netaddr from ovsdbapp.tests import base from ovsdbapp import utils class TestUtils(base.TestCase): def test_normalize_ip(self): good = [ ('4.4.4.4', '4.4.4.4'), ('10.0.0.0', '10.0.0.0'), ('123', '0.0.0.123'), ('2001:0db8:85a3:0000:0000:8a2e:0370:7334', '2001:db8:85a3::8a2e:370:7334') ] bad = ('256.1.3.2', 'bad', '192.168.1.1:80') for before, after in good: norm = utils.normalize_ip(before) self.assertEqual(after, norm, "%s does not match %s" % (after, norm)) for val in bad: self.assertRaises(netaddr.AddrFormatError, utils.normalize_ip, val) def test_normalize_ip_port(self): good = [ ('4.4.4.4:53', '4.4.4.4:53'), ('10.0.0.0:7', '10.0.0.0:7'), ('123:12', '0.0.0.123:12'), ('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80', '[2001:db8:85a3::8a2e:370:7334]:80') ] bad = ('1.2.3.4:0', '1.2.3.4:99000', '2001:0db8:85a3:0000:0000:8a2e:0370:7334:80') for before, after in good: norm = utils.normalize_ip_port(before) self.assertEqual(after, norm, "%s does not match %s" % (after, norm)) for val in bad: self.assertRaises(netaddr.AddrFormatError, utils.normalize_ip_port, val) ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/0000775000175100017510000000000013236157776020732 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/test_ovs_idl.py0000666000175100017510000000330513236157620023771 0ustar zuulzuul00000000000000# 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 from ovsdbapp.backend import ovs_idl from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.tests import base class FakeRow(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) class FakeTable(object): rows = {'fake-id-1': FakeRow(uuid='fake-id-1', name='Fake1')} indexes = [] class FakeBackend(ovs_idl.Backend): schema = "FakeSchema" tables = {'Faketable': FakeTable()} lookup_table = {'Faketable': idlutils.RowLookup('Faketable', 'name', None)} def start_connection(self, connection): pass class TestBackendOvsIdl(base.TestCase): def setUp(self): super(TestBackendOvsIdl, self).setUp() self.backend = FakeBackend(mock.Mock()) def test_lookup_found(self): row = self.backend.lookup('Faketable', 'Fake1') self.assertEqual('Fake1', row.name) def test_lookup_not_found(self): self.assertRaises(idlutils.RowNotFound, self.backend.lookup, 'Faketable', 'notthere') def test_lookup_not_found_default(self): row = self.backend.lookup('Faketable', 'notthere', "NOT_FOUND") self.assertEqual(row, "NOT_FOUND") ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/__init__.py0000666000175100017510000000000013236157620023017 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/ovs_idl/0000775000175100017510000000000013236157776022371 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/ovs_idl/test_vlog.py0000666000175100017510000000523313236157620024742 0ustar zuulzuul00000000000000# 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 ovsdbapp.backend.ovs_idl import fixtures from ovsdbapp.backend.ovs_idl import vlog from ovsdbapp.tests import base class TestOvsdbVlog(base.TestCase): def setUp(self): super(TestOvsdbVlog, self).setUp() self.useFixture(fixtures.OvsdbVlogFixture()) def test_vlog_patched(self): for log_fn in vlog.ALL_LEVELS: self.assertTrue(vlog.is_patched(log_fn)) def test_vlog_reset(self): vlog.reset_logger() for log_fn in vlog.ALL_LEVELS: self.assertFalse(vlog.is_patched(log_fn)) def test_vlog_patch_all_but_debug(self): vlog.reset_logger() removed_level = vlog.DEBUG levels = set(vlog.ALL_LEVELS) - set([removed_level]) vlog.use_python_logger(levels) for lvl in levels: self.assertTrue(vlog.is_patched(lvl)) self.assertFalse(vlog.is_patched(removed_level)) def _test_vlog_max_level_helper(self, max_level, patched_levels, unpatched_levels): vlog.reset_logger() vlog.use_python_logger(max_level=max_level) for lvl in patched_levels: self.assertTrue(vlog.is_patched(lvl)) for lvl in unpatched_levels: self.assertFalse(vlog.is_patched(lvl)) def test_vlog_max_level_WARN(self): max_level = vlog.WARN patched_levels = (vlog.CRITICAL, vlog.ERROR, vlog.WARN) unpatched_levels = (vlog.INFO, vlog.DEBUG) self._test_vlog_max_level_helper( max_level, patched_levels, unpatched_levels) def test_vlog_max_level_CRITICAL(self): max_level = vlog.CRITICAL patched_levels = (vlog.CRITICAL,) unpatched_levels = (vlog.ERROR, vlog.WARN, vlog.INFO, vlog.DEBUG) self._test_vlog_max_level_helper( max_level, patched_levels, unpatched_levels) def test_vlog_max_level_DEBUG(self): max_level = vlog.DEBUG patched_levels = (vlog.CRITICAL, vlog.ERROR, vlog.WARN, vlog.INFO, vlog.DEBUG) unpatched_levels = () self._test_vlog_max_level_helper( max_level, patched_levels, unpatched_levels) ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/ovs_idl/__init__.py0000666000175100017510000000000013236157620024456 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/ovs_idl/test_connection.py0000666000175100017510000000444513236157620026136 0ustar zuulzuul00000000000000# Copyright 2015, Red Hat, Inc. # # 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 threading from ovs import poller from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.tests import base class TestOVSNativeConnection(base.TestCase): @mock.patch.object(connection, 'TransactionQueue') def setUp(self, mock_trans_queue): super(TestOVSNativeConnection, self).setUp() self.idl = mock.Mock() self.mock_trans_queue = mock_trans_queue self.conn = connection.Connection(self.idl, timeout=1) self.mock_trans_queue.assert_called_once_with(1) @mock.patch.object(threading, 'Thread') @mock.patch.object(poller, 'Poller') @mock.patch.object(idlutils, 'wait_for_change') def test_start(self, mock_wait_for_change, mock_poller, mock_thread): self.idl.has_ever_connected.return_value = False self.conn.start() self.idl.has_ever_connected.assert_called_once() mock_wait_for_change.assert_called_once_with(self.conn.idl, self.conn.timeout) mock_poller.assert_called_once_with() mock_thread.assert_called_once_with(target=self.conn.run) mock_thread.return_value.setDaemon.assert_called_once_with(True) mock_thread.return_value.start.assert_called_once_with() def test_queue_txn(self): self.conn.start() self.conn.queue_txn('blah') self.conn.txns.put.assert_called_once_with('blah') class TestTransactionQueue(base.TestCase): def test_init(self): # a test to cover py34 failure during initialization (LP Bug #1580270) # make sure no ValueError: can't have unbuffered text I/O is raised connection.TransactionQueue() ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/ovs_idl/test_helpers.py0000666000175100017510000000220113236157620025425 0ustar zuulzuul00000000000000# Copyright 2015, Red Hat, Inc. # # 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 ovsdbapp.schema.open_vswitch import helpers from ovsdbapp.tests import base CONNECTION_TO_MANAGER_URI_MAP = ( ('unix:/path/to/file', 'punix:/path/to/file'), ('tcp:127.0.0.1:6640', 'ptcp:6640:127.0.0.1'), ('ssl:192.168.1.1:8080', 'pssl:8080:192.168.1.1')) class TestOVSNativeHelpers(base.TestCase): def test__connection_to_manager_uri(self): for conn_uri, expected in CONNECTION_TO_MANAGER_URI_MAP: self.assertEqual(expected, helpers._connection_to_manager_uri(conn_uri)) ovsdbapp-0.9.1/ovsdbapp/tests/unit/backend/ovs_idl/test_idlutils.py0000666000175100017510000001531113236157620025622 0ustar zuulzuul00000000000000# Copyright 2016, Mirantis Inc. # # 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 from ovsdbapp import api from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.tests import base class MockColumn(object): def __init__(self, name, type, is_optional=False, test_value=None): self.name = name self.type = mock.MagicMock( **{"key.type.name": type, "is_optional": mock.Mock(return_value=is_optional), }) # for test purposes only to operate with some values in condition_match # testcase self.test_value = test_value class MockTable(object): def __init__(self, name, *columns): # columns is a list of tuples (col_name, col_type) self.name = name self.columns = {c.name: c for c in columns} class MockRow(object): def __init__(self, table): self._table = table def __getattr__(self, attr): if attr in self._table.columns: return self._table.columns[attr].test_value return super(MockRow, self).__getattr__(attr) class MockCommand(api.Command): def __init__(self, result): self.result = result def execute(self, **kwargs): pass class TestIdlUtils(base.TestCase): def test_condition_match(self): """Make sure that the function respects the following: * if column type is_optional and value is a single element, value is transformed to a length-1-list * any other value is returned as it is, no type convertions """ table = MockTable("SomeTable", MockColumn("tag", "integer", is_optional=True, test_value=[42]), MockColumn("num", "integer", is_optional=True, test_value=[]), MockColumn("ids", "integer", is_optional=False, test_value=42), MockColumn("comments", "string", test_value=["a", "b", "c"]), MockColumn("status", "string", test_value="sorry for inconvenience")) row = MockRow(table=table) self.assertTrue(idlutils.condition_match(row, ("tag", "=", 42))) # optional types can be compared only as single elements self.assertRaises(ValueError, idlutils.condition_match, row, ("tag", "!=", [42])) # empty list comparison is ok for optional types though self.assertTrue(idlutils.condition_match(row, ("tag", "!=", []))) self.assertTrue(idlutils.condition_match(row, ("num", "=", []))) # value = [] may be compared to a single elem if optional column type self.assertTrue(idlutils.condition_match(row, ("num", "!=", 42))) # no type conversion for non optional types self.assertTrue(idlutils.condition_match(row, ("ids", "=", 42))) self.assertTrue(idlutils.condition_match( row, ("status", "=", "sorry for inconvenience"))) self.assertFalse(idlutils.condition_match( row, ("status", "=", "sorry"))) # bad types self.assertRaises(ValueError, idlutils.condition_match, row, ("ids", "=", "42")) self.assertRaises(ValueError, idlutils.condition_match, row, ("ids", "!=", "42")) self.assertRaises(ValueError, idlutils.condition_match, row, ("ids", "!=", {"a": "b"})) # non optional list types are kept as they are self.assertTrue(idlutils.condition_match( row, ("comments", "=", ["c", "b", "a"]))) # also true because list comparison is relaxed self.assertTrue(idlutils.condition_match( row, ("comments", "=", ["c", "b"]))) self.assertTrue(idlutils.condition_match( row, ("comments", "!=", ["d"]))) def test_db_replace_record_dict(self): obj = {'a': 1, 'b': 2} self.assertIs(obj, idlutils.db_replace_record(obj)) def test_db_replace_record_dict_cmd(self): obj = {'a': 1, 'b': MockCommand(2)} res = {'a': 1, 'b': 2} self.assertEqual(res, idlutils.db_replace_record(obj)) def test_db_replace_record_list(self): obj = [1, 2, 3] self.assertIs(obj, idlutils.db_replace_record(obj)) def test_db_replace_record_list_cmd(self): obj = [1, MockCommand(2), 3] res = [1, 2, 3] self.assertEqual(res, idlutils.db_replace_record(obj)) def test_db_replace_record_tuple(self): obj = (1, 2, 3) self.assertIs(obj, idlutils.db_replace_record(obj)) def test_db_replace_record_tuple_cmd(self): obj = (1, MockCommand(2), 3) res = (1, 2, 3) self.assertEqual(res, idlutils.db_replace_record(obj)) def test_db_replace_record(self): obj = "test" self.assertIs(obj, idlutils.db_replace_record(obj)) def test_db_replace_record_cmd(self): obj = MockCommand("test") self.assertEqual("test", idlutils.db_replace_record(obj)) @mock.patch('sys.platform', 'linux2') def test_row_by_record_linux(self): FAKE_RECORD = 'fake_record' mock_idl_ = mock.MagicMock() mock_table = mock.MagicMock( rows={mock.sentinel.row: mock.sentinel.row_value}) mock_idl_.tables = {mock.sentinel.table_name: mock_table} res = idlutils.row_by_record(mock_idl_, mock.sentinel.table_name, FAKE_RECORD) self.assertEqual(mock.sentinel.row_value, res) @mock.patch('sys.platform', 'win32') def test_row_by_record_win(self): FAKE_RECORD_GUID = '7b0f349d-5524-4d36-afff-5222b9fdee8c' mock_idl_ = mock.MagicMock() mock_table = mock.MagicMock( rows={mock.sentinel.row: mock.sentinel.row_value}) mock_idl_.tables = {mock.sentinel.table_name: mock_table} res = idlutils.row_by_record(mock_idl_, mock.sentinel.table_name, FAKE_RECORD_GUID) self.assertEqual(mock.sentinel.row_value, res) ovsdbapp-0.9.1/ovsdbapp/tests/unit/__init__.py0000666000175100017510000000000013236157620021430 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/schema/0000775000175100017510000000000013236157776020603 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/schema/__init__.py0000666000175100017510000000000013236157620022670 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/schema/open_vswitch/0000775000175100017510000000000013236157776023313 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/schema/open_vswitch/test_impl_idl.py0000666000175100017510000000270013236157620026502 0ustar zuulzuul00000000000000# Copyright (c) 2016 Red Hat, Inc. # # 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 testtools from ovsdbapp import exceptions from ovsdbapp.schema.open_vswitch import impl_idl from ovsdbapp.tests import base class TransactionTestCase(base.TestCase): def test_commit_raises_exception_on_timeout(self): transaction = impl_idl.OvsVsctlTransaction(mock.sentinel, mock.Mock(), 1) with testtools.ExpectedException(exceptions.TimeoutException): transaction.commit() def test_post_commit_does_not_raise_exception(self): with mock.patch.object(impl_idl.OvsVsctlTransaction, "do_post_commit", side_effect=Exception): transaction = impl_idl.OvsVsctlTransaction(mock.sentinel, mock.Mock(), 0) transaction.post_commit(mock.Mock()) ovsdbapp-0.9.1/ovsdbapp/tests/unit/schema/open_vswitch/__init__.py0000666000175100017510000000000013236157620025400 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp/tests/unit/test_api.py0000666000175100017510000000352013236157620021513 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat, Inc. # # 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 testtools from ovsdbapp import api from ovsdbapp.tests import base class FakeTransaction(object): def __enter__(self): return self def __exit__(self, exc_type, exc_val, tb): self.commit() def commit(self): """Serves just for mock.""" class TestingAPI(api.API): def create_transaction(self, check_error=False, log_errors=True, **kwargs): return FakeTransaction() TestingAPI.__abstractmethods__ = set() class TransactionTestCase(base.TestCase): def setUp(self): super(TransactionTestCase, self).setUp() self.api = TestingAPI() mock.patch.object(FakeTransaction, 'commit').start() def test_transaction_nested(self): with self.api.transaction() as txn1: with self.api.transaction() as txn2: self.assertIs(txn1, txn2) txn1.commit.assert_called_once_with() def test_transaction_no_nested_transaction_after_error(self): class TestException(Exception): pass with testtools.ExpectedException(TestException): with self.api.transaction() as txn1: raise TestException() with self.api.transaction() as txn2: self.assertIsNot(txn1, txn2) ovsdbapp-0.9.1/ovsdbapp/tests/unit/test_event.py0000666000175100017510000000207313236157620022065 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat, Inc. # # 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 ovsdbapp import event from ovsdbapp.tests import base class TestEvent(event.RowEvent): def __init__(self): super(TestEvent, self).__init__( (self.ROW_CREATE,), "FakeTable", (("col", "=", "val"),)) def run(self): pass def matches(self): pass class TestRowEvent(base.TestCase): def test_compare_stop_event(self): r = TestEvent() self.assertFalse((r, "fake", "fake", "fake") == event.STOP_EVENT) ovsdbapp-0.9.1/ChangeLog0000664000175100017510000001642613236157775015166 0ustar zuulzuul00000000000000CHANGES ======= 0.9.1 ----- * Ensure idl.run() called on TRY\_AGAIN * Update UPPER\_CONSTRAINTS\_FILE for stable/queens * Update .gitreview for stable/queens 0.9.0 ----- * Don't throw NotConnectedError in queue\_txn * Updated from global requirements 0.8.0 ----- * Don't wait on TRY\_AGAIN when calling commit\_block() * Add DNS APIs 0.7.0 ----- * Add set\_column(s) for ovs\_idl backend * Change parent to parent\_name in lsp\_add * Don't add non-strings to external\_ids 0.6.0 ----- * Allow to stop and restart connections in Backend * Allow use of installed OVS if OVS\_SRCDIR not set * Updated from global requirements * Updated from global requirements * Don't return garbage if table has no index col * Have debug\_venv use OVN venv * nb\_schema: Use normalize\_ip\_port even for lb ips * Add backward compatible connection attribute * Fix BridgeFixture * Move ovsdb\_connection definition to Backend * Add native IDL tests from neutron * Updated from global requirements 0.5.0 ----- * Add RowView versions of db\_find and db\_list * Add OVN\_Southbound API support * venv: Change --enable-dummy default to override * Updated from global requirements * Don't create a sandbox chassis by default * Update reno for stable/pike * venv: Split OvsVenvFixture to OVS and OVN * Return False when comparing a RowEvent to something else * Add RowEvent mechanism from networking-ovn * Add command for deleting row column values * Update the documentation link for doc migration * Adding {LS|LSP|DHCP\_options}\_get to OVN Northbound API * Removes unnecessary utf-8 encoding * Drop MANIFEST.in - it's not needed by pbr * There is no documentation for ovsdbapp * Add ability to debug OVS virtual environments * Modify LSP List command to return all ports * Fix \_ls\_add-based tests * rearrange existing documentation to fit the new standard layout * Switch from oslosphinx to openstackdocstheme * Enable warning-is-error in doc build * Add OVN\_Northbound API LR, LRP, and LB commands * Add Windows support when using eventlet with monkey\_patch * Updated from global requirements * Add OVN Northbound API for LS, LSP, and DHCP * pylint: Disable duplicate-code for check * The python 3.5 is added * Enable pylint 1.4.5 * Updated from global requirements * Remove ignoring \_ builtin usage for pep8 * Fix vlog level filtering * Fix condition\_map type checking for strings * Make schema-agnostic db commands part of base API * Updated from global requirements * Updated from global requirements 0.4.0 ----- * Updated from global requirements * Pass the Idl directly to Connection * Allow choosing vlog levels to patch and restoring the vlog * Remove all the deprecated methods and parameters from Connection * Add a description to README.rst * Remove empty unit test directory * Break out BaseCommand into backend.ovs\_idl * Fixes retrieving row\_by\_record for ports on Windows * Ignore .testrepository and editor(s) files 0.3.0 ----- * Use neutron-lib's tox\_install.sh * Remove get\_schema\_helper retry/try\_add\_manager parameters * Pass a connection to OvsdbIdl.\_\_init\_\_() * Updated from global requirements * Add unit tests from Neutron 0.2.0 ----- * Refactor to give other schemas a place to live * Updated from global requirements * Fix Python 3 compatibility in idlutils * Fix all broken jobs 0.1.0 ----- * Add some bridge and port functional tests * Add openvswitch as a bindep dependency * Add missing tenacity requirement * Updated from global requirements * Set OVS inactivity\_probe to vsctl\_timeout when adding manager * Add .gitreview * raise TimeoutException from exceptions not api * Re-add TimeoutException * Fix pep8/cookiecutter test running * Add cookiecutter output * Add requirements.txt * Fix new base exception name * Update changes to mention previous API.get() change * Add setup.py * Remove oslo\_utils dependency * Remove oslo\_log dependency * Neutron should call vlog.use\_python\_logger itself * Remove the CLI implementation * Remove neutron/oslo from helpers * Remove oslo\_config dependency * Remove neutron.\_i18n usage * Remove neutron\_lib dependency * Remove oslo uuidutils dependency * Fix imports for new project location * Add 'ovsdbapp/' from commit '10e3bdac29a6be24d2a53e78c9a00b2a8f0f6d07' * Initial commit * Clean up ovsdb-native's use of verify() * Move ovsdb\_nested transaction to ovs\_lib * Support ovsdb-client monitor with remote connection * Replaces uuid.uuid4 with uuidutils.generate\_uuid() * Fix python3 issues with devstack * ovsdb: don't erase existing ovsdb managers * Refactor OVSDB native lib to be more customizable * Handle db\_add in transaction for new objects * Log OVS IDL library errors to neutron logs * Replace retrying with tenacity * Generate OVSDB schema helper in a separate method * set\_db\_attribute differs between vsctl and native * Allow to override Idl class in OVSDB Connection * Add db\_add to OVSDB API * Handle uuid references within an ovsdb transaction * Fix wrong use of six.moves.queue.get() * Use row.uuid as getattr works for inserted row * Fix a spelling error * Fix module import for ovs\_vsctl\_timeout option * Refactoring config options of agent/common/ovs\_lib * Add in missing translations for exceptions * Wait for vswitchd to add interfaces in native ovsdb * Pass timeout in milliseconds to timer\_wait * enable OVSDB native interface by default * functional: fix OVSFW failure with native OVSDB api * ovsdb: Don't let block() wait indefinitely * unit: fix ValueError on TransactionQueue init with py34 * Allow OVSDB Connection to register certain tables * Use exceptions from neutron-lib * idlutils: add in missing translations * Fix module's import order * Python3: Fix using dictionary keys() as list * Fixes typos Openstack -> OpenStack * Make sure datapath\_type is updated on bridges changed * Wrong usage of "an" * Use \_ from neutron.\_i18n * Move i18n to \_i18n, as per oslo\_i18n guidelines * Automatically generate neutron core configuration files * Fix typo for OVSDB * Log error instead of exception trace * Fix some reST field lists in docstrings * Fix misuse of log marker functions in neutron * Do not use log hints for exceptions * Just call set-manager if connecting fails * Check idl.run() return value before blocking * ovsdb: Fix a few docstring * Add config option to specify ovs datapath * Add new ovs DB API to inquire interfaces name list in a bridge * ovs\_lib: Fix native implementation of db\_list * OVS native DBListcommand if\_exists support * Add Create/Destroy API to OVS QoS BW Limiting * ovsdb: attempt to enable connection\_uri for native impl on startup * ovsdb: session.rpc never initialized * Make \_val\_to\_py and \_py\_to\_val not private * Python 3: do not index a dict\_values object * Python 3: use six.string\_types instead of basestring * Allow users to run 'tox -epy34' * Correct typo for matching non-dict ovsdb rows * Enhance OVSDB Transaction timeout configuration * allow OVSDB connection schema to be configurable * Add OVSDB connection as a parameter to the transaction * Fix native OVSDB db\_get handling for UUID columns * OVSDB python binding should use row.delete() to remove rows * Handle non-index lookups in native OVSDB backend * Store and log correct exception info * OVS agent support on Hyper-V * Moves ovs\_lib to agent/common * Add native OVSDB implementation of OVSDB API * Migrate to oslo.log * Remove root\_helper arg for ovs\_lib * Reorganize OVSDB API ovsdbapp-0.9.1/LICENSE0000666000175100017510000002363713236157620014412 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ovsdbapp-0.9.1/.testr.conf0000666000175100017510000000062313236157620015461 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_LOG_CAPTURE=${OS_LOG_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \ ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./ovsdbapp/tests} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list ovsdbapp-0.9.1/requirements.txt0000666000175100017510000000052413236157633016663 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. fixtures>=3.0.0 # Apache-2.0/BSD netaddr>=0.7.18 # BSD ovs>=2.7.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.10.0 # MIT ovsdbapp-0.9.1/AUTHORS0000664000175100017510000000341213236157775014453 0ustar zuulzuul00000000000000Aaron Rosen Adelina Tuvenie Akihiro Motoki Akihiro Motoki Alin Balutoiu Aradhana Singh Arundhati Surpur Bhagyashri Shewale Bo Wang Boden R Cyril Roelandt Daniel Alvarez Davanum Srinivas Dong Jun Doug Wiegley Emma Foley Gal Sagie Gary Kotton Henry Gessau Hong Hui Xiao Huan Xie IWAMOTO Toshihiro Ihar Hrachyshka Inessa Vasilevskaya Isaku Yamahata Jakub Libosvar Kevin Benton Lucas Alvares Gomes Luong Anh Tuan Marcin Mirecki Martin Hickey Numan Siddique Omer Anson OpenStack Release Bot Richard Theis Sean Mooney Terry Wilson Terry Wilson YAMAMOTO Takashi Yalei Wang Yi Zhao Zuul chenxing gengchc2 hgangwx lzklibj rossella yan.haifeng ovsdbapp-0.9.1/README.rst0000666000175100017510000000112413236157620015057 0ustar zuulzuul00000000000000=============================== ovsdbapp =============================== A library for creating OVSDB applications The ovdsbapp library is useful for creating applications that communicate via Open_vSwitch's OVSDB protocol (https://tools.ietf.org/html/rfc7047). It wraps the Python 'ovs' and adds an event loop and friendly transactions. * Free software: Apache license * Source: http://git.openstack.org/cgit/openstack/ovsdbapp * Bugs: http://bugs.launchpad.net/ovsdbapp Features -------- * An thread-based event loop for using ovs.db.Idl * Transaction support * Native OVSDB communication ovsdbapp-0.9.1/releasenotes/0000775000175100017510000000000013236157776016075 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/releasenotes/source/0000775000175100017510000000000013236157776017375 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/releasenotes/source/index.rst0000666000175100017510000000024713236157633021233 0ustar zuulzuul00000000000000============================================ ovsdbapp Release Notes ============================================ .. toctree:: :maxdepth: 1 unreleased pike ovsdbapp-0.9.1/releasenotes/source/_static/0000775000175100017510000000000013236157776021023 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/releasenotes/source/_static/.placeholder0000666000175100017510000000000013236157620023262 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/releasenotes/source/unreleased.rst0000666000175100017510000000016013236157620022241 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ovsdbapp-0.9.1/releasenotes/source/conf.py0000666000175100017510000002172113236157620020665 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # 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. # Glance Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/ovsdbapp' bug_project = 'ovsdbapp' bug_tag = '' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'ovsdbapp Release Notes' copyright = u'2016, OpenStack Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'GlanceReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'GlanceReleaseNotes.tex', u'Glance Release Notes Documentation', u'Glance Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'glancereleasenotes', u'Glance Release Notes Documentation', [u'Glance Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'GlanceReleaseNotes', u'Glance Release Notes Documentation', u'Glance Developers', 'GlanceReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ovsdbapp-0.9.1/releasenotes/source/pike.rst0000666000175100017510000000021713236157620021045 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ovsdbapp-0.9.1/releasenotes/source/_templates/0000775000175100017510000000000013236157776021532 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013236157620023771 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/releasenotes/notes/0000775000175100017510000000000013236157776017225 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/releasenotes/notes/configure-ovsdb-manager-a29a148b241a125e.yaml0000666000175100017510000000067213236157620026726 0ustar zuulzuul00000000000000--- other: - | ovsdbapp must get granted access to ovsdb by adding a new Manager via ovs-vsctl command. The command must be executed with root privileges. An example of how to create a new manager for localhost on port 6640 is as follows: .. code-block:: console sudo ovs-vsctl --id=@manager \ -- create Manager target=\"ptcp:6640:127.0.0.1\" \\ -- add Open_vSwitch . manager_options @manager ovsdbapp-0.9.1/releasenotes/notes/.placeholder0000666000175100017510000000000013236157620021464 0ustar zuulzuul00000000000000ovsdbapp-0.9.1/HACKING.rst0000666000175100017510000000023613236157620015171 0ustar zuulzuul00000000000000ovsdbapp Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ovsdbapp-0.9.1/babel.cfg0000666000175100017510000000002113236157620015111 0ustar zuulzuul00000000000000[python: **.py] ovsdbapp-0.9.1/doc/0000775000175100017510000000000013236157776014151 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/doc/source/0000775000175100017510000000000013236157776015451 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/doc/source/index.rst0000666000175100017510000000074513236157620017306 0ustar zuulzuul00000000000000.. ovsdbapp documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. the main title comes from README.rst .. include:: ../../README.rst Contents -------- .. toctree:: :maxdepth: 2 install/index user/index contributor/index Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` ovsdbapp-0.9.1/doc/source/user/0000775000175100017510000000000013236157776016427 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/doc/source/user/index.rst0000666000175100017510000000011513236157620020253 0ustar zuulzuul00000000000000======== Usage ======== To use ovsdbapp in a project:: import ovsdbapp ovsdbapp-0.9.1/doc/source/conf.py0000777000175100017510000000507313236157620016746 0ustar zuulzuul00000000000000# 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 os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'openstackdocstheme', ] # openstackdocstheme options repository_name = 'openstack/ovsdbapp' bug_project = 'ovsdbapp' bug_tag = '' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'ovsdbapp' copyright = u'2016, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' # html_static_path = ['static'] html_theme = 'openstackdocs' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} ovsdbapp-0.9.1/doc/source/install/0000775000175100017510000000000013236157776017117 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/doc/source/install/index.rst0000666000175100017510000000030113236157620020740 0ustar zuulzuul00000000000000============ Installation ============ At the command line:: $ pip install ovsdbapp Or, if you have virtualenvwrapper installed:: $ mkvirtualenv ovsdbapp $ pip install ovsdbapp ovsdbapp-0.9.1/doc/source/contributor/0000775000175100017510000000000013236157776020023 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/doc/source/contributor/index.rst0000666000175100017510000000011613236157620021650 0ustar zuulzuul00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst ovsdbapp-0.9.1/ovsdbapp.egg-info/0000775000175100017510000000000013236157776016714 5ustar zuulzuul00000000000000ovsdbapp-0.9.1/ovsdbapp.egg-info/PKG-INFO0000664000175100017510000000310113236157775020003 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: ovsdbapp Version: 0.9.1 Summary: A library for creating OVSDB applications Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: =============================== ovsdbapp =============================== A library for creating OVSDB applications The ovdsbapp library is useful for creating applications that communicate via Open_vSwitch's OVSDB protocol (https://tools.ietf.org/html/rfc7047). It wraps the Python 'ovs' and adds an event loop and friendly transactions. * Free software: Apache license * Source: http://git.openstack.org/cgit/openstack/ovsdbapp * Bugs: http://bugs.launchpad.net/ovsdbapp Features -------- * An thread-based event loop for using ovs.db.Idl * Transaction support * Native OVSDB communication Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.5 ovsdbapp-0.9.1/ovsdbapp.egg-info/requires.txt0000664000175100017510000000011213236157775021305 0ustar zuulzuul00000000000000fixtures>=3.0.0 netaddr>=0.7.18 ovs>=2.7.0 pbr!=2.1.0,>=2.0.0 six>=1.10.0 ovsdbapp-0.9.1/ovsdbapp.egg-info/SOURCES.txt0000664000175100017510000000734413236157776020610 0ustar zuulzuul00000000000000.pylintrc .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst TESTING.rst babel.cfg bindep.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/user/index.rst ovsdbapp/CHANGES ovsdbapp/__init__.py ovsdbapp/api.py ovsdbapp/constants.py ovsdbapp/event.py ovsdbapp/exceptions.py ovsdbapp/utils.py ovsdbapp/venv.py ovsdbapp.egg-info/PKG-INFO ovsdbapp.egg-info/SOURCES.txt ovsdbapp.egg-info/dependency_links.txt ovsdbapp.egg-info/not-zip-safe ovsdbapp.egg-info/pbr.json ovsdbapp.egg-info/requires.txt ovsdbapp.egg-info/top_level.txt ovsdbapp/backend/__init__.py ovsdbapp/backend/ovs_idl/__init__.py ovsdbapp/backend/ovs_idl/command.py ovsdbapp/backend/ovs_idl/connection.py ovsdbapp/backend/ovs_idl/event.py ovsdbapp/backend/ovs_idl/fixtures.py ovsdbapp/backend/ovs_idl/idlutils.py ovsdbapp/backend/ovs_idl/rowview.py ovsdbapp/backend/ovs_idl/transaction.py ovsdbapp/backend/ovs_idl/vlog.py ovsdbapp/backend/ovs_idl/common/__init__.py ovsdbapp/backend/ovs_idl/common/base_connection_utils.py ovsdbapp/backend/ovs_idl/linux/__init__.py ovsdbapp/backend/ovs_idl/linux/connection_utils.py ovsdbapp/backend/ovs_idl/windows/__init__.py ovsdbapp/backend/ovs_idl/windows/connection_utils.py ovsdbapp/backend/ovs_idl/windows/utils.py ovsdbapp/schema/__init__.py ovsdbapp/schema/open_vswitch/__init__.py ovsdbapp/schema/open_vswitch/api.py ovsdbapp/schema/open_vswitch/commands.py ovsdbapp/schema/open_vswitch/helpers.py ovsdbapp/schema/open_vswitch/impl_idl.py ovsdbapp/schema/ovn_northbound/__init__.py ovsdbapp/schema/ovn_northbound/api.py ovsdbapp/schema/ovn_northbound/commands.py ovsdbapp/schema/ovn_northbound/impl_idl.py ovsdbapp/schema/ovn_southbound/__init__.py ovsdbapp/schema/ovn_southbound/api.py ovsdbapp/schema/ovn_southbound/commands.py ovsdbapp/schema/ovn_southbound/impl_idl.py ovsdbapp/tests/__init__.py ovsdbapp/tests/base.py ovsdbapp/tests/utils.py ovsdbapp/tests/functional/__init__.py ovsdbapp/tests/functional/base.py ovsdbapp/tests/functional/schema/__init__.py ovsdbapp/tests/functional/schema/fixtures.py ovsdbapp/tests/functional/schema/open_vswitch/__init__.py ovsdbapp/tests/functional/schema/open_vswitch/fixtures.py ovsdbapp/tests/functional/schema/open_vswitch/test_common_db.py ovsdbapp/tests/functional/schema/open_vswitch/test_impl_idl.py ovsdbapp/tests/functional/schema/ovn_northbound/__init__.py ovsdbapp/tests/functional/schema/ovn_northbound/fixtures.py ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py ovsdbapp/tests/functional/schema/ovn_southbound/__init__.py ovsdbapp/tests/functional/schema/ovn_southbound/fixtures.py ovsdbapp/tests/functional/schema/ovn_southbound/test_impl_idl.py ovsdbapp/tests/unit/__init__.py ovsdbapp/tests/unit/test_api.py ovsdbapp/tests/unit/test_event.py ovsdbapp/tests/unit/test_utils.py ovsdbapp/tests/unit/backend/__init__.py ovsdbapp/tests/unit/backend/test_ovs_idl.py ovsdbapp/tests/unit/backend/ovs_idl/__init__.py ovsdbapp/tests/unit/backend/ovs_idl/test_connection.py ovsdbapp/tests/unit/backend/ovs_idl/test_helpers.py ovsdbapp/tests/unit/backend/ovs_idl/test_idlutils.py ovsdbapp/tests/unit/backend/ovs_idl/test_vlog.py ovsdbapp/tests/unit/schema/__init__.py ovsdbapp/tests/unit/schema/open_vswitch/__init__.py ovsdbapp/tests/unit/schema/open_vswitch/test_impl_idl.py releasenotes/notes/.placeholder releasenotes/notes/configure-ovsdb-manager-a29a148b241a125e.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/coding-checks.sh tools/debug_venv tools/debug_venv.py tools/test-setup.sh tools/tox_install.shovsdbapp-0.9.1/ovsdbapp.egg-info/top_level.txt0000664000175100017510000000001113236157775021435 0ustar zuulzuul00000000000000ovsdbapp ovsdbapp-0.9.1/ovsdbapp.egg-info/pbr.json0000664000175100017510000000005613236157775020372 0ustar zuulzuul00000000000000{"git_version": "c3288ae", "is_release": true}ovsdbapp-0.9.1/ovsdbapp.egg-info/dependency_links.txt0000664000175100017510000000000113236157775022761 0ustar zuulzuul00000000000000 ovsdbapp-0.9.1/ovsdbapp.egg-info/not-zip-safe0000664000175100017510000000000113236157743021134 0ustar zuulzuul00000000000000 ovsdbapp-0.9.1/bindep.txt0000666000175100017510000000044213236157620015374 0ustar zuulzuul00000000000000# This file contains runtime (non-python) dependencies # More info at: http://docs.openstack.org/infra/bindep/readme.html openvswitch [platform:rpm test] openvswitch-switch [platform:dpkg test] curl [test] autoconf [test] automake [test] libtool [test] gcc [test] make [test] patch [test]