ovsdbapp-1.1.0/0000775000175000017500000000000013641423532013354 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/AUTHORS0000664000175000017500000000530413641423532014426 0ustar zuulzuul00000000000000Aaron Rosen Abhiram Sangana Adelina Tuvenie Akihiro Motoki Akihiro Motoki Alin Balutoiu Andreas Jaeger Aradhana Singh Arundhati Surpur Bernard Cafarelli Bhagyashri Shewale Bo Wang Boden R Chuck Short Corey Bryant Cyril Roelandt Daniel Alvarez Davanum Srinivas Dong Jun Doug Hellmann Doug Wiegley Emma Foley Flavio Fernandes Frank Wang Gal Sagie Gary Kotton Guoshuai Li 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 Maciej Józefczyk Marcin Mirecki Martin Hickey Miguel Duarte Barroso Nate Johnston Numan Siddique Omer Anson OpenStack Release Bot Petr Horáček Richard Theis Rodolfo Alonso Hernandez Sean Mooney Ted Elhourani Terry Wilson Terry Wilson Vu Cong Tuan YAMAMOTO Takashi Yalei Wang Yi Zhao Yunxiang Tao caoyuan chenxing gengchc2 hgangwx huang.zhiping ljhuang lzklibj melissaml pengyuesheng qingszhao reedip rossella yan.haifeng zhangzs zhouguowei ovsdbapp-1.1.0/ChangeLog0000664000175000017500000002651013641423532015132 0ustar zuulzuul00000000000000CHANGES ======= 1.1.0 ----- * Call row.verify() when updating map columns with db\_set * Do not try to execute transaction if command list is empty 1.0.0 ----- * More robustly handle venv ovs/ovn paths * Log invalid address values in lsp\_set\_addresses * Make it possible to reference a newly created object within a transaction * Make ovndir configurable * Drop python 2 support and testing * Fix debug\_venv for ovn split * Add consts for OVN install path * Switch to Ussuri jobs * ovn\_northbound: make all list commands ReadOnlyCommand 0.18.0 ------ * Handle unset OVN\_SRCDIR * Default testenv to basepython = python3 * Fix building OVS after python2 removal * Change EAFP to LBYL style in nested transaction * Handle the ovs/ovn split * Update master for stable/train * Help with troubleshooting failures from venv.py * PDF documentation build * Update Zuul jobs: Enable docs publishing 0.17.0 ------ * Add HA Chassis Group related commands * Fix the url, change http to https * Add commands to set and get LRP options * Add Python 3 Train unit tests * Sync Sphinx requirement * Update the UPPER\_CONSTRAINTS\_FILE for tox 0.16.0 ------ * Replace openstack repository by opendev * Move WaitForPortBindingEvent out from testing code * Make Event logging more useful by default * TrivialFix: Fix the chassis\_del comment in Southbound API * Change the order of parameters for lsp\_bind method * Allow match\_ip in LrNatDelCommand to be IP network * Add missing tox environment "functional-py36" * OpenDev Migration Patch * add an option to let the user choose the right time to start connection * Make it possible to opt out of nested transactions * Update master for stable/stein * Add db\_create\_row method * Add mock.patch.stopall cleanup handler to base test class * Mock Thread for both connection tests * Remove vtep-related code from venv testing * modify RowEvent hash key * Make event debug log more useful * Pass kwargs from execute to transaction() * Switch functional and tempest jobs to python3 * Bump appdirs lower constraint * Break out match\_fn from matches 0.15.0 ------ * Convert base commands to ReadOnlyCommand * Allow read-only Commands to bypass txns in execute() * Update home-page 0.14.0 ------ * Attempt to fetch the schema from any remote * Fix a typo in docstring * Change openstack-dev to openstack-discuss * Add Gateway\_Chassis support * Group tests in same class to run in same group * Fix functional tests timeout race condition * Migrate tempest job to zuul v3 native * Expand retry behavior to cover other python-ovs methods * Add WaitEvent to the API 0.13.0 ------ * ut: Patch get\_ident for race transaction test * Move ovsdbapp jobs to its tree * Make nested transaction thread aware * NBDB API: Add param if\_exists for methods using db\_remove() * add python 3.6 unit test job * import zuul job settings from project-config * Check for oslo library usage * fix tox python3 overrides * Pass posargs to pylint, not flake8 * update pylint to 1.9.2 * Remove the oslo-utils dependency * Update reno for stable/rocky * Update reno for stable/rocky * Ensure timeout on queueing transaction 0.12.0 ------ * Use api.lookup instead of idlutils.row\_by\_record * Make it possible to run functional tests again * Switch to stestr * Fix python3 compat with debug\_venv.py * Port Group's letfovers * Add Port Group ACL commands 0.11.0 ------ * Add Port\_Group commands * Improve DbListCommand operation from O(n^2) to O(n) * Add QoS command for ovn northbound db * add lower-constraints job * Change external\_ids to columns, in dns\_add API * Updated from global requirements * Transaction: Handle NOT\_LOCKED status * TOX/pep8: Enforce the use of python2 * Fix spelling error * Updated from global requirements * Update reno for stable/queens * Ensure idl.run() called on TRY\_AGAIN * Updated from global requirements * Updated from global requirements * Add LrGet command * Update OvsdbConnectionUnavailable error message * Updated from global requirements * Avoid tox\_install.sh for constraints support * Document \*\_extenal\_ids methods in open\_vswitch/api.py * Display attempt number during transaction commit * Extend transaction with multiple commands * IP should be optional in lsp\_set\_addresses * Support setting of interface external-ids 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-1.1.0/test-requirements.txt0000664000175000017500000000074513641423405017622 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 oslotest>=3.2.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 pylint==1.9.2 # GPLv2 stestr>=2.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT ovsdbapp-1.1.0/bindep.txt0000664000175000017500000000044213641423405015355 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] ovsdbapp-1.1.0/.pylintrc0000664000175000017500000000454613641423405015231 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, inconsistent-return-statements, catching-non-exception, using-constant-test, 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-1.1.0/TESTING.rst0000664000175000017500000000377213641423405015233 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-1.1.0/.stestr.conf0000664000175000017500000000010713641423405015622 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH} top_dir=./ group_regex=([^\.]+\.)+ ovsdbapp-1.1.0/ovsdbapp.egg-info/0000775000175000017500000000000013641423532016664 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp.egg-info/pbr.json0000664000175000017500000000005613641423532020343 0ustar zuulzuul00000000000000{"git_version": "85d6323", "is_release": true}ovsdbapp-1.1.0/ovsdbapp.egg-info/dependency_links.txt0000664000175000017500000000000113641423532022732 0ustar zuulzuul00000000000000 ovsdbapp-1.1.0/ovsdbapp.egg-info/SOURCES.txt0000664000175000017500000001000013641423532020537 0ustar zuulzuul00000000000000.pylintrc .stestr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst TESTING.rst babel.cfg bindep.txt lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt 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/event.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/notes/drop-py27-support-c426980520444bfa.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_templates/.placeholder tools/coding-checks.sh tools/debug_venv tools/debug_venv.py tools/setup-ovs.sh tools/test-setup.sh zuul.d/ovsdbapp-jobs.yaml zuul.d/project.yamlovsdbapp-1.1.0/ovsdbapp.egg-info/not-zip-safe0000664000175000017500000000000113641423532021112 0ustar zuulzuul00000000000000 ovsdbapp-1.1.0/ovsdbapp.egg-info/PKG-INFO0000664000175000017500000000263313641423532017765 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: ovsdbapp Version: 1.1.0 Summary: A library for creating OVSDB applications Home-page: https://pypi.org/project/ovsdbapp/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: 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: https://opendev.org/openstack/ovsdbapp/ * Bugs: https://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 :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 ovsdbapp-1.1.0/ovsdbapp.egg-info/top_level.txt0000664000175000017500000000001113641423532021406 0ustar zuulzuul00000000000000ovsdbapp ovsdbapp-1.1.0/ovsdbapp.egg-info/requires.txt0000664000175000017500000000011213641423532021256 0ustar zuulzuul00000000000000fixtures>=3.0.0 netaddr>=0.7.18 ovs>=2.8.0 pbr!=2.1.0,>=2.0.0 six>=1.10.0 ovsdbapp-1.1.0/PKG-INFO0000664000175000017500000000263313641423532014455 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: ovsdbapp Version: 1.1.0 Summary: A library for creating OVSDB applications Home-page: https://pypi.org/project/ovsdbapp/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: 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: https://opendev.org/openstack/ovsdbapp/ * Bugs: https://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 :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 ovsdbapp-1.1.0/requirements.txt0000664000175000017500000000052413641423405016640 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.8.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.10.0 # MIT ovsdbapp-1.1.0/lower-constraints.txt0000664000175000017500000000152513641423405017614 0ustar zuulzuul00000000000000alabaster==0.7.10 appdirs==1.4.3 astroid==1.3.8 Babel==2.3.4 coverage==4.0 docutils==0.11 dulwich==0.15.0 extras==1.0.0 fixtures==3.0.0 flake8==2.5.5 future==0.16.0 hacking==0.12.0 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 keystoneauth1==3.4.0 linecache2==1.0.0 logilab-common==1.4.1 MarkupSafe==1.0 mccabe==0.2.1 mock==2.0.0 mox3==0.20.0 netaddr==0.7.18 openstackdocstheme==1.18.1 os-client-config==1.28.0 os-testr==1.0.0 oslo.utils==3.33.0 oslotest==3.2.0 ovs==2.8.0 pbr==2.0.0 pep8==1.5.7 pyflakes==0.8.1 Pygments==2.2.0 pylint==1.9.2 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 reno==2.5.0 requests==2.14.2 requestsexceptions==1.2.0 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 stevedore==1.20.0 stestr==2.0.0 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 ovsdbapp-1.1.0/LICENSE0000664000175000017500000002363713641423405014373 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-1.1.0/HACKING.rst0000664000175000017500000000070413641423405015152 0ustar zuulzuul00000000000000ovsdbapp Style Commandments =========================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on ovsdbapp-specific Commandments ------------------------------ - ovsdbapp is intended to be a simple wrapper on top of python-ovs. As such, it must build and be deployable without any OpenStack dependencies (oslo projects included). It does currently use oslo.test for testing. ovsdbapp-1.1.0/tox.ini0000664000175000017500000000360113641423405014666 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py36,py37,pypy,pep8 skipsdist = True [testenv] usedevelop = True setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning OS_TEST_PATH=./ovsdbapp/tests/unit install_command = pip install {opts} {packages} deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run --slowest {posargs} [testenv:pep8] commands = flake8 {toxinidir}/tools/coding-checks.sh --all '{posargs}' [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py test --coverage --testr-args='{posargs}' [testenv:docs] deps = -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html -d doc/build/doctrees doc/source doc/build/html [testenv:pdf-docs] envdir = {toxworkdir}/docs deps = {[testenv:docs]deps} whitelist_externals = make commands = sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] deps = -r{toxinidir}/doc/requirements.txt 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 OVN_SRCDIR={envdir}/src/ovn OVS_BRANCH={env:OVS_BRANCH:} OVN_BRANCH={env:OVN_BRANCH:} passenv = KEEP_VENV commands = {toxinidir}/tools/setup-ovs.sh {[testenv]commands} [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 [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt ovsdbapp-1.1.0/tools/0000775000175000017500000000000013641423532014514 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/tools/debug_venv0000775000175000017500000000335613641423405016574 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 $VIRTUAL_ENV/src/ovn ovsdbapp-1.1.0/tools/coding-checks.sh0000775000175000017500000000350713641423405017560 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;; -O|--oslo) oslo=1;; -a|--all) oslo=1; pylint=1;; *) join_args;; esac i=$((i+1)) done } run_oslo () { echo "Checking for oslo libraries in requirements.txt..." if grep -q "^oslo[.-]" requirements.txt; then echo "oslo libraries are not allowed" exit 1 fi } 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=0 oslo=0 process_options $@ if [ $oslo -eq 1 ]; then run_oslo fi if [ $pylint -eq 1 ]; then run_pylint exit 0 fi ovsdbapp-1.1.0/tools/setup-ovs.sh0000775000175000017500000000216713641423405017025 0ustar zuulzuul00000000000000#!/bin/bash -xe OVS_BRANCH=${OVS_BRANCH:-master} OVN_BRANCH=${OVN_BRANCH:-$OVS_BRANCH} function use_new_ovn_repository { # If OVN_BRANCH > branch-2.12 return 0 return $(! printf "%s\n%s" $OVN_BRANCH branch-2.12 | sort -C -V) } # 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 branch $OVS_BRANCH in $OVS_SRCDIR" mkdir -p $OVS_SRCDIR git clone git://github.com/openvswitch/ovs.git $OVS_SRCDIR (cd $OVS_SRCDIR && git checkout $OVS_BRANCH && ./boot.sh && PYTHON=/usr/bin/python ./configure && make -j$(($(nproc) + 1))) fi if use_new_ovn_repository && [ "$OVN_SRCDIR" -a ! -d "$OVN_SRCDIR" ]; then echo "Building OVN branch $OVN_BRANCH in $OVN_SRCDIR" mkdir -p $OVN_SRCDIR git clone git://github.com/ovn-org/ovn.git $OVN_SRCDIR (cd $OVN_SRCDIR && git checkout $OVN_BRANCH && ./boot.sh && PYTHON=/usr/bin/python ./configure --with-ovs-source=$OVS_SRCDIR && make -j$(($(nproc) + 1))) fi ovsdbapp-1.1.0/tools/test-setup.sh0000775000175000017500000000042613641423405017171 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-1.1.0/tools/debug_venv.py0000775000175000017500000000277113641423405017223 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 six import subprocess import sys from fixtures import fixture from ovsdbapp import venv if len(sys.argv) != 4: print("Requires three arguments: venvdir ovsdir ovndir", 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]) ovndir = os.path.abspath(sys.argv[3]) v = venv.OvsOvnVenvFixture(venvdir, ovsdir, ovndir=ovndir) try: atexit.register(v.cleanUp) v.setUp() except fixture.MultipleExceptions as e: six.reraise(*e.args[0]) 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-1.1.0/babel.cfg0000664000175000017500000000002113641423405015072 0ustar zuulzuul00000000000000[python: **.py] ovsdbapp-1.1.0/zuul.d/0000775000175000017500000000000013641423532014575 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/zuul.d/ovsdbapp-jobs.yaml0000664000175000017500000000053013641423405020227 0ustar zuulzuul00000000000000- job: name: ovsdbapp-tempest-dsvm-networking-ovn-ovs-release description: Job testing for devstack/tempest testing networking-ovn with the latest released OVN branch and ovsdbapp from source parent: networking-ovn-tempest-dsvm-ovs-release vars: devstack_localrc: LIBS_FROM_GIT: ovsdbapp USE_PYTHON3: true ovsdbapp-1.1.0/zuul.d/project.yaml0000664000175000017500000000062013641423405017124 0ustar zuulzuul00000000000000- project: templates: - check-requirements - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - publish-openstack-docs-pti check: jobs: - openstack-tox-functional - ovsdbapp-tempest-dsvm-networking-ovn-ovs-release gate: jobs: - openstack-tox-functional - ovsdbapp-tempest-dsvm-networking-ovn-ovs-release ovsdbapp-1.1.0/README.rst0000664000175000017500000000102613641423405015041 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: https://opendev.org/openstack/ovsdbapp/ * Bugs: https://bugs.launchpad.net/ovsdbapp Features: * An thread-based event loop for using ovs.db.Idl * Transaction support * Native OVSDB communication ovsdbapp-1.1.0/releasenotes/0000775000175000017500000000000013641423532016045 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/releasenotes/source/0000775000175000017500000000000013641423532017345 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/releasenotes/source/pike.rst0000664000175000017500000000021713641423405021026 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ovsdbapp-1.1.0/releasenotes/source/train.rst0000664000175000017500000000017613641423405021217 0ustar zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ovsdbapp-1.1.0/releasenotes/source/index.rst0000664000175000017500000000031413641423405021203 0ustar zuulzuul00000000000000============================================ ovsdbapp Release Notes ============================================ .. toctree:: :maxdepth: 1 unreleased train stein rocky queens pike ovsdbapp-1.1.0/releasenotes/source/rocky.rst0000664000175000017500000000022113641423405021220 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ovsdbapp-1.1.0/releasenotes/source/_templates/0000775000175000017500000000000013641423532021502 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013641423405023752 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/releasenotes/source/unreleased.rst0000664000175000017500000000016013641423405022222 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ovsdbapp-1.1.0/releasenotes/source/queens.rst0000664000175000017500000000022313641423405021373 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ovsdbapp-1.1.0/releasenotes/source/conf.py0000664000175000017500000001726513641423405020656 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. # 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 = 'ovsdbappReleaseNotesdoc' # -- 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', 'ovsdbappNotes.tex', u'ovsdbapp Release Notes Documentation', u'ovsdbapp 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 Internationalization output ------------------------------ locale_dirs = ['locale/'] ovsdbapp-1.1.0/releasenotes/source/stein.rst0000664000175000017500000000022113641423405021213 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ovsdbapp-1.1.0/releasenotes/notes/0000775000175000017500000000000013641423532017175 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/releasenotes/notes/configure-ovsdb-manager-a29a148b241a125e.yaml0000664000175000017500000000067213641423405026707 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-1.1.0/releasenotes/notes/drop-py27-support-c426980520444bfa.yaml0000664000175000017500000000031613641423405025406 0ustar zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of ovsdbapp to support python 2.7 is OpenStack Train. The minimum version of Python now supported by ovsdbapp is Python 3.6. ovsdbapp-1.1.0/releasenotes/notes/.placeholder0000664000175000017500000000000013641423405021445 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/setup.py0000664000175000017500000000200613641423405015063 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-1.1.0/doc/0000775000175000017500000000000013641423532014121 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/doc/requirements.txt0000664000175000017500000000031613641423405017404 0ustar zuulzuul00000000000000sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD openstackdocstheme>=1.20.0 # Apache-2.0 # releasenotes reno>=2.5.0 # Apache-2.0 ovsdbapp-1.1.0/doc/source/0000775000175000017500000000000013641423532015421 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/doc/source/index.rst0000664000175000017500000000042113641423405017256 0ustar zuulzuul00000000000000.. the main title comes from README.rst .. include:: ../../README.rst ---- .. toctree:: :maxdepth: 2 install/index user/index contributor/index .. only:: html .. rubric:: Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` ovsdbapp-1.1.0/doc/source/install/0000775000175000017500000000000013641423532017067 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/doc/source/install/index.rst0000664000175000017500000000030113641423405020721 0ustar zuulzuul00000000000000============ Installation ============ At the command line:: $ pip install ovsdbapp Or, if you have virtualenvwrapper installed:: $ mkvirtualenv ovsdbapp $ pip install ovsdbapp ovsdbapp-1.1.0/doc/source/user/0000775000175000017500000000000013641423532016377 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/doc/source/user/index.rst0000664000175000017500000000010713641423405020235 0ustar zuulzuul00000000000000===== Usage ===== To use ovsdbapp in a project:: import ovsdbapp ovsdbapp-1.1.0/doc/source/conf.py0000775000175000017500000000502013641423405016717 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' # 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', 'doc-ovsdbapp.tex', u'%s Documentation' % project, u'OpenStack Foundation', 'howto'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} ovsdbapp-1.1.0/doc/source/contributor/0000775000175000017500000000000013641423532017773 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/doc/source/contributor/index.rst0000664000175000017500000000011613641423405021631 0ustar zuulzuul00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst ovsdbapp-1.1.0/CONTRIBUTING.rst0000664000175000017500000000121313641423405016011 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://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: https://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-1.1.0/ovsdbapp/0000775000175000017500000000000013641423532015172 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/constants.py0000664000175000017500000000172513641423405017564 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 QOS_DSCP_MAX = 2 ** 6 - 1 QOS_BANDWIDTH_MAX = 2 ** 32 - 1 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-1.1.0/ovsdbapp/event.py0000664000175000017500000001134013641423405016663 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, tuple(self.events)) def __hash__(self): return hash(self.key) def __eq__(self, other): try: return (self.key == other.key and self.conditions == other.conditions) except AttributeError: return False def __ne__(self, other): return not self == other def __repr__(self): return "%s(events=%r, table=%r, conditions=%r, old_conditions=%r)" % ( self.__class__.__name__, self.events, self.table, self.conditions, self.old_conditions) @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 WaitEvent(RowEvent): event_name = 'WaitEvent' ONETIME = True def __init__(self, *args, **kwargs): self.event = threading.Event() self.timeout = kwargs.pop('timeout', None) super(WaitEvent, self).__init__(*args, **kwargs) @abc.abstractmethod def matches(self, event, row, old=None): """Test that `event on `row` matches watched events. See: RowEvent""" def run(self, event, row, old): self.event.set() def wait(self): return self.event.wait(self.timeout) 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-1.1.0/ovsdbapp/venv.py0000664000175000017500000002213213641423405016521 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" OVS_PATHS = ( os.path.join(os.path.sep, 'usr', 'local', 'share', 'openvswitch'), os.path.join(os.path.sep, 'usr', 'share', 'openvswitch')) def __init__(self, venv, ovsdir=None, 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 and os.path.isdir(ovsdir): # From source directory self.env['PATH'] = (self.PATH_VAR_TEMPLATE.format(ovsdir) + ":%s" % os.getenv('PATH')) else: # Use installed OVS self.env['PATH'] = os.getenv('PATH') self.ovsdir = self._share_path(self.OVS_PATHS, ovsdir) self._dummy = dummy self.remove = remove self.ovsdb_server_dbs = [] @staticmethod def _share_path(paths, override=None, files=tuple()): if not override: try: return next( p for p in paths if os.path.isdir(p) and all(os.path.isfile(os.path.join(p, f)) for f in files)) except StopIteration: pass elif os.path.isdir(override): return override raise Exception("Invalid directories: %s" % ", ".join(paths + (str(override),))) @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, stderr=subprocess.STDOUT, 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): OVN_PATHS = ( os.path.join(os.path.sep, 'usr', 'local', 'share', 'ovn'), os.path.join(os.path.sep, 'usr', 'share', 'ovn')) + ( OvsVenvFixture.OVS_PATHS) NBSCHEMA = 'ovn-nb.ovsschema' SBSCHEMA = 'ovn-sb.ovsschema' def __init__(self, venv, ovndir=None, add_chassis=False, **kwargs): self.add_chassis = add_chassis if ovndir and os.path.isdir(ovndir): # Use OVN source dir self.PATH_VAR_TEMPLATE += ( ":{0}/controller:{0}/northd:{0}/utilities".format(ovndir)) super(OvsOvnVenvFixture, self).__init__(venv, **kwargs) self.ovndir = self._share_path(self.OVN_PATHS, ovndir, [self.SBSCHEMA, self.NBSCHEMA]) self.env.update({'OVN_RUNDIR': self.venv}) @property def ovnsb_schema(self): return os.path.join(self.ovndir, self.SBSCHEMA) @property def ovnnb_schema(self): return os.path.join(self.ovndir, self.NBSCHEMA) @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('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']) ovsdbapp-1.1.0/ovsdbapp/utils.py0000664000175000017500000000444013641423405016705 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 uuid 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) def generate_uuid(dashed=True): """Create a random uuid string. :param dashed: Generate uuid with dashes or not :type dashed: bool :returns: string """ if dashed: return str(uuid.uuid4()) return uuid.uuid4().hex def _format_uuid_string(string): return (string.replace('urn:', '') .replace('uuid:', '') .strip('{}') .replace('-', '') .lower()) def is_uuid_like(val): """Return validation of a value as a UUID. :param val: Value to verify :type val: string :returns: bool """ try: return str(uuid.UUID(val)).replace('-', '') == _format_uuid_string(val) except (TypeError, ValueError, AttributeError): return False ovsdbapp-1.1.0/ovsdbapp/api.py0000664000175000017500000002653313641423405016325 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 try: # Python 3 no longer has thread module import thread # noqa except ImportError: import threading as thread @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 Operation is returned back as a convenience. """ def extend(self, commands): """Add multiple OVSDB operations to the transaction List of operations is returned back as a convenience. """ return [self.add(command) for command in commands] 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, nested_transactions=True): # Mapping between a (green)thread and its transaction. self._nested_txns = nested_transactions self._nested_txns_map = {} @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, nested=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 :param nested: Allow nested transactions be merged into one txn :type nested: bool :returns: Either a new transaction or an existing one. :rtype: :class:`Transaction` """ # ojbect() is unique, so if we are not nested, this will always result # in a KeyError on lookup and so a unique Transaction nested = nested and self._nested_txns cur_thread_id = thread.get_ident() if nested else object() if cur_thread_id in self._nested_txns_map: yield self._nested_txns_map[cur_thread_id] else: with self.create_transaction( check_error, log_errors, **kwargs) as txn: self._nested_txns_map[cur_thread_id] = txn try: yield txn finally: del self._nested_txns_map[cur_thread_id] @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 uuid result """ def db_create_row(self, table, **col_values): """Create a command to create new record Identical to db_create, but returns a RowView result :returns: :class:`Command` with RowView result """ # vif_plug_ovs has a copy of impl_vsctl that doesn't implement this raise NotImplementedError @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-1.1.0/ovsdbapp/CHANGES0000664000175000017500000000063013641423405016163 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-1.1.0/ovsdbapp/tests/0000775000175000017500000000000013641423532016334 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/utils.py0000664000175000017500000000444513641423405020054 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-1.1.0/ovsdbapp/tests/base.py0000664000175000017500000000163513641423405017624 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. import mock from oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit and functional tests.""" def setUp(self): super(TestCase, self).setUp() self.addCleanup(mock.patch.stopall) ovsdbapp-1.1.0/ovsdbapp/tests/unit/0000775000175000017500000000000013641423532017313 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/test_utils.py0000664000175000017500000000544713641423405022075 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 uuid 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) def test_is_uuid_like(self): self.assertTrue(utils.is_uuid_like(str(uuid.uuid4()))) self.assertTrue(utils.is_uuid_like( '{12345678-1234-1234-1234-123456781234}')) self.assertTrue(utils.is_uuid_like( '12345678123412341234123456781234')) self.assertTrue(utils.is_uuid_like( 'urn:uuid:12345678-1234-1234-1234-123456781234')) self.assertTrue(utils.is_uuid_like( 'urn:bbbaaaaa-aaaa-aaaa-aabb-bbbbbbbbbbbb')) self.assertTrue(utils.is_uuid_like( 'uuid:bbbaaaaa-aaaa-aaaa-aabb-bbbbbbbbbbbb')) self.assertFalse(utils.is_uuid_like( 'uuid:batrdbaa-aaaa-aaaa-aabb-bbbbbbbbbbbb')) self.assertFalse(utils.is_uuid_like( '123456781234123412341234567812345678')) ovsdbapp-1.1.0/ovsdbapp/tests/unit/backend/0000775000175000017500000000000013641423532020702 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/backend/ovs_idl/0000775000175000017500000000000013641423532022341 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/backend/ovs_idl/test_idlutils.py0000664000175000017500000001531113641423405025603 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 conversions """ 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-1.1.0/ovsdbapp/tests/unit/backend/ovs_idl/test_helpers.py0000664000175000017500000000220113641423405025406 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-1.1.0/ovsdbapp/tests/unit/backend/ovs_idl/test_vlog.py0000664000175000017500000000523313641423405024723 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-1.1.0/ovsdbapp/tests/unit/backend/ovs_idl/test_connection.py0000664000175000017500000000456613641423405026123 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 from ovs import poller from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.tests import base @mock.patch.object(connection.threading, 'Thread') 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(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, mock_thread): self.conn.start() self.conn.queue_txn('blah') self.conn.txns.put.assert_called_once_with('blah', timeout=self.conn.timeout) 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-1.1.0/ovsdbapp/tests/unit/backend/ovs_idl/__init__.py0000664000175000017500000000000013641423405024437 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/backend/test_ovs_idl.py0000664000175000017500000000330513641423405023752 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-1.1.0/ovsdbapp/tests/unit/backend/__init__.py0000664000175000017500000000000013641423405023000 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/schema/0000775000175000017500000000000013641423532020553 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/schema/open_vswitch/0000775000175000017500000000000013641423532023263 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/schema/open_vswitch/test_impl_idl.py0000664000175000017500000000366013641423405026471 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()) class TestOvsdbIdl(base.TestCase): def setUp(self): super(TestOvsdbIdl, self).setUp() impl_idl.OvsdbIdl.ovsdb_connection = None def test_nested_txns(self): conn = mock.MagicMock() api = impl_idl.OvsdbIdl(conn, nested_transactions=False) self.assertFalse(api._nested_txns) def test_init_session(self): conn = mock.MagicMock() backend = impl_idl.OvsdbIdl(conn, start=False) self.assertIsNone(backend.ovsdb_connection) ovsdbapp-1.1.0/ovsdbapp/tests/unit/schema/open_vswitch/__init__.py0000664000175000017500000000000013641423405025361 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/schema/__init__.py0000664000175000017500000000000013641423405022651 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/test_event.py0000664000175000017500000000207313641423405022046 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-1.1.0/ovsdbapp/tests/unit/__init__.py0000664000175000017500000000000013641423405021411 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/unit/test_api.py0000664000175000017500000000764413641423405021507 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 sys import fixtures import mock import testtools from ovsdbapp import api from ovsdbapp.tests import base try: import eventlet from eventlet.green import thread sleep = eventlet.sleep def create_thread(executable): eventlet.spawn_n(executable) except ImportError: import threading import time sleep = time.sleep def create_thread(executable): thread = threading.Thread(target=executable) thread.start() class GreenThreadingFixture(fixtures.Fixture): def _setUp(self): if 'eventlet' in sys.modules: self._orig = api.thread.get_ident api.thread.get_ident = thread.get_ident self.addCleanup(self.cleanup) def cleanup(self): api.thread.get_ident = self._orig 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): txn = FakeTransaction() mock.patch.object(txn, 'commit').start() return txn TestingAPI.__abstractmethods__ = set() class TransactionTestCase(base.TestCase): def setUp(self): super(TransactionTestCase, self).setUp() self.api = TestingAPI() self.useFixture(GreenThreadingFixture()) 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_nested_false(self): with self.api.transaction(nested=False) as txn1: with self.api.transaction() as txn2: self.assertIsNot(txn1, txn2) txn1.commit.assert_called_once_with() txn2.commit.assert_called_once_with() def test_api_level_transaction_nested_fales(self): api = TestingAPI(nested_transactions=False) with api.transaction() as txn1: with api.transaction() as txn2: self.assertIsNot(txn1, txn2) txn1.commit.assert_called_once_with() txn2.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) def test_transaction_nested_multiple_threads(self): shared_resource = [] def thread1(): with self.api.transaction() as txn: shared_resource.append(txn) while len(shared_resource) == 1: sleep(0.1) shared_resource.append(0) def thread2(): while len(shared_resource) != 1: sleep(0.1) with self.api.transaction() as txn: shared_resource.append(txn) shared_resource.append(0) create_thread(thread1) create_thread(thread2) while len(shared_resource) != 4: sleep(0.1) txn1, txn2 = shared_resource[:2] self.assertNotEqual(txn1, txn2) ovsdbapp-1.1.0/ovsdbapp/tests/__init__.py0000664000175000017500000000000013641423405020432 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/0000775000175000017500000000000013641423532020476 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/base.py0000664000175000017500000000470013641423405021762 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'), ovndir=os.getenv('OVN_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-1.1.0/ovsdbapp/tests/functional/schema/0000775000175000017500000000000013641423532021736 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/open_vswitch/0000775000175000017500000000000013641423532024446 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/open_vswitch/test_impl_idl.py0000664000175000017500000002002713641423405027650 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_idl_run_exception_terminates(self): run = self.api.idl.run with mock.patch.object(self.api.idl, "run") as runmock: exceptions = iter([Exception("TestException")]) def side_effect(): try: raise next(exceptions) except StopIteration: return run() runmock.side_effect = side_effect exists = self.api.br_exists(self.brname).execute(check_error=True) self.assertFalse(exists) 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.extend([self.api.add_br(self.brname), 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)) def test_br_external_id(self): KEY = "foo" VALUE = "bar" self.api.add_br(self.brname).execute(check_error=True) self.api.br_set_external_id(self.brname, KEY, VALUE).execute( check_error=True) external_id = self.api.br_get_external_id(self.brname, KEY).execute( check_error=True) self.assertEqual(VALUE, external_id) def test_iface_external_id(self): KEY = "foo" VALUE = "bar" self.api.add_br(self.brname).execute(check_error=True) self.api.iface_set_external_id(self.brname, KEY, VALUE).execute( check_error=True) external_id = self.api.iface_get_external_id(self.brname, KEY).execute( check_error=True) self.assertEqual(VALUE, external_id) 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 mock.patch('ovsdbapp.backend.ovs_idl.transaction.' 'Transaction.timeout_exceeded', return_value=True).start() self.assertRaises((exc.TimeoutException, RuntimeError), self._add_br) ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/open_vswitch/test_common_db.py0000664000175000017500000001063113641423405030014 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 uuid from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import rowview 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_nested(self): with self.api.transaction(check_error=True): self.test_db_list() 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_multiple_records_no_exist(self): # Check the case where some records are found and some are not. We # should still be getting the RowNotFound exception in this case. cmd = self.api.db_list('Bridge', [self.bridges[0]['name'], '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))) def test_db_create(self): _uuid = self.api.db_create( 'Queue', external_ids={'x': 'x'}).execute(check_error=True) self.assertIsInstance(_uuid, uuid.UUID) self.api.db_destroy('Queue', _uuid).execute(check_error=True) def test_db_create_row(self): row = self.api.db_create_row( 'Queue', external_ids={'x': 'x'}).execute(check_error=True) self.assertIsInstance(row, rowview.RowView) self.api.db_destroy('Queue', row.uuid).execute(check_error=True) ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/open_vswitch/fixtures.py0000664000175000017500000000146413641423405026675 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-1.1.0/ovsdbapp/tests/functional/schema/open_vswitch/__init__.py0000664000175000017500000000000013641423405026544 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/ovn_southbound/0000775000175000017500000000000013641423532025012 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/ovn_southbound/test_impl_idl.py0000664000175000017500000001434113641423405030216 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 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 event from ovsdbapp.tests.functional.schema.ovn_southbound import fixtures from ovsdbapp.tests import utils # Keep the class here for backward compatiblity WaitForPortBindingEvent = event.WaitForPortBindingEvent 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 = 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)) self.assertTrue(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-1.1.0/ovsdbapp/tests/functional/schema/ovn_southbound/event.py0000664000175000017500000000162313641423405026506 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 event class WaitForPortBindingEvent(event.WaitEvent): event_name = 'WaitForPortBindingEvent' def __init__(self, port, timeout=5): super(WaitForPortBindingEvent, self).__init__( (self.ROW_CREATE,), 'Port_Binding', (('logical_port', '=', port),), timeout=timeout) ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/ovn_southbound/fixtures.py0000664000175000017500000000146113641423405027236 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-1.1.0/ovsdbapp/tests/functional/schema/ovn_southbound/__init__.py0000664000175000017500000000000013641423405027110 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/fixtures.py0000664000175000017500000000246113641423405024163 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-1.1.0/ovsdbapp/tests/functional/schema/__init__.py0000664000175000017500000000000013641423405024034 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/ovn_northbound/0000775000175000017500000000000013641423532025002 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py0000664000175000017500000020112613641423405030205 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 self.port_group = self.useFixture(fixtures.PortGroupFixture()).obj def _acl_add(self, entity, *args, **kwargs): self.assertIn(entity, ['lswitch', 'port_group']) if entity == 'lswitch': cmd = self.api.acl_add(self.switch.uuid, *args, **kwargs) resource = self.switch else: cmd = self.api.pg_acl_add(self.port_group.uuid, *args, **kwargs) resource = self.port_group aclrow = cmd.execute(check_error=True) self.assertIn(aclrow._row, resource.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('lswitch', 'from-lport', 0, 'output == "fake_port" && ip', 'drop') def test_acl_add_exists(self): args = ('lswitch', '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('lswitch', *args) row2 = self._acl_add('lswitch', *args, may_exist=True) self.assertEqual(row, row2) def test_acl_add_extids(self): external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'} acl = self._acl_add('lswitch', '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('lswitch', '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('lswitch', 'from-lport', 0, 'output == "fake_port"', 'drop') r2 = self._acl_add('lswitch', '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('lswitch', 'from-lport', 0, 'output == "fake_port"', 'drop') r2 = self._acl_add('lswitch', '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('lswitch', 'from-lport', 0, 'output == "fake_port"', 'drop') r2 = self._acl_add('lswitch', '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) def test_pg_acl_add(self): self._acl_add('port_group', 'from-lport', 0, 'output == "fake_port" && ip', 'drop') def test_pg_acl_del_all(self): r1 = self._acl_add('port_group', 'from-lport', 0, 'output == "fake_port"', 'drop') self.api.pg_acl_del(self.port_group.uuid).execute(check_error=True) self.assertNotIn(r1.uuid, self.api.tables['ACL'].rows) self.assertEqual([], self.port_group.acls) def test_pg_acl_list(self): r1 = self._acl_add('port_group', 'from-lport', 0, 'output == "fake_port"', 'drop') r2 = self._acl_add('port_group', 'from-lport', 1, 'output == "fake_port2"', 'allow') acls = self.api.pg_acl_list(self.port_group.uuid).execute( check_error=True) self.assertIn(r1, acls) self.assertIn(r2, acls) class TestQoSOps(OvnNorthboundTest): def setUp(self): super(TestQoSOps, self).setUp() self.switch = self.useFixture(fixtures.LogicalSwitchFixture()).obj def _qos_add(self, *args, **kwargs): cmd = self.api.qos_add(self.switch.uuid, *args, **kwargs) row = cmd.execute(check_error=True) self.assertIn(row._row, self.switch.qos_rules) self.assertEqual(cmd.direction, row.direction) self.assertEqual(cmd.priority, row.priority) self.assertEqual(cmd.match, row.match) self.assertEqual(cmd.rate, row.bandwidth.get('rate', None)) self.assertEqual(cmd.burst, row.bandwidth.get('burst', None)) self.assertEqual(cmd.dscp, row.action.get('dscp', None)) return row def test_qos_add_dscp(self): self._qos_add('from-lport', 0, 'output == "fake_port" && ip', dscp=33) def test_qos_add_rate(self): self._qos_add('from-lport', 0, 'output == "fake_port" && ip', rate=100) def test_qos_add_rate_burst(self): self._qos_add('from-lport', 0, 'output == "fake_port" && ip', rate=101, burst=1001) def test_qos_add_rate_dscp(self): self._qos_add('from-lport', 0, 'output == "fake_port" && ip', rate=102, burst=1002, dscp=56) def test_qos_add_raises(self): self.assertRaises(TypeError, self.api.qos_add, 'from-lport', 0, 'output == "fake_port" && ip') def test_qos_add_direction_raises(self): self.assertRaises(TypeError, self.api.qos_add, 'foo', 0, 'ip', bandwidth={'rate': 102, 'burst': 1002}) def test_qos_add_priority_raises(self): self.assertRaises(TypeError, self.api.qos_add, 'from-lport', 32768, 'ip', bandwidth={'rate': 102, 'burst': 1002}) def test_qos_add_exists(self): args = ('from-lport', 0, 'output == "fake_port" && ip', 1000) self._qos_add(*args) self.assertRaises(RuntimeError, self._qos_add, *args) def test_qos_add_may_exist(self): args = ('from-lport', 0, 'output == "fake_port" && ip', 1000) row = self._qos_add(*args) row2 = self._qos_add(*args, may_exist=True) self.assertEqual(row, row2) def test_qos_add_extids(self): external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'} qos = self._qos_add('from-lport', 0, 'output == "fake_port" && ip', dscp=11, external_ids=external_ids) self.assertEqual(external_ids, qos.external_ids) def test_qos_del_all(self): r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000) self.api.qos_del(self.switch.uuid).execute(check_error=True) self.assertNotIn(r1.uuid, self.api.tables['QoS'].rows) self.assertEqual([], self.switch.qos_rules) def test_qos_del_direction(self): r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000) r2 = self._qos_add('to-lport', 0, 'output == "fake_port"', 1000) self.api.qos_del(self.switch.uuid, 'from-lport').execute( check_error=True) self.assertNotIn(r1, self.switch.qos_rules) self.assertIn(r2, self.switch.qos_rules) def test_qos_del_direction_priority_match(self): r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000) r2 = self._qos_add('from-lport', 1, 'output == "fake_port"', 1000) cmd = self.api.qos_del(self.switch.uuid, 'from-lport', 0, 'output == "fake_port"') cmd.execute(check_error=True) self.assertNotIn(r1, self.switch.qos_rules) self.assertIn(r2, self.switch.qos_rules) def test_qos_del_priority_without_match(self): self.assertRaises(TypeError, self.api.qos_del, self.switch.uuid, 'from-lport', 0) def test_qos_del_priority_without_direction(self): self.assertRaises(TypeError, self.api.qos_del, self.switch.uuid, priority=0) def test_qos_list(self): r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000) r2 = self._qos_add('from-lport', 1, 'output == "fake_port2"', 1000) qos_rules = self.api.qos_list(self.switch.uuid).execute( check_error=True) self.assertIn(r1, qos_rules) self.assertIn(r2, qos_rules) 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 = self.useFixture(fixtures.LogicalSwitchFixture()).obj cmd = self.api.lsp_del(lsp.uuid, sw.uuid) 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', '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', ['invalidaddress']) 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_del_specific_snat_ip_network(self): lr = self._lr_add(utils.get_rand_device_name()) self._lr_nat_add(router=lr, nat_type=const.NAT_SNAT, logical_ip='10.17.4.0/24', external_ip='192.0.2.2') # Attempt to delete NAT rule of type const.NAT_SNAT by passing # an IP network (corresponding to logical_ip) as match_ip self.api.lr_nat_del(lr.name, nat_type=const.NAT_SNAT, match_ip='10.17.4.0/24').execute(check_error=True) # Assert that the NAT rule of type const.NAT_SNAT is deleted self.assertEqual([], lr.nat) def test_lr_nat_del_specific_snat_ip_network_not_found(self): self.assertRaises(idlutils.RowNotFound, self._lr_nat_del, nat_type=const.NAT_SNAT, match_ip='10.17.4.0/24') def test_lr_nat_del_specific_dnat_ip_network(self): self.assertRaises(ValueError, self._lr_nat_del, nat_type=const.NAT_DNAT, match_ip='192.0.2.1/32') def test_lr_nat_del_specific_both_ip_network(self): self.assertRaises(ValueError, self._lr_nat_del, nat_type=const.NAT_BOTH, match_ip='192.0.2.0/24') 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_add_gw_chassis(self): name, c1, c2 = [utils.get_rand_device_name() for _ in range(3)] args = (name, 'de:ad:be:ef:4d:ad') lrp = self._lrp_add(*args, gateway_chassis=(c1, c2)) c1 = self.api.lookup('Gateway_Chassis', "%s_%s" % (lrp.name, c1)) c2 = self.api.lookup('Gateway_Chassis', "%s_%s" % (lrp.name, c2)) self.assertIn(c1, lrp.gateway_chassis) self.assertIn(c2, lrp.gateway_chassis) 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 = self.useFixture(fixtures.LogicalSwitchFixture()).obj cmd = self.api.lrp_del(lrp.uuid, sw.uuid) 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)) def test_lrp_get_set_options(self): options = {'one': 'two', 'three': 'four'} lrp = self._lrp_add(None) self.api.lrp_set_options(lrp.uuid, **options).execute( check_error=True) self.assertEqual(options, self.api.lrp_get_options(lrp.uuid).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) class TestPortGroup(OvnNorthboundTest): def setUp(self): super(TestPortGroup, self).setUp() self.switch = self.useFixture(fixtures.LogicalSwitchFixture()).obj self.pg_name = 'testpg-%s' % ovsdb_utils.generate_uuid() def test_port_group(self): # Assert the Port Group was added self.api.pg_add(self.pg_name).execute(check_error=True) row = self.api.db_find( 'Port_Group', ('name', '=', self.pg_name)).execute(check_error=True) self.assertIsNotNone(row) self.assertEqual(self.pg_name, row[0]['name']) self.assertEqual([], row[0]['ports']) self.assertEqual([], row[0]['acls']) # Assert the Port Group was deleted self.api.pg_del(self.pg_name).execute(check_error=True) row = self.api.db_find( 'Port_Group', ('name', '=', self.pg_name)).execute(check_error=True) self.assertEqual([], row) def test_port_group_ports(self): lsp_add_cmd = self.api.lsp_add(self.switch.uuid, 'testport') with self.api.transaction(check_error=True) as txn: txn.add(lsp_add_cmd) txn.add(self.api.pg_add(self.pg_name)) port_uuid = lsp_add_cmd.result.uuid # Lets add the port using the UUID instead of a `Command` to # exercise the API self.api.pg_add_ports(self.pg_name, port_uuid).execute( check_error=True) row = self.api.db_find( 'Port_Group', ('name', '=', self.pg_name)).execute(check_error=True) self.assertIsNotNone(row) self.assertEqual(self.pg_name, row[0]['name']) # Assert the port was added from the Port Group self.assertEqual([port_uuid], row[0]['ports']) # Delete the Port from the Port Group with self.api.transaction(check_error=True) as txn: txn.add(self.api.pg_del_ports(self.pg_name, port_uuid)) row = self.api.db_find( 'Port_Group', ('name', '=', self.pg_name)).execute(check_error=True) self.assertIsNotNone(row) self.assertEqual(self.pg_name, row[0]['name']) # Assert the port was removed from the Port Group self.assertEqual([], row[0]['ports']) def test_pg_del_ports_if_exists(self): self.api.pg_add(self.pg_name).execute(check_error=True) non_existent_res = ovsdb_utils.generate_uuid() # Assert that if if_exists is False (default) it will raise an error self.assertRaises(RuntimeError, self.api.pg_del_ports(self.pg_name, non_existent_res).execute, True) # Assert that if if_exists is True it won't raise an error self.api.pg_del_ports(self.pg_name, non_existent_res, if_exists=True).execute(check_error=True) class TestHAChassisGroup(OvnNorthboundTest): def setUp(self): super(TestHAChassisGroup, self).setUp() self.hcg_name = 'ha-group-%s' % ovsdb_utils.generate_uuid() self.chassis = 'chassis-%s' % ovsdb_utils.generate_uuid() def test_ha_chassis_group(self): # Assert the HA Chassis Group was added self.api.ha_chassis_group_add(self.hcg_name).execute(check_error=True) hcg = self.api.ha_chassis_group_get(self.hcg_name).execute( check_error=True) self.assertEqual(self.hcg_name, hcg.name) # Assert the HA Chassis Group was deleted self.api.ha_chassis_group_del(self.hcg_name).execute(check_error=True) cmd = self.api.ha_chassis_group_get(self.hcg_name) self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) def test_ha_chassis_group_add_delete_chassis(self): self.api.ha_chassis_group_add(self.hcg_name).execute(check_error=True) priority = 20 self.api.ha_chassis_group_add_chassis( self.hcg_name, self.chassis, priority).execute(check_error=True) # Assert that the HA Chassis entry was created row = self.api.db_find( 'HA_Chassis', ('chassis_name', '=', self.chassis)).execute(check_error=True) self.assertEqual(priority, row[0]['priority']) # Assert that the HA Chassis entry was associated with # the HA Chassis Group hcg = self.api.ha_chassis_group_get(self.hcg_name).execute( check_error=True) self.assertEqual(self.chassis, hcg.ha_chassis[0].chassis_name) # Deletes the HA Chassis entry self.api.ha_chassis_group_del_chassis( self.hcg_name, self.chassis).execute(check_error=True) row = self.api.db_find( 'HA_Chassis', ('chassis_name', '=', self.chassis)).execute(check_error=True) self.assertEqual([], row) # Assert that the deleted HA Chassis entry was dissociated from # the HA Chassis Group hcg = self.api.ha_chassis_group_get(self.hcg_name).execute( check_error=True) self.assertEqual([], hcg.ha_chassis) def test_ha_chassis_group_if_exists(self): self.api.ha_chassis_group_add(self.hcg_name).execute(check_error=True) self.api.ha_chassis_group_add_chassis( self.hcg_name, self.chassis, priority=10).execute(check_error=True) # Deletes the HA Chassis entry self.api.ha_chassis_group_del_chassis( self.hcg_name, self.chassis).execute(check_error=True) row = self.api.db_find( 'HA_Chassis', ('chassis_name', '=', self.chassis)).execute(check_error=True) self.assertEqual([], row) # Tries to delete it again, since if_exists=True it shouldn't raise # any errors self.api.ha_chassis_group_del_chassis( self.hcg_name, self.chassis, if_exists=True).execute( check_error=True) # Tries to delete it again with if_exists=False, now it should raise # a RuntimeError cmd = self.api.ha_chassis_group_del_chassis( self.hcg_name, self.chassis, if_exists=False) self.assertRaises(RuntimeError, cmd.execute, check_error=True) # Deletes the HA Chassis Group entry self.api.ha_chassis_group_del(self.hcg_name).execute(check_error=True) cmd = self.api.ha_chassis_group_get(self.hcg_name) self.assertRaises(idlutils.RowNotFound, cmd.execute, check_error=True) # Tries to delete it again, since if_exists=True it shouldn't raise # any errors self.api.ha_chassis_group_del( self.hcg_name, if_exists=True).execute(check_error=True) # Tries to delete it again with if_exists=False, now it should raise # a RuntimeError cmd = self.api.ha_chassis_group_del(self.hcg_name) self.assertRaises(RuntimeError, cmd.execute, check_error=True) def test_ha_chassis_group_may_exist(self): cmd = self.api.ha_chassis_group_add(self.hcg_name, may_exist=True) hcg1 = cmd.execute(check_error=True) hcg2 = cmd.execute(check_error=True) self.assertEqual(hcg1, hcg2) class TestReferencedObjects(OvnNorthboundTest): """Exercise adding a ls, lsp and lsp_address in a single transaction. The main goal of this test is to make sure a transaction can use either a name or an object notation in order to create an ls+lsp while in a transaction context. """ def setUp(self): super(TestReferencedObjects, self).setUp() self.ls_name = utils.get_rand_device_name() self.lsp_name = utils.get_rand_device_name() self.lsp_test_addresses = ['de:ad:be:ef:4d:ad 192.0.2.1'] def _check_values(self): # Check: Make sure ls_get and lsp_get work (no RowNotFound exception) self.api.ls_get(self.ls_name).execute(check_error=True) self.api.lsp_get(self.lsp_name).execute(check_error=True) self.assertEqual(self.lsp_test_addresses, self.api.lsp_get_addresses(self.lsp_name).execute( check_error=True)) def test_lsp_add_by_name(self): with self.api.transaction(check_error=True) as txn: txn.add(self.api.ls_add(self.ls_name)) txn.add(self.api.lsp_add(self.ls_name, self.lsp_name)) txn.add(self.api.lsp_set_addresses(self.lsp_name, self.lsp_test_addresses)) self._check_values() def test_lsp_add_by_object_via_db_create(self): with self.api.transaction(check_error=True) as txn: sw = txn.add(self.api.db_create_row('Logical_Switch', name=self.ls_name)) prt = txn.add(self.api.db_create_row('Logical_Switch_Port', name=self.lsp_name)) txn.add(self.api.db_add('Logical_Switch', sw, "ports", prt)) txn.add(self.api.db_add('Logical_Switch_Port', prt, "addresses", self.lsp_test_addresses[0])) self._check_values() ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/ovn_northbound/fixtures.py0000664000175000017500000000277213641423405027234 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 = {} class PortGroupFixture(fixtures.ImplIdlFixture): api = impl_idl.OvnNbApiIdlImpl create = 'pg_add' delete = 'pg_del' ovsdbapp-1.1.0/ovsdbapp/tests/functional/schema/ovn_northbound/__init__.py0000664000175000017500000000000013641423405027100 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/tests/functional/__init__.py0000664000175000017500000000000013641423405022574 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/0000775000175000017500000000000013641423532016561 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/0000775000175000017500000000000013641423532020220 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/rowview.py0000664000175000017500000000177113641423405022301 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-1.1.0/ovsdbapp/backend/ovs_idl/event.py0000664000175000017500000000337213641423405021717 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 match_fn(self, event, row, old): 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 return True def matches(self, event, row, old=None): if event not in self.events: return False if row._table.name != self.table: return False if not self.match_fn(event, row, old): return False LOG.debug("Matched %s: %r to row=%s old=%s", event.upper(), self, idlutils.row2str(row), idlutils.row2str(old) if old else '') return True class WaitEvent(RowEvent, ovsdb_event.WaitEvent): pass ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/command.py0000664000175000017500000003000613641423405022206 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): READ_ONLY = False def __init__(self, api): self.api = api self.result = None def execute(self, check_error=False, log_errors=True, **kwargs): try: if self.READ_ONLY: self.run_idl(None) return self.result with self.api.transaction(check_error, log_errors, **kwargs) as t: t.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 ReadOnlyCommand(BaseCommand): READ_ONLY = True 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, _as_row=False, **columns): super(DbCreateCommand, self).__init__(api) self.table = table self.columns = columns self.row = _as_row 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 u = txn.get_insert_uuid(self.result.uuid) if self.row: self.result = rowview.RowView(self.api.tables[self.table].rows[u]) else: self.result = u 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 = self.api.lookup(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 = self.api.lookup(self.table, self.record) for col, val in self.col_values: if isinstance(val, collections.Mapping): # TODO(twilson) This is to make a unit/functional test that # used OrderedDict work. In Python 3.7, insertion order is # guaranteed to not change, but I need to verify this is is # still even needed if isinstance(val, collections.OrderedDict): val = dict(val) existing = getattr(record, col, {}) existing.update(val) val = existing # Since we are updating certain keys and leaving existing keys # but rewriting the whole external_ids column, we must verify() record.verify(col) # After https://patchwork.ozlabs.org/patch/1254735/ is merged, # and common, we should handle dicts with setkey like this: # for k, v in val.items(): # record.setkey(col, k, v) # For non-map columns, we unconditionally overwrite the values that # exist, so prior state doesn't matter and we don't need verify() 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 = self.api.lookup(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 = self.api.lookup(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(ReadOnlyCommand): 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 = self.api.lookup(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(ReadOnlyCommand): 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] idx = idlutils.get_index_column(table_schema) columns = self.columns or list(table_schema.columns.keys()) + ['_uuid'] # If there's an index for this table, we'll fetch all columns and # remove the unwanted ones based on self.records. Otherwise, let's try # to get the uuid of the wanted ones which is an O(n^2) operation. if not idx and self.records: rows = [] for record in self.records: try: rows.append(self.api.idl.lookup(self.table, record)) except idlutils.RowNotFound: if self.if_exists: continue raise else: rows = table_schema.rows.values() def _match(row): elem = getattr(row, idx) found = elem in self.records if found: records_found.remove(elem) return found records_found = [] if idx and self.records: if self.if_exists: match = lambda row: getattr(row, idx) in self.records else: # If we're using the approach of removing the unwanted # elements, we'll use a helper list to remove elements as we # find them in the DB contents. This will help us identify # quickly if there's some record missing to raise a RowNotFound # exception later. records_found = list(self.records) match = _match else: match = lambda row: True self.result = [ rowview.RowView(row) if self.row else { c: idlutils.get_column_value(row, c) for c in columns } for row in rows if match(row) ] if records_found: raise idlutils.RowNotFound(table=self.table, col=idx, match=records_found[0]) class DbFindCommand(ReadOnlyCommand): 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(ReadOnlyCommand): 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-1.1.0/ovsdbapp/backend/ovs_idl/common/0000775000175000017500000000000013641423532021510 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/common/base_connection_utils.py0000664000175000017500000000205113641423405026430 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-1.1.0/ovsdbapp/backend/ovs_idl/common/__init__.py0000664000175000017500000000000013641423405023606 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/connection.py0000664000175000017500000001330013641423405022725 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 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 from ovsdbapp import exceptions if os.name == 'nt': from ovsdbapp.backend.ovs_idl.windows import connection_utils else: from ovsdbapp.backend.ovs_idl.linux import connection_utils LOG = logging.getLogger(__name__) 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): errors = 0 while self._is_running: # If we fail in an Idl call, we could have missed an update # from the server, leaving us out of sync with ovsdb-server. # It is not safe to continue without restarting the connection, # though it is likely that the error is unrecoverable, so only try # a few times before bailing completely. try: 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() except Exception as e: # This shouldn't happen, but is possible if there is a bug # in python-ovs errors += 1 LOG.exception(e) if errors <= 3: self.idl.force_reconnect() idlutils.wait_for_change(self.idl, self.timeout) continue self._is_running = False break errors = 0 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 try: self.txns.put(txn, timeout=self.timeout) except Queue.Full: raise exceptions.TimeoutException(commands=txn.commands, timeout=self.timeout) 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-1.1.0/ovsdbapp/backend/ovs_idl/vlog.py0000664000175000017500000000637013641423405021546 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-1.1.0/ovsdbapp/backend/ovs_idl/linux/0000775000175000017500000000000013641423532021357 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/linux/connection_utils.py0000664000175000017500000000244513641423405025314 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-1.1.0/ovsdbapp/backend/ovs_idl/linux/__init__.py0000664000175000017500000000000013641423405023455 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/transaction.py0000664000175000017500000001205213641423405023116 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 if not self.commands: LOG.debug("There are no commands to commit") return [] 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 n=%(n)d command(idx=%(idx)s): %(cmd)s", {'idx': i, 'cmd': command, 'n': attempts}) 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 in (txn.ERROR, txn.NOT_LOCKED): msg = 'OVSDB Error: ' if status == txn.NOT_LOCKED: msg += ("The transaction failed because the IDL has " "been configured to require a database lock " "but didn't get it yet or has already lost it") else: msg += 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) else: LOG.debug("Transaction returned an unknown status: %s", status) 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-1.1.0/ovsdbapp/backend/ovs_idl/fixtures.py0000664000175000017500000000216113641423405022442 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-1.1.0/ovsdbapp/backend/ovs_idl/windows/0000775000175000017500000000000013641423532021712 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/windows/utils.py0000664000175000017500000000322313641423405023423 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) return f(*args, **kwargs) ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/windows/connection_utils.py0000664000175000017500000000416213641423405025645 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-1.1.0/ovsdbapp/backend/ovs_idl/windows/__init__.py0000664000175000017500000000000013641423405024010 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/idlutils.py0000664000175000017500000002627613641423405022437 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 logging 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 LOG = logging.getLogger(__name__) 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 """ parsed_connections = parse_connection(connection) for c in parsed_connections: err, strm = stream.Stream.open_block( stream.Stream.open(c)) if err: LOG.error("Unable to open stream to %(conn)s to retrieve schema: " "%(err)s", {'conn': c, 'err': os.strerror(err)}) continue rpc = jsonrpc.Connection(strm) req = jsonrpc.Message.create_request('get_schema', [schema_name]) err, resp = rpc.transact_block(req) rpc.close() if err: LOG.info("Could not retrieve schema from %(conn)s: " "%(err)s", {'conn': c, 'err': os.strerror(err)}) continue elif resp.error: LOG.error("TRXN error, failed to retrieve schema from %(conn)s: " "%(err)s", {'conn': c, 'err': resp.error}) continue return idl.SchemaHelper(None, resp.result) raise Exception("Could not retrieve schema from %s" % connection) def parse_connection(connection_string): """Parse a connection string. The connection string must be of the form proto:address:port,proto:address:port,... The parsing logic here must be identical to the one at https://github.com/openvswitch/ovs/blob/master/python/ovs/db/idl.py#L162 for remote connections. :param connection_string: The ovsdb-server connection string :type connection_string: string """ return [c.strip() for c in connection_string.split(',')] 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 def row2str(row): """Get a string representation of a Row""" # This is not a repr, as the Row object takes a dict of Datum objects and # we don't really want to deal with those, just what the Python values are. # Row foreign keys are printed as their UUID return "%s(%s)" % (row._table.name, ", ".join( "%s=%s" % (col, idl._row_to_uuid(getattr(row, col))) for col in row._table.columns if hasattr(row, col))) ovsdbapp-1.1.0/ovsdbapp/backend/ovs_idl/__init__.py0000664000175000017500000001342613641423405022336 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, start=True, **kwargs): super(Backend, self).__init__(**kwargs) if start: 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_create_row(self, table, **col_values): return cmd.DbCreateCommand(self, table, _as_row=True, **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") # Handle commands by simply returning its result if isinstance(record, cmd.BaseCommand): return record.result 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-1.1.0/ovsdbapp/backend/__init__.py0000664000175000017500000000000013641423405020657 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/0000775000175000017500000000000013641423532016432 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/open_vswitch/0000775000175000017500000000000013641423532021142 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/open_vswitch/impl_idl.py0000664000175000017500000001264513641423405023314 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 idlutils 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' lookup_table = idlutils._LOOKUP_TABLE @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) def iface_get_external_id(self, name, field): return cmd.IfaceGetExternalIdCommand(self, name, field) def iface_set_external_id(self, name, field, value): return cmd.IfaceSetExternalIdCommand(self, name, field, value) ovsdbapp-1.1.0/ovsdbapp/schema/open_vswitch/api.py0000664000175000017500000002027713641423405022274 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 br_set_external_id(self, name, field, value): """Create a command to set the OVS Bridge's external_ids :param name: The name of the bridge :type name: string :param field: The external_ids field to set :type field: string :param value: The external_ids value to set :type value: string :returns: :class:`Command` with field no 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 """ @abc.abstractmethod def iface_get_external_id(self, name, field): """Create a command to return a field from the Interface's external_ids :param name: The name of the interface :type name: string :param field: The external_ids field to return :type field: string :returns: :class:`Command` with field value result """ @abc.abstractmethod def iface_set_external_id(self, name, field, value): """Create a command to set the OVS Interface's external_ids :param name: The name of the interface :type name: string :param field: The external_ids field to set :type field: string :param value: The external_ids value to set :type value: string :returns: :class:`Command` with field no result """ ovsdbapp-1.1.0/ovsdbapp/schema/open_vswitch/commands.py0000664000175000017500000003306113641423405023317 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(command.ReadOnlyCommand): 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(command.ReadOnlyCommand): 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(command.ReadOnlyCommand): 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 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(command.ReadOnlyCommand): 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(command.ReadOnlyCommand): 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(command.ReadOnlyCommand): 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(command.ReadOnlyCommand): 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(command.ReadOnlyCommand): 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) class GetExternalIdCommand(command.ReadOnlyCommand): def __init__(self, api, table, name, field): super(GetExternalIdCommand, self).__init__(api) self.table = table self.name = name self.field = field def run_idl(self, txn): row = idlutils.row_by_value( self.api.idl, self.table, 'name', self.name) self.result = row.external_ids[self.field] class SetExternalIdCommand(BaseCommand): def __init__(self, api, table, name, field, value): super(SetExternalIdCommand, self).__init__(api) self.table = table self.name = name self.field = field self.value = value def run_idl(self, txn): row = idlutils.row_by_value( self.api.idl, self.table, 'name', self.name) external_ids = getattr(row, 'external_ids', {}) external_ids[self.field] = self.value row.external_ids = external_ids class BrGetExternalIdCommand(GetExternalIdCommand): def __init__(self, api, name, field): super(BrGetExternalIdCommand, self).__init__( api, 'Bridge', name, field) class BrSetExternalIdCommand(SetExternalIdCommand): def __init__(self, api, name, field, value): super(BrSetExternalIdCommand, self).__init__( api, 'Bridge', name, field, value) class IfaceGetExternalIdCommand(GetExternalIdCommand): def __init__(self, api, name, field): super(IfaceGetExternalIdCommand, self).__init__( api, 'Interface', name, field) class IfaceSetExternalIdCommand(SetExternalIdCommand): def __init__(self, api, name, field, value): super(IfaceSetExternalIdCommand, self).__init__( api, 'Interface', name, field, value) ovsdbapp-1.1.0/ovsdbapp/schema/open_vswitch/helpers.py0000664000175000017500000000370513641423405023162 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-1.1.0/ovsdbapp/schema/open_vswitch/__init__.py0000664000175000017500000000000013641423405023240 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/ovn_southbound/0000775000175000017500000000000013641423532021506 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/ovn_southbound/impl_idl.py0000664000175000017500000000323513641423405023653 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, port, chassis, may_exist=False): return cmd.LspBindCommand(self, port, chassis, may_exist) def lsp_unbind(self, port, if_exists=False): return cmd.LspUnbindCommand(self, port, if_exists) ovsdbapp-1.1.0/ovsdbapp/schema/ovn_southbound/api.py0000664000175000017500000000574313641423405022641 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 delete :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: string :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-1.1.0/ovsdbapp/schema/ovn_southbound/commands.py0000664000175000017500000001071013641423405023657 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.ReadOnlyCommand): 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-1.1.0/ovsdbapp/schema/ovn_southbound/__init__.py0000664000175000017500000000000013641423405023604 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/__init__.py0000664000175000017500000000000013641423405020530 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/ovn_northbound/0000775000175000017500000000000013641423532021476 5ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/schema/ovn_northbound/impl_idl.py0000664000175000017500000003004313641423405023640 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, if_exists=False): return self.db_remove('Logical_Switch', switch_uuid, 'dns_records', dns_uuid, if_exists=if_exists) 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 pg_acl_add(self, port_group, direction, priority, match, action, log=False, may_exist=False, **external_ids): return cmd.PgAclAddCommand(self, port_group, direction, priority, match, action, log, may_exist, **external_ids) def pg_acl_del(self, port_group, direction=None, priority=None, match=None): return cmd.PgAclDelCommand(self, port_group, direction, priority, match) def pg_acl_list(self, port_group): return cmd.PgAclListCommand(self, port_group) def qos_add(self, switch, direction, priority, match, rate=None, burst=None, dscp=None, may_exist=False, **columns): return cmd.QoSAddCommand(self, switch, direction, priority, match, rate, burst, dscp, may_exist, **columns) def qos_del(self, switch, direction=None, priority=None, match=None): return cmd.QoSDelCommand(self, switch, direction, priority, match) def qos_list(self, switch): return cmd.QoSListCommand(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 lr_get(self, router): return cmd.LrGetCommand(self, router) 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 lrp_set_options(self, port, **options): return cmd.LrpSetOptionsCommand(self, port, **options) def lrp_get_options(self, port): return cmd.LrpGetOptionsCommand(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, if_exists=False): return self.db_remove('DNS', uuid, 'records', hostname, if_exists=if_exists) def dns_set_external_ids(self, uuid, **external_ids): return cmd.DnsSetExternalIdsCommand(self, uuid, **external_ids) def pg_add(self, name=None, may_exist=False, **columns): return cmd.PgAddCommand(self, name, may_exist=may_exist, **columns) def pg_del(self, name, if_exists=False): return cmd.PgDelCommand(self, name, if_exists=if_exists) def pg_add_ports(self, pg_id, lsp): return cmd.PgAddPortCommand(self, pg_id, lsp=lsp) def pg_del_ports(self, pg_id, lsp, if_exists=False): return cmd.PgDelPortCommand(self, pg_id, lsp=lsp, if_exists=if_exists) def pg_get(self, pg): return cmd.PgGetCommand(self, pg) def ha_chassis_group_add(self, name, may_exist=False, **columns): return cmd.HAChassisGroupAddCommand( self, name, may_exist=may_exist, **columns) def ha_chassis_group_del(self, name, if_exists=False): return cmd.HAChassisGroupDelCommand(self, name, if_exists=if_exists) def ha_chassis_group_get(self, name): return cmd.HAChassisGroupGetCommand(self, name) def ha_chassis_group_add_chassis(self, hcg_id, chassis, priority, **columns): return cmd.HAChassisGroupAddChassisCommand( self, hcg_id, chassis, priority, **columns) def ha_chassis_group_del_chassis(self, hcg_id, chassis, if_exists=False): return cmd.HAChassisGroupDelChassisCommand( self, hcg_id, chassis, if_exists=if_exists) ovsdbapp-1.1.0/ovsdbapp/schema/ovn_northbound/api.py0000664000175000017500000011513513641423405022626 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, if_exists=False): """Remove the 'dns_record' from the switch's 'dns_records' list :param switch_uuid: The uuid of the switch :type switch_uuid: string or uuid.UUID :param dns_uuid: The uuid of the DNS record :type dns_uuid: string or uuid.UUID :param if_exists: If True, don't fail if the DNS record doesn't exist :type if_exists: boolean :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 pg_acl_add(self, port_group, direction, priority, match, action, log=False): """Add an ACL to 'port_group' :param port_group: The name or uuid of the port group :type port_group: 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 pg_acl_del(self, port_group, direction=None, priority=None, match=None): """Remove ACLs from 'port_group' If only port_group 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 Port Group. If all the fields are given, then only flows that match all fields will be deleted. :param port_group: The name or uuid of the port group :type port_group: 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 pg_acl_list(self, port_group): """Get the ACLs for 'port group' :param port_group: The name or uuid of the switch :type port_group: string or uuid.UUID :returns: :class:`Command` with RowView list result """ @abc.abstractmethod def qos_add(self, switch, direction, priority, match, rate=None, burst=None, dscp=None, may_exist=False, **columns): """Add an Qos rules 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 QoS :type priority: int :param match: The match rule :type match: string :param dscp: The dscp mark to take upon match :type dscp: int :param rate: The rate limit to take upon match :type rate: int :param burst: The burst rate limit to take upon match :type burst: int :param may_exist: If True, don't fail if the QoS rule 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 qos_del(self, switch, direction=None, priority=None, match=None): """Remove Qos rules from 'switch' If only switch is supplied, all the QoS rules 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 QoS :type priority: int :param match: The match rule :type match: string :returns: :class:`Command` with no result """ @abc.abstractmethod def qos_list(self, switch): """Get the Qos rules 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 lr_get(self, router): """Get logical router for 'router' :returns: :class:`Command` with RowView 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 lrp_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 lrp_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 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, **columns): """Create a DNS row with columns :param **columns: Additional columns to directly set on the dns :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, if_exists=False): """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 :param if_exists: If True, don't fail if the DNS record doesn't exist :type if_exists: boolean :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 """ @abc.abstractmethod def pg_add(self, name=None, may_exist=False, **columns): """Create a port group :param name: The name of the port group (optional) :type name: string :param may_exist: If True, don't fail if the port group already exists :type may_exist: bool :param columns: Additional columns to directly set on the port group (e.g external_ids, ports, acls) :type columns: dictionary :returns: :class:`Command` with RowView result """ @abc.abstractmethod def pg_del(self, name, if_exists=False): """Delete a port group :param name: The name of the port group :type name: string :param if_exists: If True, don't fail if the port group doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def pg_add_ports(self, pg_id, lsp): """Add a list of logical port to a port group :param pg_id: The name or uuid of the port group :type pg_id: string or uuid.UUID :param lsp: A list of :class:`Command` Logical_Switch_Port instance result or UUID :type lsp: A list of :class:`Command` Logical_Switch_Port or string or uuid.UUID A Logical_Switch_Port instance or string or uuid.UUID :returns: :class:`Command` with no result """ @abc.abstractmethod def pg_del_ports(self, pg_id, lsp, if_exists=False): """Delete a list of logical port from a port group :param pg_id: The name or uuid of the port group :type pg_id: string or uuid.UUID :param lsp: A list of :class:`Command` Logical_Switch_Port instance result or UUID :type lsp: A list of :class:`Command` Logical_Switch_Port or string or uuid.UUID :type if_exists: If True, don't fail if the logical port(s) doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def pg_get(self, pg_id): """Get port group :param pg_id: The name or uuid of the port group :type pg_id: string or uuid.UUID :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ha_chassis_group_add(self, name, may_exist=False, **columns): """Create a HA Chassis Group :param name: The name of the ha chassis group :type name: string :param may_exist: If True, don't fail if the ha chassis group already exists :type may_exist: bool :param columns: Additional columns to directly set on the ha chassis group (e.g external_ids) :type columns: dictionary :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ha_chassis_group_del(self, name, if_exists=False): """Delete a HA Chassis Group :param name: The name of the ha chassis group :type name: string :param if_exists: If True, don't fail if the ha chassis group doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ @abc.abstractmethod def ha_chassis_group_get(self, name): """Get HA Chassis Group :param name: The name or uuid of the ha chassis group :type name: string or uuid.UUID :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ha_chassis_group_add_chassis(self, hcg_id, chassis, priority, **columns): """Add a HA Chassis to a HA Chassis Group :param hcg_id: The name or uuid of the ha chassis group :type hcg_id: string or uuid.UUID :param chassis: The name of the ha chassis :type chassis: string :param priority: The priority of the ha chassis :type priority: int :param columns: Additional columns to directly set on the ha chassis (e.g external_ids) :type columns: dictionary :returns: :class:`Command` with RowView result """ @abc.abstractmethod def ha_chassis_group_del_chassis(self, hcg_id, chassis, if_exists=False): """Delete a HA Chassis from a HA Chassis Group :param hcg_id: The name or uuid of the ha chassis group :type hcg_id: string or uuid.UUID :param chassis: The name of the ha chassis :type chassis: string :param if_exists: If True, don't fail if the ha chassis doesn't exist :type if_exists: boolean :returns: :class:`Command` with no result """ ovsdbapp-1.1.0/ovsdbapp/schema/ovn_northbound/commands.py0000664000175000017500000014341313641423405023656 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.ReadOnlyCommand): 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 _AclAddHelper(cmd.AddCommand): table_name = 'ACL' def __init__(self, api, entity, direction, priority, match, action, log=False, may_exist=False, severity=None, name=None, **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 between 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(_AclAddHelper, self).__init__(api) self.entity = entity self.direction = direction self.priority = priority self.match = match self.action = action self.log = log self.may_exist = may_exist self.severity = severity self.name = name 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): entity = self.api.lookup(self.lookup_table, self.entity) acls = [acl for acl in entity.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 acl.severity = self.severity acl.name = self.name entity.addvalue('acls', acl) for col, value in self.external_ids.items(): acl.setkey('external_ids', col, value) self.result = acl.uuid class AclAddCommand(_AclAddHelper): lookup_table = 'Logical_Switch' def __init__(self, api, switch, direction, priority, match, action, log=False, may_exist=False, severity=None, name=None, **external_ids): # NOTE: we're overriding the constructor here to not break any # existing callers before we introduced Port Groups. super(AclAddCommand, self).__init__(api, switch, direction, priority, match, action, log, may_exist, severity, name, **external_ids) class PgAclAddCommand(_AclAddHelper): lookup_table = 'Port_Group' class _AclDelHelper(cmd.BaseCommand): def __init__(self, api, entity, 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(_AclDelHelper, self).__init__(api) self.entity = entity self.conditions = [] if direction: self.conditions.append(('direction', '=', direction)) # priority can be 0 if match: # and therefore priority due to the above check self.conditions += [('priority', '=', priority), ('match', '=', match)] def run_idl(self, txn): entity = self.api.lookup(self.lookup_table, self.entity) for acl in [a for a in entity.acls if idlutils.row_match(a, self.conditions)]: entity.delvalue('acls', acl) acl.delete() class AclDelCommand(_AclDelHelper): lookup_table = 'Logical_Switch' def __init__(self, api, switch, direction=None, priority=None, match=None): # NOTE: we're overriding the constructor here to not break any # existing callers before we introduced Port Groups. super(AclDelCommand, self).__init__(api, switch, direction, priority, match) class PgAclDelCommand(_AclDelHelper): lookup_table = 'Port_Group' class _AclListHelper(cmd.ReadOnlyCommand): def __init__(self, api, entity): super(_AclListHelper, self).__init__(api) self.entity = entity def run_idl(self, txn): entity = self.api.lookup(self.lookup_table, self.entity) self.result = [rowview.RowView(acl) for acl in entity.acls] class AclListCommand(_AclListHelper): lookup_table = 'Logical_Switch' class PgAclListCommand(_AclListHelper): lookup_table = 'Port_Group' class QoSAddCommand(cmd.AddCommand): table_name = 'QoS' def __init__(self, api, switch, direction, priority, match, rate=None, burst=None, dscp=None, may_exist=False, **columns): 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 between 0 and %s, inclusive" % const.ACL_PRIORITY_MAX) if rate is not None and not 1 <= rate <= const.QOS_BANDWIDTH_MAX: raise ValueError("rate(%s) must be between 1 and %s, inclusive" % rate, const.QOS_BANDWIDTH_MAX) if burst is not None and not 1 <= burst <= const.QOS_BANDWIDTH_MAX: raise ValueError("burst(%s) must be between 1 and %s, " "inclusive" % burst, const.QOS_BANDWIDTH_MAX) if dscp is not None and not 0 <= dscp <= const.QOS_DSCP_MAX: raise ValueError("dscp(%s) must be between 0 and %s, inclusive" % dscp, const.QOS_DSCP_MAX) if rate is None and dscp is None: raise ValueError("One of the rate or dscp must be configured") super(QoSAddCommand, self).__init__(api) self.switch = switch self.direction = direction self.priority = priority self.match = match self.rate = rate self.burst = burst self.dscp = dscp self.may_exist = may_exist self.columns = columns def qos_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) qos_rules = [row for row in ls.qos_rules if self.qos_match(row)] if qos_rules: if self.may_exist: self.result = rowview.RowView(qos_rules[0]) return raise RuntimeError("QoS (%s, %s, %s) already exists" % ( self.direction, self.priority, self.match)) row = txn.insert(self.api.tables[self.table_name]) row.direction = self.direction row.priority = self.priority row.match = self.match if self.rate: row.setkey('bandwidth', 'rate', self.rate) if self.burst: row.setkey('bandwidth', 'burst', self.burst) if self.dscp is not None: row.setkey('action', 'dscp', self.dscp) self.set_columns(row, **self.columns) ls.addvalue('qos_rules', row) self.result = row.uuid class QoSDelCommand(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(QoSDelCommand, self).__init__(api) self.switch = switch self.conditions = [] if direction: self.conditions.append(('direction', '=', direction)) # priority can be 0 if match: # and therefor priority 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 row in ls.qos_rules: if idlutils.row_match(row, self.conditions): ls.delvalue('qos_rules', row) row.delete() class QoSListCommand(cmd.ReadOnlyCommand): def __init__(self, api, switch): super(QoSListCommand, self).__init__(api) self.switch = switch def run_idl(self, txn): ls = self.api.lookup('Logical_Switch', self.switch) self.result = [rowview.RowView(row) for row in ls.qos_rules] 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.ReadOnlyCommand): 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 (%s) must be router/unknown/dynamic/" "ethaddr[ ipaddr...]" % (addr,)) 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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): table = 'Logical_Switch_Port' 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(self.table, self.port) lsp.options = self.options class LspGetOptionsCommand(cmd.ReadOnlyCommand): table = 'Logical_Switch_Port' def __init__(self, api, port): super(LspGetOptionsCommand, self).__init__(api) self.port = port def run_idl(self, txn): lsp = self.api.lookup(self.table, 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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.ReadOnlyCommand): def run_idl(self, txn): self.result = [rowview.RowView(r) for r in self.api.tables['Logical_Router'].rows.values()] class LrGetCommand(cmd.BaseGetRowCommand): table = 'Logical_Router' 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) gwcs = self.columns.pop('gateway_chassis', []) for n, chassis in enumerate(gwcs): gwc_name = '%s_%s' % (lrp.name, chassis) cmd = GatewayChassisAddCommand(self.api, gwc_name, chassis, len(gwcs) - n, may_exist=True) cmd.run_idl(txn) lrp.addvalue('gateway_chassis', cmd.result) 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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 LrpSetOptionsCommand(LspSetOptionsCommand): table = 'Logical_Router_Port' class LrpGetOptionsCommand(LspGetOptionsCommand): table = 'Logical_Router_Port' 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.ReadOnlyCommand): 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: try: match_ip = str(netaddr.IPAddress(match_ip)) except ValueError: # logical_ip can be IPNetwork if nat_type == const.NAT_SNAT: match_ip = str(netaddr.IPNetwork(match_ip)) else: raise 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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.ReadOnlyCommand): 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) class PgAddCommand(cmd.AddCommand): table_name = 'Port_Group' def __init__(self, api, name, may_exist=False, **columns): super(PgAddCommand, self).__init__(api) self.name = name self.may_exist = may_exist self.columns = columns def run_idl(self, txn): if self.may_exist: try: pg = self.api.lookup(self.table_name, self.name) self.result = rowview.RowView(pg) return except idlutils.RowNotFound: pass pg = txn.insert(self.api._tables[self.table_name]) pg.name = self.name or "" self.set_columns(pg, **self.columns) self.result = pg.uuid class PgDelCommand(cmd.BaseCommand): table_name = 'Port_Group' def __init__(self, api, name, if_exists=False): super(PgDelCommand, self).__init__(api) self.name = name self.if_exists = if_exists def run_idl(self, txn): try: pg = self.api.lookup(self.table_name, self.name) pg.delete() except idlutils.RowNotFound: if self.if_exists: return raise RuntimeError('Port group %s does not exist' % self.name) class _PgUpdatePortsHelper(cmd.BaseCommand): method = None def __init__(self, api, port_group, lsp=None, if_exists=False): super(_PgUpdatePortsHelper, self).__init__(api) self.port_group = port_group self.lsp = [] if lsp is None else self._listify(lsp) self.if_exists = if_exists def _listify(self, res): return res if isinstance(res, (list, tuple)) else [res] def _run_method(self, pg, port): if not port: return if isinstance(port, cmd.BaseCommand): port = port.result elif utils.is_uuid_like(port): try: port = self.api.lookup('Logical_Switch_Port', port) except idlutils.RowNotFound: if self.if_exists: return raise RuntimeError( 'Port %s does not exist' % port) getattr(pg, self.method)('ports', port) def run_idl(self, txn): try: pg = self.api.lookup('Port_Group', self.port_group) except idlutils.RowNotFound: raise RuntimeError('Port group %s does not exist' % self.port_group) for lsp in self.lsp: self._run_method(pg, lsp) class PgAddPortCommand(_PgUpdatePortsHelper): method = 'addvalue' class PgDelPortCommand(_PgUpdatePortsHelper): method = 'delvalue' class PgGetCommand(cmd.BaseGetRowCommand): table = 'Port_Group' class GatewayChassisAddCommand(cmd.AddCommand): table_name = 'Gateway_Chassis' def __init__(self, api, name, chassis_name, priority=0, may_exist=False, **columns): super(GatewayChassisAddCommand, self).__init__(api) self.name = name self.chassis_name = chassis_name self.priority = priority self.may_exist = may_exist self.columns = columns def run_idl(self, txn): if self.may_exist: gwc = self.api.lookup(self.table_name, self.name, None) else: gwc = None if not gwc: # If gwc exists with name, this will properly fail if not may_exist # since 'name' is indexed gwc = txn.insert(self.api.tables[self.table_name]) gwc.name = self.name gwc.priority = self.priority self.set_columns(gwc, **self.columns) self.result = gwc class HAChassisGroupAddCommand(cmd.AddCommand): table_name = 'HA_Chassis_Group' def __init__(self, api, name, may_exist=False, **columns): super(HAChassisGroupAddCommand, self).__init__(api) self.name = name self.may_exist = may_exist self.columns = columns def run_idl(self, txn): if self.may_exist: try: hcg = self.api.lookup(self.table_name, self.name) self.result = rowview.RowView(hcg) return except idlutils.RowNotFound: pass hcg = txn.insert(self.api._tables[self.table_name]) hcg.name = self.name self.set_columns(hcg, **self.columns) self.result = hcg.uuid class HAChassisGroupDelCommand(cmd.BaseCommand): table_name = 'HA_Chassis_Group' def __init__(self, api, name, if_exists=False): super(HAChassisGroupDelCommand, self).__init__(api) self.name = name self.if_exists = if_exists def run_idl(self, txn): try: hcg = self.api.lookup(self.table_name, self.name) hcg.delete() except idlutils.RowNotFound: if self.if_exists: return raise RuntimeError( 'HA Chassis Group %s does not exist' % self.name) class HAChassisGroupGetCommand(cmd.BaseGetRowCommand): table = 'HA_Chassis_Group' class HAChassisGroupAddChassisCommand(cmd.AddCommand): table_name = 'HA_Chassis' def __init__(self, api, hcg_id, chassis, priority, **columns): super(HAChassisGroupAddChassisCommand, self).__init__(api) self.hcg_id = hcg_id self.chassis = chassis self.priority = priority self.columns = columns def run_idl(self, txn): hc_group = self.api.lookup('HA_Chassis_Group', self.hcg_id) found = False hc = None for hc in hc_group.ha_chassis: if hc.chassis_name != self.chassis: continue found = True break else: hc = txn.insert(self.api.tables[self.table_name]) hc.chassis_name = self.chassis hc.priority = self.priority self.set_columns(hc, **self.columns) if not found: hc_group.addvalue('ha_chassis', hc) self.result = hc.uuid class HAChassisGroupDelChassisCommand(cmd.BaseCommand): table_name = 'HA_Chassis' def __init__(self, api, hcg_id, chassis, if_exists=False): super(HAChassisGroupDelChassisCommand, self).__init__(api) self.hcg_id = hcg_id self.chassis = chassis self.if_exists = if_exists def run_idl(self, txn): try: hc_group = self.api.lookup('HA_Chassis_Group', self.hcg_id) except idlutils.RowNotFound: if self.if_exists: return hc = None for hc in hc_group.ha_chassis: if hc.chassis_name == self.chassis: break else: if self.if_exists: return raise RuntimeError( 'HA Chassis %s does not exist' % self.hcg_id) hc_group.delvalue('ha_chassis', hc) hc.delete() ovsdbapp-1.1.0/ovsdbapp/schema/ovn_northbound/__init__.py0000664000175000017500000000000013641423405023574 0ustar zuulzuul00000000000000ovsdbapp-1.1.0/ovsdbapp/__init__.py0000664000175000017500000000123013641423405017276 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-1.1.0/ovsdbapp/exceptions.py0000664000175000017500000000411213641423405017722 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 pylint: disable=undefined-variable 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 OVS and related services are " "available and that the relevant configuration options " "are correct.") ovsdbapp-1.1.0/setup.cfg0000664000175000017500000000174213641423532015201 0ustar zuulzuul00000000000000[metadata] name = ovsdbapp summary = A library for creating OVSDB applications description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://pypi.org/project/ovsdbapp/ python-requires = >=3.6 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 :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 [files] packages = ovsdbapp [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 [egg_info] tag_build = tag_date = 0