├── requirements.txt ├── .gitignore ├── tests ├── unit │ ├── __init__.py │ ├── mock │ │ ├── __init__.py │ │ ├── path.py │ │ ├── vault_helper.py │ │ ├── procenv.py │ │ ├── loader.py │ │ └── yaml_helper.py │ ├── compat │ │ ├── __init__.py │ │ ├── unittest.py │ │ └── mock.py │ ├── modules │ │ ├── __init__.py │ │ ├── network │ │ │ ├── __init__.py │ │ │ └── ovs │ │ │ │ ├── __init__.py │ │ │ │ ├── fixtures │ │ │ │ ├── __init__.py │ │ │ │ ├── br_to_vlan_zero.cfg │ │ │ │ ├── get_port_eth2_tag.cfg │ │ │ │ ├── list_br_test_br.cfg │ │ │ │ ├── list_ports_bond_br.cfg │ │ │ │ ├── list_ports_test_br.cfg │ │ │ │ ├── br_to_parent_test_br.cfg │ │ │ │ ├── get_fail_mode_secure.cfg │ │ │ │ ├── br_get_external_id_foo_bar.cfg │ │ │ │ ├── get_port_bond0_external_ids.cfg │ │ │ │ ├── get_port_eth2_external_ids.cfg │ │ │ │ ├── get_port_bond0_other_config.cfg │ │ │ │ ├── openvswitch_db_disable_in_band_missing.cfg │ │ │ │ └── openvswitch_db_disable_in_band_true.cfg │ │ │ │ ├── ovs_module.py │ │ │ │ ├── test_openvswitch_bond.py │ │ │ │ ├── test_openvswitch_port.py │ │ │ │ ├── test_openvswitch_bridge.py │ │ │ │ └── test_openvswitch_db.py │ │ ├── conftest.py │ │ └── utils.py │ └── requirements.txt ├── .gitignore └── integration │ ├── target-prefixes.network │ ├── targets │ ├── openvswitch_db │ │ ├── aliases │ │ ├── defaults │ │ │ └── main.yaml │ │ ├── meta │ │ │ └── main.yaml │ │ ├── tasks │ │ │ └── main.yml │ │ └── tests │ │ │ └── basic.yaml │ ├── openvswitch_bridge │ │ ├── aliases │ │ ├── defaults │ │ │ └── main.yaml │ │ ├── meta │ │ │ └── main.yaml │ │ ├── tasks │ │ │ └── main.yml │ │ └── tests │ │ │ └── basic.yaml │ └── prepare_ovs_tests │ │ └── tasks │ │ └── main.yml │ └── network-integration.cfg ├── plugins └── modules │ ├── __init__.py │ ├── openvswitch_port.py │ ├── openvswitch_bridge.py │ ├── openvswitch_db.py │ └── openvswitch_bond.py ├── changelogs ├── fragments │ └── add_gha_periodic.yaml ├── config.yaml ├── .plugin-cache.yaml └── changelog.yaml ├── .isort.cfg ├── test-requirements.txt ├── codecov.yml ├── .yamllint ├── pyproject.toml ├── .prettierignore ├── bindep.txt ├── meta └── runtime.yml ├── .github └── workflows │ ├── codecoverage.yml │ └── tests.yml ├── galaxy.yml ├── tox.ini ├── .pre-commit-config.yaml ├── CHANGELOG.rst ├── README.md └── docs ├── openvswitch.openvswitch.openvswitch_port_module.rst ├── openvswitch.openvswitch.openvswitch_bridge_module.rst ├── openvswitch.openvswitch.openvswitch_db_module.rst └── openvswitch.openvswitch.openvswitch_bond_module.rst /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tox 2 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | -------------------------------------------------------------------------------- /tests/unit/mock/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/compat/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/modules/network/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/target-prefixes.network: -------------------------------------------------------------------------------- 1 | openvswitch 2 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_db/aliases: -------------------------------------------------------------------------------- 1 | non_local 2 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_bridge/aliases: -------------------------------------------------------------------------------- 1 | non_local 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/br_to_vlan_zero.cfg: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/get_port_eth2_tag.cfg: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/list_br_test_br.cfg: -------------------------------------------------------------------------------- 1 | test-br 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/list_ports_bond_br.cfg: -------------------------------------------------------------------------------- 1 | bond0 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/list_ports_test_br.cfg: -------------------------------------------------------------------------------- 1 | eth2 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/br_to_parent_test_br.cfg: -------------------------------------------------------------------------------- 1 | test-br 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/get_fail_mode_secure.cfg: -------------------------------------------------------------------------------- 1 | secure 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/br_get_external_id_foo_bar.cfg: -------------------------------------------------------------------------------- 1 | foo=bar 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/get_port_bond0_external_ids.cfg: -------------------------------------------------------------------------------- 1 | {foo=bar} 2 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/get_port_eth2_external_ids.cfg: -------------------------------------------------------------------------------- 1 | {foo=bar} 2 | -------------------------------------------------------------------------------- /changelogs/fragments/add_gha_periodic.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | trivial: 3 | - Enable nightly GHA runs. 4 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/get_port_bond0_other_config.cfg: -------------------------------------------------------------------------------- 1 | {bond-detect-mode=miimon} 2 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_bridge/defaults/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | testcase: "*" 3 | test_items: [] 4 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_db/defaults/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | testcase: "*" 3 | test_items: [] 4 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_db/meta/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - prepare_ovs_tests 4 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_bridge/meta/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - prepare_ovs_tests 4 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | known_first_party=ansible_collections.openvswitch.openvswitch 3 | line_length=100 4 | profile=black 5 | -------------------------------------------------------------------------------- /tests/integration/network-integration.cfg: -------------------------------------------------------------------------------- 1 | [persistent_connection] 2 | command_timeout = 100 3 | connect_timeout = 100 4 | connect_retry_timeout = 100 5 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # zuul linters via tox 2 | black==24.4.2 3 | flake8 4 | yamllint 5 | 6 | # Unit test runner 7 | pytest-ansible 8 | pytest-xdist 9 | pytest-cov 10 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | --- 2 | codecov: 3 | require_ci_to_pass: true 4 | comment: false 5 | coverage: 6 | status: 7 | patch: false 8 | project: 9 | default: 10 | threshold: 0.3% 11 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | ignore: | 5 | .tox 6 | changelogs/* 7 | 8 | rules: 9 | braces: 10 | max-spaces-inside: 1 11 | level: error 12 | brackets: 13 | max-spaces-inside: 1 14 | level: error 15 | line-length: disable 16 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | 4 | [tool.pytest.ini_options] 5 | addopts = ["-vvv", "-n", "2", "--log-level", "WARNING", "--color", "yes"] 6 | testpaths = ["tests"] 7 | filterwarnings = ['ignore:AnsibleCollectionFinder has already been configured'] 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Stuff we don't want prettier to ever to look into 2 | .*/ 3 | coverage/ 4 | 5 | # Environments 6 | .env 7 | .venv 8 | env/ 9 | venv/ 10 | ENV/ 11 | env.bak/ 12 | venv.bak/ 13 | 14 | # A linked collection directory created by pytest-ansible-units 15 | collections/ 16 | 17 | README.md 18 | -------------------------------------------------------------------------------- /tests/unit/mock/path.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __metaclass__ = type 4 | 5 | from ansible.utils.path import unfrackpath 6 | 7 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat.mock import MagicMock 8 | 9 | mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) 10 | -------------------------------------------------------------------------------- /bindep.txt: -------------------------------------------------------------------------------- 1 | # This is a cross-platform list tracking distribution packages needed by tests; 2 | # see https://docs.openstack.org/infra/bindep/ for additional information. 3 | 4 | gcc-c++ [doc test platform:rpm] 5 | libyaml-devel [test platform:rpm] 6 | libyaml-dev [test platform:dpkg] 7 | libssh-devel [test platform:rpm] 8 | libffi-devel [test platform:rpm] 9 | openssl-devel [test platform:rpm] 10 | -------------------------------------------------------------------------------- /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | --- 2 | requires_ansible: ">=2.9.10,<2.18.0" 3 | plugin_routing: 4 | modules: 5 | bridge: 6 | redirect: openvswitch.openvswitch.openvswitch_bridge 7 | db: 8 | redirect: openvswitch.openvswitch.openvswitch_db 9 | port: 10 | redirect: openvswitch.openvswitch.openvswitch_port 11 | bond: 12 | redirect: openvswitch.openvswitch.openvswitch_bond 13 | -------------------------------------------------------------------------------- /.github/workflows/codecoverage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Code Coverage 3 | 4 | on: # yamllint disable-line rule:truthy 5 | push: 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | codecoverage: 11 | uses: ansible-network/github_actions/.github/workflows/coverage_network_devices.yml@main 12 | with: 13 | collection_pre_install: >- 14 | git+https://github.com/ansible-collections/ansible.utils.git 15 | git+https://github.com/ansible-collections/ansible.netcommon.git 16 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_bridge/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Collect all test cases 3 | ansible.builtin.find: 4 | paths: "{{ role_path }}/tests" 5 | patterns: "{{ testcase }}.yaml" 6 | delegate_to: localhost 7 | register: test_cases 8 | 9 | - name: Set test_items 10 | ansible.builtin.set_fact: 11 | test_items: "{{ test_cases.files | map(attribute='path') | list }}" 12 | 13 | - name: Run test case 14 | ansible.builtin.include_tasks: "{{ test_case_to_run }}" 15 | with_items: "{{ test_items }}" 16 | loop_control: 17 | loop_var: test_case_to_run 18 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_db/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Collect all test cases 3 | ansible.builtin.find: 4 | paths: "{{ role_path }}/tests" 5 | patterns: "{{ testcase }}.yaml" 6 | delegate_to: localhost 7 | register: test_cases 8 | 9 | - name: Set test_items 10 | ansible.builtin.set_fact: 11 | test_items: "{{ test_cases.files | map(attribute='path') | list }}" 12 | 13 | - name: Run test case 14 | ansible.builtin.include_tasks: "{{ test_case_to_run }}" 15 | with_items: "{{ test_items }}" 16 | loop_control: 17 | loop_var: test_case_to_run 18 | -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | authors: 3 | - Ansible Network Community (ansible-network) 4 | license_file: LICENSE 5 | name: openvswitch 6 | tags: [networking, vswitch, bridge, vlan] 7 | # NOTE(pabelanger): We create an empty version key to keep ansible-galaxy 8 | # happy. We dynamically inject version info based on git information. 9 | version: 2.1.2-dev 10 | namespace: openvswitch 11 | readme: README.md 12 | description: Ansible Network Collection for Open vSwitch 13 | repository: https://github.com/ansible-collections/openvswitch.openvswitch 14 | issues: https://github.com/ansible-collections/openvswitch.openvswitch/issues 15 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 1.4.2 3 | envlist = linters 4 | skipsdist = True 5 | 6 | [testenv] 7 | deps = -r{toxinidir}/requirements.txt 8 | -r{toxinidir}/test-requirements.txt 9 | 10 | [testenv:black] 11 | install_command = pip install {opts} {packages} 12 | commands = 13 | black -v {toxinidir} 14 | 15 | [testenv:linters] 16 | install_command = pip install {opts} {packages} 17 | commands = 18 | black -v --diff --check {toxinidir} 19 | flake8 {posargs} 20 | 21 | [testenv:venv] 22 | commands = {posargs} 23 | 24 | [flake8] 25 | # E123, E125 skipped as they are invalid PEP-8. 26 | 27 | show-source = True 28 | ignore = E123,E125,E402,E501,E741,W503 29 | max-line-length = 160 30 | builtins = _ 31 | exclude = .git,.tox,tests/unit/compat/ 32 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/openvswitch_db_disable_in_band_missing.cfg: -------------------------------------------------------------------------------- 1 | _uuid : 64f04422-d510-4258-9648-ee0a982f58c1 2 | auto_attach : [] 3 | controller : [] 4 | datapath_id : "00002244f0645842" 5 | datapath_type : "" 6 | datapath_version : "" 7 | external_ids : {} 8 | fail_mode : [] 9 | flood_vlans : [] 10 | flow_tables : {} 11 | ipfix : [] 12 | mcast_snooping_enable: false 13 | mirrors : [] 14 | name : test-br 15 | netflow : [] 16 | other_config : {} 17 | ports : [2c9c1b35-a304-4dee-bb7a-092d656543c7] 18 | protocols : [] 19 | rstp_enable : false 20 | rstp_status : {} 21 | sflow : [] 22 | status : {} 23 | stp_enable : false 24 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/fixtures/openvswitch_db_disable_in_band_true.cfg: -------------------------------------------------------------------------------- 1 | _uuid : 64f04422-d510-4258-9648-ee0a982f58c1 2 | auto_attach : [] 3 | controller : [] 4 | datapath_id : "00002244f0645842" 5 | datapath_type : "" 6 | datapath_version : "" 7 | external_ids : {} 8 | fail_mode : [] 9 | flood_vlans : [] 10 | flow_tables : {} 11 | ipfix : [] 12 | mcast_snooping_enable: false 13 | mirrors : [] 14 | name : test-br 15 | netflow : [] 16 | other_config : {disable-in-band=true} 17 | ports : [2c9c1b35-a304-4dee-bb7a-092d656543c7] 18 | protocols : [] 19 | rstp_enable : false 20 | rstp_status : {} 21 | sflow : [] 22 | status : {} 23 | stp_enable : false 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/ansible-network/collection_prep 4 | rev: 1.1.1 5 | hooks: 6 | - id: autoversion 7 | 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v4.6.0 10 | hooks: 11 | - id: check-merge-conflict 12 | - id: debug-statements 13 | - id: end-of-file-fixer 14 | - id: no-commit-to-branch 15 | - id: trailing-whitespace 16 | 17 | - repo: https://github.com/pre-commit/mirrors-prettier 18 | rev: "v4.0.0-alpha.8" 19 | hooks: 20 | - id: prettier 21 | additional_dependencies: 22 | - prettier 23 | - prettier-plugin-toml 24 | 25 | - repo: https://github.com/PyCQA/isort 26 | rev: 5.13.2 27 | hooks: 28 | - id: isort 29 | args: ["--filter-files"] 30 | 31 | - repo: https://github.com/psf/black 32 | rev: 24.8.0 33 | hooks: 34 | - id: black 35 | -------------------------------------------------------------------------------- /changelogs/config.yaml: -------------------------------------------------------------------------------- 1 | changelog_filename_template: ../CHANGELOG.rst 2 | changelog_filename_version_depth: 0 3 | changes_file: changelog.yaml 4 | changes_format: combined 5 | keep_fragments: false 6 | mention_ancestor: true 7 | new_plugins_after_name: removed_features 8 | notesdir: fragments 9 | prelude_section_name: release_summary 10 | prelude_section_title: Release Summary 11 | flatmap: true 12 | sections: 13 | - - major_changes 14 | - Major Changes 15 | - - minor_changes 16 | - Minor Changes 17 | - - breaking_changes 18 | - Breaking Changes / Porting Guide 19 | - - deprecated_features 20 | - Deprecated Features 21 | - - removed_features 22 | - Removed Features (previously deprecated) 23 | - - security_fixes 24 | - Security Fixes 25 | - - bugfixes 26 | - Bugfixes 27 | - - known_issues 28 | - Known Issues 29 | - - doc_changes 30 | - Documentation Changes 31 | title: Openvswitch Collection 32 | trivial_section_name: trivial 33 | -------------------------------------------------------------------------------- /changelogs/.plugin-cache.yaml: -------------------------------------------------------------------------------- 1 | objects: 2 | role: {} 3 | plugins: 4 | become: {} 5 | cache: {} 6 | callback: {} 7 | cliconf: {} 8 | connection: {} 9 | filter: {} 10 | httpapi: {} 11 | inventory: {} 12 | lookup: {} 13 | module: 14 | openvswitch_bond: 15 | description: Manage Open vSwitch bonds 16 | name: openvswitch_bond 17 | namespace: "" 18 | version_added: 1.0.0 19 | openvswitch_bridge: 20 | description: Manage Open vSwitch bridges 21 | name: openvswitch_bridge 22 | namespace: "" 23 | version_added: 1.0.0 24 | openvswitch_db: 25 | description: Configure open vswitch database. 26 | name: openvswitch_db 27 | namespace: "" 28 | version_added: 1.0.0 29 | openvswitch_port: 30 | description: Manage Open vSwitch ports 31 | name: openvswitch_port 32 | namespace: "" 33 | version_added: 1.0.0 34 | netconf: {} 35 | shell: {} 36 | strategy: {} 37 | test: {} 38 | vars: {} 39 | version: 2.1.1 40 | -------------------------------------------------------------------------------- /tests/unit/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | placebo 3 | pycrypto 4 | passlib 5 | pypsrp 6 | python-memcached 7 | pytz 8 | pyvmomi 9 | redis 10 | requests 11 | setuptools > 0.6 # pytest-xdist installed via requirements does not work with very old setuptools (sanity_ok) 12 | unittest2 ; python_version < '2.7' 13 | importlib ; python_version < '2.7' 14 | netaddr 15 | ipaddress 16 | netapp-lib 17 | solidfire-sdk-python 18 | 19 | # requirements for F5 specific modules 20 | f5-sdk ; python_version >= '2.7' 21 | f5-icontrol-rest ; python_version >= '2.7' 22 | deepdiff 23 | 24 | # requirement for Fortinet specific modules 25 | pyFMG 26 | 27 | # requirement for aci_rest module 28 | xmljson 29 | 30 | # requirement for winrm connection plugin tests 31 | pexpect 32 | 33 | # requirement for the linode module 34 | linode-python # APIv3 35 | linode_api4 ; python_version > '2.6' # APIv4 36 | 37 | # requirement for the gitlab module 38 | python-gitlab 39 | httmock 40 | 41 | # requirment for kubevirt modules 42 | openshift ; python_version >= '2.7' 43 | -------------------------------------------------------------------------------- /tests/integration/targets/prepare_ovs_tests/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # No easy way to know OS type without python or without ansible facts. 3 | # Run below raw commands and one would succeed with apt/yum on debian/centos 4 | - name: Run prepare_ovs_tests 5 | when: prepare_ovs_tests_task | default(False) | bool 6 | block: 7 | # network-integration test are ran with gather_facts: no 8 | # We need to explicitly call setup so ansible_distribution is set 9 | - name: Gather facts 10 | ansible.builtin.setup: 11 | become: true 12 | 13 | - name: Install openvswitch-switch package if we are on Ubuntu 14 | ansible.builtin.apt: 15 | name: openvswitch-switch 16 | state: present 17 | update_cache: true 18 | become: true 19 | when: ansible_distribution == 'Ubuntu' 20 | 21 | - name: Install openvswitch package if we are on Fedora 22 | ansible.builtin.yum: 23 | name: openvswitch 24 | state: installed 25 | update_cache: true 26 | become: true 27 | when: ansible_distribution == 'Fedora' 28 | -------------------------------------------------------------------------------- /tests/unit/modules/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ansible Project 2 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | __metaclass__ = type 7 | 8 | import json 9 | 10 | import pytest 11 | from ansible.module_utils._text import to_bytes 12 | from ansible.module_utils.common._collections_compat import MutableMapping 13 | from ansible.module_utils.six import string_types 14 | 15 | 16 | @pytest.fixture 17 | def patch_ansible_module(request, mocker): 18 | if isinstance(request.param, string_types): 19 | args = request.param 20 | elif isinstance(request.param, MutableMapping): 21 | if "ANSIBLE_MODULE_ARGS" not in request.param: 22 | request.param = {"ANSIBLE_MODULE_ARGS": request.param} 23 | if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]: 24 | request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp" 25 | if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]: 26 | request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False 27 | args = json.dumps(request.param) 28 | else: 29 | raise Exception("Malformed data to the patch_ansible_module pytest fixture") 30 | 31 | mocker.patch("ansible.module_utils.basic._ANSIBLE_ARGS", to_bytes(args)) 32 | -------------------------------------------------------------------------------- /tests/unit/compat/unittest.py: -------------------------------------------------------------------------------- 1 | # (c) 2014, Toshio Kuratomi 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | # Make coding more python3-ish 19 | from __future__ import absolute_import, division, print_function 20 | 21 | __metaclass__ = type 22 | 23 | """ 24 | Compat module for Python2.7's unittest module 25 | """ 26 | 27 | import sys 28 | 29 | # Allow wildcard import because we really do want to import all of 30 | # unittests's symbols into this compat shim 31 | # pylint: disable=wildcard-import,unused-wildcard-import 32 | if sys.version_info < (2, 7): 33 | try: 34 | # Need unittest2 on python2.6 35 | from unittest2 import * 36 | except ImportError: 37 | print("You need unittest2 installed on python2.6.x to run tests") 38 | else: 39 | from unittest import * 40 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_bridge/tests/basic.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Make sure test bridge does not exist before tests 3 | command: ovs-vsctl del-br br-test 4 | become: true 5 | ignore_errors: true 6 | 7 | - name: Create bridge 8 | register: result 9 | become: true 10 | openvswitch.openvswitch.openvswitch_bridge: 11 | bridge: br-test 12 | 13 | - assert: 14 | that: 15 | - result is changed 16 | 17 | - name: Create bridge again (idempotent) 18 | register: result 19 | become: true 20 | openvswitch.openvswitch.openvswitch_bridge: 21 | bridge: br-test 22 | 23 | - assert: 24 | that: 25 | - result is not changed 26 | 27 | - name: Add fake bridge 28 | register: result 29 | become: true 30 | openvswitch.openvswitch.openvswitch_bridge: 31 | bridge: fake-br-test 32 | parent: br-test 33 | vlan: 100 34 | 35 | - assert: 36 | that: 37 | - result is changed 38 | 39 | - name: Change fake bridge vlan 40 | register: result 41 | become: true 42 | openvswitch.openvswitch.openvswitch_bridge: 43 | bridge: fake-br-test 44 | parent: br-test 45 | vlan: 300 46 | 47 | - assert: 48 | that: 49 | - result is changed 50 | 51 | - name: Change fake bridge vlan again (idempotent) 52 | register: result 53 | become: true 54 | openvswitch.openvswitch.openvswitch_bridge: 55 | bridge: fake-br-test 56 | parent: br-test 57 | vlan: 300 58 | 59 | - assert: 60 | that: 61 | - result is not changed 62 | 63 | - name: Tear down test bridges 64 | become: true 65 | command: ovs-vsctl del-br br-test 66 | -------------------------------------------------------------------------------- /tests/unit/modules/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __metaclass__ = type 4 | 5 | import json 6 | 7 | from ansible.module_utils import basic 8 | from ansible.module_utils._text import to_bytes 9 | 10 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat import unittest 11 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat.mock import patch 12 | 13 | 14 | def set_module_args(args): 15 | if "_ansible_remote_tmp" not in args: 16 | args["_ansible_remote_tmp"] = "/tmp" 17 | if "_ansible_keep_remote_files" not in args: 18 | args["_ansible_keep_remote_files"] = False 19 | 20 | args = json.dumps({"ANSIBLE_MODULE_ARGS": args}) 21 | basic._ANSIBLE_ARGS = to_bytes(args) 22 | 23 | 24 | class AnsibleExitJson(Exception): 25 | pass 26 | 27 | 28 | class AnsibleFailJson(Exception): 29 | pass 30 | 31 | 32 | def exit_json(*args, **kwargs): 33 | if "changed" not in kwargs: 34 | kwargs["changed"] = False 35 | raise AnsibleExitJson(kwargs) 36 | 37 | 38 | def fail_json(*args, **kwargs): 39 | kwargs["failed"] = True 40 | raise AnsibleFailJson(kwargs) 41 | 42 | 43 | class ModuleTestCase(unittest.TestCase): 44 | def setUp(self): 45 | self.mock_module = patch.multiple( 46 | basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json 47 | ) 48 | self.mock_module.start() 49 | self.mock_sleep = patch("time.sleep") 50 | self.mock_sleep.start() 51 | set_module_args({}) 52 | self.addCleanup(self.mock_module.stop) 53 | self.addCleanup(self.mock_sleep.stop) 54 | -------------------------------------------------------------------------------- /tests/unit/compat/mock.py: -------------------------------------------------------------------------------- 1 | # (c) 2014, Toshio Kuratomi 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | # Make coding more python3-ish 19 | from __future__ import absolute_import, division, print_function 20 | 21 | __metaclass__ = type 22 | 23 | """ 24 | Compat module for Python3.x's unittest.mock module 25 | """ 26 | 27 | # Python 2.7 28 | 29 | # Note: Could use the pypi mock library on python3.x as well as python2.x. It 30 | # is the same as the python3 stdlib mock library 31 | 32 | try: 33 | # Allow wildcard import because we really do want to import all of mock's 34 | # symbols into this compat shim 35 | # pylint: disable=wildcard-import,unused-wildcard-import 36 | from unittest.mock import * 37 | except ImportError: 38 | # Python 2 39 | # pylint: disable=wildcard-import,unused-wildcard-import 40 | try: 41 | from mock import * 42 | except ImportError: 43 | print("You need the mock library installed on python2.x to run tests") 44 | -------------------------------------------------------------------------------- /tests/unit/mock/vault_helper.py: -------------------------------------------------------------------------------- 1 | # Ansible is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # Ansible is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with Ansible. If not, see . 13 | 14 | # Make coding more python3-ish 15 | from __future__ import absolute_import, division, print_function 16 | 17 | __metaclass__ = type 18 | 19 | from ansible.module_utils._text import to_bytes 20 | from ansible.parsing.vault import VaultSecret 21 | 22 | 23 | class TextVaultSecret(VaultSecret): 24 | """A secret piece of text. ie, a password. Tracks text encoding. 25 | 26 | The text encoding of the text may not be the default text encoding so 27 | we keep track of the encoding so we encode it to the same bytes.""" 28 | 29 | def __init__(self, text, encoding=None, errors=None, _bytes=None): 30 | super(TextVaultSecret, self).__init__() 31 | self.text = text 32 | self.encoding = encoding or "utf-8" 33 | self._bytes = _bytes 34 | self.errors = errors or "strict" 35 | 36 | @property 37 | def bytes(self): 38 | """The text encoded with encoding, unless we specifically set _bytes.""" 39 | return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) 40 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test collection 3 | 4 | concurrency: 5 | group: ${{ github.head_ref || github.run_id }} 6 | cancel-in-progress: true 7 | 8 | on: # yamllint disable-line rule:truthy 9 | pull_request: 10 | branches: [main] 11 | workflow_dispatch: 12 | schedule: 13 | - cron: '0 0 * * *' 14 | 15 | # Due to an issue in GHA runner, MATRIX_EXCLUDE is set as an environment variable 16 | # https://github.com/actions/runner/issues/2372 17 | jobs: 18 | ansible-lint: 19 | uses: ansible-network/github_actions/.github/workflows/ansible-lint.yml@main 20 | changelog: 21 | uses: ansible-network/github_actions/.github/workflows/changelog.yml@main 22 | if: github.event_name == 'pull_request' 23 | sanity: 24 | uses: ansible-network/github_actions/.github/workflows/sanity.yml@main 25 | with: 26 | matrix_exclude: ${{ vars.MATRIX_EXCLUDE }} 27 | unit-galaxy: 28 | uses: ansible-network/github_actions/.github/workflows/unit_galaxy.yml@main 29 | with: 30 | matrix_exclude: ${{ vars.MATRIX_EXCLUDE }} 31 | unit-source: 32 | uses: ansible-network/github_actions/.github/workflows/unit_source.yml@main 33 | with: 34 | collection_pre_install: >- 35 | git+https://github.com/ansible-collections/ansible.utils.git 36 | git+https://github.com/ansible-collections/ansible.netcommon.git 37 | matrix_exclude: ${{ vars.MATRIX_EXCLUDE }} 38 | all_green: 39 | if: ${{ always() && (github.event_name != 'schedule') }} 40 | needs: 41 | - ansible-lint 42 | - changelog 43 | - sanity 44 | - unit-galaxy 45 | - unit-source 46 | runs-on: ubuntu-latest 47 | steps: 48 | - run: >- 49 | python -c "assert 'failure' not in 50 | set([ 51 | '${{ needs.ansible-lint.result }}', 52 | '${{ needs.changelog.result }}', 53 | '${{ needs.sanity.result }}', 54 | '${{ needs.unit-galaxy.result }}', 55 | '${{ needs.unit-source.result }}' 56 | ])" 57 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/ovs_module.py: -------------------------------------------------------------------------------- 1 | # (c) 2017 Red Hat Inc. 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | # Make coding more python3-ish 19 | from __future__ import absolute_import, division, print_function 20 | 21 | __metaclass__ = type 22 | 23 | import json 24 | import os 25 | 26 | from ansible_collections.openvswitch.openvswitch.tests.unit.modules.utils import ( 27 | AnsibleExitJson, 28 | AnsibleFailJson, 29 | ModuleTestCase, 30 | ) 31 | 32 | fixture_path = os.path.join(os.path.dirname(__file__), "fixtures") 33 | fixture_data = {} 34 | 35 | 36 | def load_fixture(name): 37 | path = os.path.join(fixture_path, name) 38 | 39 | if path in fixture_data: 40 | return fixture_data[path] 41 | 42 | with open(path) as f: 43 | data = f.read() 44 | 45 | try: 46 | data = json.loads(data) 47 | except Exception: 48 | pass 49 | 50 | fixture_data[path] = data 51 | return data 52 | 53 | 54 | class TestOpenVSwitchModule(ModuleTestCase): 55 | def execute_module(self, failed=False, changed=False, commands=None, test_name=None): 56 | self.load_fixtures(test_name) 57 | 58 | if failed: 59 | result = self.failed() 60 | self.assertTrue(result["failed"], result) 61 | else: 62 | result = self.changed(changed) 63 | self.assertEqual(result["changed"], changed, result) 64 | 65 | if commands is not None: 66 | self.assertEqual(commands, result["commands"], result["commands"]) 67 | 68 | return result 69 | 70 | def failed(self): 71 | with self.assertRaises(AnsibleFailJson) as exc: 72 | self.module.main() 73 | 74 | result = exc.exception.args[0] 75 | self.assertTrue(result["failed"], result) 76 | return result 77 | 78 | def changed(self, changed=False): 79 | with self.assertRaises(AnsibleExitJson) as exc: 80 | self.module.main() 81 | 82 | result = exc.exception.args[0] 83 | self.assertEqual(result["changed"], changed, result) 84 | return result 85 | 86 | def load_fixtures(self, test_name): 87 | pass 88 | -------------------------------------------------------------------------------- /tests/unit/mock/procenv.py: -------------------------------------------------------------------------------- 1 | # (c) 2016, Matt Davis 2 | # (c) 2016, Toshio Kuratomi 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Ansible is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Ansible is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Ansible. If not, see . 18 | 19 | # Make coding more python3-ish 20 | from __future__ import absolute_import, division, print_function 21 | 22 | __metaclass__ = type 23 | 24 | import json 25 | import sys 26 | from contextlib import contextmanager 27 | from io import BytesIO, StringIO 28 | 29 | from ansible.module_utils._text import to_bytes 30 | from ansible.module_utils.six import PY3 31 | 32 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat import unittest 33 | 34 | 35 | @contextmanager 36 | def swap_stdin_and_argv(stdin_data="", argv_data=tuple()): 37 | """ 38 | context manager that temporarily masks the test runner's values for stdin and argv 39 | """ 40 | real_stdin = sys.stdin 41 | real_argv = sys.argv 42 | 43 | if PY3: 44 | fake_stream = StringIO(stdin_data) 45 | fake_stream.buffer = BytesIO(to_bytes(stdin_data)) 46 | else: 47 | fake_stream = BytesIO(to_bytes(stdin_data)) 48 | 49 | try: 50 | sys.stdin = fake_stream 51 | sys.argv = argv_data 52 | 53 | yield 54 | finally: 55 | sys.stdin = real_stdin 56 | sys.argv = real_argv 57 | 58 | 59 | @contextmanager 60 | def swap_stdout(): 61 | """ 62 | context manager that temporarily replaces stdout for tests that need to verify output 63 | """ 64 | old_stdout = sys.stdout 65 | 66 | if PY3: 67 | fake_stream = StringIO() 68 | else: 69 | fake_stream = BytesIO() 70 | 71 | try: 72 | sys.stdout = fake_stream 73 | 74 | yield fake_stream 75 | finally: 76 | sys.stdout = old_stdout 77 | 78 | 79 | class ModuleTestCase(unittest.TestCase): 80 | def setUp(self, module_args=None): 81 | if module_args is None: 82 | module_args = { 83 | "_ansible_remote_tmp": "/tmp", 84 | "_ansible_keep_remote_files": False, 85 | } 86 | 87 | args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) 88 | 89 | # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually 90 | self.stdin_swap = swap_stdin_and_argv(stdin_data=args) 91 | self.stdin_swap.__enter__() 92 | 93 | def tearDown(self): 94 | # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually 95 | self.stdin_swap.__exit__(None, None, None) 96 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Openvswitch Collection Release Notes 3 | ==================================== 4 | 5 | .. contents:: Topics 6 | 7 | 8 | v2.1.1 9 | ====== 10 | 11 | Bugfixes 12 | -------- 13 | 14 | - Fix galaxy version issue when installing this collection. 15 | 16 | Documentation Changes 17 | --------------------- 18 | 19 | - Update module documentation and examples. 20 | 21 | v2.1.0 22 | ====== 23 | 24 | Minor Changes 25 | ------------- 26 | 27 | - Allows read operation in openvswitch_db module(https://github.com/ansible-collections/openvswitch.openvswitch/pull/88) 28 | - openvswitch modules got support for database socket parameter. 29 | 30 | v2.0.2 31 | ====== 32 | 33 | Bugfixes 34 | -------- 35 | 36 | - `openvswitch_bridge` - Fix idempotency for VLAN bridges 37 | 38 | v2.0.1 39 | ====== 40 | 41 | Major Changes 42 | ------------- 43 | 44 | - By mistake we tagged the repo to 2.0.0 and as it wasn't intended and cannot be reverted we're releasing 2.0.1 to make the community aware of the major version update. 45 | 46 | v2.0.0 47 | ====== 48 | 49 | Major Changes 50 | ------------- 51 | 52 | - There is no major changes for this particular release and it was tagged by mistake and cannot be reverted. 53 | 54 | v1.2.0 55 | ====== 56 | 57 | Minor Changes 58 | ------------- 59 | 60 | - Allow setting multiple properties on a port (https://github.com/ansible-collections/openvswitch.openvswitch/issues/63). 61 | 62 | Bugfixes 63 | -------- 64 | 65 | - Allow deleting key from table without specifying value (https://github.com/ansible-collections/openvswitch.openvswitch/issues/64). 66 | 67 | v1.1.0 68 | ====== 69 | 70 | Minor Changes 71 | ------------- 72 | 73 | - openvswitch_bond - New module for managing Open vSwitch bonds (https://github.com/ansible-collections/openvswitch.openvswitch/pull/58). 74 | 75 | Bugfixes 76 | -------- 77 | 78 | - Add version key to galaxy.yaml to work around ansible-galaxy bug (https://github.com/ansible-collections/openvswitch.openvswitch/issues/59) 79 | 80 | v1.0.5 81 | ====== 82 | 83 | Minor Changes 84 | ------------- 85 | 86 | - Regenerated docs, add description to galaxy.yml and linked changelog to README (https://github.com/ansible-collections/openvswitch.openvswitch/pull/53). 87 | 88 | v1.0.4 89 | ====== 90 | 91 | Release Summary 92 | --------------- 93 | 94 | Rereleased 1.0.3 with updated changelog. 95 | 96 | v1.0.3 97 | ====== 98 | 99 | Release Summary 100 | --------------- 101 | 102 | Released for testing. 103 | 104 | v1.0.2 105 | ====== 106 | 107 | Release Summary 108 | --------------- 109 | 110 | Rereleased 1.0.1 with updated changelog. 111 | 112 | v1.0.1 113 | ====== 114 | 115 | Bugfixes 116 | -------- 117 | 118 | - Makes sure that docstring and argspec are in sync and removes sanity ignores (https://github.com/ansible-collections/openvswitch.openvswitch/pull/46). 119 | - Update docs after sanity fixes to modules. 120 | 121 | v1.0.0 122 | ====== 123 | 124 | New Modules 125 | ----------- 126 | 127 | - openvswitch_bridge - Manage Open vSwitch bridges 128 | - openvswitch_db - Configure open vswitch database. 129 | - openvswitch_port - Manage Open vSwitch ports 130 | -------------------------------------------------------------------------------- /changelogs/changelog.yaml: -------------------------------------------------------------------------------- 1 | ancestor: null 2 | releases: 3 | 1.0.0: 4 | modules: 5 | - description: Manage Open vSwitch bridges 6 | name: openvswitch_bridge 7 | namespace: "" 8 | - description: Configure open vswitch database. 9 | name: openvswitch_db 10 | namespace: "" 11 | - description: Manage Open vSwitch ports 12 | name: openvswitch_port 13 | namespace: "" 14 | release_date: "2020-06-23" 15 | 1.0.1: 16 | changes: 17 | bugfixes: 18 | - Makes sure that docstring and argspec are in sync and removes sanity ignores 19 | (https://github.com/ansible-collections/openvswitch.openvswitch/pull/46). 20 | - Update docs after sanity fixes to modules. 21 | fragments: 22 | - remove_ignores.yaml 23 | - update_docs.yaml 24 | release_date: "2020-08-03" 25 | 1.0.2: 26 | changes: 27 | release_summary: Rereleased 1.0.1 with updated changelog. 28 | fragments: 29 | - 1.0.2.yaml 30 | release_date: "2020-08-07" 31 | 1.0.3: 32 | changes: 33 | release_summary: Released for testing. 34 | fragments: 35 | - 1.0.3.yaml 36 | release_date: "2020-08-07" 37 | 1.0.4: 38 | changes: 39 | release_summary: Rereleased 1.0.3 with updated changelog. 40 | fragments: 41 | - 1.0.4.yaml 42 | release_date: "2020-08-08" 43 | 1.0.5: 44 | changes: 45 | minor_changes: 46 | - Regenerated docs, add description to galaxy.yml and linked changelog to README 47 | (https://github.com/ansible-collections/openvswitch.openvswitch/pull/53). 48 | fragments: 49 | - fixes_to_readme_and_doc.yaml 50 | release_date: "2020-08-28" 51 | 1.1.0: 52 | changes: 53 | bugfixes: 54 | - Add version key to galaxy.yaml to work around ansible-galaxy bug (https://github.com/ansible-collections/openvswitch.openvswitch/issues/59) 55 | minor_changes: 56 | - openvswitch_bond - New module for managing Open vSwitch bonds (https://github.com/ansible-collections/openvswitch.openvswitch/pull/58). 57 | fragments: 58 | - 1-openvswitch_bond-new-module.yaml 59 | - fix_download_git.yaml 60 | release_date: "2020-11-26" 61 | 1.2.0: 62 | changes: 63 | bugfixes: 64 | - Allow deleting key from table without specifying value (https://github.com/ansible-collections/openvswitch.openvswitch/issues/64). 65 | minor_changes: 66 | - Allow setting multiple properties on a port (https://github.com/ansible-collections/openvswitch.openvswitch/issues/63). 67 | fragments: 68 | - fix_openvswitch_db.yaml 69 | - fix_openvswitch_port.yaml 70 | release_date: "2021-02-24" 71 | 2.0.0: 72 | changes: 73 | major_changes: 74 | - There is no major changes for this particular release and it was tagged by 75 | mistake and cannot be reverted. 76 | release_date: "2021-02-24" 77 | 2.0.1: 78 | changes: 79 | major_changes: 80 | - By mistake we tagged the repo to 2.0.0 and as it wasn't intended and cannot 81 | be reverted we're releasing 2.0.1 to make the community aware of the major 82 | version update. 83 | release_date: "2021-03-03" 84 | 2.0.2: 85 | changes: 86 | bugfixes: 87 | - "`openvswitch_bridge` - Fix idempotency for VLAN bridges" 88 | fragments: 89 | - 69-remove_tests_sanity_requirements.yml 90 | - fix_validate_modules.yaml 91 | - fix_vlan_bridge_idempotency.yaml 92 | - pylint_fixes.yaml 93 | release_date: "2021-09-24" 94 | 2.1.0: 95 | changes: 96 | minor_changes: 97 | - Allows read operation in openvswitch_db module(https://github.com/ansible-collections/openvswitch.openvswitch/pull/88) 98 | - openvswitch modules got support for database socket parameter. 99 | fragments: 100 | - 84-ovs-modules-database-socket.yaml 101 | - openvswitch_db_read.yaml 102 | release_date: "2021-12-07" 103 | 2.1.1: 104 | changes: 105 | bugfixes: 106 | - Fix galaxy version issue when installing this collection. 107 | doc_changes: 108 | - Update module documentation and examples. 109 | fragments: 110 | - black.yaml 111 | - fix.yaml 112 | - galaxy.yml 113 | - gha.yaml 114 | release_date: "2023-04-27" 115 | -------------------------------------------------------------------------------- /tests/unit/mock/loader.py: -------------------------------------------------------------------------------- 1 | # (c) 2012-2014, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | # Make coding more python3-ish 19 | from __future__ import absolute_import, division, print_function 20 | 21 | __metaclass__ = type 22 | 23 | import os 24 | 25 | from ansible.errors import AnsibleParserError 26 | from ansible.module_utils._text import to_bytes, to_text 27 | from ansible.parsing.dataloader import DataLoader 28 | 29 | 30 | class DictDataLoader(DataLoader): 31 | def __init__(self, file_mapping=None): 32 | file_mapping = {} if file_mapping is None else file_mapping 33 | assert isinstance(file_mapping, dict) 34 | 35 | super(DictDataLoader, self).__init__() 36 | 37 | self._file_mapping = file_mapping 38 | self._build_known_directories() 39 | self._vault_secrets = None 40 | 41 | def load_from_file(self, path, cache=True, unsafe=False): 42 | path = to_text(path) 43 | if path in self._file_mapping: 44 | return self.load(self._file_mapping[path], path) 45 | return None 46 | 47 | # TODO: the real _get_file_contents returns a bytestring, so we actually convert the 48 | # unicode/text it's created with to utf-8 49 | def _get_file_contents(self, file_name): 50 | file_name = to_text(file_name) 51 | if file_name in self._file_mapping: 52 | return (to_bytes(self._file_mapping[file_name]), False) 53 | else: 54 | raise AnsibleParserError("file not found: %s" % file_name) 55 | 56 | def path_exists(self, path): 57 | path = to_text(path) 58 | return path in self._file_mapping or path in self._known_directories 59 | 60 | def is_file(self, path): 61 | path = to_text(path) 62 | return path in self._file_mapping 63 | 64 | def is_directory(self, path): 65 | path = to_text(path) 66 | return path in self._known_directories 67 | 68 | def list_directory(self, path): 69 | ret = [] 70 | path = to_text(path) 71 | for x in list(self._file_mapping.keys()) + self._known_directories: 72 | if x.startswith(path): 73 | if os.path.dirname(x) == path: 74 | ret.append(os.path.basename(x)) 75 | return ret 76 | 77 | def is_executable(self, path): 78 | # FIXME: figure out a way to make paths return true for this 79 | return False 80 | 81 | def _add_known_directory(self, directory): 82 | if directory not in self._known_directories: 83 | self._known_directories.append(directory) 84 | 85 | def _build_known_directories(self): 86 | self._known_directories = [] 87 | for path in self._file_mapping: 88 | dirname = os.path.dirname(path) 89 | while dirname not in ("/", ""): 90 | self._add_known_directory(dirname) 91 | dirname = os.path.dirname(dirname) 92 | 93 | def push(self, path, content): 94 | rebuild_dirs = False 95 | if path not in self._file_mapping: 96 | rebuild_dirs = True 97 | 98 | self._file_mapping[path] = content 99 | 100 | if rebuild_dirs: 101 | self._build_known_directories() 102 | 103 | def pop(self, path): 104 | if path in self._file_mapping: 105 | del self._file_mapping[path] 106 | self._build_known_directories() 107 | 108 | def clear(self): 109 | self._file_mapping = dict() 110 | self._known_directories = [] 111 | 112 | def get_basedir(self): 113 | return os.getcwd() 114 | 115 | def set_vault_secrets(self, vault_secrets): 116 | self._vault_secrets = vault_secrets 117 | -------------------------------------------------------------------------------- /tests/integration/targets/openvswitch_db/tests/basic.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Make sure test bridge does not exist before tests 3 | command: ovs-vsctl del-br br-test 4 | become: true 5 | ignore_errors: true 6 | 7 | - name: Create test bridge 8 | command: ovs-vsctl add-br br-test 9 | become: true 10 | 11 | - name: Create bridge 12 | become: true 13 | register: result 14 | openvswitch.openvswitch.openvswitch_db: 15 | table: Bridge 16 | record: br-test 17 | col: other_config 18 | key: disable-in-band 19 | value: true 20 | 21 | - assert: 22 | that: 23 | - result is changed 24 | 25 | - name: Create bridge again (idempotent) 26 | become: true 27 | register: result 28 | openvswitch.openvswitch.openvswitch_db: 29 | table: Bridge 30 | record: br-test 31 | col: other_config 32 | key: disable-in-band 33 | value: true 34 | 35 | - assert: 36 | that: 37 | - result is not changed 38 | 39 | - name: Change key value with quotes 40 | become: true 41 | register: result 42 | openvswitch.openvswitch.openvswitch_db: 43 | table: open_vswitch 44 | record: . 45 | col: other_config 46 | key: pmd-cpu-mask 47 | value: "0xaaa00000000" 48 | 49 | - assert: 50 | that: 51 | - result is changed 52 | 53 | - name: Change keyvalue with quotes(idempotent) 54 | become: true 55 | register: result 56 | openvswitch.openvswitch.openvswitch_db: 57 | table: open_vswitch 58 | record: . 59 | col: other_config 60 | key: pmd-cpu-mask 61 | value: "0xaaa00000000" 62 | 63 | - assert: 64 | that: 65 | - result is not changed 66 | 67 | - name: Remove key value with quotes 68 | become: true 69 | register: result 70 | openvswitch.openvswitch.openvswitch_db: 71 | table: open_vswitch 72 | record: . 73 | col: other_config 74 | key: pmd-cpu-mask 75 | value: "0xaaa00000000" 76 | state: absent 77 | 78 | - assert: 79 | that: 80 | - result is changed 81 | 82 | - name: Change column value in a map 83 | become: true 84 | register: result 85 | openvswitch.openvswitch.openvswitch_db: 86 | table: Bridge 87 | record: br-test 88 | col: other_config 89 | key: disable-in-band 90 | value: false 91 | 92 | - assert: 93 | that: 94 | - result is changed 95 | 96 | - name: Change column value in a map again (idempotent) 97 | become: true 98 | register: result 99 | openvswitch.openvswitch.openvswitch_db: 100 | table: Bridge 101 | record: br-test 102 | col: other_config 103 | key: disable-in-band 104 | value: false 105 | 106 | - assert: 107 | that: 108 | - result is not changed 109 | 110 | - name: Change column value 111 | become: true 112 | register: result 113 | openvswitch.openvswitch.openvswitch_db: 114 | table: Bridge 115 | record: br-test 116 | col: stp_enable 117 | value: true 118 | 119 | - assert: 120 | that: 121 | - result is changed 122 | 123 | - name: Change column value again (idempotent) 124 | become: true 125 | register: result 126 | openvswitch.openvswitch.openvswitch_db: 127 | table: Bridge 128 | record: br-test 129 | col: stp_enable 130 | value: true 131 | 132 | - assert: 133 | that: 134 | - result is not changed 135 | 136 | - name: Try to set value on a map type without a key (negative) 137 | ignore_errors: true 138 | become: true 139 | register: result 140 | openvswitch.openvswitch.openvswitch_db: 141 | table: Bridge 142 | record: br-test 143 | col: other_config 144 | value: true 145 | 146 | - assert: 147 | that: 148 | - result is failed 149 | 150 | - name: Remove bridge 151 | become: true 152 | register: result 153 | openvswitch.openvswitch.openvswitch_db: 154 | table: Bridge 155 | record: br-test 156 | col: other_config 157 | key: disable-in-band 158 | value: false 159 | state: absent 160 | 161 | - assert: 162 | that: 163 | - result is changed 164 | 165 | - name: Remove bridge again (idempotent) 166 | become: true 167 | register: result 168 | openvswitch.openvswitch.openvswitch_db: 169 | table: Bridge 170 | record: br-test 171 | col: other_config 172 | key: disable-in-band 173 | value: false 174 | state: absent 175 | 176 | - assert: 177 | that: 178 | - result is not changed 179 | 180 | - name: Tear down test bridge 181 | command: ovs-vsctl del-br br-test 182 | become: true 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open vSwitch Collection 2 | 3 | [![CI](https://zuul-ci.org/gated.svg)](https://dashboard.zuul.ansible.com/t/ansible/project/github.com/ansible-collections/openvswitch.openvswitch) 4 | 5 | ⚠️ **The openvswitch.openvswitch collection has been [deprecated](https://forum.ansible.com/t/the-bullhorn-123/2568#project-updates-8) and will reach it's end-of-life on December, 2025. We are no longer accepting new pull requests, except for ones that fix critical bugs or security vulnerabilities. This collection is not supported with ansible-core>2.17.** 6 | 7 | The Open vSwitch collection includes a variety of Ansible content to help automate the management of Open vSwitch. 8 | 9 | 10 | ## Ansible version compatibility 11 | 12 | This collection has been tested against following Ansible versions: **<2.18.0,>=2.15.0**. 13 | 14 | Plugins and modules within a collection may be tested with only specific Ansible versions. 15 | A collection may contain metadata that identifies these versions. 16 | PEP440 is the schema used to describe the versions of Ansible. 17 | 18 | 19 | ### Supported connections 20 | 21 | The Open vSwitch collection supports local connections only. 22 | 23 | ## Included content 24 | 25 | Click the ``Content`` button to see the list of content included in this collection. 26 | 27 | 28 | ### Modules 29 | 30 | Name | Description 31 | --- | --- 32 | [openvswitch.openvswitch.openvswitch_bond](https://github.com/ansible-collections/openvswitch.openvswitch/blob/main/docs/openvswitch.openvswitch.openvswitch_bond_module.rst)|Manage Open vSwitch bonds 33 | [openvswitch.openvswitch.openvswitch_bridge](https://github.com/ansible-collections/openvswitch.openvswitch/blob/main/docs/openvswitch.openvswitch.openvswitch_bridge_module.rst)|Manage Open vSwitch bridges 34 | [openvswitch.openvswitch.openvswitch_db](https://github.com/ansible-collections/openvswitch.openvswitch/blob/main/docs/openvswitch.openvswitch.openvswitch_db_module.rst)|Configure open vswitch database. 35 | [openvswitch.openvswitch.openvswitch_port](https://github.com/ansible-collections/openvswitch.openvswitch/blob/main/docs/openvswitch.openvswitch.openvswitch_port_module.rst)|Manage Open vSwitch ports 36 | 37 | 38 | 39 | ## Installing this collection 40 | 41 | You can install the Open vSwitch collection with the Ansible Galaxy CLI: 42 | 43 | ansible-galaxy collection install openvswitch.openvswitch 44 | 45 | You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format: 46 | 47 | ```yaml 48 | --- 49 | collections: 50 | - name: openvswitch.openvswitch 51 | ``` 52 | 53 | ## Using this collection 54 | 55 | You can call modules by their Fully Qualified Collection Namespace (FQCN), such as `openvswitch.openvswitch.openvswitch_port`. 56 | The following example task replaces configuration changes in the existing configuration on a Open vSwitch network device, using the FQCN: 57 | 58 | ```yaml 59 | --- 60 | - name: Creates port eth2 on bridge br-ex 61 | openvswitch.openvswitch.openvswitch_port: 62 | bridge: br-ex 63 | port: eth2 64 | state: present 65 | ``` 66 | 67 | **NOTE**: For Ansible 2.9, you may not see deprecation warnings when you run your playbooks with this collection. Use this documentation to track when a module is deprecated. 68 | 69 | ### See Also: 70 | 71 | * [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. 72 | 73 | ## Contributing to this collection 74 | 75 | We welcome community contributions to this collection. If you find problems, please open an issue or create a PR against the [Open vSwitch collection repository](https://github.com/ansible-collections/openvswitch.openvswitch). See [Contributing to Ansible-maintained collections](https://docs.ansible.com/ansible/devel/community/contributing_maintained_collections.html#contributing-maintained-collections) for complete details. 76 | 77 | You can also join us on: 78 | 79 | - IRC - the ``#ansible-network`` [irc.libera.chat](https://libera.chat/) channel 80 | - Slack - https://ansiblenetwork.slack.com 81 | 82 | See the [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html) for details on contributing to Ansible. 83 | 84 | ### Code of Conduct 85 | This collection follows the Ansible project's 86 | [Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html). 87 | Please read and familiarize yourself with this document. 88 | 89 | 90 | ## Changelogs 91 | 92 | Release notes are available [here](https://github.com/ansible-collections/openvswitch.openvswitch/blob/main/changelogs/CHANGELOG.rst). 93 | 94 | ## Roadmap 95 | 96 | 97 | 98 | ## More information 99 | 100 | - [Ansible network resources](https://docs.ansible.com/ansible/latest/network/getting_started/network_resources.html) 101 | - [Ansible Collection overview](https://github.com/ansible-collections/overview) 102 | - [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) 103 | - [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) 104 | - [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) 105 | 106 | ## Licensing 107 | 108 | GNU General Public License v3.0 or later. 109 | 110 | See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. 111 | -------------------------------------------------------------------------------- /tests/unit/mock/yaml_helper.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __metaclass__ = type 4 | 5 | import io 6 | 7 | import yaml 8 | from ansible.module_utils.six import PY3 9 | from ansible.parsing.yaml.dumper import AnsibleDumper 10 | from ansible.parsing.yaml.loader import AnsibleLoader 11 | 12 | 13 | class YamlTestUtils(object): 14 | """Mixin class to combine with a unittest.TestCase subclass.""" 15 | 16 | def _loader(self, stream): 17 | """Vault related tests will want to override this. 18 | 19 | Vault cases should setup a AnsibleLoader that has the vault password.""" 20 | return AnsibleLoader(stream) 21 | 22 | def _dump_stream(self, obj, stream, dumper=None): 23 | """Dump to a py2-unicode or py3-string stream.""" 24 | if PY3: 25 | return yaml.dump(obj, stream, Dumper=dumper) 26 | else: 27 | return yaml.dump(obj, stream, Dumper=dumper, encoding=None) 28 | 29 | def _dump_string(self, obj, dumper=None): 30 | """Dump to a py2-unicode or py3-string""" 31 | if PY3: 32 | return yaml.dump(obj, Dumper=dumper) 33 | else: 34 | return yaml.dump(obj, Dumper=dumper, encoding=None) 35 | 36 | def _dump_load_cycle(self, obj): 37 | # Each pass though a dump or load revs the 'generation' 38 | # obj to yaml string 39 | string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper) 40 | 41 | # wrap a stream/file like StringIO around that yaml 42 | stream_from_object_dump = io.StringIO(string_from_object_dump) 43 | loader = self._loader(stream_from_object_dump) 44 | # load the yaml stream to create a new instance of the object (gen 2) 45 | obj_2 = loader.get_data() 46 | 47 | # dump the gen 2 objects directory to strings 48 | string_from_object_dump_2 = self._dump_string(obj_2, dumper=AnsibleDumper) 49 | 50 | # The gen 1 and gen 2 yaml strings 51 | self.assertEqual(string_from_object_dump, string_from_object_dump_2) 52 | # the gen 1 (orig) and gen 2 py object 53 | self.assertEqual(obj, obj_2) 54 | 55 | # again! gen 3... load strings into py objects 56 | stream_3 = io.StringIO(string_from_object_dump_2) 57 | loader_3 = self._loader(stream_3) 58 | obj_3 = loader_3.get_data() 59 | 60 | string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper) 61 | 62 | self.assertEqual(obj, obj_3) 63 | # should be transitive, but... 64 | self.assertEqual(obj_2, obj_3) 65 | self.assertEqual(string_from_object_dump, string_from_object_dump_3) 66 | 67 | def _old_dump_load_cycle(self, obj): 68 | """Dump the passed in object to yaml, load it back up, dump again, compare.""" 69 | stream = io.StringIO() 70 | 71 | yaml_string = self._dump_string(obj, dumper=AnsibleDumper) 72 | self._dump_stream(obj, stream, dumper=AnsibleDumper) 73 | 74 | yaml_string_from_stream = stream.getvalue() 75 | 76 | # reset stream 77 | stream.seek(0) 78 | 79 | loader = self._loader(stream) 80 | # loader = AnsibleLoader(stream, vault_password=self.vault_password) 81 | obj_from_stream = loader.get_data() 82 | 83 | stream_from_string = io.StringIO(yaml_string) 84 | loader2 = self._loader(stream_from_string) 85 | # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password) 86 | obj_from_string = loader2.get_data() 87 | 88 | stream_obj_from_stream = io.StringIO() 89 | stream_obj_from_string = io.StringIO() 90 | 91 | if PY3: 92 | yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) 93 | yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) 94 | else: 95 | yaml.dump( 96 | obj_from_stream, 97 | stream_obj_from_stream, 98 | Dumper=AnsibleDumper, 99 | encoding=None, 100 | ) 101 | yaml.dump( 102 | obj_from_stream, 103 | stream_obj_from_string, 104 | Dumper=AnsibleDumper, 105 | encoding=None, 106 | ) 107 | 108 | yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() 109 | yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() 110 | 111 | stream_obj_from_stream.seek(0) 112 | stream_obj_from_string.seek(0) 113 | 114 | if PY3: 115 | yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) 116 | yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) 117 | else: 118 | yaml_string_obj_from_stream = yaml.dump( 119 | obj_from_stream, Dumper=AnsibleDumper, encoding=None 120 | ) 121 | yaml_string_obj_from_string = yaml.dump( 122 | obj_from_string, Dumper=AnsibleDumper, encoding=None 123 | ) 124 | 125 | assert yaml_string == yaml_string_obj_from_stream 126 | assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string 127 | assert ( 128 | yaml_string 129 | == yaml_string_obj_from_stream 130 | == yaml_string_obj_from_string 131 | == yaml_string_stream_obj_from_stream 132 | == yaml_string_stream_obj_from_string 133 | ) 134 | assert obj == obj_from_stream 135 | assert obj == obj_from_string 136 | assert obj == yaml_string_obj_from_stream 137 | assert obj == yaml_string_obj_from_string 138 | assert ( 139 | obj 140 | == obj_from_stream 141 | == obj_from_string 142 | == yaml_string_obj_from_stream 143 | == yaml_string_obj_from_string 144 | ) 145 | return { 146 | "obj": obj, 147 | "yaml_string": yaml_string, 148 | "yaml_string_from_stream": yaml_string_from_stream, 149 | "obj_from_stream": obj_from_stream, 150 | "obj_from_string": obj_from_string, 151 | "yaml_string_obj_from_string": yaml_string_obj_from_string, 152 | } 153 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/test_openvswitch_bond.py: -------------------------------------------------------------------------------- 1 | # 2 | # (c) 2020, James Denton 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Ansible is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Ansible is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Ansible. If not, see . 18 | 19 | # Make coding more python3-ish 20 | from __future__ import absolute_import, division, print_function 21 | 22 | __metaclass__ = type 23 | 24 | from ansible_collections.openvswitch.openvswitch.plugins.modules import openvswitch_bond 25 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat.mock import patch 26 | from ansible_collections.openvswitch.openvswitch.tests.unit.modules.utils import set_module_args 27 | 28 | from .ovs_module import TestOpenVSwitchModule, load_fixture 29 | 30 | test_name_side_effect_matrix = { 31 | "test_openvswitch_bond_absent_idempotent": [(0, "", "")], 32 | "test_openvswitch_bond_absent_removes_bond": [ 33 | (0, "list_ports_bond_br.cfg", ""), 34 | (0, "get_port_bond0_other_config.cfg", ""), 35 | (0, "get_port_bond0_external_ids.cfg", ""), 36 | (0, "", ""), 37 | (0, "", ""), 38 | ], 39 | "test_openvswitch_bond_present_idempotent": [ 40 | (0, "list_ports_bond_br.cfg", ""), 41 | (0, "get_port_bond0_other_config.cfg", ""), 42 | (0, "get_port_bond0_external_ids.cfg", ""), 43 | (0, "", ""), 44 | (0, "", ""), 45 | ], 46 | "test_openvswitch_bond_present_creates_bond": [ 47 | (0, "", ""), 48 | (0, "", ""), 49 | (0, "", ""), 50 | (0, "", ""), 51 | (0, "", ""), 52 | ], 53 | "test_openvswitch_bond_present_creates_lacp_bond": [ 54 | (0, "", ""), 55 | (0, "", ""), 56 | (0, "", ""), 57 | (0, "", ""), 58 | (0, "", ""), 59 | ], 60 | } 61 | 62 | 63 | class TestOpenVSwitchBondModule(TestOpenVSwitchModule): 64 | module = openvswitch_bond 65 | 66 | def setUp(self): 67 | super(TestOpenVSwitchBondModule, self).setUp() 68 | 69 | self.mock_run_command = patch("ansible.module_utils.basic.AnsibleModule.run_command") 70 | self.run_command = self.mock_run_command.start() 71 | self.mock_get_bin_path = patch("ansible.module_utils.basic.AnsibleModule.get_bin_path") 72 | self.get_bin_path = self.mock_get_bin_path.start() 73 | 74 | def tearDown(self): 75 | super(TestOpenVSwitchBondModule, self).tearDown() 76 | 77 | self.mock_run_command.stop() 78 | self.mock_get_bin_path.stop() 79 | 80 | def load_fixtures(self, test_name): 81 | test_side_effects = [] 82 | for s in test_name_side_effect_matrix[test_name]: 83 | rc = s[0] 84 | out = s[1] if s[1] == "" else str(load_fixture(s[1])) 85 | err = s[2] 86 | side_effect_with_fixture_loaded = (rc, out, err) 87 | test_side_effects.append(side_effect_with_fixture_loaded) 88 | self.run_command.side_effect = test_side_effects 89 | 90 | self.get_bin_path.return_value = "/usr/bin/ovs-vsctl" 91 | 92 | def test_openvswitch_bond_absent_idempotent(self): 93 | set_module_args(dict(state="absent", bridge="bond-br", port="bond0")) 94 | self.execute_module(commands=[], test_name="test_openvswitch_bond_absent_idempotent") 95 | 96 | def test_openvswitch_bond_absent_removes_bond(self): 97 | set_module_args(dict(state="absent", bridge="bond-br", port="bond0")) 98 | commands = ["/usr/bin/ovs-vsctl -t 5 del-port bond-br bond0"] 99 | self.execute_module( 100 | changed=True, 101 | commands=commands, 102 | test_name="test_openvswitch_bond_absent_removes_bond", 103 | ) 104 | 105 | def test_openvswitch_bond_database_socket(self): 106 | set_module_args( 107 | dict( 108 | state="absent", 109 | bridge="bond-br", 110 | port="bond0", 111 | database_socket="unix:/opt/second.sock", 112 | ) 113 | ) 114 | commands = ["/usr/bin/ovs-vsctl --db=unix:/opt/second.sock -t 5 del-port bond-br bond0"] 115 | self.execute_module( 116 | changed=True, 117 | commands=commands, 118 | test_name="test_openvswitch_bond_absent_removes_bond", 119 | ) 120 | 121 | def test_openvswitch_bond_present_idempotent(self): 122 | set_module_args( 123 | dict( 124 | state="present", 125 | bridge="bond-br", 126 | port="bond0", 127 | external_ids={"foo": "bar"}, 128 | other_config={"bond-detect-mode": "miimon"}, 129 | ) 130 | ) 131 | self.execute_module(commands=[], test_name="test_openvswitch_bond_present_idempotent") 132 | 133 | def test_openvswitch_bond_present_creates_bond(self): 134 | set_module_args( 135 | dict( 136 | state="present", 137 | bridge="bond-br", 138 | port="bond0", 139 | bond_updelay="100", 140 | bond_downdelay="100", 141 | interfaces=["eth3", "eth4"], 142 | other_config={"bond-detect-mode": "miimon"}, 143 | ) 144 | ) 145 | commands = [ 146 | "/usr/bin/ovs-vsctl -t 5 add-bond bond-br bond0 eth3 eth4" 147 | + " bond_updelay=100 bond_downdelay=100", 148 | "/usr/bin/ovs-vsctl -t 5 set port bond0 other_config:bond-detect-mode=miimon", 149 | ] 150 | self.execute_module( 151 | changed=True, 152 | commands=commands, 153 | test_name="test_openvswitch_bond_present_creates_bond", 154 | ) 155 | 156 | def test_openvswitch_bond_present_creates_lacp_bond(self): 157 | set_module_args( 158 | dict( 159 | state="present", 160 | lacp="active", 161 | bridge="bond-br", 162 | port="bond0", 163 | interfaces=["eth3", "eth4"], 164 | other_config={"bond-detect-mode": "miimon"}, 165 | ) 166 | ) 167 | commands = [ 168 | "/usr/bin/ovs-vsctl -t 5 add-bond bond-br bond0 eth3 eth4 lacp=active", 169 | "/usr/bin/ovs-vsctl -t 5 set port bond0 other_config:bond-detect-mode=miimon", 170 | ] 171 | self.execute_module( 172 | changed=True, 173 | commands=commands, 174 | test_name="test_openvswitch_bond_present_creates_bond", 175 | ) 176 | -------------------------------------------------------------------------------- /docs/openvswitch.openvswitch.openvswitch_port_module.rst: -------------------------------------------------------------------------------- 1 | .. _openvswitch.openvswitch.openvswitch_port_module: 2 | 3 | 4 | **************************************** 5 | openvswitch.openvswitch.openvswitch_port 6 | **************************************** 7 | 8 | **Manage Open vSwitch ports** 9 | 10 | 11 | Version added: 1.0.0 12 | 13 | .. contents:: 14 | :local: 15 | :depth: 1 16 | 17 | 18 | Synopsis 19 | -------- 20 | - Manage Open vSwitch ports 21 | 22 | 23 | 24 | Requirements 25 | ------------ 26 | The below requirements are needed on the host that executes this module. 27 | 28 | - ovs-vsctl 29 | 30 | 31 | Parameters 32 | ---------- 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 52 | 54 | 57 | 58 | 59 | 67 | 69 | 74 | 75 | 76 | 84 | 87 | 90 | 91 | 92 | 101 | 103 | 106 | 107 | 108 | 117 | 119 | 122 | 123 | 124 | 132 | 138 | 141 | 142 | 143 | 151 | 153 | 156 | 157 | 158 | 166 | 169 | 172 | 173 |
ParameterChoices/DefaultsComments
44 |
45 | bridge 46 | 47 |
48 | string 49 | / required 50 |
51 |
53 | 55 |
Name of bridge to manage
56 |
60 |
61 | database_socket 62 | 63 |
64 | string 65 |
66 |
68 | 70 |
Path/ip to datbase socket to use
71 |
Default path is used if not specified
72 |
Path should start with 'unix:' prefix
73 |
77 |
78 | external_ids 79 | 80 |
81 | dictionary 82 |
83 |
85 | Default:
{}
86 |
88 |
Dictionary of external_ids applied to a port.
89 |
93 |
94 | port 95 | 96 |
97 | string 98 | / required 99 |
100 |
102 | 104 |
Name of port to manage on the bridge
105 |
109 |
110 | set 111 | 112 |
113 | list 114 | / elements=string 115 |
116 |
118 | 120 |
Set multiple properties on a port.
121 |
125 |
126 | state 127 | 128 |
129 | string 130 |
131 |
133 |
    Choices: 134 |
  • present ←
  • 135 |
  • absent
  • 136 |
