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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
package updates
import "reflect"
// difference between value 'a' and value 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the
// difference is 'b' in which case 'b' is returned unmodified. Also returns a
// boolean indicating if there is an actual difference.
func difference(a, b any) (any, bool) {
return mergeDifference(nil, a, b)
}
// applyDifference returns the result of applying difference 'd' to value 'v'
// along with a boolean indicating if 'v' was changed.
func applyDifference(v, d any) (any, bool) {
if d == nil {
return v, false
}
// difference can be applied with the same algorithm used to calculate it
// f(x,f(x,y))=y
result, changed := difference(v, d)
dv := reflect.ValueOf(d)
switch dv.Kind() {
case reflect.Slice:
fallthrough
case reflect.Map:
// but we need to tweak the interpretation of change for map and slices:
// when there is no difference between the value and non-empty delta, it
// actually means the value needs to be emptied so there is actually a
// change
if !changed && dv.Len() > 0 {
return result, true
}
// there are no changes when delta is empty
return result, changed && dv.Len() > 0
}
return result, changed
}
// mergeDifference, given an original value 'o' and two differences 'a' and 'b',
// returns a new equivalent difference that when applied on 'o' it would have
// the same result as applying 'a' and 'b' consecutively.
// If 'o' is nil, returns the difference between 'a' and 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the result is
// 'b' in which case 'b' is returned unmodified. Also returns a boolean
// indicating if there is an actual difference.
func mergeDifference(o, a, b any) (any, bool) {
kind := reflect.ValueOf(b).Kind()
if kind == reflect.Invalid {
kind = reflect.ValueOf(a).Kind()
}
switch kind {
case reflect.Invalid:
return nil, false
case reflect.Slice:
// set differences are transitive
return setDifference(a, b)
case reflect.Map:
return mergeMapDifference(o, a, b)
case reflect.Array:
panic("Not implemented")
default:
return mergeAtomicDifference(o, a, b)
}
}
// setDifference calculates the difference between set 'a' and set 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the difference
// is 'b' in which case 'b' is returned unmodified. Also returns a boolean
// indicating if there is an actual difference.
func setDifference(a, b any) (any, bool) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
if !av.IsValid() && !bv.IsValid() {
return nil, false
} else if (!av.IsValid() || av.Len() == 0) && bv.IsValid() {
return b, bv.Len() != 0
} else if (!bv.IsValid() || bv.Len() == 0) && av.IsValid() {
return a, av.Len() != 0
}
// From https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The difference between two sets are all elements that only belong to one
// of the sets.
difference := make(map[any]struct{}, bv.Len())
for i := 0; i < bv.Len(); i++ {
// supossedly we are working with comparable atomic types with no
// pointers so we can use the values as map key
difference[bv.Index(i).Interface()] = struct{}{}
}
j := av.Len()
for i := 0; i < j; {
vv := av.Index(i)
vi := vv.Interface()
if _, ok := difference[vi]; ok {
// this value of 'a' is in 'b', so remove it from 'a'; to do that,
// overwrite it with the last value and re-evaluate
vv.Set(av.Index(j - 1))
// decrease where the last 'a' value is at
j--
// remove from 'b' values
delete(difference, vi)
} else {
// this value of 'a' is not in 'b', evaluate the next value
i++
}
}
// trim the slice to the actual values held
av = av.Slice(0, j)
for item := range difference {
// this value of 'b' is not in 'a', so add it
av = reflect.Append(av, reflect.ValueOf(item))
}
if av.Len() == 0 {
return reflect.Zero(av.Type()).Interface(), false
}
return av.Interface(), true
}
// mergeMapDifference, given an original map 'o' and two differences 'a' and
// 'b', returns a new equivalent difference that when applied on 'o' it would
// have the same result as applying 'a' and 'b' consecutively.
// If 'o' is nil, returns the difference between 'a' and 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the result is
// 'b' in which case 'b' is returned unmodified.
// Returns a boolean indicating if there is an actual difference.
func mergeMapDifference(o, a, b any) (any, bool) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
if !av.IsValid() && !bv.IsValid() {
return nil, false
} else if (!av.IsValid() || av.Len() == 0) && bv.IsValid() {
return b, bv.Len() != 0
} else if (!bv.IsValid() || bv.Len() == 0) && av.IsValid() {
return a, av.Len() != 0
}
ov := reflect.ValueOf(o)
if !ov.IsValid() {
ov = reflect.Zero(av.Type())
}
// From
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The difference between two maps are all key-value pairs whose keys
// appears in only one of the maps, plus the key-value pairs whose keys
// appear in both maps but with different values. For the latter elements,
// <row> includes the value from the new column.
// We can assume that difference is a transitive operation so we calculate
// the difference between 'a' and 'b' but we need to handle exceptions when
// the same key is present in all values.
for i := bv.MapRange(); i.Next(); {
kv := i.Key()
bvv := i.Value()
avv := av.MapIndex(kv)
ovv := ov.MapIndex(kv)
// supossedly we are working with comparable types with no pointers so
// we can compare directly here
switch {
case ovv.IsValid() && avv.IsValid() && ovv.Interface() == bvv.Interface():
// key is present in the three values
// final result would restore key to the original value, delete from 'a'
av.SetMapIndex(kv, reflect.Value{})
case ovv.IsValid() && avv.IsValid() && avv.Interface() == bvv.Interface():
// key is present in the three values
// final result would remove key, set in 'a' with 'o' value
av.SetMapIndex(kv, ovv)
case avv.IsValid() && avv.Interface() == bvv.Interface():
// key/value is in 'a' and 'b', delete from 'a'
av.SetMapIndex(kv, reflect.Value{})
default:
// key/value in 'b' is not in 'a', set in 'a' with 'b' value
av.SetMapIndex(kv, bvv)
}
}
if av.Len() == 0 {
return reflect.Zero(av.Type()).Interface(), false
}
return av.Interface(), true
}
// mergeAtomicDifference, given an original atomic value 'o' and two differences
// 'a' and 'b', returns a new equivalent difference that when applied on 'o' it
// would have the same result as applying 'a' and 'b' consecutively.
// If 'o' is nil, returns the difference between 'a' and 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// Returns a boolean indicating if there is an actual difference.
func mergeAtomicDifference(o, a, b any) (any, bool) {
if o != nil {
return b, !reflect.DeepEqual(o, b)
}
return b, !reflect.DeepEqual(a, b)
}
|