Skip to content

Commit

Permalink
Extend OpenVSwitch modules (closes saltstack#58986).
Browse files Browse the repository at this point in the history
This adds the new openvswitch_db state module. It also  adds the new
functions bridge_to_parent, bridge_to_vlan, db_get, and db_set to the
openvswitch execution module.

Besides, it adds two new optional parameters parent and vlan to the
openvswitch_bridge.present state module function and the
openvswitch.bridge_create execution module function.
  • Loading branch information
smarsching committed Dec 9, 2022
1 parent 2485503 commit 02a1ee4
Show file tree
Hide file tree
Showing 9 changed files with 670 additions and 11 deletions.
5 changes: 5 additions & 0 deletions changelog/58986.added
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added `openvswitch_db` state module and functions `bridge_to_parent`,
`bridge_to_vlan`, `db_get`, and `db_set` to the `openvswitch` execution module.
Also added optional `parent` and `vlan` parameters to the
`openvswitch_bridge.present` state module function and the
`openvswitch.bridge_create` execution module function.
1 change: 1 addition & 0 deletions doc/ref/states/all/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ state modules
nxos_upgrade
openstack_config
openvswitch_bridge
openvswitch_db
openvswitch_port
opsgenie
pagerduty
Expand Down
5 changes: 5 additions & 0 deletions doc/ref/states/all/salt.states.openvswitch_db.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
salt.states.openvswitch_db module
=================================

.. automodule:: salt.states.openvswitch_db
:members:
207 changes: 203 additions & 4 deletions salt/modules/openvswitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import logging

import salt.utils.path
from salt.exceptions import ArgumentValueError, CommandExecutionError
from salt.utils import json

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -89,6 +91,53 @@ def _stdout_list_split(retcode, stdout="", splitstring="\n"):
return False


def _convert_json(obj):
"""
Converts from the JSON output provided by ovs-vsctl into a usable Python
object tree. In particular, sets and maps are converted from lists to
actual sets or maps.
Args:
obj: Object that shall be recursively converted.
Returns:
Converted version of object.
"""
if isinstance(obj, dict):
return {_convert_json(key): _convert_json(val) for (key, val) in obj.items()}
elif isinstance(obj, list) and len(obj) == 2:
first = obj[0]
second = obj[1]
if first == "set" and isinstance(second, list):
return [_convert_json(elem) for elem in second]
elif first == "map" and isinstance(second, list):
for elem in second:
if not isinstance(elem, list) or len(elem) != 2:
return obj
return {elem[0]: _convert_json(elem[1]) for elem in second}
else:
return obj
elif isinstance(obj, list):
return [_convert_json(elem) for elem in obj]
else:
return obj


def _stdout_parse_json(stdout):
"""
Parses JSON output from ovs-vsctl and returns the corresponding object
tree.
Args:
stdout: Output that shall be parsed.
Returns:
Object represented by the output.
"""
obj = json.loads(stdout)
return _convert_json(obj)


def bridge_list():
"""
Lists all existing real and fake bridges.
Expand Down Expand Up @@ -132,13 +181,23 @@ def bridge_exists(br):
return _retcode_to_bool(retcode)


def bridge_create(br, may_exist=True):
def bridge_create(br, may_exist=True, parent=None, vlan=None):
"""
Creates a new bridge.
Args:
br: A string - bridge name
may_exist: Bool, if False - attempting to create a bridge that exists returns False.
br : string
bridge name
may_exist : bool
if False - attempting to create a bridge that exists returns False.
parent : string
name of the parent bridge (if the bridge shall be created as a fake
bridge). If specified, vlan must also be specified.
.. versionadded:: 3006
vlan : int
VLAN ID of the bridge (if the bridge shall be created as a fake
bridge). If specified, parent must also be specified.
.. versionadded:: 3006
Returns:
True on success, else False.
Expand All @@ -152,7 +211,15 @@ def bridge_create(br, may_exist=True):
salt '*' openvswitch.bridge_create br0
"""
param_may_exist = _param_may_exist(may_exist)
cmd = "ovs-vsctl {1}add-br {0}".format(br, param_may_exist)
if parent is not None and vlan is None:
raise ArgumentValueError("If parent is specified, vlan must also be specified.")
if vlan is not None and parent is None:
raise ArgumentValueError("If vlan is specified, parent must also be specified.")
param_parent = "" if parent is None else " {}".format(parent)
param_vlan = "" if vlan is None else " {}".format(vlan)
cmd = "ovs-vsctl {1}add-br {0}{2}{3}".format(
br, param_may_exist, param_parent, param_vlan
)
result = __salt__["cmd.run_all"](cmd)
return _retcode_to_bool(result["retcode"])

Expand Down Expand Up @@ -183,6 +250,61 @@ def bridge_delete(br, if_exists=True):
return _retcode_to_bool(retcode)


def bridge_to_parent(br):
"""
.. versionadded:: 3006
Returns the parent bridge of a bridge.
Args:
br : string
bridge name
Returns:
Name of the parent bridge. This is the same as the bridge name if the
bridge is not a fake bridge. If the bridge does not exist, False is
returned.
CLI Example:
.. code-block:: bash
salt '*' openvswitch.bridge_to_parent br0
"""
cmd = "ovs-vsctl br-to-parent {}".format(br)
result = __salt__["cmd.run_all"](cmd)
if result["retcode"] != 0:
return False
return result["stdout"].strip()


def bridge_to_vlan(br):
"""
.. versionadded:: 3006
Returns the VLAN ID of a bridge.
Args:
br : string
bridge name
Returns:
VLAN ID of the bridge. The VLAN ID is 0 if the bridge is not a fake
bridge. If the bridge does not exist, False is returned.
CLI Example:
.. code-block:: bash
salt '*' openvswitch.bridge_to_parent br0
"""
cmd = "ovs-vsctl br-to-vlan {}".format(br)
result = __salt__["cmd.run_all"](cmd)
if result["retcode"] != 0:
return False
return int(result["stdout"])


def port_add(br, port, may_exist=False, internal=False):
"""
Creates on bridge a new port named port.
Expand Down Expand Up @@ -473,3 +595,80 @@ def port_create_vxlan(br, port, id, remote, dst_port=None):
)
result = __salt__["cmd.run_all"](cmd)
return _retcode_to_bool(result["retcode"])


