Last active
April 30, 2019 18:45
-
-
Save victorock/2cb16e46068639e4eceb6578cb65a955 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# (c) 2019 Red Hat Inc. | |
# | |
# This file is part of Ansible | |
# | |
# Ansible is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# Ansible is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. | |
# | |
# (c) 2018, Victor da Costa <[email protected]> | |
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | |
from __future__ import (absolute_import, division, print_function) | |
__metaclass__ = type | |
import json | |
import uuid | |
from itertools import izip | |
from ansible.module_utils.network.common.network import NetworkModule | |
from ansible.module_utils.common.process import get_bin_path | |
from ansible.module_utils._text import to_bytes, to_text | |
ovs_argument_spec = { | |
'db': { | |
'required': False, | |
'default': 'unix:/var/run/openvswitch/db.sock', | |
'type': 'str', | |
'aliases': ['server'] | |
}, | |
'timeout': { | |
'required': False, | |
'default': 5, | |
'type': 'int' | |
}, | |
'private_key': { | |
'required': False, | |
'default': None, | |
'type': 'str' | |
}, | |
'certificate': { | |
'required': False, | |
'default': None, | |
'type': 'str' | |
}, | |
'ca_cert': { | |
'required': False, | |
'default': None, | |
'type': 'str' | |
}, | |
'bootstrap_ca_cert': { | |
'required': False, | |
'default': None, | |
'type': 'str' | |
}, | |
'peer_ca_cert': { | |
'required': False, | |
'default': None, | |
'type': 'str' | |
} | |
} | |
# Annotation to sanitize inline arguments to ovs input | |
def no_ovs_commands(func): | |
def wrapper(*args, **kwargs): | |
forbidden = ['--'] | |
for arg in args: | |
if isinstance(arg, basestring): | |
for f in forbidden: | |
if f in arg: | |
raise ValueError( | |
( | |
"Illegal arguments: {f}" | |
).format( | |
f=f | |
) | |
) | |
return func(*args , **kwargs) | |
return wrapper | |
def flatten(args): | |
if isinstance(args, (list, tuple, set)): | |
return sum(map(flatten, args), []) | |
else: | |
return [args] | |
def split_commas(data): | |
return list(data.split(",")) | |
def to_kdv(d='', **kv): | |
''' | |
transform key/value dict to delimited list: [ {k}{d}{v}, ...] | |
''' | |
if not isinstance(kv, dict): | |
return None | |
dl = [] | |
for k,v in kv.iteritems(): | |
# check if dict is too deep | |
if not isinstance(v, str): | |
raise TypeError("expected str") | |
dl.append("{0}{1}{2}".format(k,d,v)) | |
return list(dl) | |
def to_json(data): | |
""" | |
return: dict | |
""" | |
if isinstance(data, (str, buffer)): | |
return dict(json.loads(str(data), cls=json.JSONDecoder)) | |
elif isinstance(data, dict): | |
return dict(data) | |
else: | |
raise TypeError("expected dict, str or buffer") | |
def to_type(*args): | |
""" | |
transform cell containing tuple ('type', 'value') to variable of `type` containing `value` | |
""" | |
_type = { | |
'integer': 'int', | |
'map': 'dict', | |
'string': 'str', | |
'set': 'set', | |
'str': 'str', | |
'dict': 'dict', | |
'uuid': 'uuid.UUID', | |
'decimal': 'floating', | |
'boolean': 'bool' | |
} | |
if isinstance(args, (list, tuple)) and len(args) == 2: | |
# if arg is complex, with tuples of tuples, expand recursively... | |
if isinstance(args[1], (list, tuple)): | |
return to_type(args[1]) | |
return eval(_type[args[0]])(args[1]) | |
return args | |
class OvsCommand(object): | |
def __init__(self, *args, **kwargs): | |
self.commands = kwargs.pop("commands", []) | |
self.format = kwargs.pop("format", "json") | |
self.data = kwargs.pop("data", "json") | |
self.server = kwargs.pop("server", "unix:/var/run/openvswitch/db.sock") | |
self.table = kwargs.pop("table", "Open_vSwitch") | |
self.timeout = kwargs.pop("timeout", 5) | |
self.private_key = kwargs.pop("private_key", False) | |
self.certificate = kwargs.pop("certificate", False) | |
self.ca_cert = kwargs.pop("ca_cert", False) | |
self.bootstrap_ca_cert = kwargs.pop("bootstrap_ca_cert", False) | |
self.peer_ca_cert = kwargs.pop("peer_ca_cert", False) | |
self.lines = [] | |
def add_line(self, *args): | |
line = " ".join(flatten([args])) | |
self.lines.append(line) | |
return line | |
class OvsDbClient(OvsCommand): | |
def __init__(self, *args, **kwargs): | |
super(OvsDbClient, self).__init__(*args, **kwargs) | |
self.database = kwargs.pop("database", "Open_vSwitch") | |
@property | |
def command(self): | |
''' Build ovs-vsctl command | |
''' | |
# ovs-vsctl | |
cli = [ | |
get_bin_path("ovsdb-client",True) | |
] | |
# --private-key={private_key} | |
if self.private_key: | |
cli.append( | |
"--private-key={0}".format(self.private_key) | |
) | |
# --certificate={certificate} | |
if self.certificate: | |
cli.append( | |
"--certificate={0}".format(self.certificate) | |
) | |
# --ca-cert={ca_cert} | |
if self.ca_cert: | |
cli.append( | |
"--ca-cert={0}".format(self.ca_cert) | |
) | |
# --bootstrap-ca-cert={bootstrap_ca_cert} | |
if self.bootstrap_ca_cert: | |
cli.append( | |
"--bootstrap-ca-cert={0}".format(self.bootstrap_ca_cert) | |
) | |
# --format={format} | |
if self.format: | |
cli.append( | |
"--format={0}".format(self.format) | |
) | |
# --data={data} | |
if self.data: | |
cli.append( | |
"--data={0}".format(self.data) | |
) | |
cli = ' '.join(flatten(cli)) | |
args = ' --- '.join(flatten(self.lines)) | |
return str(cli + args) | |
@no_ovs_commands | |
def schema(self): | |
return str( | |
self.add_line( | |
"get-schema", | |
to_text(self.server), | |
to_text(self.database) | |
) | |
) | |
@no_ovs_commands | |
def schema_version(self): | |
return str( | |
self.add_line( | |
"get-schema-version", | |
to_text(self.server), | |
to_text(self.database) | |
) | |
) | |
@no_ovs_commands | |
def transact(self, **transaction): | |
return str( | |
self.add_line( | |
"list-tables", | |
to_text(self.server), | |
to_json(transaction) | |
) | |
) | |
@no_ovs_commands | |
def monitor(self, table, *columns): | |
return str( | |
self.add_line( | |
"monitor", | |
to_text(self.server), | |
to_text(self.database), | |
to_text(table), | |
join_commas(*columns) | |
) | |
) | |
@no_ovs_commands | |
def dbs(self): | |
return str( | |
self.add_line( | |
"list-dbs", | |
to_text(self.server) | |
) | |
) | |
@no_ovs_commands | |
def columns(self, table): | |
return str( | |
self.add_line( | |
"list-columns", | |
to_text(self.server), | |
to_text(self.database), | |
to_text(table) | |
) | |
) | |
@no_ovs_commands | |
def tables(self): | |
return str( | |
self.add_line( | |
"list-tables", | |
to_text(self.server), | |
to_text(self.database) | |
) | |
) | |
@no_ovs_commands | |
def dump(self, database): | |
return str( | |
self.add_line( | |
"dump", | |
to_text(self.server), | |
to_text(self.database) | |
) | |
) | |
class OvsVsctl(OvsCommand): | |
def __init__(self, *args, **kwargs): | |
super(OvsVsctl, self).__init__(*args, **kwargs) | |
@property | |
def command(self): | |
''' Build ovs-vsctl command | |
''' | |
# ovs-vsctl --db="{server}" --timeout={timeout}" | |
cli = [ | |
get_bin_path("ovs-vsctl",True), | |
"--db=\"{0}\"".format(self.server), | |
"--timeout=\"{0}\"".format(self.timeout) | |
] | |
# --private-key={private_key} | |
if self.private_key: | |
cli.append( | |
"--private-key={0}".format(self.private_key) | |
) | |
# --certificate={certificate} | |
if self.certificate: | |
cli.append( | |
"--certificate={0}".format(self.certificate) | |
) | |
# --ca-cert={ca_cert} | |
if self.ca_cert: | |
cli.append( | |
"--ca-cert={0}".format(self.ca_cert) | |
) | |
# --bootstrap-ca-cert={bootstrap_ca_cert} | |
if self.bootstrap_ca_cert: | |
cli.append( | |
"--bootstrap-ca-cert={0}".format(self.bootstrap_ca_cert) | |
) | |
# --per-ca-cert={peer_ca_cert} | |
if self.peer_ca_cert: | |
cli.append( | |
"--peer-ca-cert={0}".format(self.peer_ca_cert) | |
) | |
# --format={format} | |
if self.format: | |
cli.append( | |
"--format={0}".format(self.format) | |
) | |
# --data={data} | |
if self.data: | |
cli.append( | |
"--data={0}".format(self.data) | |
) | |
cli = ' '.join(flatten(cli)) | |
args = ' --- '.join(flatten(self.lines)) | |
return str(cli + args) | |
@no_ovs_commands | |
def list(self, record=str()): | |
''' | |
--if-exists list table [record]... | |
Lists the data in each specified record. If no records are | |
specified, lists all the records in table. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--if-exists", | |
"list", | |
to_text(self.table), | |
to_text(record) | |
) | |
) | |
@no_ovs_commands | |
def find(self, **kwargs): | |
''' | |
find table [column[:key]=value]... | |
Lists the data in each record in table whose column equals value | |
or, if key is specified, whose column contains a key with the | |
specified value. The following operators may be used where = is | |
written in the syntax summary: | |
= != < > <= >= | |
Selects records in which column[:key] equals, does not | |
equal, is less than, is greater than, is less than or | |
equal to, or is greater than or equal to value, respec‐ | |
tively. | |
Consider column[:key] and value as sets of elements. | |
Identical sets are considered equal. Otherwise, if the | |
sets have different numbers of elements, then the set | |
with more elements is considered to be larger. Other‐ | |
wise, consider a element from each set pairwise, in in‐ | |
creasing order within each set. The first pair that dif‐ | |
fers determines the result. (For a column that contains | |
key-value pairs, first all the keys are compared, and | |
values are considered only if the two sets contain iden‐ | |
tical keys.) | |
{=} {!=} | |
Test for set equality or inequality, respectively. | |
{<=} Selects records in which column[:key] is a subset of | |
value. For example, flood-vlans{<=}1,2 selects records | |
in which the flood-vlans column is the empty set or con‐ | |
tains 1 or 2 or both. | |
{<} Selects records in which column[:key] is a proper subset | |
of value. For example, flood-vlans{<}1,2 selects records | |
in which the flood-vlans column is the empty set or con‐ | |
tains 1 or 2 but not both. | |
{>=} {>} | |
Same as {<=} and {<}, respectively, except that the rela‐ | |
tionship is reversed. For example, flood-vlans{>=}1,2 | |
selects records in which the flood-vlans column contains | |
both 1 and 2. | |
For arithmetic operators (= != < > <= >=), when key is specified | |
but a particular record's column does not contain key, the | |
record is always omitted from the results. Thus, the condition | |
other-config:mtu!=1500 matches records that have a mtu key whose | |
value is not 1500, but not those that lack an mtu key. | |
For the set operators, when key is specified but a particular | |
record's column does not contain key, the comparison is done | |
against an empty set. Thus, the condition other-con‐ | |
fig:mtu{!=}1500 matches records that have a mtu key whose value | |
is not 1500 and those that lack an mtu key. | |
Don't forget to escape < or > from interpretation by the shell. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--if-exists", | |
"find", | |
to_text(self.table), | |
" ".join(to_kdv(d='=', **kwargs)) | |
) | |
) | |
@no_ovs_commands | |
def get(self, id, record, columns): | |
''' | |
--id=@{id} get table record [column[:key]] | |
Prints the value of each specified column in the given record in | |
table. For map columns, a key may optionally be specified, in | |
which case the value associated with key in the column is | |
printed, instead of the entire map. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--id=@{0}".format(id), | |
"get", | |
to_text(self.table), | |
to_text(record), | |
" ".join(flatten(columns)) | |
) | |
) | |
@no_ovs_commands | |
def set(self, record, kwargs): | |
''' --if-exists set table record column[:key]=value... | |
Sets the value of each specified column in the given record in | |
table to value. For map columns, a key may optionally be speci‐ | |
fied, in which case the value associated with key in that column | |
is changed (or added, if none exists), instead of the entire | |
map. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--if-exists", | |
"set", | |
to_text(self.table), | |
to_text(record), | |
" ".join(to_kdv(d='=', kwargs)) | |
) | |
) | |
@no_ovs_commands | |
def add(self, record, column, value): | |
''' | |
--if-exists add table record column [key=]value | |
Adds the specified value or key-value pair to column in record | |
in table. If column is a map, then key is required, otherwise | |
it is prohibited. If key already exists in a map column, then | |
the current value is not replaced (use the set command to re‐ | |
place an existing value). | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--if-exists", | |
"add", | |
to_text(self.table), | |
to_text(record), | |
to_text(column), | |
to_text(value) | |
) | |
) | |
@no_ovs_commands | |
def remove(self, record, column, value): | |
''' | |
--if-exists remove table record column value... | |
--if-exists remove table record column key... | |
--if-exists remove table record column key=value... | |
Removes the specified values or key-value pairs from column in | |
record in table. The first form applies to columns that are not | |
maps: each specified value is removed from the column. The sec‐ | |
ond and third forms apply to map columns: if only a key is spec‐ | |
ified, then any key-value pair with the given key is removed, | |
regardless of its value; if a value is given then a pair is re‐ | |
moved only if both key and value match. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--if-exists", | |
"remove", | |
to_text(self.table), | |
to_text(record), | |
to_text(column), | |
to_text(value) | |
) | |
) | |
@no_ovs_commands | |
def clear(self, record, *args): | |
''' | |
--if-exists clear table record column | |
Sets each column in record in table to the empty set or empty | |
map, as appropriate. This command applies only to columns that | |
are allowed to be empty. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--if-exists", | |
"clear", | |
to_text(self.table), | |
to_text(record), | |
" ".join(list(args)) | |
) | |
) | |
@no_ovs_commands | |
def create(self, id, *args): | |
''' | |
--id=@{id} create table column[:key]=value... | |
Creates a new record in table and sets the initial values of | |
each column. Columns not explicitly set will receive their de‐ | |
fault values. Outputs the UUID of the new row. | |
The UUID for the new row is stored in @create and may be re‐ | |
ferred to by that name elsewhere in the same ovs-vsctl invoca‐ | |
tion in contexts where a UUID is expected. Such references may | |
precede or follow the create command. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"--id=@{0}".format(str(id)), | |
"create", | |
to_text(self.table), | |
to_text(" ".join(list(args))) | |
) | |
) | |
@no_ovs_commands | |
def wait_until(self, record, **kwargs): | |
''' | |
wait-until table record [column[:key]=value]... | |
Waits until table contains a record named record whose column | |
equals value or, if key is specified, whose column contains a | |
key with the specified value. Any of the operators !=, <, >, | |
<=, or >= may be substituted for = to test for inequality, less | |
than, greater than, less than or equal to, or greater than or | |
equal to, respectively. (Don't forget to escape < or > from in‐ | |
terpretation by the shell.) | |
If no column[:key]=value arguments are given, this command waits | |
only until record exists. If more than one such argument is | |
given, the command waits until all of them are satisfied. | |
Caution (ovs-vsctl as example) | |
Usually wait-until should be placed at the beginning of a | |
set of ovs-vsctl commands. For example, wait-until | |
bridge br0 -- get bridge br0 datapath_id waits until a | |
bridge named br0 is created, then prints its datapath_id | |
column, whereas get bridge br0 datapath_id -- wait-until | |
bridge br0 will abort if no bridge named br0 exists when | |
ovs-vsctl initially connects to the database. | |
Consider specifying --timeout=0 along with --wait-until, to pre‐ | |
vent ovs-vsctl from terminating after waiting only at most 5 | |
seconds. | |
return: str | |
''' | |
return str( | |
self.add_line( | |
"wait_until", | |
to_text(self.table), | |
to_text(record), | |
" ".join(to_kdv(d='=',**kwargs)) | |
) | |
) | |
class OvsRow(object): | |
def __init__(self, columns, row): | |
for column, cell in izip(list(columns), list(row)): | |
setattr(self, column, to_type(cell)) | |
def __getitem__(self, column): | |
return getattr(self, column) | |
class OvsTable(object): | |
def __init__(self, data): | |
''' | |
data = { | |
'headings': [ 'a', 'b', 'c', ...], | |
'data': [ | |
[(type, data), (type, data), (type, data), ...], | |
[(type, data), (type, data), (type, data), ...], | |
.... | |
] | |
} | |
''' | |
self.__json = to_json(data) | |
self.__columns = list(self.__json.pop("headings")) | |
self.__rows = [ OvsRow(self.__columns, row) for row in list(self.__json.pop("data")) ] | |
def __getitem__(self, name): | |
return getattr(self, name) | |
def __getattr__(self, column): | |
return [str(getattr(rows, column)) for rows in self.rows] | |
@property | |
def columns(self): | |
return self.__columns | |
@property | |
def rows(self): | |
return self.__rows | |
class OvsCli(NetworkModule): | |
def __init__(self, *args, **kwargs): | |
super(OvsCli, self).__init__(*args, **kwargs) | |
def get_device_info(self): | |
return self.connection.get_device_info() | |
def get_config(self): | |
return self.connection.get_config() | |
def get_capabilities(self): | |
return self.connection.get_capabilities() | |
def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): | |
return self.connection.get_diff(candidate=candidate, running=running, diff_match=diff_match, diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace) | |
def edit_config(self, candidate=None, commit=True, replace=None, diff=False, comment=None, cli=None): | |
return self.connection.edit_config(candidate=candidate, commit=commit, replace=replace, diff=diff, comment=comment, cli=cli) | |
def get(self, ) | |
def load_config(self, commands, return_error=False, opts=None, replace=None): | |
return self.connection.load_config(commands=commands, return_error=return_error, opts=opts, replace=replace) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment