Skip to content

Commit

Permalink
Merge pull request #50564 from smarsching/openvswitch-extensions
Browse files Browse the repository at this point in the history
Add more functions for OpenVSwitch
  • Loading branch information
Mike Place authored Nov 27, 2018
2 parents 3af4c0c + 89bb6ae commit 57674c3
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 8 deletions.
182 changes: 180 additions & 2 deletions salt/modules/openvswitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

# Import salt libs
from salt.ext import six
from salt.exceptions import ArgumentValueError, CommandExecutionError
from salt.utils import json
import salt.utils.path

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -94,6 +96,54 @@ 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 six.iteritems(obj)}
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 @@ -135,13 +185,18 @@ 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.
parent: String, the name of the parent bridge (if the bridge shall be
created as a fake bridge). If specified, vlan must also be
specified.
vlan: Int, the VLAN ID of the bridge (if the bridge shall be created as
a fake bridge). If specified, parent must also be specified.
Returns:
True on success, else False.
Expand All @@ -154,7 +209,16 @@ 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 ' {0}'.format(parent)
param_vlan = '' if vlan is None else ' {0}'.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 @@ -184,6 +248,56 @@ def bridge_delete(br, if_exists=True):
return _retcode_to_bool(retcode)


def bridge_to_parent(br):
'''
Returns the parent bridge of a bridge.
Args:
br: A 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 {0}'.format(br)
result = __salt__['cmd.run_all'](cmd)
if result['retcode'] != 0:
return False
return result['stdout']


def bridge_to_vlan(br):
'''
Returns the VLAN ID of a bridge.
Args:
br: A 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 {0}'.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):
'''
Creates on bridge a new port named port.
Expand Down Expand Up @@ -452,3 +566,67 @@ def port_create_vxlan(br, port, id, remote, dst_port=None):
'options:key={3}{4}'.format(br, port, remote, id, dst_port)
result = __salt__['cmd.run_all'](cmd)
return _retcode_to_bool(result['retcode'])


def db_get(table, record, column, if_exists=False):
'''
Gets a column's value for a specific record.
Args:
table: A string - name of the database table.
record: A string - identifier of the record.
column: A string - name of the column.
if_exists: A 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={0}'.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):
'''
Sets a column's value for a specific record.
Args:
table: A string - name of the database table.
record: A string - identifier of the record.
column: A string - name of the column.
value: A string - the value to be set
if_exists: A 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, '{0}={1}'.format(column, json.dumps(value))]
result = __salt__['cmd.run_all'](cmd)
if result['retcode'] != 0:
return result['stderr']
else:
return None
34 changes: 28 additions & 6 deletions salt/states/openvswitch_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ def __virtual__():
return 'openvswitch.bridge_create' in __salt__


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.
parent: The name of the parent bridge (if the bridge shall be created
as a fake bridge). If specified, vlan must also be specified.
vlan: The VLAN ID of the bridge (if the bridge shall be created as a
fake bridge). If specified, parent must also be specified.
'''
ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
Expand All @@ -28,29 +32,47 @@ def present(name):
comment_bridge_created = 'Bridge {0} created.'.format(name)
comment_bridge_notcreated = 'Unable to create bridge: {0}.'.format(name)
comment_bridge_exists = 'Bridge {0} already exists.'.format(name)
comment_bridge_mismatch = ('Bridge {0} already exists, but has a different'
' parent or VLAN ID.').format(name)
changes_bridge_created = {name: {'old': 'Bridge {0} does not exist.'.format(name),
'new': 'Bridge {0} created'.format(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
69 changes: 69 additions & 0 deletions salt/states/openvswitch_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
'''
Management of Open vSwitch database records.
'''

# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals


def __virtual__():
'''
Only make these states available if Open vSwitch module is available.
'''
return 'openvswitch.db_get' in __salt__


def managed(name, table, data, record=None):
'''
Ensures that the specified columns of the named record have the specified
values.
Args:
name: The name of the record.
table: The name of the table to which the record belongs.
data: Dictionary containing a mapping from column names to the desired
values. Columns that exist, but are not specified in this
dictionary are not touched.
record: The name of the record (optional). Replaces name if specified.
'''
ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
if record is None:
record = name
current_data = {
column: __salt__['openvswitch.db_get'](table, record, column)
for column in data
}

# Comment and change messages
comment_changes = 'Columns have been updated.'
comment_no_changes = 'All columns are already up to date.'
comment_error = 'Error while updating column {0}: {1}'

# Dry run, test=true mode
if __opts__['test']:
for column in data:
if data[column] != current_data[column]:
ret['changes'][column] = {'old': current_data[column],
'new': data[column]}
if ret['changes']:
ret['result'] = None
ret['comment'] = comment_changes
else:
ret['result'] = True
ret['comment'] = comment_no_changes
return ret

for column in data:
if data[column] != current_data[column]:
result = __salt__['openvswitch.db_set'](table, record, column,
data[column])
if result is not None:
ret['comment'] = comment_error.format(column, result)
ret['result'] = False
return ret
ret['changes'][column] = {'old': current_data[column],
'new': data[column]}
ret['result'] = True
ret['comment'] = comment_no_changes
return ret

0 comments on commit 57674c3

Please sign in to comment.