137 |
139 |
Whether the port should exist
140 |
144 |
145 | tag 146 | 147 |
148 | string 149 |
150 |
152 | 154 |
VLAN tag for this port. Must be a value between 0 and 4095.
155 |
159 |
160 | timeout 161 | 162 |
163 | integer 164 |
165 |
167 | Default:
5
168 |
170 |
How long to wait for ovs-vswitchd to respond
171 |
174 |
175 | 176 | 177 | 178 | 179 | Examples 180 | -------- 181 | 182 | .. code-block:: yaml 183 | 184 | # Creates port eth2 on bridge br-ex 185 | - openvswitch.openvswitch.openvswitch_port: 186 | bridge: br-ex 187 | port: eth2 188 | state: present 189 | 190 | # Creates port eth6 191 | - openvswitch.openvswitch.openvswitch_port: 192 | bridge: bridge-loop 193 | port: eth6 194 | state: present 195 | set: Interface eth6 196 | 197 | # Creates port vlan10 with tag 10 on bridge br-ex 198 | - openvswitch.openvswitch.openvswitch_port: 199 | bridge: br-ex 200 | port: vlan10 201 | tag: 10 202 | state: present 203 | set: Interface vlan10 204 | 205 | # Assign interface id server1-vifeth6 and mac address 00:00:5E:00:53:23 206 | # to port vifeth6 and setup port to be managed by a controller. 207 | - openvswitch.openvswitch.openvswitch_port: 208 | bridge: br-int 209 | port: vifeth6 210 | state: present 211 | args: 212 | external_ids: 213 | iface-id: '{{ inventory_hostname }}-vifeth6' 214 | attached-mac: 00:00:5E:00:53:23 215 | vm-id: '{{ inventory_hostname }}' 216 | iface-status: active 217 | 218 | # Plugs port veth0 into brdige br0 for database for OVSDB instance 219 | # with socket unix:/opt/second_ovsdb.sock 220 | - openvswitch.openvswitch.openvswitch_port: 221 | bridge: br0 222 | port: veth0 223 | state: present 224 | database_socket: unix:/opt/second_ovsdb.sock 225 | 226 | 227 | 228 | 229 | Status 230 | ------ 231 | 232 | 233 | Authors 234 | ~~~~~~~ 235 | 236 | - David Stygstra (@stygstra) 237 | -------------------------------------------------------------------------------- /docs/openvswitch.openvswitch.openvswitch_bridge_module.rst: -------------------------------------------------------------------------------- 1 | .. _openvswitch.openvswitch.openvswitch_bridge_module: 2 | 3 | 4 | ****************************************** 5 | openvswitch.openvswitch.openvswitch_bridge 6 | ****************************************** 7 | 8 | **Manage Open vSwitch bridges** 9 | 10 | 11 | Version added: 1.0.0 12 | 13 | .. contents:: 14 | :local: 15 | :depth: 1 16 | 17 | 18 | Synopsis 19 | -------- 20 | - Manage Open vSwitch bridges 21 | 22 | 23 | 24 | Requirements 25 | ------------ 26 | The below requirements are needed on the host that executes this module. 27 | 28 | - ovs-vsctl 29 | 30 | 31 | Parameters 32 | ---------- 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 52 | 54 | 57 | 58 | 59 | 67 | 69 | 74 | 75 | 76 | 84 | 86 | 89 | 90 | 91 | 99 | 101 | 104 | 105 | 106 | 114 | 116 | 119 | 120 | 121 | 129 | 131 | 134 | 135 | 136 | 144 | 150 | 153 | 154 | 155 | 163 | 166 | 169 | 170 | 171 | 179 | 181 | 184 | 185 |
ParameterChoices/DefaultsComments
44 |
45 | bridge 46 | 47 |
48 | string 49 | / required 50 |
51 |
53 | 55 |
Name of bridge or fake bridge to manage
56 |
60 |
61 | database_socket 62 | 63 |
64 | string 65 |
66 |
68 | 70 |
Path/ip to datbase socket to use
71 |
Default path is used if not specified
72 |
Path should start with 'unix:' prefix
73 |
77 |
78 | external_ids 79 | 80 |
81 | dictionary 82 |
83 |
85 | 87 |
A dictionary of external-ids. Omitting this parameter is a No-op. To clear all external-ids pass an empty value.
88 |
92 |
93 | fail_mode 94 | 95 |
96 | string 97 |
98 |
100 | 102 |
Set bridge fail-mode. The default value (None) is a No-op.
103 |
107 |
108 | parent 109 | 110 |
111 | string 112 |
113 |
115 | 117 |
Bridge parent of the fake bridge to manage
118 |
122 |
123 | set 124 | 125 |
126 | string 127 |
128 |
130 | 132 |
Run set command after bridge configuration. This parameter is non-idempotent, play will always return changed state if present
133 |
137 |
138 | state 139 | 140 |
141 | string 142 |
143 |
145 |
    Choices: 146 |
  • present ←
  • 147 |
  • absent
  • 148 |