def db_get(table, record, column, if_exists=False):
"""
.. versionadded:: 3006
Gets a column's value for a specific record.
Args:
table : string
name of the database table
record : string
identifier of the record
column : string
name of the column
if_exists : boolean
if True, it is not an error if the record does not exist.
Returns:
The column's value.
CLI Example:
.. code-block:: bash
salt '*' openvswitch.db_get Port br0 vlan_mode
"""
cmd = ["ovs-vsctl", "--format=json", "--columns={}".format(column)]
if if_exists:
cmd += ["--if-exists"]
cmd += ["list", table, record]
result = __salt__["cmd.run_all"](cmd)
if result["retcode"] != 0:
raise CommandExecutionError(result["stderr"])
output = _stdout_parse_json(result["stdout"])
if output["data"] and output["data"][0]:
return output["data"][0][0]
else:
return None


def db_set(table, record, column, value, if_exists=False):
"""
.. versionadded:: 3006
Sets a column's value for a specific record.
Args:
table : string
name of the database table
record : string
identifier of the record
column : string
name of the column
value : string
the value to be set
if_exists : boolean
if True, it is not an error if the record does not exist.
Returns:
None on success and an error message on failure.
CLI Example:
.. code-block:: bash
salt '*' openvswitch.db_set Interface br0 mac 02:03:04:05:06:07
"""
cmd = ["ovs-vsctl"]
if if_exists:
cmd += ["--if-exists"]
cmd += ["set", table, record, "{}={}".format(column, json.dumps(value))]
result = __salt__["cmd.run_all"](cmd)
if result["retcode"] != 0:
return result["stderr"]
else:
return None
43 changes: 36 additions & 7 deletions salt/states/openvswitch_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ def __virtual__():
return (False, "openvswitch module could not be loaded")


def present(name):
def present(name, parent=None, vlan=None):
"""
Ensures that the named bridge exists, eventually creates it.
Args:
name: The name of the bridge.
name : string
name of the bridge
parent : string
name of the parent bridge (if the bridge shall be created as a fake
bridge). If specified, vlan must also be specified.
.. versionadded:: 3006
vlan: int
VLAN ID of the bridge (if the bridge shall be created as a fake
bridge). If specified, parent must also be specified.
.. versionadded:: 3006
"""
ret = {"name": name, "changes": {}, "result": False, "comment": ""}
Expand All @@ -26,6 +35,9 @@ def present(name):
comment_bridge_created = "Bridge {} created.".format(name)
comment_bridge_notcreated = "Unable to create bridge: {}.".format(name)
comment_bridge_exists = "Bridge {} already exists.".format(name)
comment_bridge_mismatch = (
"Bridge {} already exists, but has a different" " parent or VLAN ID."
).format(name)
changes_bridge_created = {
name: {
"old": "Bridge {} does not exist.".format(name),
Expand All @@ -34,23 +46,40 @@ def present(name):
}

bridge_exists = __salt__["openvswitch.bridge_exists"](name)
if bridge_exists:
current_parent = __salt__["openvswitch.bridge_to_parent"](name)
if current_parent == name:
current_parent = None
current_vlan = __salt__["openvswitch.bridge_to_vlan"](name)
if current_vlan == 0:
current_vlan = None

# Dry run, test=true mode
if __opts__["test"]:
if bridge_exists:
ret["result"] = True
ret["comment"] = comment_bridge_exists
if current_parent == parent and current_vlan == vlan:
ret["result"] = True
ret["comment"] = comment_bridge_exists
else:
ret["result"] = False
ret["comment"] = comment_bridge_mismatch
else:
ret["result"] = None
ret["comment"] = comment_bridge_created

return ret

if bridge_exists:
ret["result"] = True
ret["comment"] = comment_bridge_exists
if current_parent == parent and current_vlan == vlan:
ret["result"] = True
ret["comment"] = comment_bridge_exists
else:
ret["result"] = False
ret["comment"] = comment_bridge_mismatch
else:
bridge_create = __salt__["openvswitch.bridge_create"](name)
bridge_create = __salt__["openvswitch.bridge_create"](
name, parent=parent, vlan=vlan
)
if bridge_create:
ret["result"] = True
ret["comment"] = comment_bridge_created
Expand Down
Loading

0 comments on commit 02a1ee4

Please sign in to comment.