Vendor openvswitch_bridge

The upstream openvswitch collection has been deprecated and was not
included in newer Ansible (like Ansible 11). Vendor the
openvswitch_bridge module from that collection as we use it for
multinode bridge setups. The code was fetched from:

  https://raw.githubusercontent.com/ansible-collections/openvswitch.openvswitch/d375078cfd942599c42fe28e46f59f063c9d3a9d/plugins/modules/openvswitch_bridge.py

And is currently unmodified except for removal of the shebang (to make
linter rules happy), flake8: noqa at the file level (to disable python
linter checks), and a new comment block explaining why the code
was vendored and where it originated from.

A warning is added to the role README indicating that this role is now
effectively deprecated due to its dependencies. An alternative approach
that should be easier to maintain long term is suggested as well.

Change-Id: I2c90d3145b50498b4759046d43b02f70c10715e7
This commit is contained in:
Clark Boylan
2025-08-12 15:32:42 -07:00
parent d0c1ccc856
commit 5c9a384abc
3 changed files with 311 additions and 0 deletions

View File

@@ -1,3 +1,18 @@
.. warning::
This role currently depends on openvswitch and the now deprecated by
Ansible openvswitch_bridge module. This transitively means this role is
effectively deprecated as well. In order to get around the deprecation
and removal of this Ansible module we have vendored it in this role. This
may not work with future versions of Ansible
Ideally we would rewrite the role to use Linux bridges instead of
openvswitch as this set of tooling is more readily available and common
on Linux machines. We could continue to use VXLAN with Linux bridge or
consider switching to GENEVE or maybe even Wireguard as alternative
overlay methods during that switch.
Help is very much appreciated to make this rewrite happen.
Configures a VXLAN virtual network overlay through an openvswitch network
bridge between a 'switch' node and 'peer' nodes.

View File