149 |
151 |
Whether the bridge should exist
152 |
156 |
157 | timeout 158 | 159 |
160 | integer 161 |
162 |
164 | Default:
5
165 |
167 |
How long to wait for ovs-vswitchd to respond
168 |
172 |
173 | vlan 174 | 175 |
176 | integer 177 |
178 |
180 | 182 |
The VLAN id of the fake bridge to manage (must be between 0 and 4095). This parameter is required if parent parameter is set.
183 |
186 |
187 | 188 | 189 | 190 | 191 | Examples 192 | -------- 193 | 194 | .. code-block:: yaml 195 | 196 | # Create a bridge named br-int 197 | - openvswitch.openvswitch.openvswitch_bridge: 198 | bridge: br-int 199 | state: present 200 | 201 | # Create a fake bridge named br-int within br-parent on the VLAN 405 202 | - openvswitch.openvswitch.openvswitch_bridge: 203 | bridge: br-int 204 | parent: br-parent 205 | vlan: 405 206 | state: present 207 | 208 | # Create an integration bridge 209 | - openvswitch.openvswitch.openvswitch_bridge: 210 | bridge: br-int 211 | state: present 212 | fail_mode: secure 213 | args: 214 | external_ids: 215 | bridge-id: br-int 216 | # Create a bridge named br0 in database with socket at /opt/second.sock 217 | - openvswitch.openvswitch.openvswitch_bridge: 218 | bridge: br0 219 | state: present 220 | database_socket: unix:/opt/second.sock 221 | 222 | 223 | 224 | 225 | Status 226 | ------ 227 | 228 | 229 | Authors 230 | ~~~~~~~ 231 | 232 | - David Stygstra (@stygstra) 233 | -------------------------------------------------------------------------------- /plugins/modules/openvswitch_port.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2013, David Stygstra 4 | # Portions copyright @ 2015 VMware, Inc. 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | 9 | __metaclass__ = type 10 | 11 | 12 | DOCUMENTATION = """ 13 | module: openvswitch_port 14 | author: David Stygstra (@stygstra) 15 | short_description: Manage Open vSwitch ports 16 | requirements: 17 | - ovs-vsctl 18 | description: 19 | - Manage Open vSwitch ports 20 | version_added: 1.0.0 21 | options: 22 | bridge: 23 | required: true 24 | description: 25 | - Name of bridge to manage 26 | type: str 27 | port: 28 | required: true 29 | description: 30 | - Name of port to manage on the bridge 31 | type: str 32 | tag: 33 | description: 34 | - VLAN tag for this port. Must be a value between 0 and 4095. 35 | type: str 36 | state: 37 | default: present 38 | choices: 39 | - present 40 | - absent 41 | description: 42 | - Whether the port should exist 43 | type: str 44 | timeout: 45 | default: 5 46 | description: 47 | - How long to wait for ovs-vswitchd to respond 48 | type: int 49 | external_ids: 50 | default: {} 51 | description: 52 | - Dictionary of external_ids applied to a port. 53 | type: dict 54 | set: 55 | description: 56 | - Set multiple properties on a port. 57 | type: list 58 | elements: str 59 | database_socket: 60 | description: 61 | - Path/ip to datbase socket to use 62 | - Default path is used if not specified 63 | - Path should start with 'unix:' prefix 64 | type: str 65 | """ 66 | 67 | EXAMPLES = """ 68 | # Creates port eth2 on bridge br-ex 69 | - openvswitch.openvswitch.openvswitch_port: 70 | bridge: br-ex 71 | port: eth2 72 | state: present 73 | 74 | # Creates port eth6 75 | - openvswitch.openvswitch.openvswitch_port: 76 | bridge: bridge-loop 77 | port: eth6 78 | state: present 79 | set: Interface eth6 80 | 81 | # Creates port vlan10 with tag 10 on bridge br-ex 82 | - openvswitch.openvswitch.openvswitch_port: 83 | bridge: br-ex 84 | port: vlan10 85 | tag: 10 86 | state: present 87 | set: Interface vlan10 88 | 89 | # Assign interface id server1-vifeth6 and mac address 00:00:5E:00:53:23 90 | # to port vifeth6 and setup port to be managed by a controller. 91 | - openvswitch.openvswitch.openvswitch_port: 92 | bridge: br-int 93 | port: vifeth6 94 | state: present 95 | args: 96 | external_ids: 97 | iface-id: '{{ inventory_hostname }}-vifeth6' 98 | attached-mac: 00:00:5E:00:53:23 99 | vm-id: '{{ inventory_hostname }}' 100 | iface-status: active 101 | 102 | # Plugs port veth0 into brdige br0 for database for OVSDB instance 103 | # with socket unix:/opt/second_ovsdb.sock 104 | - openvswitch.openvswitch.openvswitch_port: 105 | bridge: br0 106 | port: veth0 107 | state: present 108 | database_socket: unix:/opt/second_ovsdb.sock 109 | 110 | """ 111 | 112 | from ansible.module_utils.basic import AnsibleModule 113 | from ansible.module_utils.six import iteritems 114 | 115 | 116 | def _external_ids_to_dict(text): 117 | text = text.strip() 118 | 119 | if text == "{}": 120 | return None 121 | else: 122 | d = {} 123 | 124 | for kv in text[1:-1].split(","): 125 | kv = kv.strip() 126 | k, v = kv.split("=") 127 | d[k] = v 128 | 129 | return d 130 | 131 | 132 | def _tag_to_str(text): 133 | text = text.strip() 134 | 135 | if text == "[]": 136 | return None 137 | else: 138 | return text 139 | 140 | 141 | def map_obj_to_commands(want, have, module): 142 | commands = list() 143 | 144 | if module.params["state"] == "absent": 145 | if have: 146 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s del-port %(bridge)s %(port)s" 147 | command = templatized_command % module.params 148 | commands.append(command) 149 | else: 150 | if have: 151 | if want["tag"] != have["tag"]: 152 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s set port %(port)s tag=%(tag)s" 153 | command = templatized_command % module.params 154 | commands.append(command) 155 | 156 | if want["external_ids"] != have["external_ids"]: 157 | for k, v in iteritems(want["external_ids"]): 158 | if ( 159 | not have["external_ids"] 160 | or k not in have["external_ids"] 161 | or want["external_ids"][k] != have["external_ids"][k] 162 | ): 163 | if v is None: 164 | templatized_command = ( 165 | "%(ovs-vsctl)s -t %(timeout)s" 166 | " remove port %(port)s" 167 | " external_ids " + k 168 | ) 169 | command = templatized_command % module.params 170 | commands.append(command) 171 | else: 172 | templatized_command = ( 173 | "%(ovs-vsctl)s -t %(timeout)s set port %(port)s external_ids:" 174 | ) 175 | command = templatized_command % module.params 176 | command += k + "=" + v 177 | commands.append(command) 178 | else: 179 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s add-port %(bridge)s %(port)s" 180 | command = templatized_command % module.params 181 | 182 | if want["tag"]: 183 | templatized_command = " tag=%(tag)s" 184 | command += templatized_command % module.params 185 | 186 | if want["set"]: 187 | set_command = "" 188 | for x in want["set"]: 189 | set_command += " -- set {0}".format(x) 190 | command += set_command 191 | 192 | commands.append(command) 193 | 194 | if want["external_ids"]: 195 | for k, v in iteritems(want["external_ids"]): 196 | templatized_command = ( 197 | "%(ovs-vsctl)s -t %(timeout)s set port %(port)s external_ids:" 198 | ) 199 | command = templatized_command % module.params 200 | command += k + "=" + v 201 | commands.append(command) 202 | 203 | return commands 204 | 205 | 206 | def map_config_to_obj(module): 207 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s list-ports %(bridge)s" 208 | command = templatized_command % module.params 209 | rc, out, err = module.run_command(command, check_rc=True) 210 | if rc != 0: 211 | module.fail_json(msg=err) 212 | 213 | obj = {} 214 | 215 | if module.params["port"] in out.splitlines(): 216 | obj["bridge"] = module.params["bridge"] 217 | obj["port"] = module.params["port"] 218 | 219 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s get Port %(port)s tag" 220 | command = templatized_command % module.params 221 | rc, out, err = module.run_command(command, check_rc=True) 222 | obj["tag"] = _tag_to_str(out) 223 | 224 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s get Port %(port)s external_ids" 225 | command = templatized_command % module.params 226 | rc, out, err = module.run_command(command, check_rc=True) 227 | obj["external_ids"] = _external_ids_to_dict(out) 228 | 229 | return obj 230 | 231 | 232 | def map_params_to_obj(module): 233 | obj = { 234 | "bridge": module.params["bridge"], 235 | "port": module.params["port"], 236 | "tag": module.params["tag"], 237 | "external_ids": module.params["external_ids"], 238 | "set": module.params["set"], 239 | } 240 | 241 | return obj 242 | 243 | 244 | def main(): 245 | """Entry point.""" 246 | argument_spec = { 247 | "bridge": {"required": True}, 248 | "port": {"required": True}, 249 | "state": {"default": "present", "choices": ["present", "absent"]}, 250 | "timeout": {"default": 5, "type": "int"}, 251 | "external_ids": {"default": {}, "type": "dict"}, 252 | "tag": {"default": None}, 253 | "database_socket": {"default": None}, 254 | "set": {"required": False, "type": "list", "elements": "str"}, 255 | } 256 | 257 | module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) 258 | 259 | result = {"changed": False} 260 | 261 | # We add ovs-vsctl to module_params to later build up templatized commands 262 | module.params["ovs-vsctl"] = module.get_bin_path("ovs-vsctl", True) 263 | if module.params.get("database_socket"): 264 | module.params["ovs-vsctl"] += " --db=" + module.params.get("database_socket") 265 | 266 | want = map_params_to_obj(module) 267 | have = map_config_to_obj(module) 268 | 269 | commands = map_obj_to_commands(want, have, module) 270 | result["commands"] = commands 271 | 272 | if commands: 273 | if not module.check_mode: 274 | for c in commands: 275 | module.run_command(c, check_rc=True) 276 | result["changed"] = True 277 | 278 | module.exit_json(**result) 279 | 280 | 281 | if __name__ == "__main__": 282 | main() 283 | -------------------------------------------------------------------------------- /plugins/modules/openvswitch_bridge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2013, David Stygstra 4 | # Portions copyright @ 2015 VMware, Inc. 5 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 | 7 | from __future__ import absolute_import, division, print_function 8 | 9 | __metaclass__ = type 10 | 11 | 12 | DOCUMENTATION = """ 13 | module: openvswitch_bridge 14 | author: David Stygstra (@stygstra) 15 | short_description: Manage Open vSwitch bridges 16 | requirements: 17 | - ovs-vsctl 18 | description: 19 | - Manage Open vSwitch bridges 20 | version_added: 1.0.0 21 | options: 22 | bridge: 23 | required: true 24 | description: 25 | - Name of bridge or fake bridge to manage 26 | type: str 27 | parent: 28 | description: 29 | - Bridge parent of the fake bridge to manage 30 | type: str 31 | vlan: 32 | description: 33 | - The VLAN id of the fake bridge to manage (must be between 0 and 4095). This 34 | parameter is required if I(parent) parameter is set. 35 | type: int 36 | state: 37 | default: present 38 | choices: 39 | - present 40 | - absent 41 | description: 42 | - Whether the bridge should exist 43 | type: str 44 | timeout: 45 | default: 5 46 | description: 47 | - How long to wait for ovs-vswitchd to respond 48 | type: int 49 | external_ids: 50 | description: 51 | - A dictionary of external-ids. Omitting this parameter is a No-op. To clear 52 | all external-ids pass an empty value. 53 | type: dict 54 | fail_mode: 55 | description: 56 | - Set bridge fail-mode. The default value (None) is a No-op. 57 | type: str 58 | set: 59 | description: 60 | - Run set command after bridge configuration. This parameter is non-idempotent, 61 | play will always return I(changed) state if present 62 | type: str 63 | database_socket: 64 | description: 65 | - Path/ip to datbase socket to use 66 | - Default path is used if not specified 67 | - Path should start with 'unix:' prefix 68 | type: str 69 | """ 70 | 71 | EXAMPLES = """ 72 | # Create a bridge named br-int 73 | - openvswitch.openvswitch.openvswitch_bridge: 74 | bridge: br-int 75 | state: present 76 | 77 | # Create a fake bridge named br-int within br-parent on the VLAN 405 78 | - openvswitch.openvswitch.openvswitch_bridge: 79 | bridge: br-int 80 | parent: br-parent 81 | vlan: 405 82 | state: present 83 | 84 | # Create an integration bridge 85 | - openvswitch.openvswitch.openvswitch_bridge: 86 | bridge: br-int 87 | state: present 88 | fail_mode: secure 89 | args: 90 | external_ids: 91 | bridge-id: br-int 92 | # Create a bridge named br0 in database with socket at /opt/second.sock 93 | - openvswitch.openvswitch.openvswitch_bridge: 94 | bridge: br0 95 | state: present 96 | database_socket: unix:/opt/second.sock 97 | """ 98 | 99 | from ansible.module_utils._text import to_text 100 | from ansible.module_utils.basic import AnsibleModule 101 | from ansible.module_utils.six import iteritems 102 | 103 | 104 | def _fail_mode_to_str(text): 105 | if not text: 106 | return None 107 | else: 108 | return text.strip() 109 | 110 | 111 | def _external_ids_to_dict(text): 112 | if not text: 113 | return None 114 | else: 115 | d = {} 116 | 117 | for l in text.splitlines(): 118 | if l: 119 | k, v = l.split("=") 120 | d[k] = v 121 | 122 | return d 123 | 124 | 125 | def map_obj_to_commands(want, have, module): 126 | commands = list() 127 | 128 | if module.params["state"] == "absent": 129 | if have: 130 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s del-br %(bridge)s" 131 | command = templatized_command % module.params 132 | commands.append(command) 133 | else: 134 | if have: 135 | if want["fail_mode"] != have["fail_mode"]: 136 | templatized_command = ( 137 | "%(ovs-vsctl)s -t %(timeout)s set-fail-mode %(bridge)s %(fail_mode)s" 138 | ) 139 | command = templatized_command % module.params 140 | commands.append(command) 141 | 142 | if want["external_ids"] != have["external_ids"]: 143 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-set-external-id %(bridge)s" 144 | command = templatized_command % module.params 145 | if want["external_ids"]: 146 | for k, v in iteritems(want["external_ids"]): 147 | if ( 148 | k not in have["external_ids"] 149 | or want["external_ids"][k] != have["external_ids"][k] 150 | ): 151 | command += " " + k + " " + v 152 | commands.append(command) 153 | 154 | if want["vlan"] and to_text(want["vlan"]) != have["vlan"]: 155 | templatized_command = ( 156 | "%(ovs-vsctl)s -t %(timeout)s set port %(bridge)s tag=%(vlan)s" 157 | ) 158 | command = templatized_command % module.params 159 | commands.append(command) 160 | else: 161 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s add-br %(bridge)s" 162 | command = templatized_command % module.params 163 | 164 | if want["parent"]: 165 | templatized_command = "%(parent)s %(vlan)s" 166 | command += " " + templatized_command % module.params 167 | 168 | if want["set"]: 169 | templatized_command = " -- set %(set)s" 170 | command += templatized_command % module.params 171 | 172 | commands.append(command) 173 | 174 | if want["fail_mode"]: 175 | templatized_command = ( 176 | "%(ovs-vsctl)s -t %(timeout)s set-fail-mode %(bridge)s %(fail_mode)s" 177 | ) 178 | command = templatized_command % module.params 179 | commands.append(command) 180 | 181 | if want["external_ids"]: 182 | for k, v in iteritems(want["external_ids"]): 183 | templatized_command = ( 184 | "%(ovs-vsctl)s -t %(timeout)s br-set-external-id %(bridge)s" 185 | ) 186 | command = templatized_command % module.params 187 | command += " " + k + " " + v 188 | commands.append(command) 189 | 190 | return commands 191 | 192 | 193 | def map_config_to_obj(module): 194 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s list-br" 195 | command = templatized_command % module.params 196 | rc, out, err = module.run_command(command, check_rc=True) 197 | if rc != 0: 198 | module.fail_json(msg=err) 199 | 200 | obj = {} 201 | 202 | if module.params["bridge"] in out.splitlines(): 203 | obj["bridge"] = module.params["bridge"] 204 | 205 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-to-parent %(bridge)s" 206 | command = templatized_command % module.params 207 | rc, out, err = module.run_command(command, check_rc=True) 208 | obj["parent"] = out.strip() 209 | 210 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-to-vlan %(bridge)s" 211 | command = templatized_command % module.params 212 | rc, out, err = module.run_command(command, check_rc=True) 213 | obj["vlan"] = out.strip() 214 | 215 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s get-fail-mode %(bridge)s" 216 | command = templatized_command % module.params 217 | rc, out, err = module.run_command(command, check_rc=True) 218 | obj["fail_mode"] = _fail_mode_to_str(out) 219 | 220 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-get-external-id %(bridge)s" 221 | command = templatized_command % module.params 222 | rc, out, err = module.run_command(command, check_rc=True) 223 | obj["external_ids"] = _external_ids_to_dict(out) 224 | 225 | return obj 226 | 227 | 228 | def map_params_to_obj(module): 229 | obj = { 230 | "bridge": module.params["bridge"], 231 | "parent": module.params["parent"], 232 | "vlan": module.params["vlan"], 233 | "fail_mode": module.params["fail_mode"], 234 | "external_ids": module.params["external_ids"], 235 | "set": module.params["set"], 236 | } 237 | 238 | return obj 239 | 240 | 241 | def main(): 242 | """Entry point.""" 243 | argument_spec = { 244 | "bridge": {"required": True}, 245 | "parent": {"default": None}, 246 | "vlan": {"default": None, "type": "int"}, 247 | "state": {"default": "present", "choices": ["present", "absent"]}, 248 | "timeout": {"default": 5, "type": "int"}, 249 | "external_ids": {"default": None, "type": "dict"}, 250 | "fail_mode": {"default": None}, 251 | "set": {"required": False, "default": None}, 252 | "database_socket": {"default": None}, 253 | } 254 | 255 | required_if = [("parent", not None, ("vlan",))] 256 | 257 | module = AnsibleModule( 258 | argument_spec=argument_spec, 259 | required_if=required_if, 260 | supports_check_mode=True, 261 | ) 262 | 263 | result = {"changed": False} 264 | 265 | # We add ovs-vsctl to module_params to later build up templatized commands 266 | module.params["ovs-vsctl"] = module.get_bin_path("ovs-vsctl", True) 267 | if module.params.get("database_socket"): 268 | module.params["ovs-vsctl"] += " --db=" + module.params.get("database_socket") 269 | 270 | want = map_params_to_obj(module) 271 | have = map_config_to_obj(module) 272 | 273 | commands = map_obj_to_commands(want, have, module) 274 | result["commands"] = commands 275 | 276 | if commands: 277 | if not module.check_mode: 278 | for c in commands: 279 | module.run_command(c, check_rc=True) 280 | result["changed"] = True 281 | 282 | module.exit_json(**result) 283 | 284 | 285 | if __name__ == "__main__": 286 | main() 287 | -------------------------------------------------------------------------------- /plugins/modules/openvswitch_db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # 4 | # (c) 2015, Mark Hamilton 5 | # Portions copyright @ 2015 VMware, Inc. 6 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 | 8 | from __future__ import absolute_import, division, print_function 9 | 10 | __metaclass__ = type 11 | 12 | 13 | DOCUMENTATION = """ 14 | module: openvswitch_db 15 | author: Mark Hamilton (@markleehamilton) 16 | short_description: Configure open vswitch database. 17 | requirements: 18 | - ovs-vsctl >= 2.3.3 19 | description: 20 | - Set column values in record in database table. 21 | version_added: 1.0.0 22 | options: 23 | state: 24 | required: false 25 | description: 26 | - Configures the state of the key. When set to I(present), the I(key) and I(value) 27 | pair will be set on the I(record) and when set to I(absent) the I(key) will 28 | not be set. 29 | default: present 30 | choices: 31 | - present 32 | - absent 33 | - read 34 | type: str 35 | table: 36 | required: true 37 | description: 38 | - Identifies the table in the database. 39 | type: str 40 | record: 41 | required: true 42 | description: 43 | - Identifies the record in the table. 44 | type: str 45 | col: 46 | required: true 47 | description: 48 | - Identifies the column in the record. 49 | type: str 50 | key: 51 | required: false 52 | description: 53 | - Identifies the key in the record column, when the column is a map type. 54 | type: str 55 | value: 56 | description: 57 | - Expected value for the table, record, column and key. 58 | type: str 59 | timeout: 60 | required: false 61 | default: 5 62 | description: 63 | - How long to wait for ovs-vswitchd to respond 64 | type: int 65 | database_socket: 66 | description: 67 | - Path/ip to datbase socket to use 68 | - Default path is used if not specified 69 | - Path should start with 'unix:' prefix 70 | type: str 71 | """ 72 | 73 | EXAMPLES = """ 74 | # Increase the maximum idle time to 50 seconds before pruning unused kernel 75 | # rules. 76 | - openvswitch.openvswitch.openvswitch_db: 77 | table: open_vswitch 78 | record: . 79 | col: other_config 80 | key: max-idle 81 | value: 50000 82 | 83 | # Disable in band copy 84 | - openvswitch.openvswitch.openvswitch_db: 85 | table: Bridge 86 | record: br-int 87 | col: other_config 88 | key: disable-in-band 89 | value: true 90 | 91 | # Remove in band key 92 | - openvswitch.openvswitch.openvswitch_db: 93 | state: present 94 | table: Bridge 95 | record: br-int 96 | col: other_config 97 | key: disable-in-band 98 | 99 | # Mark port with tag 10 100 | - openvswitch.openvswitch.openvswitch_db: 101 | table: Port 102 | record: port0 103 | col: tag 104 | value: 10 105 | 106 | # Mark port with tag 10 for OVSDB with socket in /opt/second.sock 107 | - openvswitch.openvswitch.openvswitch_db: 108 | table: Port 109 | record: port0 110 | col: tag 111 | value: 10 112 | database_socket: unix:/opt/second.sock 113 | 114 | # Get interface statistics 115 | - openvswitch.openvswitch.openvswitch_db: 116 | state: read 117 | table: interface 118 | record: ifname 119 | col: statistics 120 | 121 | # Get tx_packets value 122 | - openvswitch.openvswitch.openvswitch_db: 123 | state: read 124 | table: interface 125 | record: ifname 126 | col: statistics 127 | key: tx_packets 128 | 129 | # Get mtu value 130 | - openvswitch.openvswitch.openvswitch_db: 131 | state: read 132 | table: interface 133 | record: ifname 134 | col: mtu 135 | """ 136 | 137 | RETURN = """ 138 | commands: 139 | description: List of commands sent 140 | returned: when state is read 141 | type: list 142 | sample: ["/usr/local/bin/ovs-vsctl -t 5 get interface vhuclient1 statistics:tx_packets"] 143 | output: 144 | description: Output of the commands 145 | returned: when state is read 146 | type: dict 147 | sample: {"tx_packets": "0"} 148 | """ 149 | import re 150 | 151 | from ansible.module_utils.basic import AnsibleModule 152 | 153 | # Regular expression for map type, must not be empty 154 | NON_EMPTY_MAP_RE = re.compile(r"{.+}") 155 | # Regular expression for a map column type 156 | MAP_RE = re.compile(r"{.*}") 157 | 158 | 159 | def map_obj_to_commands(want, have, module): 160 | """Define ovs-vsctl command to meet desired state""" 161 | commands = list() 162 | 163 | if module.params["state"] == "absent": 164 | if "key" in have.keys(): 165 | templatized_command = ( 166 | "%(ovs-vsctl)s -t %(timeout)s remove %(table)s %(record)s %(col)s %(key)s" 167 | ) 168 | if module.params.get("value"): 169 | templatized_command += "=%(value)s" 170 | commands.append(templatized_command % module.params) 171 | elif module.params["key"] is None: 172 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s remove %(table)s %(record)s %(col)s" 173 | commands.append(templatized_command % module.params) 174 | elif module.params["state"] == "read": 175 | if module.params["key"] is None: 176 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s get %(table)s %(record)s %(col)s" 177 | commands.append(templatized_command % module.params) 178 | else: 179 | templatized_command = ( 180 | "%(ovs-vsctl)s -t %(timeout)s get %(table)s %(record)s %(col)s:%(key)s" 181 | ) 182 | commands.append(templatized_command % module.params) 183 | else: 184 | if want == have: 185 | # Nothing to commit 186 | return commands 187 | if module.params["key"] is None: 188 | templatized_command = ( 189 | "%(ovs-vsctl)s -t %(timeout)s set %(table)s %(record)s %(col)s=%(value)s" 190 | ) 191 | commands.append(templatized_command % module.params) 192 | else: 193 | templatized_command = ( 194 | "%(ovs-vsctl)s -t %(timeout)s set %(table)s %(record)s %(col)s:%(key)s=%(value)s" 195 | ) 196 | commands.append(templatized_command % module.params) 197 | 198 | return commands 199 | 200 | 201 | def map_config_to_obj(module): 202 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s list %(table)s %(record)s" 203 | command = templatized_command % module.params 204 | rc, out, err = module.run_command(command, check_rc=True) 205 | if rc != 0: 206 | module.fail_json(msg=err) 207 | 208 | match = re.search(r"^" + module.params["col"] + r"(\s+):(\s+)(.*)$", out, re.M) 209 | 210 | col_value = match.group(3) 211 | 212 | # Map types require key argument 213 | has_key = module.params["key"] is not None 214 | is_map = MAP_RE.match(col_value) 215 | if is_map and not has_key and module.params["state"] != "read": 216 | module.fail_json(msg="missing required arguments: key for map type of column") 217 | 218 | col_value_to_dict = {} 219 | if NON_EMPTY_MAP_RE.match(col_value): 220 | for kv in col_value[1:-1].split(", "): 221 | k, v = kv.split("=", 1) 222 | col_value_to_dict[k.strip()] = v.strip('"') 223 | 224 | obj = { 225 | "table": module.params["table"], 226 | "record": module.params["record"], 227 | "col": module.params["col"], 228 | } 229 | 230 | if has_key and is_map: 231 | if module.params["key"] in col_value_to_dict: 232 | obj["key"] = module.params["key"] 233 | obj["value"] = col_value_to_dict[module.params["key"]] 234 | else: 235 | obj["value"] = str(col_value.strip()) 236 | 237 | return obj 238 | 239 | 240 | def map_params_to_obj(module): 241 | if module.params["value"] in ["True", "False"]: 242 | module.params["value"] = module.params["value"].lower() 243 | obj = { 244 | "table": module.params["table"], 245 | "record": module.params["record"], 246 | "col": module.params["col"], 247 | "value": module.params["value"], 248 | } 249 | 250 | key = module.params["key"] 251 | if key is not None: 252 | obj["key"] = key 253 | 254 | return obj 255 | 256 | 257 | def main(): 258 | """Entry point for ansible module.""" 259 | argument_spec = { 260 | "state": { 261 | "default": "present", 262 | "choices": ["present", "absent", "read"], 263 | }, 264 | "table": {"required": True}, 265 | "record": {"required": True}, 266 | "col": {"required": True}, 267 | "key": {"required": False, "no_log": False}, 268 | "value": {"type": "str"}, 269 | "timeout": {"default": 5, "type": "int"}, 270 | "database_socket": {"default": None}, 271 | } 272 | 273 | module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) 274 | 275 | result = {"changed": False} 276 | 277 | # We add ovs-vsctl to module_params to later build up templatized commands 278 | module.params["ovs-vsctl"] = module.get_bin_path("ovs-vsctl", True) 279 | if module.params.get("database_socket"): 280 | module.params["ovs-vsctl"] += " --db=" + module.params.get("database_socket") 281 | 282 | if module.params["state"] == "present" and not module.params["value"]: 283 | module.fail_json(msg="missing required argument value for state: present") 284 | 285 | want = map_params_to_obj(module) 286 | have = map_config_to_obj(module) 287 | 288 | commands = map_obj_to_commands(want, have, module) 289 | result["commands"] = commands 290 | 291 | if commands: 292 | if not module.check_mode: 293 | for c in commands: 294 | rc, out, err = module.run_command(c, check_rc=True) 295 | result["changed"] = True 296 | 297 | string_to_dict = {} 298 | 299 | if NON_EMPTY_MAP_RE.match(str(out)): 300 | for kv in re.split(r", ", out[1:-1]): 301 | k, v = re.split(r"=", kv, 1) 302 | string_to_dict[re.search("\\w*", k).group(0)] = re.search("\\d*", v).group(0) 303 | else: 304 | if module.params["key"] is not None: 305 | string_to_dict[module.params["key"]] = re.search("\\w*", str(out)).group(0) 306 | else: 307 | string_to_dict[module.params["col"]] = re.search("\\w*", str(out)).group(0) 308 | result["output"] = string_to_dict 309 | 310 | module.exit_json(**result) 311 | 312 | 313 | if __name__ == "__main__": 314 | main() 315 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/test_openvswitch_port.py: -------------------------------------------------------------------------------- 1 | # 2 | # (c) 2016 Red Hat Inc. 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Ansible is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Ansible is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Ansible. If not, see . 18 | 19 | # Make coding more python3-ish 20 | from __future__ import absolute_import, division, print_function 21 | 22 | __metaclass__ = type 23 | 24 | from ansible_collections.openvswitch.openvswitch.plugins.modules import openvswitch_port 25 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat.mock import patch 26 | from ansible_collections.openvswitch.openvswitch.tests.unit.modules.utils import set_module_args 27 | 28 | from .ovs_module import TestOpenVSwitchModule, load_fixture 29 | 30 | test_name_side_effect_matrix = { 31 | "test_openvswitch_port_absent_idempotent": [(0, "", "")], 32 | "test_openvswitch_port_absent_removes_port": [ 33 | (0, "list_ports_test_br.cfg", ""), 34 | (0, "get_port_eth2_tag.cfg", ""), 35 | (0, "get_port_eth2_external_ids.cfg", ""), 36 | (0, "", ""), 37 | ], 38 | "test_openvswitch_port_present_idempotent": [ 39 | (0, "list_ports_test_br.cfg", ""), 40 | (0, "get_port_eth2_tag.cfg", ""), 41 | (0, "get_port_eth2_external_ids.cfg", ""), 42 | (0, "", ""), 43 | ], 44 | "test_openvswitch_port_present_creates_port": [ 45 | (0, "", ""), 46 | (0, "", ""), 47 | (0, "", ""), 48 | ], 49 | "test_openvswitch_port_present_changes_tag": [ 50 | (0, "list_ports_test_br.cfg", ""), 51 | (0, "get_port_eth2_tag.cfg", ""), 52 | (0, "get_port_eth2_external_ids.cfg", ""), 53 | (0, "", ""), 54 | ], 55 | "test_openvswitch_port_present_changes_external_id": [ 56 | (0, "list_ports_test_br.cfg", ""), 57 | (0, "get_port_eth2_tag.cfg", ""), 58 | (0, "get_port_eth2_external_ids.cfg", ""), 59 | (0, "", ""), 60 | ], 61 | "test_openvswitch_port_present_adds_external_id": [ 62 | (0, "list_ports_test_br.cfg", ""), 63 | (0, "get_port_eth2_tag.cfg", ""), 64 | (0, "get_port_eth2_external_ids.cfg", ""), 65 | (0, "", ""), 66 | ], 67 | "test_openvswitch_port_present_clears_external_id": [ 68 | (0, "list_ports_test_br.cfg", ""), 69 | (0, "get_port_eth2_tag.cfg", ""), 70 | (0, "get_port_eth2_external_ids.cfg", ""), 71 | (0, "", ""), 72 | ], 73 | "test_openvswitch_port_present_runs_set_mode": [ 74 | (0, "", ""), 75 | (0, "", ""), 76 | (0, "", ""), 77 | ], 78 | } 79 | 80 | 81 | class TestOpenVSwitchPortModule(TestOpenVSwitchModule): 82 | module = openvswitch_port 83 | 84 | def setUp(self): 85 | super(TestOpenVSwitchPortModule, self).setUp() 86 | 87 | self.mock_run_command = patch("ansible.module_utils.basic.AnsibleModule.run_command") 88 | self.run_command = self.mock_run_command.start() 89 | self.mock_get_bin_path = patch("ansible.module_utils.basic.AnsibleModule.get_bin_path") 90 | self.get_bin_path = self.mock_get_bin_path.start() 91 | 92 | def tearDown(self): 93 | super(TestOpenVSwitchPortModule, self).tearDown() 94 | 95 | self.mock_run_command.stop() 96 | self.mock_get_bin_path.stop() 97 | 98 | def load_fixtures(self, test_name): 99 | test_side_effects = [] 100 | for s in test_name_side_effect_matrix[test_name]: 101 | rc = s[0] 102 | out = s[1] if s[1] == "" else str(load_fixture(s[1])) 103 | err = s[2] 104 | side_effect_with_fixture_loaded = (rc, out, err) 105 | test_side_effects.append(side_effect_with_fixture_loaded) 106 | self.run_command.side_effect = test_side_effects 107 | 108 | self.get_bin_path.return_value = "/usr/bin/ovs-vsctl" 109 | 110 | def test_openvswitch_port_absent_idempotent(self): 111 | set_module_args(dict(state="absent", bridge="test-br", port="eth2")) 112 | self.execute_module(test_name="test_openvswitch_port_absent_idempotent") 113 | 114 | def test_openvswitch_port_absent_removes_port(self): 115 | set_module_args(dict(state="absent", bridge="test-br", port="eth2")) 116 | commands = ["/usr/bin/ovs-vsctl -t 5 del-port test-br eth2"] 117 | self.execute_module( 118 | changed=True, 119 | commands=commands, 120 | test_name="test_openvswitch_port_absent_removes_port", 121 | ) 122 | 123 | def test_openvswitch_port_present_idempotent(self): 124 | set_module_args( 125 | dict( 126 | state="present", 127 | bridge="test-br", 128 | port="eth2", 129 | tag=10, 130 | external_ids={"foo": "bar"}, 131 | ) 132 | ) 133 | self.execute_module(test_name="test_openvswitch_port_present_idempotent") 134 | 135 | def test_openvswitch_port_present_creates_port(self): 136 | set_module_args( 137 | dict( 138 | state="present", 139 | bridge="test-br", 140 | port="eth2", 141 | tag=10, 142 | external_ids={"foo": "bar"}, 143 | ) 144 | ) 145 | commands = [ 146 | "/usr/bin/ovs-vsctl -t 5 add-port test-br eth2 tag=10", 147 | "/usr/bin/ovs-vsctl -t 5 set port eth2 external_ids:foo=bar", 148 | ] 149 | self.execute_module( 150 | changed=True, 151 | commands=commands, 152 | test_name="test_openvswitch_port_present_creates_port", 153 | ) 154 | 155 | def test_openvswitch_port_present_changes_tag(self): 156 | set_module_args( 157 | dict( 158 | state="present", 159 | bridge="test-br", 160 | port="eth2", 161 | tag=20, 162 | external_ids={"foo": "bar"}, 163 | ) 164 | ) 165 | commands = ["/usr/bin/ovs-vsctl -t 5 set port eth2 tag=20"] 166 | self.execute_module( 167 | changed=True, 168 | commands=commands, 169 | test_name="test_openvswitch_port_present_changes_tag", 170 | ) 171 | 172 | def test_openvswitch_port_present_changes_external_id(self): 173 | set_module_args( 174 | dict( 175 | state="present", 176 | bridge="test-br", 177 | port="eth2", 178 | tag=10, 179 | external_ids={"foo": "baz"}, 180 | ) 181 | ) 182 | commands = ["/usr/bin/ovs-vsctl -t 5 set port eth2 external_ids:foo=baz"] 183 | self.execute_module( 184 | changed=True, 185 | commands=commands, 186 | test_name="test_openvswitch_port_present_changes_external_id", 187 | ) 188 | 189 | def test_openvswitch_port_present_adds_external_id(self): 190 | set_module_args( 191 | dict( 192 | state="present", 193 | bridge="test-br", 194 | port="eth2", 195 | tag=10, 196 | external_ids={"foo2": "bar2"}, 197 | ) 198 | ) 199 | commands = ["/usr/bin/ovs-vsctl -t 5 set port eth2 external_ids:foo2=bar2"] 200 | self.execute_module( 201 | changed=True, 202 | commands=commands, 203 | test_name="test_openvswitch_port_present_adds_external_id", 204 | ) 205 | 206 | def test_openvswitch_port_present_clears_external_id(self): 207 | set_module_args( 208 | dict( 209 | state="present", 210 | bridge="test-br", 211 | port="eth2", 212 | tag=10, 213 | external_ids={"foo": None}, 214 | ) 215 | ) 216 | commands = ["/usr/bin/ovs-vsctl -t 5 remove port eth2 external_ids foo"] 217 | self.execute_module( 218 | changed=True, 219 | commands=commands, 220 | test_name="test_openvswitch_port_present_clears_external_id", 221 | ) 222 | 223 | def test_openvswitch_port_present_runs_set_mode(self): 224 | set_module_args( 225 | dict( 226 | state="present", 227 | bridge="test-br", 228 | port="eth2", 229 | tag=10, 230 | external_ids={"foo": "bar"}, 231 | set="port eth2 other_config:stp-path-cost=10", 232 | ) 233 | ) 234 | commands = [ 235 | "/usr/bin/ovs-vsctl -t 5 add-port test-br eth2 tag=10 -- set" 236 | " port eth2 other_config:stp-path-cost=10", 237 | "/usr/bin/ovs-vsctl -t 5 set port eth2 external_ids:foo=bar", 238 | ] 239 | self.execute_module( 240 | changed=True, 241 | commands=commands, 242 | test_name="test_openvswitch_port_present_runs_set_mode", 243 | ) 244 | 245 | def test_openvswitch_port_present_runs_set_mode_multiple(self): 246 | set_module_args( 247 | dict( 248 | state="present", 249 | bridge="test-br", 250 | port="eth2", 251 | tag=10, 252 | external_ids={"foo": "bar"}, 253 | set=[ 254 | "port eth2 other_config:stp-path-cost=10", 255 | "port eth2 type=patch", 256 | ], 257 | ) 258 | ) 259 | commands = [ 260 | "/usr/bin/ovs-vsctl -t 5 add-port test-br eth2 tag=10 -- set" 261 | " port eth2 other_config:stp-path-cost=10 -- set port eth2 type=patch", 262 | "/usr/bin/ovs-vsctl -t 5 set port eth2 external_ids:foo=bar", 263 | ] 264 | self.execute_module( 265 | changed=True, 266 | commands=commands, 267 | test_name="test_openvswitch_port_present_runs_set_mode", 268 | ) 269 | 270 | def test_openvswitch_database_socket(self): 271 | set_module_args( 272 | dict( 273 | state="present", 274 | bridge="test-br", 275 | port="eth2", 276 | database_socket="unix:/opt/second.sock", 277 | tag=10, 278 | external_ids={"foo": "bar"}, 279 | ) 280 | ) 281 | commands = [ 282 | "/usr/bin/ovs-vsctl --db=unix:/opt/second.sock -t 5 add-port test-br eth2 tag=10", 283 | "/usr/bin/ovs-vsctl --db=unix:/opt/second.sock -t 5 set port eth2 external_ids:foo=bar", 284 | ] 285 | self.execute_module( 286 | changed=True, 287 | commands=commands, 288 | test_name="test_openvswitch_port_present_creates_port", 289 | ) 290 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/test_openvswitch_bridge.py: -------------------------------------------------------------------------------- 1 | # 2 | # (c) 2016 Red Hat Inc. 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Ansible is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Ansible is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Ansible. If not, see . 18 | 19 | # Make coding more python3-ish 20 | from __future__ import absolute_import, division, print_function 21 | 22 | __metaclass__ = type 23 | 24 | import pytest 25 | 26 | from ansible_collections.openvswitch.openvswitch.plugins.modules import openvswitch_bridge 27 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat.mock import MagicMock, patch 28 | from ansible_collections.openvswitch.openvswitch.tests.unit.modules.utils import set_module_args 29 | 30 | from .ovs_module import TestOpenVSwitchModule, load_fixture 31 | 32 | 33 | @pytest.fixture 34 | def patched_openvswitch_bridge(monkeypatch): 35 | mocked_bridge = MagicMock() 36 | mocked_bridge.return_value = { 37 | "bridge": "test-br2", 38 | "parent": "test-br", 39 | "vlan": 200, 40 | "fail_mode": None, 41 | "external_ids": None, 42 | "set": None, 43 | } 44 | monkeypatch.setattr(openvswitch_bridge, "map_config_to_obj", mocked_bridge) 45 | return openvswitch_bridge 46 | 47 | 48 | test_name_side_effect_matrix = { 49 | "test_openvswitch_bridge_absent_idempotent": [(0, "", "")], 50 | "test_openvswitch_bridge_absent_removes_bridge": [ 51 | (0, "list_br_test_br.cfg", ""), 52 | (0, "", ""), 53 | (0, "", ""), 54 | (0, "", ""), 55 | (0, "", ""), 56 | (0, "", ""), 57 | ], 58 | "test_openvswitch_bridge_present_idempotent": [ 59 | (0, "list_br_test_br.cfg", ""), 60 | (0, "br_to_parent_test_br.cfg", ""), 61 | (0, "br_to_vlan_zero.cfg", ""), 62 | (0, "get_fail_mode_secure.cfg", ""), 63 | (0, "br_get_external_id_foo_bar.cfg", ""), 64 | ], 65 | "test_openvswitch_bridge_present_creates_bridge": [ 66 | (0, "", ""), 67 | (0, "", ""), 68 | (0, "", ""), 69 | (0, "", ""), 70 | ], 71 | "test_openvswitch_bridge_present_creates_fake_bridge": [ 72 | (0, "", ""), 73 | (0, "", ""), 74 | (0, "", ""), 75 | (0, "", ""), 76 | ], 77 | "test_openvswitch_bridge_updates_vlan": [ 78 | (0, "", ""), 79 | (0, "", ""), 80 | (0, "", ""), 81 | (0, "", ""), 82 | ], 83 | "test_openvswitch_bridge_present_adds_external_id": [ 84 | (0, "list_br_test_br.cfg", ""), 85 | (0, "br_to_parent_test_br.cfg", ""), 86 | (0, "br_to_vlan_zero.cfg", ""), 87 | (0, "get_fail_mode_secure.cfg", ""), 88 | (0, "br_get_external_id_foo_bar.cfg", ""), 89 | (0, "", ""), 90 | ], 91 | "test_openvswitch_bridge_present_clears_external_id": [ 92 | (0, "list_br_test_br.cfg", ""), 93 | (0, "br_to_parent_test_br.cfg", ""), 94 | (0, "br_to_vlan_zero.cfg", ""), 95 | (0, "get_fail_mode_secure.cfg", ""), 96 | (0, "br_get_external_id_foo_bar.cfg", ""), 97 | (0, "", ""), 98 | ], 99 | "test_openvswitch_bridge_present_changes_fail_mode": [ 100 | (0, "list_br_test_br.cfg", ""), 101 | (0, "br_to_parent_test_br.cfg", ""), 102 | (0, "br_to_vlan_zero.cfg", ""), 103 | (0, "get_fail_mode_secure.cfg", ""), 104 | (0, "br_get_external_id_foo_bar.cfg", ""), 105 | (0, "", ""), 106 | ], 107 | "test_openvswitch_bridge_present_runs_set_mode": [ 108 | (0, "", ""), 109 | (0, "", ""), 110 | (0, "", ""), 111 | (0, "", ""), 112 | ], 113 | } 114 | 115 | 116 | class TestOpenVSwitchBridgeModule(TestOpenVSwitchModule): 117 | module = openvswitch_bridge 118 | 119 | def setUp(self): 120 | super(TestOpenVSwitchBridgeModule, self).setUp() 121 | 122 | self.mock_run_command = patch("ansible.module_utils.basic.AnsibleModule.run_command") 123 | self.run_command = self.mock_run_command.start() 124 | self.mock_get_bin_path = patch("ansible.module_utils.basic.AnsibleModule.get_bin_path") 125 | self.get_bin_path = self.mock_get_bin_path.start() 126 | 127 | def tearDown(self): 128 | super(TestOpenVSwitchBridgeModule, self).tearDown() 129 | 130 | self.mock_run_command.stop() 131 | self.mock_get_bin_path.stop() 132 | 133 | def load_fixtures(self, test_name): 134 | test_side_effects = [] 135 | for s in test_name_side_effect_matrix[test_name]: 136 | rc = s[0] 137 | out = s[1] if s[1] == "" else str(load_fixture(s[1])) 138 | err = s[2] 139 | side_effect_with_fixture_loaded = (rc, out, err) 140 | test_side_effects.append(side_effect_with_fixture_loaded) 141 | self.run_command.side_effect = test_side_effects 142 | 143 | self.get_bin_path.return_value = "/usr/bin/ovs-vsctl" 144 | 145 | def test_openvswitch_bridge_absent_idempotent(self): 146 | set_module_args(dict(state="absent", bridge="test-br")) 147 | self.execute_module(test_name="test_openvswitch_bridge_absent_idempotent") 148 | 149 | def test_openvswitch_bridge_absent_removes_bridge(self): 150 | set_module_args(dict(state="absent", bridge="test-br")) 151 | commands = ["/usr/bin/ovs-vsctl -t 5 del-br test-br"] 152 | self.execute_module( 153 | changed=True, 154 | commands=commands, 155 | test_name="test_openvswitch_bridge_absent_removes_bridge", 156 | ) 157 | 158 | def test_openvswitch_bridge_present_idempotent(self): 159 | set_module_args( 160 | dict( 161 | state="present", 162 | bridge="test-br", 163 | fail_mode="secure", 164 | external_ids={"foo": "bar"}, 165 | ) 166 | ) 167 | self.execute_module(test_name="test_openvswitch_bridge_present_idempotent") 168 | 169 | def test_openvswitch_bridge_present_creates_bridge(self): 170 | set_module_args( 171 | dict( 172 | state="present", 173 | bridge="test-br", 174 | fail_mode="secure", 175 | external_ids={"foo": "bar"}, 176 | ) 177 | ) 178 | commands = [ 179 | "/usr/bin/ovs-vsctl -t 5 add-br test-br", 180 | "/usr/bin/ovs-vsctl -t 5 set-fail-mode test-br secure", 181 | "/usr/bin/ovs-vsctl -t 5 br-set-external-id test-br foo bar", 182 | ] 183 | self.execute_module( 184 | changed=True, 185 | commands=commands, 186 | test_name="test_openvswitch_bridge_present_creates_bridge", 187 | ) 188 | 189 | def test_openvswitch_bridge_present_creates_fake_bridge(self): 190 | set_module_args(dict(state="present", bridge="test-br2", parent="test-br", vlan=10)) 191 | commands = ["/usr/bin/ovs-vsctl -t 5 add-br test-br2 test-br 10"] 192 | self.execute_module( 193 | changed=True, 194 | commands=commands, 195 | test_name="test_openvswitch_bridge_present_creates_fake_bridge", 196 | ) 197 | 198 | def test_openvswitch_bridge_uses_database_socket(self): 199 | set_module_args( 200 | dict( 201 | state="present", 202 | bridge="test-br2", 203 | parent="test-br", 204 | database_socket="unix:/opt/second.sock", 205 | vlan=10, 206 | ) 207 | ) 208 | commands = ["/usr/bin/ovs-vsctl --db=unix:/opt/second.sock -t 5 add-br test-br2 test-br 10"] 209 | self.execute_module( 210 | changed=True, 211 | commands=commands, 212 | test_name="test_openvswitch_bridge_present_creates_fake_bridge", 213 | ) 214 | 215 | @pytest.mark.usefixtures("patched_openvswitch_bridge") 216 | def test_openvswitch_bridge_updates_vlan(self): 217 | set_module_args( 218 | { 219 | "state": "present", 220 | "bridge": "test-br2", 221 | "parent": "test-br", 222 | "vlan": 300, 223 | } 224 | ) 225 | commands = ["/usr/bin/ovs-vsctl -t 5 set port test-br2 tag=300"] 226 | self.execute_module( 227 | changed=True, 228 | commands=commands, 229 | test_name="test_openvswitch_bridge_updates_vlan", 230 | ) 231 | 232 | def test_openvswitch_bridge_present_adds_external_id(self): 233 | set_module_args( 234 | dict( 235 | state="present", 236 | bridge="test-br", 237 | fail_mode="secure", 238 | external_ids={"bip": "bop"}, 239 | ) 240 | ) 241 | commands = ["/usr/bin/ovs-vsctl -t 5 br-set-external-id test-br bip bop"] 242 | self.execute_module( 243 | changed=True, 244 | commands=commands, 245 | test_name="test_openvswitch_bridge_present_adds_external_id", 246 | ) 247 | 248 | def test_openvswitch_bridge_present_clears_external_id(self): 249 | set_module_args( 250 | dict( 251 | state="present", 252 | bridge="test-br", 253 | fail_mode="secure", 254 | external_ids={"foo": ""}, 255 | ) 256 | ) 257 | commands = ["/usr/bin/ovs-vsctl -t 5 br-set-external-id test-br foo "] 258 | self.execute_module( 259 | changed=True, 260 | commands=commands, 261 | test_name="test_openvswitch_bridge_present_clears_external_id", 262 | ) 263 | 264 | def test_openvswitch_bridge_present_changes_fail_mode(self): 265 | set_module_args( 266 | dict( 267 | state="present", 268 | bridge="test-br", 269 | fail_mode="standalone", 270 | external_ids={"foo": "bar"}, 271 | ) 272 | ) 273 | commands = ["/usr/bin/ovs-vsctl -t 5 set-fail-mode test-br standalone"] 274 | self.execute_module( 275 | changed=True, 276 | commands=commands, 277 | test_name="test_openvswitch_bridge_present_changes_fail_mode", 278 | ) 279 | 280 | def test_openvswitch_bridge_present_runs_set_mode(self): 281 | set_module_args( 282 | dict( 283 | state="present", 284 | bridge="test-br", 285 | fail_mode="secure", 286 | external_ids={"foo": "bar"}, 287 | set="bridge test-br datapath_type=netdev", 288 | ) 289 | ) 290 | commands = [ 291 | "/usr/bin/ovs-vsctl -t 5 add-br test-br -- set bridge test-br datapath_type=netdev", 292 | "/usr/bin/ovs-vsctl -t 5 set-fail-mode test-br secure", 293 | "/usr/bin/ovs-vsctl -t 5 br-set-external-id test-br foo bar", 294 | ] 295 | self.execute_module( 296 | changed=True, 297 | commands=commands, 298 | test_name="test_openvswitch_bridge_present_runs_set_mode", 299 | ) 300 | -------------------------------------------------------------------------------- /docs/openvswitch.openvswitch.openvswitch_db_module.rst: -------------------------------------------------------------------------------- 1 | .. _openvswitch.openvswitch.openvswitch_db_module: 2 | 3 | 4 | ************************************** 5 | openvswitch.openvswitch.openvswitch_db 6 | ************************************** 7 | 8 | **Configure open vswitch database.** 9 | 10 | 11 | Version added: 1.0.0 12 | 13 | .. contents:: 14 | :local: 15 | :depth: 1 16 | 17 | 18 | Synopsis 19 | -------- 20 | - Set column values in record in database table. 21 | 22 | 23 | 24 | Requirements 25 | ------------ 26 | The below requirements are needed on the host that executes this module. 27 | 28 | - ovs-vsctl >= 2.3.3 29 | 30 | 31 | Parameters 32 | ---------- 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 52 | 54 | 57 | 58 | 59 | 67 | 69 | 74 | 75 | 76 | 84 | 86 | 89 | 90 | 91 | 100 | 102 | 105 | 106 | 107 | 115 | 122 | 125 | 126 | 127 | 136 | 138 | 141 | 142 | 143 | 151 | 154 | 157 | 158 | 159 | 167 | 169 | 172 | 173 |
ParameterChoices/DefaultsComments
44 |
45 | col 46 | 47 |
48 | string 49 | / required 50 |
51 |
53 | 55 |
Identifies the column in the record.
56 |
60 |
61 | database_socket 62 | 63 |
64 | string 65 |
66 |
68 | 70 |
Path/ip to datbase socket to use
71 |
Default path is used if not specified
72 |
Path should start with 'unix:' prefix
73 |
77 |
78 | key 79 | 80 |
81 | string 82 |
83 |
85 | 87 |
Identifies the key in the record column, when the column is a map type.
88 |
92 |
93 | record 94 | 95 |
96 | string 97 | / required 98 |
99 |
101 | 103 |
Identifies the record in the table.
104 |
108 |
109 | state 110 | 111 |
112 | string 113 |
114 |
116 |
    Choices: 117 |
  • present ←
  • 118 |
  • absent
  • 119 |
  • read
  • 120 |
