1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
package updates
import (
"fmt"
"reflect"
"github.com/ovn-kubernetes/libovsdb/ovsdb"
)
func merge(ts *ovsdb.TableSchema, a, b modelUpdate) (modelUpdate, error) {
// handle model update
switch {
case b.old == nil && b.new == nil:
// noop
case a.old == nil && a.new == nil:
// first op
a.old = b.old
a.new = b.new
case a.new != nil && b.old != nil && b.new != nil:
// update after an insert or an update
a.new = b.new
case b.old != nil && b.new == nil:
// a final delete
a.new = nil
default:
return modelUpdate{}, fmt.Errorf("sequence of updates not supported")
}
// handle row update
ru2, err := mergeRowUpdate(ts, a.rowUpdate2, b.rowUpdate2)
if err != nil {
return modelUpdate{}, err
}
if ru2 == nil {
return modelUpdate{}, nil
}
a.rowUpdate2 = ru2
return a, nil
}
func mergeRowUpdate(ts *ovsdb.TableSchema, a, b *rowUpdate2) (*rowUpdate2, error) {
switch {
case b == nil:
// noop
case a == nil:
// first op
a = b
case a.Insert != nil && b.Modify != nil:
// update after an insert
a.New = b.New
a.Insert = b.New
case a.Modify != nil && b.Modify != nil:
// update after update
a.New = b.New
a.Modify = mergeModifyRow(ts, a.Old, a.Modify, b.Modify)
if a.Modify == nil {
// we merged two modifications that brought back the row to its
// original value which is a no op
a = nil
}
case a.Insert != nil && b.Delete != nil:
// delete after insert
a = nil
case b.Delete != nil:
// a final delete
a.Initial = nil
a.Insert = nil
a.Modify = nil
a.New = nil
a.Delete = b.Delete
default:
return &rowUpdate2{}, fmt.Errorf("sequence of updates not supported")
}
return a, nil
}
// mergeModifyRow merges two modification rows 'a' and 'b' with respect an
// original row 'o'. Two modifications that restore the original value cancel
// each other and won't be included in the result. Returns nil if there are no
// resulting modifications.
func mergeModifyRow(ts *ovsdb.TableSchema, o, a, b *ovsdb.Row) *ovsdb.Row {
original := *o
aMod := *a
bMod := *b
for k, v := range bMod {
if _, ok := aMod[k]; !ok {
aMod[k] = v
continue
}
var result any
var changed bool
// handle maps or sets first
switch v.(type) {
// difference only supports set or map values that are comparable with
// no pointers. This should be currently fine because the set or map
// values should only be non pointer atomic types or the UUID struct.
case ovsdb.OvsSet:
aSet := aMod[k].(ovsdb.OvsSet)
bSet := v.(ovsdb.OvsSet)
// handle sets of multiple values, single value sets are handled as
// atomic values
if ts.Column(k).TypeObj.Max() != 1 {
// set difference is a fully transitive operation so we dont
// need to do anything special to merge two differences
result, changed = setDifference(aSet.GoSet, bSet.GoSet)
result = ovsdb.OvsSet{GoSet: result.([]any)}
}
case ovsdb.OvsMap:
aMap := aMod[k].(ovsdb.OvsMap)
bMap := v.(ovsdb.OvsMap)
var originalMap ovsdb.OvsMap
if v, ok := original[k]; ok {
originalMap = v.(ovsdb.OvsMap)
}
// map difference is not transitive with respect to the original
// value so we have to take the original value into account when
// merging
result, changed = mergeMapDifference(originalMap.GoMap, aMap.GoMap, bMap.GoMap)
result = ovsdb.OvsMap{GoMap: result.(map[any]any)}
}
// was neither a map nor a set
if result == nil {
// atomic difference is not transitive with respect to the original
// value so we have to take the original value into account when
// merging
o := original[k]
if o == nil {
// assume zero value if original does not have the column
o = reflect.Zero(reflect.TypeOf(v)).Interface()
}
if set, ok := o.(ovsdb.OvsSet); ok {
// atomic optional values are cleared out with an empty set
// if the original value was also cleared out, use an empty set
// instead of a nil set so that mergeAtomicDifference notices
// that we are returning to the original value
if set.GoSet == nil {
set.GoSet = []any{}
}
o = set
}
result, changed = mergeAtomicDifference(o, aMod[k], v)
}
if !changed {
delete(aMod, k)
continue
}
aMod[k] = result
}
if len(aMod) == 0 {
return nil
}
return a
}
|