From 0c96aafad5341a513184de1486501288242297a2 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 15 Jun 2023 15:12:35 -0500 Subject: [PATCH 1/4] api: send real UUID if given ovsdb-server handles both 'uuid' and 'uuid-name' in ovsdb_execute_insert() and technically allows the client specifying the UUID at creation time. Signed-off-by: Dan Williams --- client/api.go | 10 ++++++++-- ovsdb/bindings.go | 2 +- ovsdb/notation.go | 1 + ovsdb/notation_test.go | 24 ++++++++---------------- ovsdb/uuid.go | 10 +++++++++- ovsdb/uuid_test.go | 2 +- 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/client/api.go b/client/api.go index 5665c51e..d63b97ee 100644 --- a/client/api.go +++ b/client/api.go @@ -283,7 +283,7 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) { var operations []ovsdb.Operation for _, model := range models { - var namedUUID string + var realUUID, namedUUID string var err error tableName, err := a.getTableFromModel(model) @@ -297,7 +297,12 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) { return nil, err } if uuid, err := info.FieldByColumn("_uuid"); err == nil { - namedUUID = uuid.(string) + tmpUUID := uuid.(string) + if ovsdb.IsNamedUUID(tmpUUID) { + namedUUID = tmpUUID + } else if ovsdb.IsValidUUID(tmpUUID) { + realUUID = tmpUUID + } } else { return nil, err } @@ -311,6 +316,7 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) { Op: ovsdb.OperationInsert, Table: tableName, Row: row, + UUID: realUUID, UUIDName: namedUUID, }) } diff --git a/ovsdb/bindings.go b/ovsdb/bindings.go index 93c6a688..dbaa3b10 100644 --- a/ovsdb/bindings.go +++ b/ovsdb/bindings.go @@ -409,7 +409,7 @@ func isDefaultBaseValue(elem interface{}, etype ExtendedType) bool { } switch etype { case TypeUUID: - return elem.(string) == "00000000-0000-0000-0000-000000000000" || elem.(string) == "" || isNamed(elem.(string)) + return elem.(string) == "00000000-0000-0000-0000-000000000000" || elem.(string) == "" || IsNamedUUID(elem.(string)) case TypeMap, TypeSet: if value.Kind() == reflect.Array { return value.Len() == 0 diff --git a/ovsdb/notation.go b/ovsdb/notation.go index 1476f5e5..afad87cd 100644 --- a/ovsdb/notation.go +++ b/ovsdb/notation.go @@ -41,6 +41,7 @@ type Operation struct { Durable *bool `json:"durable,omitempty"` Comment *string `json:"comment,omitempty"` Lock *string `json:"lock,omitempty"` + UUID string `json:"uuid,omitempty"` UUIDName string `json:"uuid-name,omitempty"` } diff --git a/ovsdb/notation_test.go b/ovsdb/notation_test.go index e397971d..c519c69e 100644 --- a/ovsdb/notation_test.go +++ b/ovsdb/notation_test.go @@ -137,32 +137,24 @@ func TestValidateOvsMap(t *testing.T) { } func TestValidateUuid(t *testing.T) { - uuid1 := UUID{"this is a bad uuid"} // Bad - uuid2 := UUID{"alsoabaduuid"} // Bad - uuid3 := UUID{"550e8400-e29b-41d4-a716-446655440000"} // Good - uuid4 := UUID{"thishoul-dnot-pass-vali-dationchecks"} // Bad + uuid1 := "this is a bad uuid" // Bad + uuid2 := "alsoabaduuid" // Bad + uuid3 := "550e8400-e29b-41d4-a716-446655440000" // Good + uuid4 := "thishoul-dnot-pass-vali-dationchecks" // Bad - err := uuid1.validateUUID() - - if err == nil { + if IsValidUUID(uuid1) { t.Error(uuid1, " is not a valid UUID") } - err = uuid2.validateUUID() - - if err == nil { + if IsValidUUID(uuid2) { t.Error(uuid2, " is not a valid UUID") } - err = uuid3.validateUUID() - - if err != nil { + if !IsValidUUID(uuid3) { t.Error(uuid3, " is a valid UUID") } - err = uuid4.validateUUID() - - if err == nil { + if IsValidUUID(uuid4) { t.Error(uuid4, " is not a valid UUID") } } diff --git a/ovsdb/uuid.go b/ovsdb/uuid.go index 9caba43d..e3ed4b1a 100644 --- a/ovsdb/uuid.go +++ b/ovsdb/uuid.go @@ -47,6 +47,14 @@ func (u UUID) validateUUID() error { return nil } -func isNamed(uuid string) bool { +func IsNamedUUID(uuid string) bool { return len(uuid) > 0 && !validUUID.MatchString(uuid) } + +func IsValidUUID(uuid string) bool { + u := UUID{GoUUID: uuid} + if err := u.validateUUID(); err != nil { + return false + } + return true +} diff --git a/ovsdb/uuid_test.go b/ovsdb/uuid_test.go index 7ea96787..288fb0aa 100644 --- a/ovsdb/uuid_test.go +++ b/ovsdb/uuid_test.go @@ -26,7 +26,7 @@ func TestUUIDIsNamed(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := isNamed(tt.uuid); got != tt.want { + if got := IsNamedUUID(tt.uuid); got != tt.want { t.Errorf("UUID.Named() = %v, want %v", got, tt.want) } }) From 7affb06c778c34ca5a1c5bae65a9cbb225766f54 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 29 Jun 2023 12:58:00 -0500 Subject: [PATCH 2/4] ovsdb: add generic Named UUID handling code and testcases By moving the Named UUID expansion code into ovsdb/ itself, we reduce use of ovsdb internals in other parts of libobsdb. Plus, exporting it allows testcases or other code to use it independently. Signed-off-by: Dan Williams --- ovsdb/named_uuid.go | 165 +++++++++++++ ovsdb/named_uuid_test.go | 514 +++++++++++++++++++++++++++++++++++++++ ovsdb/uuid.go | 11 +- 3 files changed, 684 insertions(+), 6 deletions(-) create mode 100644 ovsdb/named_uuid.go create mode 100644 ovsdb/named_uuid_test.go diff --git a/ovsdb/named_uuid.go b/ovsdb/named_uuid.go new file mode 100644 index 00000000..29034ee9 --- /dev/null +++ b/ovsdb/named_uuid.go @@ -0,0 +1,165 @@ +package ovsdb + +import ( + "fmt" +) + +// ExpandNamedUUIDs replaces named UUIDs in columns that contain UUID types +// throughout the operation. The caller must ensure each input operation has +// a valid UUID, which may be replaced if a previous operation created a +// matching named UUID mapping. Returns the updated operations or an error. +func ExpandNamedUUIDs(ops []Operation, schema *DatabaseSchema) ([]Operation, error) { + uuidMap := make(map[string]string) + + // Pass 1: replace the named UUID with a real UUID for each operation and + // build the substitution map + for i := range ops { + op := &ops[i] + if op.Op != OperationInsert { + // Only Insert operations can specify a Named UUID + continue + } + + if err := ValidateUUID(op.UUID); err != nil { + return nil, fmt.Errorf("operation UUID %q invalid: %v", op.UUID, err) + } + + if op.UUIDName != "" { + if uuid, ok := uuidMap[op.UUIDName]; ok { + if op.UUID != "" && op.UUID != uuid { + return nil, fmt.Errorf("named UUID %q maps to UUID %q but found existing UUID %q", + op.UUIDName, uuid, op.UUID) + } + // If there's already a mapping for this named UUID use it + op.UUID = uuid + } else { + uuidMap[op.UUIDName] = op.UUID + } + op.UUIDName = "" + } + } + + // Pass 2: replace named UUIDs in operation fields with the real UUID + for i := range ops { + op := &ops[i] + tableSchema := schema.Table(op.Table) + if tableSchema == nil { + return nil, fmt.Errorf("table %q not found in schema %q", op.Table, schema.Name) + } + + for i, condition := range op.Where { + newVal, err := expandColumnNamedUUIDs(tableSchema, op.Table, condition.Column, condition.Value, uuidMap) + if err != nil { + return nil, err + } + op.Where[i].Value = newVal + } + for i, mutation := range op.Mutations { + newVal, err := expandColumnNamedUUIDs(tableSchema, op.Table, mutation.Column, mutation.Value, uuidMap) + if err != nil { + return nil, err + } + op.Mutations[i].Value = newVal + } + for _, row := range op.Rows { + for k, v := range row { + newVal, err := expandColumnNamedUUIDs(tableSchema, op.Table, k, v, uuidMap) + if err != nil { + return nil, err + } + row[k] = newVal + } + } + for k, v := range op.Row { + newVal, err := expandColumnNamedUUIDs(tableSchema, op.Table, k, v, uuidMap) + if err != nil { + return nil, err + } + op.Row[k] = newVal + } + } + + return ops, nil +} + +func expandColumnNamedUUIDs(tableSchema *TableSchema, tableName, columnName string, value interface{}, uuidMap map[string]string) (interface{}, error) { + column := tableSchema.Column(columnName) + if column == nil { + return nil, fmt.Errorf("column %q not found in table %q", columnName, tableName) + } + return expandNamedUUID(column, value, uuidMap), nil +} + +func expandNamedUUID(column *ColumnSchema, value interface{}, namedUUIDs map[string]string) interface{} { + var keyType, valType ExtendedType + + switch column.Type { + case TypeUUID: + keyType = column.Type + case TypeSet: + keyType = column.TypeObj.Key.Type + case TypeMap: + keyType = column.TypeObj.Key.Type + valType = column.TypeObj.Value.Type + } + + if valType == TypeUUID { + if m, ok := value.(OvsMap); ok { + for k, v := range m.GoMap { + if newUUID, ok := expandNamedUUIDAtomic(keyType, k, namedUUIDs); ok { + m.GoMap[newUUID] = m.GoMap[k] + delete(m.GoMap, k) + k = newUUID + } + if newUUID, ok := expandNamedUUIDAtomic(valType, v, namedUUIDs); ok { + m.GoMap[k] = newUUID + } + } + } + } else if keyType == TypeUUID { + if ovsSet, ok := value.(OvsSet); ok { + for i, s := range ovsSet.GoSet { + if newUUID, ok := expandNamedUUIDAtomic(keyType, s, namedUUIDs); ok { + ovsSet.GoSet[i] = newUUID + } + } + return value + } else if strSet, ok := value.([]string); ok { + for i, s := range strSet { + if newUUID, ok := expandNamedUUIDAtomic(keyType, s, namedUUIDs); ok { + strSet[i] = newUUID.(string) + } + } + return value + } else if uuidSet, ok := value.([]UUID); ok { + for i, s := range uuidSet { + if newUUID, ok := expandNamedUUIDAtomic(keyType, s, namedUUIDs); ok { + uuidSet[i] = newUUID.(UUID) + } + } + return value + } + + if newUUID, ok := expandNamedUUIDAtomic(keyType, value, namedUUIDs); ok { + return newUUID + } + } + + // No expansion required; return original value + return value +} + +func expandNamedUUIDAtomic(valueType ExtendedType, value interface{}, namedUUIDs map[string]string) (interface{}, bool) { + if valueType == TypeUUID { + if uuid, ok := value.(UUID); ok { + if newUUID, ok := namedUUIDs[uuid.GoUUID]; ok { + return UUID{GoUUID: newUUID}, true + } + } else if uuid, ok := value.(string); ok { + if newUUID, ok := namedUUIDs[uuid]; ok { + return newUUID, true + } + } + } + return value, false +} diff --git a/ovsdb/named_uuid_test.go b/ovsdb/named_uuid_test.go new file mode 100644 index 00000000..78f3f9f6 --- /dev/null +++ b/ovsdb/named_uuid_test.go @@ -0,0 +1,514 @@ +package ovsdb + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const uuidTestSchema = ` +{ + "name": "UUID_Test", + "version": "0.0.1", + "tables": { + "UUID_Test": { + "columns": { + "_uuid": { + "type": "uuid" + }, + "real_uuid": { + "type": "uuid" + }, + "str": { + "type": "string" + }, + "int": { + "type": "integer" + }, + "uuidset": { + "type": { + "key": { + "type": "uuid" + }, + "min": 0, + "max": "unlimited" + } + }, + "real_uuidset": { + "type": { + "key": { + "type": "uuid" + }, + "min": 0, + "max": "unlimited" + } + }, + "strset": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "uuidmap": { + "type": { + "key": { + "type": "uuid" + }, + "value": { + "type": "uuid" + }, + "min": 1, + "max": "unlimited" + } + }, + "real_uuidmap": { + "type": { + "key": { + "type": "uuid" + }, + "value": { + "type": "uuid" + }, + "min": 1, + "max": "unlimited" + } + }, + "struuidmap": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "uuid" + }, + "min": 1, + "max": "unlimited" + } + }, + "real_struuidmap": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "uuid" + }, + "min": 1, + "max": "unlimited" + } + }, + "strmap": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 1, + "max": "unlimited" + } + } + }, + "isRoot": true + } + } +} +` + +type UUIDTestType struct { + UUID string `ovsdb:"_uuid"` + RealUUID UUID `ovsdb:"real_uuid"` + String string `ovsdb:"str"` + Int string `ovsdb:"int"` + UUIDSet []string `ovsdb:"uuidset"` + RealUUIDSet []UUID `ovsdb:"real_uuidset"` + StrSet []string `ovsdb:"strset"` + UUIDMap map[string]string `ovsdb:"uuidmap"` + RealUUIDMap map[UUID]UUID `ovsdb:"real_uuidmap"` + StrUUIDMap map[string]string `ovsdb:"struuidmap"` + RealStrUUIDMap map[string]UUID `ovsdb:"real_struuidmap"` + StrMap map[string]string `ovsdb:"strmap"` +} + +func getUUIDTestSchema() (DatabaseSchema, error) { + var dbSchema DatabaseSchema + err := json.Unmarshal([]byte(uuidTestSchema), &dbSchema) + return dbSchema, err +} + +func TestStandaloneExpandNamedUUID(t *testing.T) { + testUUID := uuid.NewString() + testUUID1 := uuid.NewString() + tests := []struct { + name string + namedUUIDs map[string]string + column string + value interface{} + expected interface{} + }{ + { + "uuid", + map[string]string{"foo": testUUID}, + "_uuid", + "foo", + testUUID, + }, + { + "real uuid", + map[string]string{"foo": testUUID}, + "real_uuid", + UUID{GoUUID: "foo"}, + UUID{GoUUID: testUUID}, + }, + { + "string (no replace)", + map[string]string{"foo": testUUID}, + "str", + "foo", + "foo", + }, + { + "int (no replace)", + map[string]string{"foo": testUUID}, + "int", + 15, + 15, + }, + // OVS []UUID == Go []string + { + "UUID set", + map[string]string{"foo": testUUID}, + "uuidset", + OvsSet{GoSet: []interface{}{"foo"}}, + OvsSet{GoSet: []interface{}{testUUID}}, + }, + // OVS []UUID == Go []UUID + { + "real UUID set", + map[string]string{"foo": testUUID}, + "real_uuidset", + OvsSet{GoSet: []interface{}{UUID{GoUUID: "foo"}}}, + OvsSet{GoSet: []interface{}{UUID{GoUUID: testUUID}}}, + }, + { + "set multiple", + map[string]string{"foo": testUUID, "bar": testUUID1}, + "uuidset", + OvsSet{GoSet: []interface{}{"foo", "bar", "baz"}}, + OvsSet{GoSet: []interface{}{testUUID, testUUID1, "baz"}}, + }, + // OVS [UUID]UUID == Go [string]string + { + "map key", + map[string]string{"foo": testUUID}, + "uuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + OvsMap{GoMap: map[interface{}]interface{}{testUUID: "bar"}}, + }, + { + "map values", + map[string]string{"bar": testUUID1}, + "uuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": testUUID1}}, + }, + { + "map key and values", + map[string]string{"foo": testUUID, "bar": testUUID1}, + "uuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + OvsMap{GoMap: map[interface{}]interface{}{testUUID: testUUID1}}, + }, + // OVS [UUID]UUID == Go [UUID]UUID + { + "real UUID map key", + map[string]string{"foo": testUUID}, + "real_uuidmap", + OvsMap{GoMap: map[interface{}]interface{}{UUID{GoUUID: "foo"}: UUID{GoUUID: "bar"}}}, + OvsMap{GoMap: map[interface{}]interface{}{UUID{GoUUID: testUUID}: UUID{GoUUID: "bar"}}}, + }, + { + "real UUID map values", + map[string]string{"bar": testUUID1}, + "real_uuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: "bar"}}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: testUUID1}}}, + }, + { + "real UUID map key and values", + map[string]string{"foo": testUUID, "bar": testUUID1}, + "real_uuidmap", + OvsMap{GoMap: map[interface{}]interface{}{UUID{GoUUID: "foo"}: UUID{GoUUID: "bar"}}}, + OvsMap{GoMap: map[interface{}]interface{}{UUID{GoUUID: testUUID}: UUID{GoUUID: testUUID1}}}, + }, + // OVS [string]UUID == Go [string]string + { + "string UUID map key (no replace)", + map[string]string{"foo": testUUID}, + "struuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + }, + { + "string UUID map values (replace)", + map[string]string{"foo": testUUID}, + "struuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": "foo"}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": testUUID}}, + }, + { + "string UUID map key (no replace) and values (replace)", + map[string]string{"foo": testUUID, "bar": testUUID1}, + "struuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": testUUID1}}, + }, + // OVS [string]UUID == Go [string]UUID + { + "real string UUID map key (no replace)", + map[string]string{"foo": testUUID}, + "real_struuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: "bar"}}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: "bar"}}}, + }, + { + "real string UUID map values (replace)", + map[string]string{"foo": testUUID}, + "real_struuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: "foo"}}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: testUUID}}}, + }, + { + "real string UUID map key (no replace) and values (replace)", + map[string]string{"foo": testUUID, "bar": testUUID1}, + "real_struuidmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: "bar"}}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": UUID{GoUUID: testUUID1}}}, + }, + // OVS [string]string == Go [string]string + { + "string map key and values (no replace)", + map[string]string{"foo": testUUID, "bar": testUUID1}, + "strmap", + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + OvsMap{GoMap: map[interface{}]interface{}{"foo": "bar"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + schema, err := getUUIDTestSchema() + require.Nil(t, err) + ts := schema.Table("UUID_Test") + require.NotNil(t, ts) + cs := ts.Column(tt.column) + require.NotNil(t, cs) + + got := expandNamedUUID(cs, tt.value, tt.namedUUIDs) + assert.Equal(t, tt.expected, got) + }) + } +} + +func makeOp(table, uuid, uuidName string, rows ...Row) Operation { + op := Operation{ + Op: OperationInsert, + Table: table, + UUID: uuid, + UUIDName: uuidName, + } + if len(rows) == 1 { + op.Row = rows[0] + } else { + op.Rows = rows + } + return op +} + +func makeOpWhere(table, uuid, uuidName string, row Row, w ...Condition) Operation { + op := makeOp(table, uuid, uuidName, row) + op.Where = w + return op +} + +func makeOpMutation(table, uuid, uuidName string, row Row, m ...Mutation) Operation { + op := makeOp(table, uuid, uuidName, row) + op.Mutations = m + return op +} + +func TestOperationExpandNamedUUID(t *testing.T) { + testUUID := uuid.NewString() + testUUID1 := uuid.NewString() + testUUID2 := uuid.NewString() + namedUUID := "adsfasdfadsf" + namedUUID1 := "142124521551" + badUUID := "asdfadsfasdfasf" + + namedUUIDSet, _ := NewOvsSet([]UUID{{GoUUID: namedUUID}}) + testUUIDSet, _ := NewOvsSet([]UUID{{GoUUID: testUUID}}) + + namedUUID1Map, _ := NewOvsMap(map[string]string{"foo": namedUUID1}) + testUUID1Map, _ := NewOvsMap(map[string]string{"foo": testUUID1}) + + tests := []struct { + name string + ops []Operation + expected []Operation + expectedErr string + }{ + { + "simple replace", + []Operation{ + makeOp("UUID_Test", testUUID, namedUUID, + Row(map[string]interface{}{"uuidset": []string{namedUUID}})), + }, + []Operation{ + makeOp("UUID_Test", testUUID, "", + Row(map[string]interface{}{"uuidset": []string{testUUID}})), + }, + "", + }, + { + "simple replace multiple rows", + []Operation{ + makeOp("UUID_Test", testUUID, namedUUID, + Row(map[string]interface{}{"uuidset": []string{namedUUID}}), + Row(map[string]interface{}{"real_uuidset": namedUUIDSet}), + ), + }, + []Operation{ + makeOp("UUID_Test", testUUID, "", + Row(map[string]interface{}{"uuidset": []string{testUUID}}), + Row(map[string]interface{}{"real_uuidset": testUUIDSet}), + ), + }, + "", + }, + { + "chained ops", + []Operation{ + makeOp("UUID_Test", testUUID, namedUUID, + Row(map[string]interface{}{"uuidset": []string{namedUUID}})), + makeOp("UUID_Test", testUUID1, namedUUID1, + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: namedUUID}})), + makeOp("UUID_Test", testUUID2, "", + Row(map[string]interface{}{"struuidmap": namedUUID1Map})), + }, + []Operation{ + makeOp("UUID_Test", testUUID, "", + Row(map[string]interface{}{"uuidset": []string{testUUID}})), + makeOp("UUID_Test", testUUID1, "", + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: testUUID}})), + makeOp("UUID_Test", testUUID2, "", + Row(map[string]interface{}{"struuidmap": testUUID1Map})), + }, + "", + }, + { + "reverse ordered ops", + []Operation{ + makeOp("UUID_Test", testUUID1, namedUUID1, + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: namedUUID}})), + makeOp("UUID_Test", testUUID, namedUUID, + Row(map[string]interface{}{"uuidset": []string{namedUUID}})), + }, + []Operation{ + makeOp("UUID_Test", testUUID1, "", + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: testUUID}})), + makeOp("UUID_Test", testUUID, "", + Row(map[string]interface{}{"uuidset": []string{testUUID}})), + }, + "", + }, + { + "where ops", + []Operation{ + makeOpWhere("UUID_Test", testUUID, namedUUID, + Row(map[string]interface{}{"_uuid": namedUUID}), + NewCondition("_uuid", ConditionEqual, namedUUID), + ), + makeOpWhere("UUID_Test", testUUID1, namedUUID1, + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: namedUUID}}), + NewCondition("_uuid", ConditionEqual, namedUUID), + ), + }, + []Operation{ + makeOpWhere("UUID_Test", testUUID, "", + Row(map[string]interface{}{"_uuid": testUUID}), + NewCondition("_uuid", ConditionEqual, testUUID), + ), + makeOpWhere("UUID_Test", testUUID1, "", + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: testUUID}}), + NewCondition("_uuid", ConditionEqual, testUUID), + ), + }, + "", + }, + { + "mutation ops", + []Operation{ + makeOpMutation("UUID_Test", testUUID, namedUUID, + Row(map[string]interface{}{"_uuid": namedUUID}), + *NewMutation("_uuid", MutateOperationAdd, namedUUID), + ), + makeOpMutation("UUID_Test", testUUID1, namedUUID1, + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: namedUUID}}), + *NewMutation("_uuid", MutateOperationAdd, namedUUID), + ), + }, + []Operation{ + makeOpMutation("UUID_Test", testUUID, "", + Row(map[string]interface{}{"_uuid": testUUID}), + *NewMutation("_uuid", MutateOperationAdd, testUUID), + ), + makeOpMutation("UUID_Test", testUUID1, "", + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: testUUID}}), + *NewMutation("_uuid", MutateOperationAdd, testUUID), + ), + }, + "", + }, + { + "invalid UUID", + []Operation{ + makeOp("UUID_Test", badUUID, "", + Row(map[string]interface{}{"uuidset": []string{namedUUID}})), + }, + []Operation{}, + fmt.Sprintf("operation UUID %q invalid", badUUID), + }, + { + "mismatched UUID for named UUID", + []Operation{ + makeOp("UUID_Test", testUUID, namedUUID, + Row(map[string]interface{}{"uuidset": []string{namedUUID}})), + makeOp("UUID_Test", testUUID1, namedUUID, + Row(map[string]interface{}{"real_uuid": UUID{GoUUID: namedUUID}})), + }, + []Operation{}, + fmt.Sprintf("named UUID %q maps to UUID %q but found existing UUID %q", namedUUID, testUUID, testUUID1), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + schema, err := getUUIDTestSchema() + require.Nil(t, err) + + got, err := ExpandNamedUUIDs(tt.ops, &schema) + if tt.expectedErr != "" { + require.Error(t, err, tt.expectedErr) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected, got) + } + }) + } +} diff --git a/ovsdb/uuid.go b/ovsdb/uuid.go index e3ed4b1a..6bc46365 100644 --- a/ovsdb/uuid.go +++ b/ovsdb/uuid.go @@ -16,7 +16,7 @@ type UUID struct { // MarshalJSON will marshal an OVSDB style UUID to a JSON encoded byte array func (u UUID) MarshalJSON() ([]byte, error) { var uuidSlice []string - err := u.validateUUID() + err := ValidateUUID(u.GoUUID) if err == nil { uuidSlice = []string{"uuid", u.GoUUID} } else { @@ -35,12 +35,12 @@ func (u *UUID) UnmarshalJSON(b []byte) (err error) { return err } -func (u UUID) validateUUID() error { - if len(u.GoUUID) != 36 { +func ValidateUUID(uuid string) error { + if len(uuid) != 36 { return fmt.Errorf("uuid exceeds 36 characters") } - if !validUUID.MatchString(u.GoUUID) { + if !validUUID.MatchString(uuid) { return fmt.Errorf("uuid does not match regexp") } @@ -52,8 +52,7 @@ func IsNamedUUID(uuid string) bool { } func IsValidUUID(uuid string) bool { - u := UUID{GoUUID: uuid} - if err := u.validateUUID(); err != nil { + if err := ValidateUUID(uuid); err != nil { return false } return true From 8573f02d0027cd55f2cd02333fb977dbfb669ed7 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2023 09:28:23 -0500 Subject: [PATCH 3/4] ovsdb: move Named UUID handling from Server to Transactions By moving the expansion into the Transaction object instead of the Server, we allow testcases to perform transactions on databases without having to create a full Server, or to pre-seed a database with correct data before a Server is created. (FWIW, this also mimics ovsdb-server's organization; the server-type code is separate from the transaction code, and the transaction code is where expansion takes place.) Signed-off-by: Dan Williams --- database/transaction.go | 25 ++++++++-- database/transaction_test.go | 95 +++++++++++++++++++----------------- server/server.go | 55 --------------------- server/server_test.go | 86 ++++++-------------------------- 4 files changed, 87 insertions(+), 174 deletions(-) diff --git a/database/transaction.go b/database/transaction.go index 7e62929f..97d586a4 100644 --- a/database/transaction.go +++ b/database/transaction.go @@ -45,6 +45,22 @@ func (t *Transaction) Transact(operations []ovsdb.Operation) ([]*ovsdb.Operation results := []*ovsdb.OperationResult{} update := updates.ModelUpdates{} + // Every Insert operation must have a UUID + for i := range operations { + op := &operations[i] + if op.Op == ovsdb.OperationInsert && op.UUID == "" { + op.UUID = uuid.NewString() + } + } + + // Ensure Named UUIDs are expanded in all operations + var err error + operations, err = ovsdb.ExpandNamedUUIDs(operations, &t.Model.Schema) + if err != nil { + r := ovsdb.ResultFromError(err) + return []*ovsdb.OperationResult{&r}, nil + } + var r ovsdb.OperationResult for _, op := range operations { // if we had a previous error, just append a nil result for every op @@ -200,19 +216,18 @@ func (t *Transaction) checkIndexes() error { } func (t *Transaction) Insert(op *ovsdb.Operation) (ovsdb.OperationResult, *updates.ModelUpdates) { - rowUUID := op.UUIDName - if rowUUID == "" { - rowUUID = uuid.NewString() + if err := ovsdb.ValidateUUID(op.UUID); err != nil { + return ovsdb.ResultFromError(err), nil } update := updates.ModelUpdates{} - err := update.AddOperation(t.Model, op.Table, rowUUID, nil, op) + err := update.AddOperation(t.Model, op.Table, op.UUID, nil, op) if err != nil { return ovsdb.ResultFromError(err), nil } result := ovsdb.OperationResult{ - UUID: ovsdb.UUID{GoUUID: rowUUID}, + UUID: ovsdb.UUID{GoUUID: op.UUID}, } return result, &update diff --git a/database/transaction_test.go b/database/transaction_test.go index 3a6a5aac..6b4e550e 100644 --- a/database/transaction_test.go +++ b/database/transaction_test.go @@ -48,20 +48,20 @@ func TestWaitOpEquals(t *testing.T) { transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) operation := ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Open_vSwitch", - UUIDName: ovsUUID, - Row: ovsRow, + Op: ovsdb.OperationInsert, + Table: "Open_vSwitch", + UUID: ovsUUID, + Row: ovsRow, } res, updates := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) require.Nil(t, err) operation = ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: bridgeUUID, - Row: bridgeRow, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: bridgeRow, } res, update2 := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -175,20 +175,20 @@ func TestWaitOpNotEquals(t *testing.T) { transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) operation := ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Open_vSwitch", - UUIDName: ovsUUID, - Row: ovsRow, + Op: ovsdb.OperationInsert, + Table: "Open_vSwitch", + UUID: ovsUUID, + Row: ovsRow, } res, updates := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) require.Nil(t, err) operation = ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: bridgeUUID, - Row: bridgeRow, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: bridgeRow, } res, update2 := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -295,20 +295,20 @@ func TestMutateOp(t *testing.T) { transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) operation := ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Open_vSwitch", - UUIDName: ovsUUID, - Row: ovsRow, + Op: ovsdb.OperationInsert, + Table: "Open_vSwitch", + UUID: ovsUUID, + Row: ovsRow, } res, updates := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) require.Nil(t, err) operation = ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: bridgeUUID, - Row: bridgeRow, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: bridgeRow, } res, update2 := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -411,10 +411,10 @@ func TestOvsdbServerInsert(t *testing.T) { transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) operation := ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: bridgeUUID, - Row: bridgeRow, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: bridgeRow, } res, updates := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -464,10 +464,10 @@ func TestOvsdbServerUpdate(t *testing.T) { transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) operation := ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: bridgeUUID, - Row: bridgeRow, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: bridgeRow, } res, updates := transaction.Insert(&operation) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -556,9 +556,9 @@ func TestMultipleOps(t *testing.T) { bridgeUUID := uuid.NewString() op = ovsdb.Operation{ - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: bridgeUUID, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, Row: ovsdb.Row{ "name": "a_bridge_to_nowhere", }, @@ -667,9 +667,9 @@ func TestOvsdbServerDbDoesNotExist(t *testing.T) { ops := []ovsdb.Operation{ { - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: uuid.NewString(), + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: uuid.NewString(), Row: ovsdb.Row{ "name": "bridge", }, @@ -706,19 +706,21 @@ func TestCheckIndexes(t *testing.T) { bridgeUUID := uuid.NewString() fscsUUID := uuid.NewString() + fscsUUID2 := uuid.NewString() + fscsUUID3 := uuid.NewString() ops := []ovsdb.Operation{ { - Table: "Bridge", - Op: ovsdb.OperationInsert, - UUIDName: bridgeUUID, + Table: "Bridge", + Op: ovsdb.OperationInsert, + UUID: bridgeUUID, Row: ovsdb.Row{ "name": "a_bridge_to_nowhere", }, }, { - Table: "Flow_Sample_Collector_Set", - Op: ovsdb.OperationInsert, - UUIDName: fscsUUID, + Table: "Flow_Sample_Collector_Set", + Op: ovsdb.OperationInsert, + UUID: fscsUUID, Row: ovsdb.Row{ "id": 1, "bridge": ovsdb.UUID{GoUUID: bridgeUUID}, @@ -727,6 +729,7 @@ func TestCheckIndexes(t *testing.T) { { Table: "Flow_Sample_Collector_Set", Op: ovsdb.OperationInsert, + UUID: fscsUUID2, Row: ovsdb.Row{ "id": 2, "bridge": ovsdb.UUID{GoUUID: bridgeUUID}, @@ -755,6 +758,7 @@ func TestCheckIndexes(t *testing.T) { { Table: "Flow_Sample_Collector_Set", Op: ovsdb.OperationInsert, + UUID: fscsUUID3, Row: ovsdb.Row{ "id": 1, "bridge": ovsdb.UUID{GoUUID: bridgeUUID}, @@ -790,6 +794,7 @@ func TestCheckIndexes(t *testing.T) { { Table: "Flow_Sample_Collector_Set", Op: ovsdb.OperationInsert, + UUID: fscsUUID3, Row: ovsdb.Row{ "id": 3, "bridge": ovsdb.UUID{GoUUID: bridgeUUID}, @@ -817,6 +822,7 @@ func TestCheckIndexes(t *testing.T) { { Table: "Flow_Sample_Collector_Set", Op: ovsdb.OperationInsert, + UUID: fscsUUID3, Row: ovsdb.Row{ "id": 1, "bridge": ovsdb.UUID{GoUUID: bridgeUUID}, @@ -844,6 +850,7 @@ func TestCheckIndexes(t *testing.T) { { Table: "Flow_Sample_Collector_Set", Op: ovsdb.OperationInsert, + UUID: fscsUUID3, Row: ovsdb.Row{ "id": 1, "bridge": ovsdb.UUID{GoUUID: bridgeUUID}, diff --git a/server/server.go b/server/server.go index 91a2303d..a5b8ac79 100644 --- a/server/server.go +++ b/server/server.go @@ -185,32 +185,12 @@ func (o *OvsdbServer) Transact(client *rpc2.Client, args []json.RawMessage, repl return fmt.Errorf("database %v is not a string", args[0]) } var ops []ovsdb.Operation - namedUUID := make(map[string]ovsdb.UUID) for i := 1; i < len(args); i++ { var op ovsdb.Operation err = json.Unmarshal(args[i], &op) if err != nil { return err } - if op.UUIDName != "" { - newUUID := uuid.NewString() - namedUUID[op.UUIDName] = ovsdb.UUID{GoUUID: newUUID} - op.UUIDName = newUUID - } - for i, condition := range op.Where { - op.Where[i].Value = expandNamedUUID(condition.Value, namedUUID) - } - for i, mutation := range op.Mutations { - op.Mutations[i].Value = expandNamedUUID(mutation.Value, namedUUID) - } - for _, row := range op.Rows { - for k, v := range row { - row[k] = expandNamedUUID(v, namedUUID) - } - } - for k, v := range op.Row { - op.Row[k] = expandNamedUUID(v, namedUUID) - } ops = append(ops, op) } response, updates := o.transact(db, ops) @@ -424,38 +404,3 @@ func (o *OvsdbServer) processMonitors(id uuid.UUID, update database.Update) { } o.monitorMutex.RUnlock() } - -func expandNamedUUID(value interface{}, namedUUID map[string]ovsdb.UUID) interface{} { - if uuid, ok := value.(ovsdb.UUID); ok { - if newUUID, ok := namedUUID[uuid.GoUUID]; ok { - return newUUID - } - } - if set, ok := value.(ovsdb.OvsSet); ok { - for i, s := range set.GoSet { - if _, ok := s.(ovsdb.UUID); !ok { - return value - } - uuid := s.(ovsdb.UUID) - if newUUID, ok := namedUUID[uuid.GoUUID]; ok { - set.GoSet[i] = newUUID - } - } - } - if m, ok := value.(ovsdb.OvsMap); ok { - for k, v := range m.GoMap { - if uuid, ok := v.(ovsdb.UUID); ok { - if newUUID, ok := namedUUID[uuid.GoUUID]; ok { - m.GoMap[k] = newUUID - } - } - if uuid, ok := k.(ovsdb.UUID); ok { - if newUUID, ok := namedUUID[uuid.GoUUID]; ok { - m.GoMap[newUUID] = m.GoMap[k] - delete(m.GoMap, uuid) - } - } - } - } - return value -} diff --git a/server/server_test.go b/server/server_test.go index 9776c470..36cf4d87 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -14,60 +14,6 @@ import ( . "github.com/ovn-org/libovsdb/test" ) -func TestExpandNamedUUID(t *testing.T) { - testUUID := uuid.NewString() - testUUID1 := uuid.NewString() - tests := []struct { - name string - namedUUIDs map[string]ovsdb.UUID - value interface{} - expected interface{} - }{ - { - "uuid", - map[string]ovsdb.UUID{"foo": {GoUUID: testUUID}}, - ovsdb.UUID{GoUUID: "foo"}, - ovsdb.UUID{GoUUID: testUUID}, - }, - { - "set", - map[string]ovsdb.UUID{"foo": {GoUUID: testUUID}}, - ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: "foo"}}}, - ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: testUUID}}}, - }, - { - "set multiple", - map[string]ovsdb.UUID{"foo": {GoUUID: testUUID}, "bar": {GoUUID: testUUID1}}, - ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: "foo"}, ovsdb.UUID{GoUUID: "bar"}, ovsdb.UUID{GoUUID: "baz"}}}, - ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: testUUID}, ovsdb.UUID{GoUUID: testUUID1}, ovsdb.UUID{GoUUID: "baz"}}}, - }, - { - "map key", - map[string]ovsdb.UUID{"foo": {GoUUID: testUUID}}, - ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ovsdb.UUID{GoUUID: "foo"}: "foo"}}, - ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ovsdb.UUID{GoUUID: testUUID}: "foo"}}, - }, - { - "map values", - map[string]ovsdb.UUID{"foo": {GoUUID: testUUID}}, - ovsdb.OvsMap{GoMap: map[interface{}]interface{}{"foo": ovsdb.UUID{GoUUID: "foo"}}}, - ovsdb.OvsMap{GoMap: map[interface{}]interface{}{"foo": ovsdb.UUID{GoUUID: testUUID}}}, - }, - { - "map key and values", - map[string]ovsdb.UUID{"foo": {GoUUID: testUUID}, "bar": {GoUUID: testUUID1}}, - ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ovsdb.UUID{GoUUID: "foo"}: ovsdb.UUID{GoUUID: "bar"}}}, - ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ovsdb.UUID{GoUUID: testUUID}: ovsdb.UUID{GoUUID: testUUID1}}}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := expandNamedUUID(tt.value, tt.namedUUIDs) - assert.Equal(t, tt.expected, got) - }) - } -} - func TestOvsdbServerMonitor(t *testing.T) { dbModel, err := GetModel() require.NoError(t, err) @@ -95,28 +41,28 @@ func TestOvsdbServerMonitor(t *testing.T) { operations := []ovsdb.Operation{ { - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: fooUUID, - Row: ovsdb.Row{"name": "foo"}, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: fooUUID, + Row: ovsdb.Row{"name": "foo"}, }, { - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: barUUID, - Row: ovsdb.Row{"name": "bar"}, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: barUUID, + Row: ovsdb.Row{"name": "bar"}, }, { - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: bazUUID, - Row: ovsdb.Row{"name": "baz"}, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bazUUID, + Row: ovsdb.Row{"name": "baz"}, }, { - Op: ovsdb.OperationInsert, - Table: "Bridge", - UUIDName: quuxUUID, - Row: ovsdb.Row{"name": "quux"}, + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: quuxUUID, + Row: ovsdb.Row{"name": "quux"}, }, } transaction := database.NewTransaction(dbModel, "Open_vSwitch", o.db, &o.logger) From f5b801a4340d2318adce044a22d0cafe9684425d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2023 15:04:41 -0500 Subject: [PATCH 4/4] ovsdb: partial revert of "mapper: Add _uuid when calling NewRow" This is a partial revert of commit bbcbf5a93ea866ec16ec58886c636d6daa887239 The reverted commit prevents marshalling named UUIDs into a Row's _uuid field. But that's kind of silly because the _uuid field shouldn't ever be filled in for requests from client -> server, whether named or real. There are only two operations that use Rows: Insert and Update. On Insert ovsdb-server overwrites the given Row's _uuid with either the UUID given in the Operation, or a generated one. On Update it throws an error since _uuid is immutable. So there's zero point to letting _uuid through. But it may be useful internally when dealing with Rows to allow both regular and named UUIDs in the _uuid field, whether for testcases or constructing strings of Operations before sending to the server. Thus, do the same thing in Insert that Update already does: specifically remove _uuid from the row as late as possible, right before sending the Operation to the server via RPC. Signed-off-by: Dan Williams --- client/api.go | 2 ++ ovsdb/bindings.go | 2 +- updates/updates_test.go | 31 ++++++++++++++++++++++++++----- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/client/api.go b/client/api.go index d63b97ee..8d5a30ef 100644 --- a/client/api.go +++ b/client/api.go @@ -311,6 +311,8 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) { if err != nil { return nil, err } + // UUID is given in the operation, not the object + delete(row, "_uuid") operations = append(operations, ovsdb.Operation{ Op: ovsdb.OperationInsert, diff --git a/ovsdb/bindings.go b/ovsdb/bindings.go index dbaa3b10..aebe2c2d 100644 --- a/ovsdb/bindings.go +++ b/ovsdb/bindings.go @@ -409,7 +409,7 @@ func isDefaultBaseValue(elem interface{}, etype ExtendedType) bool { } switch etype { case TypeUUID: - return elem.(string) == "00000000-0000-0000-0000-000000000000" || elem.(string) == "" || IsNamedUUID(elem.(string)) + return elem.(string) == "00000000-0000-0000-0000-000000000000" || elem.(string) == "" case TypeMap, TypeSet: if value.Kind() == reflect.Array { return value.Len() == 0 diff --git a/updates/updates_test.go b/updates/updates_test.go index 0977428e..5d318f3d 100644 --- a/updates/updates_test.go +++ b/updates/updates_test.go @@ -6,6 +6,7 @@ import ( "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/ovn-org/libovsdb/test" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -53,10 +54,12 @@ func TestUpdates_AddOperation(t *testing.T) { }, rowUpdate2: &ovsdb.RowUpdate2{ Insert: &ovsdb.Row{ - "name": "bridge", + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, + "name": "bridge", }, New: &ovsdb.Row{ - "name": "bridge", + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, + "name": "bridge", }, }, }, @@ -200,8 +203,12 @@ func TestUpdates_AddOperation(t *testing.T) { UUID: "uuid", }, rowUpdate2: &ovsdb.RowUpdate2{ - Insert: &ovsdb.Row{}, - New: &ovsdb.Row{}, + Insert: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, + }, + New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, + }, }, }, }, @@ -257,10 +264,12 @@ func TestUpdates_AddOperation(t *testing.T) { }, rowUpdate2: &ovsdb.RowUpdate2{ Old: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{"key": "value", "key1": "value1"}}, }, New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "datapath_type": "type", "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{"key": "value1", "key2": "value2"}}, @@ -340,10 +349,12 @@ func TestUpdates_AddOperation(t *testing.T) { }, rowUpdate2: &ovsdb.RowUpdate2{ New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "datapath_type": "type", }, Insert: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "datapath_type": "type", }, @@ -417,6 +428,7 @@ func TestUpdates_AddOperation(t *testing.T) { "name": "bridge", }, New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "datapath_type": "new", }, @@ -635,10 +647,12 @@ func TestUpdates_AddOperation(t *testing.T) { }, rowUpdate2: &ovsdb.RowUpdate2{ Old: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{"key1": "value1", "key2": "value2"}}, }, New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{"key1": "value1", "key3": "value3"}}, }, @@ -693,10 +707,12 @@ func TestUpdates_AddOperation(t *testing.T) { }, rowUpdate2: &ovsdb.RowUpdate2{ Old: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: "uuid1"}, ovsdb.UUID{GoUUID: "uuid2"}}}, }, New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: "uuid2"}}}, }, @@ -797,10 +813,12 @@ func TestUpdates_AddOperation(t *testing.T) { }, rowUpdate2: &ovsdb.RowUpdate2{ New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: "uuid"}}}, }, Insert: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: "uuid"}}}, }, @@ -875,6 +893,7 @@ func TestUpdates_AddOperation(t *testing.T) { "name": "bridge", }, New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge2", "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: "uuid"}}}, }, @@ -956,6 +975,7 @@ func TestUpdates_AddOperation(t *testing.T) { "name": "bridge", }, New: &ovsdb.Row{ + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, "name": "bridge", "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{"key1": "value1", "key2": "value2"}}, }, @@ -1109,7 +1129,8 @@ func TestUpdates_AddOperation(t *testing.T) { }, rowUpdate2: &ovsdb.RowUpdate2{ Old: &ovsdb.Row{ - "name": "bridge", + "_uuid": ovsdb.UUID{GoUUID: "uuid"}, + "name": "bridge", }, Delete: &ovsdb.Row{}, },