121 |
123 |
Configures the state of the key. When set to present, the key and value pair will be set on the record and when set to absent the key will not be set.
124 |
128 |
129 | table 130 | 131 |
132 | string 133 | / required 134 |
135 |
137 | 139 |
Identifies the table in the database.
140 |
144 |
145 | timeout 146 | 147 |
148 | integer 149 |
150 |
152 | Default:
5
153 |
155 |
How long to wait for ovs-vswitchd to respond
156 |
160 |
161 | value 162 | 163 |
164 | string 165 |
166 |
168 | 170 |
Expected value for the table, record, column and key.
171 |
174 |
175 | 176 | 177 | 178 | 179 | Examples 180 | -------- 181 | 182 | .. code-block:: yaml 183 | 184 | # Increase the maximum idle time to 50 seconds before pruning unused kernel 185 | # rules. 186 | - openvswitch.openvswitch.openvswitch_db: 187 | table: open_vswitch 188 | record: . 189 | col: other_config 190 | key: max-idle 191 | value: 50000 192 | 193 | # Disable in band copy 194 | - openvswitch.openvswitch.openvswitch_db: 195 | table: Bridge 196 | record: br-int 197 | col: other_config 198 | key: disable-in-band 199 | value: true 200 | 201 | # Remove in band key 202 | - openvswitch.openvswitch.openvswitch_db: 203 | state: present 204 | table: Bridge 205 | record: br-int 206 | col: other_config 207 | key: disable-in-band 208 | 209 | # Mark port with tag 10 210 | - openvswitch.openvswitch.openvswitch_db: 211 | table: Port 212 | record: port0 213 | col: tag 214 | value: 10 215 | 216 | # Mark port with tag 10 for OVSDB with socket in /opt/second.sock 217 | - openvswitch.openvswitch.openvswitch_db: 218 | table: Port 219 | record: port0 220 | col: tag 221 | value: 10 222 | database_socket: unix:/opt/second.sock 223 | 224 | # Get interface statistics 225 | - openvswitch.openvswitch.openvswitch_db: 226 | state: read 227 | table: interface 228 | record: ifname 229 | col: statistics 230 | 231 | # Get tx_packets value 232 | - openvswitch.openvswitch.openvswitch_db: 233 | state: read 234 | table: interface 235 | record: ifname 236 | col: statistics 237 | key: tx_packets 238 | 239 | # Get mtu value 240 | - openvswitch.openvswitch.openvswitch_db: 241 | state: read 242 | table: interface 243 | record: ifname 244 | col: mtu 245 | 246 | 247 | 248 | Return Values 249 | ------------- 250 | Common return values are documented `here `_, the following are the fields unique to this module: 251 | 252 | .. raw:: html 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 269 | 270 | 276 | 277 | 278 | 286 | 287 | 293 | 294 |
KeyReturnedDescription
262 |
263 | commands 264 | 265 |
266 | list 267 |
268 |
when state is read 271 |
List of commands sent
272 |
273 |
Sample:
274 |
['/usr/local/bin/ovs-vsctl -t 5 get interface vhuclient1 statistics:tx_packets']
275 |
279 |
280 | output 281 | 282 |
283 | dictionary 284 |
285 |
when state is read 288 |
Output of the commands
289 |
290 |
Sample:
291 |
{'tx_packets': '0'}
292 |
295 |

296 | 297 | 298 | Status 299 | ------ 300 | 301 | 302 | Authors 303 | ~~~~~~~ 304 | 305 | - Mark Hamilton (@markleehamilton) 306 | -------------------------------------------------------------------------------- /tests/unit/modules/network/ovs/test_openvswitch_db.py: -------------------------------------------------------------------------------- 1 | # 2 | # (c) 2016 Red Hat Inc. 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Ansible is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Ansible is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Ansible. If not, see . 18 | 19 | # Make coding more python3-ish 20 | from __future__ import absolute_import, division, print_function 21 | 22 | __metaclass__ = type 23 | 24 | import pytest 25 | 26 | from ansible_collections.openvswitch.openvswitch.plugins.modules import openvswitch_db 27 | from ansible_collections.openvswitch.openvswitch.tests.unit.compat.mock import MagicMock, patch 28 | from ansible_collections.openvswitch.openvswitch.tests.unit.modules.utils import set_module_args 29 | 30 | from .ovs_module import TestOpenVSwitchModule, load_fixture 31 | 32 | 33 | @pytest.fixture 34 | def patched_openvswitch_db(monkeypatch): 35 | mocked_ovs_db = MagicMock() 36 | mocked_ovs_db.return_value = { 37 | "table": "open_vswitch", 38 | "record": ".", 39 | "col": "other_config", 40 | "key": "pmd-cpu-mask", 41 | "value": "0xaaa00000000", 42 | } 43 | monkeypatch.setattr(openvswitch_db, "map_config_to_obj", mocked_ovs_db) 44 | return openvswitch_db 45 | 46 | 47 | test_name_side_effect_matrix = { 48 | "test_openvswitch_db_absent_idempotent": [ 49 | (0, "openvswitch_db_disable_in_band_missing.cfg", None), 50 | (0, None, None), 51 | ], 52 | "test_openvswitch_db_absent_removes_key": [ 53 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 54 | (0, None, None), 55 | ], 56 | "test_openvswitch_db_present_idempotent": [ 57 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 58 | (0, None, None), 59 | ], 60 | "test_openvswitch_db_present_idempotent_value": [(0, None, None)], 61 | "test_openvswitch_db_present_adds_key": [ 62 | (0, "openvswitch_db_disable_in_band_missing.cfg", None), 63 | (0, None, None), 64 | ], 65 | "test_openvswitch_db_present_updates_key": [ 66 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 67 | (0, None, None), 68 | ], 69 | "test_openvswitch_db_present_missing_key_on_map": [ 70 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 71 | (0, None, None), 72 | ], 73 | "test_openvswitch_db_present_stp_enable": [ 74 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 75 | (0, None, None), 76 | ], 77 | "test_openvswitch_db_get_with_key": [ 78 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 79 | (0, None, None), 80 | ], 81 | "test_openvswitch_db_get_without_key": [ 82 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 83 | (0, None, None), 84 | ], 85 | "test_openvswitch_db_get_non_dict": [ 86 | (0, "openvswitch_db_disable_in_band_true.cfg", None), 87 | (0, None, None), 88 | ], 89 | } 90 | 91 | 92 | class TestOpenVSwitchDBModule(TestOpenVSwitchModule): 93 | module = openvswitch_db 94 | 95 | def setUp(self): 96 | super(TestOpenVSwitchDBModule, self).setUp() 97 | 98 | self.mock_run_command = patch("ansible.module_utils.basic.AnsibleModule.run_command") 99 | self.run_command = self.mock_run_command.start() 100 | self.mock_get_bin_path = patch("ansible.module_utils.basic.AnsibleModule.get_bin_path") 101 | self.get_bin_path = self.mock_get_bin_path.start() 102 | 103 | def tearDown(self): 104 | super(TestOpenVSwitchDBModule, self).tearDown() 105 | 106 | self.mock_run_command.stop() 107 | self.mock_get_bin_path.stop() 108 | 109 | def load_fixtures(self, test_name): 110 | test_side_effects = [] 111 | for s in test_name_side_effect_matrix[test_name]: 112 | rc = s[0] 113 | out = load_fixture(s[1]) if s[1] else None 114 | err = s[2] 115 | side_effect_with_fixture_loaded = (rc, out, err) 116 | test_side_effects.append(side_effect_with_fixture_loaded) 117 | self.run_command.side_effect = test_side_effects 118 | 119 | self.get_bin_path.return_value = "/usr/bin/ovs-vsctl" 120 | 121 | def test_openvswitch_db_absent_idempotent(self): 122 | set_module_args( 123 | dict( 124 | state="absent", 125 | table="Bridge", 126 | record="test-br", 127 | col="other_config", 128 | key="disable-in-band", 129 | value="true", 130 | ) 131 | ) 132 | self.execute_module(test_name="test_openvswitch_db_absent_idempotent") 133 | 134 | def test_openvswitch_db_absent_removes_key(self): 135 | set_module_args( 136 | dict( 137 | state="absent", 138 | table="Bridge", 139 | record="test-br", 140 | col="other_config", 141 | key="disable-in-band", 142 | value="true", 143 | ) 144 | ) 145 | self.execute_module( 146 | changed=True, 147 | commands=[ 148 | "/usr/bin/ovs-vsctl -t 5 remove Bridge test-br other_config disable-in-band=true" 149 | ], 150 | test_name="test_openvswitch_db_absent_removes_key", 151 | ) 152 | 153 | def test_openvswitch_db_absent_removes_key_no_value(self): 154 | set_module_args( 155 | dict( 156 | state="absent", 157 | table="Bridge", 158 | record="test-br", 159 | col="other_config", 160 | key="disable-in-band", 161 | ) 162 | ) 163 | self.execute_module( 164 | changed=True, 165 | commands=["/usr/bin/ovs-vsctl -t 5 remove Bridge test-br other_config disable-in-band"], 166 | test_name="test_openvswitch_db_absent_removes_key", 167 | ) 168 | 169 | def test_openvswitch_db_present_idempotent(self): 170 | set_module_args( 171 | dict( 172 | state="present", 173 | table="Bridge", 174 | record="test-br", 175 | col="other_config", 176 | key="disable-in-band", 177 | value="true", 178 | ) 179 | ) 180 | self.execute_module(test_name="test_openvswitch_db_present_idempotent") 181 | 182 | @pytest.mark.usefixtures("patched_openvswitch_db") 183 | def test_openvswitch_db_present_idempotent_value(self): 184 | set_module_args( 185 | { 186 | "col": "other_config", 187 | "key": "pmd-cpu-mask", 188 | "record": ".", 189 | "table": "open_vswitch", 190 | "value": "0xaaa00000000", 191 | } 192 | ) 193 | self.execute_module(test_name="test_openvswitch_db_present_idempotent_value") 194 | 195 | def test_openvswitch_db_present_adds_key(self): 196 | set_module_args( 197 | dict( 198 | state="present", 199 | table="Bridge", 200 | record="test-br", 201 | col="other_config", 202 | key="disable-in-band", 203 | value="true", 204 | ) 205 | ) 206 | self.execute_module( 207 | changed=True, 208 | commands=[ 209 | "/usr/bin/ovs-vsctl -t 5 set Bridge test-br other_config:disable-in-band=true" 210 | ], 211 | test_name="test_openvswitch_db_present_adds_key", 212 | ) 213 | 214 | def test_openvswitch_db_present_updates_key(self): 215 | set_module_args( 216 | dict( 217 | state="present", 218 | table="Bridge", 219 | record="test-br", 220 | col="other_config", 221 | key="disable-in-band", 222 | value="false", 223 | ) 224 | ) 225 | self.execute_module( 226 | changed=True, 227 | commands=[ 228 | "/usr/bin/ovs-vsctl -t 5 set Bridge test-br other_config:disable-in-band=false" 229 | ], 230 | test_name="test_openvswitch_db_present_updates_key", 231 | ) 232 | 233 | def test_openvswitch_uses_database_socket(self): 234 | set_module_args( 235 | dict( 236 | state="present", 237 | table="Bridge", 238 | record="test-br", 239 | col="other_config", 240 | key="disable-in-band", 241 | database_socket="unix:/opt/second.sock", 242 | value="false", 243 | ) 244 | ) 245 | self.execute_module( 246 | changed=True, 247 | commands=[ 248 | "/usr/bin/ovs-vsctl --db=unix:/opt/second.sock -t 5 set Bridge test-br other_config" 249 | ":disable-in-band=false" 250 | ], 251 | test_name="test_openvswitch_db_present_updates_key", 252 | ) 253 | 254 | def test_openvswitch_db_present_missing_key_on_map(self): 255 | set_module_args( 256 | dict( 257 | state="present", 258 | table="Bridge", 259 | record="test-br", 260 | col="other_config", 261 | value="false", 262 | ) 263 | ) 264 | self.execute_module( 265 | failed=True, 266 | test_name="test_openvswitch_db_present_missing_key_on_map", 267 | ) 268 | 269 | def test_openvswitch_db_present_stp_enable(self): 270 | set_module_args( 271 | dict( 272 | state="present", 273 | table="Bridge", 274 | record="test-br", 275 | col="stp_enable", 276 | value="true", 277 | ) 278 | ) 279 | self.execute_module(changed=True, test_name="test_openvswitch_db_present_stp_enable") 280 | 281 | def test_openvswitch_db_get_with_key(self): 282 | set_module_args( 283 | dict( 284 | state="read", 285 | table="Bridge", 286 | record="test-br", 287 | col="other_config", 288 | key="disable-in-band", 289 | value="true", 290 | ) 291 | ) 292 | self.execute_module( 293 | changed=True, 294 | commands=["/usr/bin/ovs-vsctl -t 5 get Bridge test-br other_config:disable-in-band"], 295 | test_name="test_openvswitch_db_get_with_key", 296 | ) 297 | 298 | def test_openvswitch_db_get_without_key(self): 299 | set_module_args( 300 | dict( 301 | state="read", 302 | table="Bridge", 303 | record="test-br", 304 | col="other_config", 305 | value="true", 306 | ) 307 | ) 308 | self.execute_module( 309 | changed=True, 310 | commands=["/usr/bin/ovs-vsctl -t 5 get Bridge test-br other_config"], 311 | test_name="test_openvswitch_db_get_without_key", 312 | ) 313 | 314 | def test_openvswitch_db_get_non_dict(self): 315 | set_module_args( 316 | dict( 317 | state="read", 318 | table="Bridge", 319 | record="test-br", 320 | col="name", 321 | value="true", 322 | ) 323 | ) 324 | self.execute_module( 325 | changed=True, 326 | commands=["/usr/bin/ovs-vsctl -t 5 get Bridge test-br name"], 327 | test_name="test_openvswitch_db_get_non_dict", 328 | ) 329 | -------------------------------------------------------------------------------- /docs/openvswitch.openvswitch.openvswitch_bond_module.rst: -------------------------------------------------------------------------------- 1 | .. _openvswitch.openvswitch.openvswitch_bond_module: 2 | 3 | 4 | **************************************** 5 | openvswitch.openvswitch.openvswitch_bond 6 | **************************************** 7 | 8 | **Manage Open vSwitch bonds** 9 | 10 | 11 | Version added: 1.0.0 12 | 13 | .. contents:: 14 | :local: 15 | :depth: 1 16 | 17 | 18 | Synopsis 19 | -------- 20 | - Manage Open vSwitch bonds and associated options. 21 | 22 | 23 | 24 | Requirements 25 | ------------ 26 | The below requirements are needed on the host that executes this module. 27 | 28 | - ovs-vsctl 29 | 30 | 31 | Parameters 32 | ---------- 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 51 | 53 | 56 | 57 | 58 | 66 | 73 | 76 | 77 | 78 | 86 | 88 | 91 | 92 | 93 | 102 | 104 | 107 | 108 | 109 | 117 | 119 | 124 | 125 | 126 | 134 | 137 | 140 | 141 | 142 | 151 | 153 | 156 | 157 | 158 | 166 | 173 | 176 | 177 | 178 | 186 | 189 | 192 | 193 | 194 | 203 | 205 | 208 | 209 | 210 | 219 | 221 | 224 | 225 | 226 | 234 | 240 | 243 | 244 | 245 | 253 | 256 | 259 | 260 |
ParameterChoices/DefaultsComments
44 |
45 | bond_downdelay 46 | 47 |
48 | integer 49 |
50 |
52 | 54 |
Number of milliseconds a link must be down to be deactivated to prevent flapping.
55 |
59 |
60 | bond_mode 61 | 62 |
63 | string 64 |
65 |
67 |
    Choices: 68 |
  • active-backup
  • 69 |
  • balance-tcp
  • 70 |
  • balance-slb
  • 71 |
