2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-31 06:15:47 +00:00

python: Add support for partial map and partial set updates

Allow the python IDL to use mutate operations more freely
by mimicing the partial map and partial set operations now
available in the C IDL.

Unit tests for both of these types of operations are included.
They are not carbon copies of the C tests, because testing
idempotency is a bit difficult for the current python IDL
test harness.

Signed-off-by: Ryan Moats <rmoats@us.ibm.com>
Signed-off-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
Ryan Moats
2016-08-06 17:46:30 -05:00
committed by Ben Pfaff
parent 98e88b5b60
commit a59912a0ee
3 changed files with 303 additions and 11 deletions

View File

@@ -18,6 +18,7 @@ import uuid
import six
import ovs.jsonrpc
import ovs.db.data as data
import ovs.db.parser
import ovs.db.schema
from ovs.db import error
@@ -588,8 +589,7 @@ class Idl(object):
continue
try:
datum_diff = ovs.db.data.Datum.from_json(column.type,
datum_diff_json)
datum_diff = data.Datum.from_json(column.type, datum_diff_json)
except error.Error as e:
# XXX rate-limit
vlog.warn("error parsing column %s in table %s: %s"
@@ -614,7 +614,7 @@ class Idl(object):
continue
try:
datum = ovs.db.data.Datum.from_json(column.type, datum_json)
datum = data.Datum.from_json(column.type, datum_json)
except error.Error as e:
# XXX rate-limit
vlog.warn("error parsing column %s in table %s: %s"
@@ -730,6 +730,24 @@ class Row(object):
# - None, if this transaction deletes this row.
self.__dict__["_changes"] = {}
# _mutations describes changes to this row to be handled via a
# mutate operation on the wire. It takes the following values:
#
# - {}, the empty dictionary, if no transaction is active or if the
# row has yet not been mutated within this transaction.
#
# - A dictionary that contains two keys:
#
# - "_inserts" contains a dictionary that maps column names to
# new keys/key-value pairs that should be inserted into the
# column
# - "_removes" contains a dictionary that maps column names to
# the keys/key-value pairs that should be removed from the
# column
#
# - None, if this transaction deletes this row.
self.__dict__["_mutations"] = {}
# A dictionary whose keys are the names of columns that must be
# verified as prerequisites when the transaction commits. The values
# in the dictionary are all None.
@@ -750,17 +768,47 @@ class Row(object):
def __getattr__(self, column_name):
assert self._changes is not None
assert self._mutations is not None
datum = self._changes.get(column_name)
inserts = None
if '_inserts' in self._mutations.keys():
inserts = self._mutations['_inserts'].get(column_name)
removes = None
if '_removes' in self._mutations.keys():
removes = self._mutations['_removes'].get(column_name)
if datum is None:
if self._data is None:
raise AttributeError("%s instance has no attribute '%s'" %
(self.__class__.__name__, column_name))
if inserts is None:
raise AttributeError("%s instance has no attribute '%s'" %
(self.__class__.__name__,
column_name))
else:
datum = inserts
if column_name in self._data:
datum = self._data[column_name]
try:
if inserts is not None:
datum.extend(inserts)
if removes is not None:
datum = [x for x in datum if x not in removes]
except error.Error:
pass
try:
if inserts is not None:
datum.merge(inserts)
if removes is not None:
for key in removes.keys():
del datum[key]
except error.Error:
pass
else:
raise AttributeError("%s instance has no attribute '%s'" %
(self.__class__.__name__, column_name))
if inserts is None:
raise AttributeError("%s instance has no attribute '%s'" %
(self.__class__.__name__,
column_name))
else:
datum = inserts
return datum.to_python(_uuid_to_row)
@@ -776,8 +824,7 @@ class Row(object):
column = self._table.columns[column_name]
try:
datum = ovs.db.data.Datum.from_python(column.type, value,
_row_to_uuid)
datum = data.Datum.from_python(column.type, value, _row_to_uuid)
except error.Error as e:
# XXX rate-limit
vlog.err("attempting to write bad value to column %s (%s)"
@@ -785,6 +832,74 @@ class Row(object):
return
self._idl.txn._write(self, column, datum)
def _init_mutations_if_needed(self):
if '_inserts' not in self._mutations.keys():
self._mutations['_inserts'] = {}
if '_removes' not in self._mutations.keys():
self._mutations['_removes'] = {}
return
def addvalue(self, column_name, key):
self._idl.txn._txn_rows[self.uuid] = self
self._init_mutations_if_needed()
if column_name in self._data.keys():
if column_name not in self._mutations['_inserts'].keys():
self._mutations['_inserts'][column_name] = []
self._mutations['_inserts'][column_name].append(key)
def delvalue(self, column_name, key):
self._idl.txn._txn_rows[self.uuid] = self
self._init_mutations_if_needed()
if column_name in self._data.keys():
if column_name not in self._mutations['_removes'].keys():
self._mutations['_removes'][column_name] = []
self._mutations['_removes'][column_name].append(key)
def setkey(self, column_name, key, value):
self._idl.txn._txn_rows[self.uuid] = self
column = self._table.columns[column_name]
try:
datum = data.Datum.from_python(column.type, {key: value},
_row_to_uuid)
except error.Error as e:
# XXX rate-limit
vlog.err("attempting to write bad value to column %s (%s)"
% (column_name, e))
return
self._init_mutations_if_needed()
if column_name in self._data.keys():
if column_name not in self._mutations['_removes'].keys():
self._mutations['_removes'][column_name] = []
self._mutations['_removes'][column_name].append(key)
if self._mutations['_inserts'] is None:
self._mutations['_inserts'] = {}
self._mutations['_inserts'][column_name] = datum
def delkey(self, column_name, key):
self._idl.txn._txn_rows[self.uuid] = self
self._init_mutations_if_needed()
if column_name not in self._mutations['_removes'].keys():
self._mutations['_removes'][column_name] = []
self._mutations['_removes'][column_name].append(key)
def delkey(self, column_name, key, value):
self._idl.txn._txn_rows[self.uuid] = self
try:
old_value = data.Datum.to_python(self._data[column_name],
_uuid_to_row)
except error.Error:
return
if key not in old_value.keys():
return
if old_value[key] != value:
return
self._init_mutations_if_needed()
if column_name not in self._mutations['_removes'].keys():
self._mutations['_removes'][column_name] = []
self._mutations['_removes'][column_name].append(key)
return
@classmethod
def from_json(cls, idl, table, uuid, row_json):
data = {}
@@ -1024,6 +1139,7 @@ class Transaction(object):
elif row._data is None:
del row._table.rows[row.uuid]
row.__dict__["_changes"] = {}
row.__dict__["_mutations"] = {}
row.__dict__["_prereqs"] = {}
self._txn_rows = {}
@@ -1155,6 +1271,57 @@ class Transaction(object):
if row._data is None or row_json:
operations.append(op)
if row._mutations:
addop = False
op = {"table": row._table.name}
op["op"] = "mutate"
op["where"] = _where_uuid_equals(row.uuid)
op["mutations"] = []
if '_removes' in row._mutations.keys():
for col, dat in six.iteritems(row._mutations['_removes']):
column = row._table.columns[col]
if column.type.is_map():
opdat = ["set"]
opdat.append(dat)
else:
opdat = ["set"]
inner_opdat = []
for ele in dat:
try:
datum = data.Datum.from_python(column.type,
ele, _row_to_uuid)
except error.Error:
return
inner_opdat.append(
self._substitute_uuids(datum.to_json()))
opdat.append(inner_opdat)
mutation = [col, "delete", opdat]
op["mutations"].append(mutation)
addop = True
if '_inserts' in row._mutations.keys():
for col, dat in six.iteritems(row._mutations['_inserts']):
column = row._table.columns[col]
if column.type.is_map():
opdat = ["map"]
opdat.append(dat.as_list())
else:
opdat = ["set"]
inner_opdat = []
for ele in dat:
try:
datum = data.Datum.from_python(column.type,
ele, _row_to_uuid)
except error.Error:
return
inner_opdat.append(
self._substitute_uuids(datum.to_json()))
opdat.append(inner_opdat)
mutation = [col, "insert", opdat]
op["mutations"].append(mutation)
addop = True
if addop:
operations.append(op)
any_updates = True
if self._fetch_requests:
for fetch in self._fetch_requests:
@@ -1281,6 +1448,7 @@ class Transaction(object):
def _write(self, row, column, datum):
assert row._changes is not None
assert row._mutations is not None
txn = row._idl.txn
@@ -1303,7 +1471,13 @@ class Transaction(object):
return
txn._txn_rows[row.uuid] = row
row._changes[column.name] = datum.copy()
if '_inserts' in row._mutations.keys():
if column.name in row._mutations['_inserts']:
row._mutations['_inserts'][column.name] = datum.copy()
else:
row._changes[column.name] = datum.copy()
else:
row._changes[column.name] = datum.copy()
def insert(self, table, new_uuid=None):
"""Inserts and returns a new row in 'table', which must be one of the
@@ -1419,7 +1593,7 @@ class Transaction(object):
column = table.columns.get(column_name)
datum_json = fetched_row.get(column_name)
datum = ovs.db.data.Datum.from_json(column.type, datum_json)
datum = data.Datum.from_json(column.type, datum_json)
row._data[column_name] = datum
update = True