@@ -0,0 +1,296 @@
# flake8: noqa
# (c) 2013, David Stygstra <david.stygstra@gmail.com>
# Portions copyright @ 2015 VMware, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# This code originates from the openvswitch ansible collection. This collection
# was deprecated and not included in Ansible 11. We vendor it here so that
# we can continue to use it against newer Ansible. This code was retrieved
# from:
# https://raw.githubusercontent.com/ansible-collections/openvswitch.openvswitch/d375078cfd942599c42fe28e46f59f063c9d3a9d/plugins/modules/openvswitch_bridge.py
# The only modifications made to the original code are the removal of the
# shebang at the top of the file (we check that with an explicit command),
# the addition of flake8: noqa at the file level to disable all python linter
# checks, and the inclusion of this explanatory comment block.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: openvswitch_bridge
author: David Stygstra (@stygstra)
short_description: Manage Open vSwitch bridges
requirements:
- ovs-vsctl
description:
- Manage Open vSwitch bridges
version_added: 1.0.0
options:
bridge:
required: true
description:
- Name of bridge or fake bridge to manage
type: str
parent:
description:
- Bridge parent of the fake bridge to manage
type: str
vlan:
description:
- The VLAN id of the fake bridge to manage (must be between 0 and 4095). This
parameter is required if I(parent) parameter is set.
type: int
state:
default: present
choices:
- present
- absent
description:
- Whether the bridge should exist
type: str
timeout:
default: 5
description:
- How long to wait for ovs-vswitchd to respond
type: int
external_ids:
description:
- A dictionary of external-ids. Omitting this parameter is a No-op. To clear
all external-ids pass an empty value.
type: dict
fail_mode:
description:
- Set bridge fail-mode. The default value (None) is a No-op.
type: str
set:
description:
- Run set command after bridge configuration. This parameter is non-idempotent,
play will always return I(changed) state if present
type: str
database_socket:
description:
- Path/ip to datbase socket to use
- Default path is used if not specified
- Path should start with 'unix:' prefix
type: str
"""
EXAMPLES = """
# Create a bridge named br-int
- openvswitch.openvswitch.openvswitch_bridge:
bridge: br-int
state: present
# Create a fake bridge named br-int within br-parent on the VLAN 405
- openvswitch.openvswitch.openvswitch_bridge:
bridge: br-int
parent: br-parent
vlan: 405
state: present
# Create an integration bridge
- openvswitch.openvswitch.openvswitch_bridge:
bridge: br-int
state: present
fail_mode: secure
args:
external_ids:
bridge-id: br-int
# Create a bridge named br0 in database with socket at /opt/second.sock
- openvswitch.openvswitch.openvswitch_bridge:
bridge: br0
state: present
database_socket: unix:/opt/second.sock
"""
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
def _fail_mode_to_str(text):
if not text:
return None
else:
return text.strip()
def _external_ids_to_dict(text):
if not text:
return None
else:
d = {}
for l in text.splitlines():
if l:
k, v = l.split("=")
d[k] = v
return d
def map_obj_to_commands(want, have, module):
commands = list()
if module.params["state"] == "absent":
if have:
templatized_command = "%(ovs-vsctl)s -t %(timeout)s del-br %(bridge)s"
command = templatized_command % module.params
commands.append(command)
else:
if have:
if want["fail_mode"] != have["fail_mode"]:
templatized_command = (
"%(ovs-vsctl)s -t %(timeout)s set-fail-mode %(bridge)s %(fail_mode)s"
)
command = templatized_command % module.params
commands.append(command)
if want["external_ids"] != have["external_ids"]:
templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-set-external-id %(bridge)s"
command = templatized_command % module.params
if want["external_ids"]:
for k, v in iteritems(want["external_ids"]):
if (
k not in have["external_ids"]
or want["external_ids"][k] != have["external_ids"][k]
):
command += " " + k + " " + v
commands.append(command)
if want["vlan"] and to_text(want["vlan"]) != have["vlan"]:
templatized_command = (
"%(ovs-vsctl)s -t %(timeout)s set port %(bridge)s tag=%(vlan)s"
)
command = templatized_command % module.params
commands.append(command)
else:
templatized_command = "%(ovs-vsctl)s -t %(timeout)s add-br %(bridge)s"
command = templatized_command % module.params
if want["parent"]:
templatized_command = "%(parent)s %(vlan)s"
command += " " + templatized_command % module.params
if want["set"]:
templatized_command = " -- set %(set)s"
command += templatized_command % module.params
commands.append(command)
if want["fail_mode"]:
templatized_command = (
"%(ovs-vsctl)s -t %(timeout)s set-fail-mode %(bridge)s %(fail_mode)s"
)
command = templatized_command % module.params
commands.append(command)
if want["external_ids"]:
for k, v in iteritems(want["external_ids"]):
templatized_command = (
"%(ovs-vsctl)s -t %(timeout)s br-set-external-id %(bridge)s"
)
command = templatized_command % module.params
command += " " + k + " " + v
commands.append(command)
return commands
def map_config_to_obj(module):
templatized_command = "%(ovs-vsctl)s -t %(timeout)s list-br"
command = templatized_command % module.params
rc, out, err = module.run_command(command, check_rc=True)
if rc != 0:
module.fail_json(msg=err)
obj = {}
if module.params["bridge"] in out.splitlines():
obj["bridge"] = module.params["bridge"]
templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-to-parent %(bridge)s"
command = templatized_command % module.params
rc, out, err = module.run_command(command, check_rc=True)
obj["parent"] = out.strip()
templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-to-vlan %(bridge)s"
command = templatized_command % module.params
rc, out, err = module.run_command(command, check_rc=True)
obj["vlan"] = out.strip()
templatized_command = "%(ovs-vsctl)s -t %(timeout)s get-fail-mode %(bridge)s"
command = templatized_command % module.params
rc, out, err = module.run_command(command, check_rc=True)
obj["fail_mode"] = _fail_mode_to_str(out)
templatized_command = "%(ovs-vsctl)s -t %(timeout)s br-get-external-id %(bridge)s"
command = templatized_command % module.params
rc, out, err = module.run_command(command, check_rc=True)
obj["external_ids"] = _external_ids_to_dict(out)
return obj
def map_params_to_obj(module):
obj = {
"bridge": module.params["bridge"],
"parent": module.params["parent"],
"vlan": module.params["vlan"],
"fail_mode": module.params["fail_mode"],
"external_ids": module.params["external_ids"],
"set": module.params["set"],
}
return obj
def main():
"""Entry point."""
argument_spec = {
"bridge": {"required": True},
"parent": {"default": None},
"vlan": {"default": None, "type": "int"},
"state": {"default": "present", "choices": ["present", "absent"]},
"timeout": {"default": 5, "type": "int"},
"external_ids": {"default": None, "type": "dict"},
"fail_mode": {"default": None},
"set": {"required": False, "default": None},
"database_socket": {"default": None},
}
required_if = [("parent", not None, ("vlan",))]
module = AnsibleModule(
argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True,
)
result = {"changed": False}
# We add ovs-vsctl to module_params to later build up templatized commands
module.params["ovs-vsctl"] = module.get_bin_path("ovs-vsctl", True)
if module.params.get("database_socket"):
module.params["ovs-vsctl"] += " --db=" + module.params.get("database_socket")
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands(want, have, module)
result["commands"] = commands
if commands:
if not module.check_mode:
for c in commands:
module.run_command(c, check_rc=True)
result["changed"] = True
module.exit_json(**result)
if __name__ == "__main__":
main()