72 |
74 |
Sets the bond mode
75 |
79 |
80 | bond_updelay 81 | 82 |
83 | integer 84 |
85 |
87 | 89 |
Number of milliseconds a link must be up to be activated to prevent flapping.
90 |
94 |
95 | bridge 96 | 97 |
98 | string 99 | / required 100 |
101 |
103 | 105 |
Name of bridge to manage
106 |
110 |
111 | database_socket 112 | 113 |
114 | string 115 |
116 |
118 | 120 |
Path/ip to datbase socket to use
121 |
Default path is used if not specified
122 |
Path should start with 'unix:' prefix
123 |
127 |
128 | external_ids 129 | 130 |
131 | dictionary 132 |
133 |
135 | Default:
{}
136 |
138 |
Dictionary of external_ids applied to a port.
139 |
143 |
144 | interfaces 145 | 146 |
147 | list 148 | / elements=string 149 |
150 |
152 | 154 |
List of interfaces to add to the bond
155 |
159 |
160 | lacp 161 | 162 |
163 | string 164 |
165 |
167 |
    Choices: 168 |
  • active
  • 169 |
  • passive
  • 170 |
  • off
  • 171 |
172 |
174 |
Sets LACP mode
175 |
179 |
180 | other_config 181 | 182 |
183 | dictionary 184 |
185 |
187 | Default:
{}
188 |
190 |
Dictionary of other_config applied to a port.
191 |
195 |
196 | port 197 | 198 |
199 | string 200 | / required 201 |
202 |
204 | 206 |
Name of port to manage on the bridge
207 |
211 |
212 | set 213 | 214 |
215 | list 216 | / elements=string 217 |
218 |
220 | 222 |
Sets one or more properties on a port.
223 |
227 |
228 | state 229 | 230 |
231 | string 232 |
233 |
235 |
    Choices: 236 |
  • present ←
  • 237 |
  • absent
  • 238 |
239 |
241 |
Whether the port should exist
242 |
246 |
247 | timeout 248 | 249 |
250 | integer 251 |
252 |
254 | Default:
5
255 |
257 |
How long to wait for ovs-vswitchd to respond in seconds
258 |
261 |
262 | 263 | 264 | 265 | 266 | Examples 267 | -------- 268 | 269 | .. code-block:: yaml 270 | 271 | - name: Create an active-backup bond using eth4 and eth5 on bridge br-ex 272 | openvswitch.openvswitch.openvswitch_bond: 273 | bridge: br-ex 274 | port: bond1 275 | interfaces: 276 | - eth4 277 | - eth5 278 | state: present 279 | - name: Delete the bond from bridge br-ex 280 | openvswitch.openvswitch.openvswitch_bond: 281 | bridge: br-ex 282 | port: bond1 283 | state: absent 284 | - name: Create an active LACP bond using eth4 and eth5 on bridge br-ex 285 | openvswitch.openvswitch.openvswitch_bond: 286 | bridge: br-ex 287 | port: bond1 288 | interfaces: 289 | - eth4 290 | - eth5 291 | lacp: active 292 | state: present 293 | # NOTE: other_config values of integer type must be represented 294 | # as literal strings 295 | - name: Configure bond with miimon link monitoring at 100 millisecond intervals 296 | openvswitch.openvswitch.openvswitch_bond: 297 | bridge: br-ex 298 | port: bond1 299 | interfaces: 300 | - eth4 301 | - eth5 302 | bond_updelay: 100 303 | bond_downdelay: 100 304 | state: present 305 | args: 306 | other_config: 307 | bond-detect-mode: miimon 308 | bond-miimon-interval: '"100"' 309 | - name: Create an active LACP bond using DPDK interfaces 310 | openvswitch.openvswitch.openvswitch_bond: 311 | bridge: br-provider 312 | port: dpdkbond 313 | interfaces: 314 | - "0000:04:00.0" 315 | - "0000:04:00.1" 316 | lacp: active 317 | set: 318 | - "interface 0000:04:00.0 type=dpdk options:dpdk-devargs=0000:04:00.0" 319 | - "interface 0000:04:00.1 type=dpdk options:dpdk-devargs=0000:04:00.1" 320 | state: present 321 | - name: Create an active-backup bond using eth4 and eth5 on bridge br-ex in second OVS database 322 | openvswitch.openvswitch.openvswitch_bond: 323 | bridge: br-ex 324 | port: bond1 325 | interfaces: 326 | - eth4 327 | - eth5 328 | state: present 329 | database_socket: unix:/opt/second.sock 330 | 331 | 332 | 333 | 334 | Status 335 | ------ 336 | 337 | 338 | Authors 339 | ~~~~~~~ 340 | 341 | - James Denton (@busterswt) 342 | -------------------------------------------------------------------------------- /plugins/modules/openvswitch_bond.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding: utf-8 -*- 3 | 4 | # (c) 2020, James Denton 5 | # Portions copyright @ 2013 David Stygstra 6 | # Portions copyright @ 2015 VMware, Inc. 7 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 8 | 9 | from __future__ import absolute_import, division, print_function 10 | 11 | __metaclass__ = type 12 | 13 | DOCUMENTATION = """ 14 | --- 15 | module: openvswitch_bond 16 | author: "James Denton (@busterswt)" 17 | short_description: Manage Open vSwitch bonds 18 | requirements: 19 | - ovs-vsctl 20 | description: 21 | - Manage Open vSwitch bonds and associated options. 22 | version_added: '1.0.0' 23 | options: 24 | bridge: 25 | required: true 26 | description: 27 | - Name of bridge to manage 28 | type: str 29 | port: 30 | required: true 31 | description: 32 | - Name of port to manage on the bridge 33 | type: str 34 | interfaces: 35 | description: 36 | - List of interfaces to add to the bond 37 | type: list 38 | elements: str 39 | bond_mode: 40 | choices: [ active-backup, balance-tcp, balance-slb ] 41 | description: 42 | - Sets the bond mode 43 | type: str 44 | lacp: 45 | choices: [ 'active', 'passive', 'off' ] 46 | description: 47 | - Sets LACP mode 48 | type: str 49 | bond_updelay: 50 | description: 51 | - Number of milliseconds a link must be up to be activated 52 | to prevent flapping. 53 | type: int 54 | bond_downdelay: 55 | description: 56 | - Number of milliseconds a link must be down to be deactivated 57 | to prevent flapping. 58 | type: int 59 | state: 60 | default: 'present' 61 | choices: [ 'present', 'absent' ] 62 | description: 63 | - Whether the port should exist 64 | type: str 65 | timeout: 66 | default: 5 67 | description: 68 | - How long to wait for ovs-vswitchd to respond in seconds 69 | type: int 70 | external_ids: 71 | default: {} 72 | description: 73 | - Dictionary of external_ids applied to a port. 74 | type: dict 75 | other_config: 76 | default: {} 77 | description: 78 | - Dictionary of other_config applied to a port. 79 | type: dict 80 | set: 81 | description: 82 | - Sets one or more properties on a port. 83 | type: list 84 | elements: str 85 | database_socket: 86 | description: 87 | - Path/ip to datbase socket to use 88 | - Default path is used if not specified 89 | - Path should start with 'unix:' prefix 90 | type: str 91 | """ 92 | 93 | EXAMPLES = """ 94 | - name: Create an active-backup bond using eth4 and eth5 on bridge br-ex 95 | openvswitch.openvswitch.openvswitch_bond: 96 | bridge: br-ex 97 | port: bond1 98 | interfaces: 99 | - eth4 100 | - eth5 101 | state: present 102 | - name: Delete the bond from bridge br-ex 103 | openvswitch.openvswitch.openvswitch_bond: 104 | bridge: br-ex 105 | port: bond1 106 | state: absent 107 | - name: Create an active LACP bond using eth4 and eth5 on bridge br-ex 108 | openvswitch.openvswitch.openvswitch_bond: 109 | bridge: br-ex 110 | port: bond1 111 | interfaces: 112 | - eth4 113 | - eth5 114 | lacp: active 115 | state: present 116 | # NOTE: other_config values of integer type must be represented 117 | # as literal strings 118 | - name: Configure bond with miimon link monitoring at 100 millisecond intervals 119 | openvswitch.openvswitch.openvswitch_bond: 120 | bridge: br-ex 121 | port: bond1 122 | interfaces: 123 | - eth4 124 | - eth5 125 | bond_updelay: 100 126 | bond_downdelay: 100 127 | state: present 128 | args: 129 | other_config: 130 | bond-detect-mode: miimon 131 | bond-miimon-interval: '"100"' 132 | - name: Create an active LACP bond using DPDK interfaces 133 | openvswitch.openvswitch.openvswitch_bond: 134 | bridge: br-provider 135 | port: dpdkbond 136 | interfaces: 137 | - "0000:04:00.0" 138 | - "0000:04:00.1" 139 | lacp: active 140 | set: 141 | - "interface 0000:04:00.0 type=dpdk options:dpdk-devargs=0000:04:00.0" 142 | - "interface 0000:04:00.1 type=dpdk options:dpdk-devargs=0000:04:00.1" 143 | state: present 144 | - name: Create an active-backup bond using eth4 and eth5 on bridge br-ex in second OVS database 145 | openvswitch.openvswitch.openvswitch_bond: 146 | bridge: br-ex 147 | port: bond1 148 | interfaces: 149 | - eth4 150 | - eth5 151 | state: present 152 | database_socket: unix:/opt/second.sock 153 | """ 154 | 155 | from ansible.module_utils.basic import AnsibleModule 156 | from ansible.module_utils.six import iteritems 157 | 158 | 159 | def _external_ids_to_dict(text): 160 | text = text.strip() 161 | 162 | if text == "{}": 163 | return None 164 | 165 | else: 166 | d = {} 167 | 168 | for kv in text[1:-1].split(","): 169 | kv = kv.strip() 170 | k, v = kv.split("=") 171 | d[k] = v 172 | 173 | return d 174 | 175 | 176 | def _other_config_to_dict(text): 177 | text = text.strip() 178 | 179 | if text == "{}": 180 | return None 181 | else: 182 | d = {} 183 | 184 | for kv in text[1:-1].split(","): 185 | kv = kv.strip() 186 | k, v = kv.split("=") 187 | d[k] = v 188 | 189 | return d 190 | 191 | 192 | def map_obj_to_commands(want, have, module): 193 | commands = list() 194 | 195 | if module.params["state"] == "absent": 196 | if have: 197 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s del-port %(bridge)s %(port)s" 198 | command = templatized_command % module.params 199 | commands.append(command) 200 | else: 201 | if have: 202 | if want["other_config"] != have["other_config"]: 203 | for k, v in iteritems(want["other_config"]): 204 | if ( 205 | not have["other_config"] 206 | or k not in have["other_config"] 207 | or want["other_config"][k] != have["other_config"][k] 208 | ): 209 | if v is None: 210 | templatized_command = ( 211 | "%(ovs-vsctl)s -t %(timeout)s remove port %(port)s other_config " 212 | + k 213 | ) 214 | command = templatized_command % module.params 215 | commands.append(command) 216 | else: 217 | templatized_command = ( 218 | "%(ovs-vsctl)s -t %(timeout)s set port %(port)s other_config:" 219 | ) 220 | command = templatized_command % module.params 221 | command += k + "=" + v 222 | commands.append(command) 223 | 224 | if want["external_ids"] != have["external_ids"]: 225 | for k, v in iteritems(want["external_ids"]): 226 | if ( 227 | not have["external_ids"] 228 | or k not in have["external_ids"] 229 | or want["external_ids"][k] != have["external_ids"][k] 230 | ): 231 | if v is None: 232 | templatized_command = ( 233 | "%(ovs-vsctl)s -t %(timeout)s remove port %(port)s external_ids " 234 | + k 235 | ) 236 | command = templatized_command % module.params 237 | commands.append(command) 238 | else: 239 | templatized_command = ( 240 | "%(ovs-vsctl)s -t %(timeout)s set port %(port)s external_ids:" 241 | ) 242 | command = templatized_command % module.params 243 | command += k + "=" + v 244 | commands.append(command) 245 | 246 | else: 247 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s add-bond %(bridge)s %(port)s" 248 | command = templatized_command % module.params 249 | 250 | if want["interfaces"]: 251 | for interface in want["interfaces"]: 252 | command += " " + interface 253 | 254 | if want["bond_mode"]: 255 | templatized_command = " bond_mode=%(bond_mode)s" 256 | command += templatized_command % module.params 257 | 258 | if want["lacp"]: 259 | templatized_command = " lacp=%(lacp)s" 260 | command += templatized_command % module.params 261 | 262 | if want["bond_updelay"]: 263 | templatized_command = " bond_updelay=%(bond_updelay)s" 264 | command += templatized_command % module.params 265 | 266 | if want["bond_downdelay"]: 267 | templatized_command = " bond_downdelay=%(bond_downdelay)s" 268 | command += templatized_command % module.params 269 | 270 | if want["set"]: 271 | for set in want["set"]: 272 | command += " -- set " + set 273 | 274 | commands.append(command) 275 | 276 | if want["other_config"]: 277 | for k, v in iteritems(want["other_config"]): 278 | templatized_command = ( 279 | "%(ovs-vsctl)s -t %(timeout)s set port %(port)s other_config:" 280 | ) 281 | command = templatized_command % module.params 282 | command += k + "=" + v 283 | commands.append(command) 284 | 285 | if want["external_ids"]: 286 | for k, v in iteritems(want["external_ids"]): 287 | templatized_command = ( 288 | "%(ovs-vsctl)s -t %(timeout)s set port %(port)s external_ids:" 289 | ) 290 | command = templatized_command % module.params 291 | command += k + "=" + v 292 | commands.append(command) 293 | 294 | return commands 295 | 296 | 297 | def map_config_to_obj(module): 298 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s list-ports %(bridge)s" 299 | command = templatized_command % module.params 300 | rc, out, err = module.run_command(command, check_rc=True) 301 | if rc != 0: 302 | module.fail_json(msg=err) 303 | 304 | obj = {} 305 | 306 | if module.params["port"] in out.splitlines(): 307 | obj["bridge"] = module.params["bridge"] 308 | obj["port"] = module.params["port"] 309 | 310 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s get Port %(port)s other_config" 311 | command = templatized_command % module.params 312 | rc, out, err = module.run_command(command, check_rc=True) 313 | obj["other_config"] = _other_config_to_dict(out) 314 | 315 | templatized_command = "%(ovs-vsctl)s -t %(timeout)s get Port %(port)s external_ids" 316 | command = templatized_command % module.params 317 | rc, out, err = module.run_command(command, check_rc=True) 318 | obj["external_ids"] = _external_ids_to_dict(out) 319 | 320 | return obj 321 | 322 | 323 | def map_params_to_obj(module): 324 | obj = { 325 | "bridge": module.params["bridge"], 326 | "port": module.params["port"], 327 | "interfaces": module.params["interfaces"], 328 | "bond_mode": module.params["bond_mode"], 329 | "lacp": module.params["lacp"], 330 | "bond_updelay": module.params["bond_updelay"], 331 | "bond_downdelay": module.params["bond_downdelay"], 332 | "external_ids": module.params["external_ids"], 333 | "other_config": module.params["other_config"], 334 | "set": module.params["set"], 335 | } 336 | 337 | return obj 338 | 339 | 340 | def main(): 341 | """Entry point.""" 342 | argument_spec = { 343 | "bridge": {"required": True}, 344 | "port": {"required": True}, 345 | "interfaces": {"type": "list", "elements": "str"}, 346 | "bond_mode": { 347 | "default": None, 348 | "choices": ["active-backup", "balance-tcp", "balance-slb"], 349 | }, 350 | "lacp": {"default": None, "choices": ["active", "passive", "off"]}, 351 | "bond_updelay": {"default": None, "type": "int"}, 352 | "bond_downdelay": {"default": None, "type": "int"}, 353 | "state": {"default": "present", "choices": ["present", "absent"]}, 354 | "timeout": {"default": 5, "type": "int"}, 355 | "external_ids": {"default": {}, "type": "dict"}, 356 | "other_config": {"default": {}, "type": "dict"}, 357 | "set": { 358 | "required": False, 359 | "type": "list", 360 | "default": None, 361 | "elements": "str", 362 | }, 363 | "database_socket": {"default": None}, 364 | } 365 | 366 | module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) 367 | 368 | result = {"changed": False} 369 | 370 | # We add ovs-vsctl to module_params to later build up templatized commands 371 | module.params["ovs-vsctl"] = module.get_bin_path("ovs-vsctl", True) 372 | if module.params.get("database_socket"): 373 | module.params["ovs-vsctl"] += " --db=" + module.params.get("database_socket") 374 | 375 | want = map_params_to_obj(module) 376 | have = map_config_to_obj(module) 377 | 378 | commands = map_obj_to_commands(want, have, module) 379 | result["commands"] = commands 380 | 381 | if commands: 382 | if not module.check_mode: 383 | for c in commands: 384 | module.run_command(c, check_rc=True) 385 | result["changed"] = True 386 | 387 | module.exit_json(**result) 388 | 389 | 390 | if __name__ == "__main__": 391 | main() 392 | --------------------------------------------------------------------------------